参考:https://frxcat.fun/
Java 多线程
线程的相关概念
程序
进程
什么是线程
其他相关概念
线程的基本使用
创建线程的两种方式
- 继承Thread类,重写run方法。
- 实现Runnable接口,重写run方法。
线程应用案例 1-继承 Thread 类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| package study.threaduse;
public class Thread01 { public static void main(String[] args) throws InterruptedException{ Cat cat = new Cat(); cat.start(); System.out.println("主线程继续执行"+Thread.currentThread().getName()); for (int i = 0; i < 10; i++) { System.out.println("主线程i"+i); Thread.sleep(1000); }
}
}
class Cat extends Thread{ int times=0; public void run(){ while(true){ System.out.println("喵喵,我是小喵咪"+(++times)+" 线程名"+Thread.currentThread().getName()); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } if(times==8){ break; } } }
}
|
线程应用案例 2-实现 Runnable 接口
- java是单继承的,在某些情况下一个类可能已经继承了某个父类,这时在用继承Thread类方法来创建线程显然不可能了。
- java设计者们提供了另外一个方式创建线程,就是通过实现Runnable接口来创建线程。
请编写程序,该程序可以每隔1秒。在控制台输出“hi!”,当输出10次后,自动退出。请使用实现Runnable接口的方式实现。Thread02.java,这里底层使用了设计模式[代理模式=>代码模拟实现Runnable接口开发线程的机制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| package study.threaduse;
public class Thread02 { public static void main(String[] args) throws InterruptedException {
Tiger tiger = new Tiger(); ThreadProxy threadProxy = new ThreadProxy(tiger); threadProxy.start(); } } class Animal{} class Tiger extends Animal implements Runnable{
@Override public void run() { System.out.println("老虎嗷嗷叫"); } }
class ThreadProxy implements Runnable{ private Runnable target=null;
@Override public void run() { if(target!=null){ target.run(); } }
public ThreadProxy(Runnable target) { this.target = target; } public void start(){ start0(); } public void start0(){ run(); } }
class Dod implements Runnable {
int count = 0;
@Override public void run() { while (true) { System.out.println("小狗汪汪叫" + (++count) + Thread.currentThread().getName()); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } if (count == 10) { break; } } } }
|
线程使用应用案例-多线程执行
请编写一个程序,创建两个线程,一个线程每隔1秒输出“hello,world”,输出10次,退出,一个线程每隔1秒输出“hi”,输出5次退出.Thread03.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| package study.threaduse;
public class Thread03 { public static void main(String[] args) { T1 t1 = new T1(); new Thread(t1).start(); T2 t2 = new T2(); new Thread(t2).start(); new Thread(t2).start();
} }
class T1 implements Runnable { int count = 0;
@Override public void run() { while (true) { System.out.println("Hello,world" + (++count)); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } if (count == 100) break;
}
} }
class T2 implements Runnable { int count = 0;
@Override public void run() { while (true) { System.out.println("Hi" + (++count)); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } if (count == 50) break;
}
} }
|
线程如何理解
继承 Thread vs 实现 Runnable 的区别
从java的设计来看,通过继承Thread或者实现Runnable接口来创建线程本质上没有区别,从jdk帮助文档我们可以看到Thread类本身就实现了Runnable接口。
实现Runnable接口方式更加适合多个线程共享一个资源的情况,并且避免了单继承的限制,建议使用Runnable。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| package study.threaduse.ticket.synchronized_;
public class SellTicket { public static void main(String[] args) {
SellTicket02 sellTicket02 = new SellTicket02(); new Thread(sellTicket02).start(); new Thread(sellTicket02).start(); new Thread(sellTicket02).start(); } }
class SellTicket02 implements Runnable { private int tickNum = 100; @Override public void run() { while (true) { if (tickNum <= 0) { System.out.println("售票结束..."); break; } try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("窗口线程名称" + Thread.currentThread().getName() + "售出一张票" + "剩余票数为" + (--tickNum));
} }
}
|
线程终止
基本说明
- 当线程完成任务后,会自动退出。
- 还可以通过使用变量来控制run方法退出的方式停止线程,即通知方式。
应用案列
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| package study.threaduse.exit_;
public class ThreadExit_ { public static void main(String[] args) throws InterruptedException { T t = new T(); t.start(); System.out.println("主线程休眠10秒"); Thread.sleep(10*1000); t.setLoop(false);
} } class T extends Thread{ int count=0; private boolean loop=true; @Override public void run() { while (loop){ try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("T 运行中..."+count++); } }
public void setLoop(boolean loop) { this.loop = loop; }
}
|
线程常用方法
常用方法第一组
注意事项和细节
应用案例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| package study.threaduse.method_;
public class ThreadMethod01 { public static void main(String[] args) throws InterruptedException { T t = new T(); t.setName("frx"); t.setPriority(Thread.MIN_PRIORITY); t.start(); for (int i = 0; i < 5; i++) { Thread.sleep(1000); System.out.println("hi"+i); } System.out.println(t.getName()+"程序的优先级是"+t.getPriority()); t.interrupt();
} } class T extends Thread{
@Override public void run() { while (true) { for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName() + "吃包子.."); } System.out.println(Thread.currentThread().getName() + "休眠中..."); try { Thread.sleep(20000); } catch (InterruptedException e) { System.out.println(Thread.currentThread().getName() + "被 interrupt了"); } } } }
|
方法第二组
应用案例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| package study.threaduse.method_;
public class ThreadMethod02 { public static void main(String[] args) throws InterruptedException { T2 t2 = new T2(); t2.start(); for (int i = 0; i <=20; i++) { Thread.sleep(1000); System.out.println("主线程(小弟)吃了"+i+"个包子"); if(i==5){ System.out.println("主线程让子线程 全部吃完"); Thread.yield();
} }
} } class T2 extends Thread{ @Override public void run() { for (int i = 0; i <=20; i++) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("子线程(大哥)吃了"+i+"个包子"); } } }
|
课堂练习
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| package study.threaduse.method_;
public class ThreadMethodExercise { public static void main(String[] args) throws InterruptedException { T3 t3 = new T3(); Thread thread = new Thread(t3); for (int i = 0; i <=10; i++) { Thread.sleep(1000); System.out.println("hi"+i); if(i==5){ thread.start(); thread.join(); } } System.out.println("主线程结束...");
} } class T3 implements Runnable{ @Override public void run() { for (int i = 0; i <=10; i++) { System.out.println("hello"+i);
try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
} System.out.println("子线程结束.."); } }
|
用户线程和守护线程
- 用户线程:也叫工作线程,当线程的任务执行完或通知方式结束。
- 守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束。
- 常见的守护线程:垃圾回收机制。
应用案例 ThreadMethod03.java
下面我们测试如何将一个线程设置成守护线程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| package study.threaduse.method_;
public class ThreadMethod03 { public static void main(String[] args) throws InterruptedException { MyDaemonThread myDaemonThread = new MyDaemonThread(); myDaemonThread.setDaemon(true); myDaemonThread.start(); for (int i = 0; i <=10; i++) { System.out.println("小可爱"+i); Thread.sleep(1000);
}
} } class MyDaemonThread extends Thread{ @Override public void run() { while (true){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("ok"); } } }
|
线程的生命周期
JDK 中用 Thread.State 枚举表示了线程的几种状态
线程状态转换图
写程序查看线程状态
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| package study.threaduse.state_;
public class ThreadState_ { public static void main(String[] args) throws InterruptedException { T t = new T(); System.out.println(t.getName() + " 状态 " + t.getState()); t.start(); while (Thread.State.TERMINATED != t.getState()) { System.out.println(t.getName() + " 状态 " + t.getState()); Thread.sleep(500);
} System.out.println(t.getName() + " 状态 " + t.getState()); } }
class T extends Thread { @Override public void run() { while (true) { for (int i = 0; i < 10; i++) { System.out.println("hi" + i); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
} break;
} }
}
|
线程的同步
先看一个问题
Synchronized
线程同步机制
- 在多线程编程,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何同一时刻,最多有一个线程访问,以保证数据的完整性。也可以这里理解:
- 线程同步,即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作。
同步具体方法
析同步原理
互斥锁
基本介绍
Java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。
每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。
关键字synchronized来与对象的互斥锁联系。当某个对象用synchronized修饰时,表明该对象在任一时刻只能由一个线程访问。
同步的局限性:导致程序的执行效率要降低。
同步方法(非静态的)的锁可以是this,也可以是其他对象(要求是同一个对象)。
同步方法(静态的)的锁为当前类本身。
使用互斥锁来解决售票问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
| package study.threaduse.ticket.synchronized_;
public class SellTicket { public static void main(String[] args) {
SellTicket02 sellTicket02 = new SellTicket02(); new Thread(sellTicket02).start(); new Thread(sellTicket02).start(); new Thread(sellTicket02).start(); } }
class SellTicket02 implements Runnable { private int tickNum = 100; private boolean loop = true; Object object =new Object();
public void sell() { synchronized (this) { if (tickNum <= 0) { System.out.println("售票结束..."); loop = false; return; } try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("窗口线程名称" + Thread.currentThread().getName() + "售出一张票" + "剩余票数为" + (--tickNum)); } } @Override public void run() { while (loop) { sell(); }
} }
|
注意事项和细节
同步方法如果没有使用static修饰:默认锁对象为this。
如果方法使用static修饰,默认锁对象:当前类.class。
实现的落地步骤:
- 需要先分析上锁的代码选择同步代码块或同步方法。
- 要求多个线程的锁对象为同一个即可!
线程的死锁
基本介绍
- 多个线程都占用了对方的锁资源,但不肯相让,导致了死锁,在编程是一定要避免死锁的发生.
应用案列
妈妈:你先完成作业,才让你玩手机。
小明:你先让我玩手机,我才完成作业。
应用案例 DeadLock_.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| package study.threaduse.ticket.synchronized_;
public class DeadLock_ { public static void main(String[] args) { DeadLockDemo A = new DeadLockDemo(true); A.setName("A线程"); DeadLockDemo B = new DeadLockDemo(false); B.setName("B线程"); A.start(); B.start();
} } class DeadLockDemo extends Thread{ static Object o1=new Object(); static Object o2=new Object(); boolean flag;
public DeadLockDemo(boolean flag) { this.flag = flag; }
@Override public void run() { if(flag){ synchronized (o1) { System.out.println(Thread.currentThread().getName() + "进入1"); synchronized (o2) { System.out.println(Thread.currentThread().getName() + "进入2"); } } } else { synchronized (o2) { System.out.println(Thread.currentThread().getName() + "进入3"); synchronized (o1) { System.out.println(Thread.currentThread().getName() + "进入4"); } } } } }
|
释放锁
下面操作会释放锁
- 当前线程的同步方法、同步代码块执行结束
- 当前线程在同步代码块、同步方法中遇到break.return。
- 案例:没有正常的完事,经理叫他修改bug,不得已出来。
- 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束
- 当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁。
- 案例:没有正常完事,觉得需要酝酿下,所以出来等会再进去。
下面操作不会释放锁
- 线程执行同步代码块或同步方法时,程序调用Thread.sleep(),Thread.yield()方 法暂停当前线程的执行,不会释放锁。
- 线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起, 该线程不会释放锁。
- 提示;应尽量避免使用suspend()和resume()来控制线程,方法不再推荐使用
本章作业
在main()方法中启动两个线程。
第1个线程循环随机打印100以内的整数。
直到第2个线程从键盘读取了”Q“命令。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
| package study.threaduse.homework;
import java.util.Scanner;
public class Homework01 { public static void main(String[] args) { A a = new A(); B b = new B(a); a.start(); b.start(); } }
class A extends Thread { private boolean loop = true;
@Override public void run() { while (loop) { System.out.println((int)(Math.random() * 100 + 1)); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("a线程退出...");
}
public void setLoop(boolean loop) { this.loop = loop; } }
class B extends Thread { private A a; private Scanner scanner = new Scanner(System.in);
public B(A a) { this.a = a; }
@Override public void run() { while (true) { System.out.println("请输入你指令(Q)表示退出:"); char key = scanner.next().toUpperCase().charAt(0); if(key == 'Q') { a.setLoop(false); System.out.println("b线程退出."); break; } } } }
|
- 有两个用户分别从同一个卡上取钱(总额:10000).
- 每次都取1000,当余额不足时,就不能取款了。
- 不能出现超取现象->线程同步问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| package study.threaduse.homework;
public class Homework02 { public static void main(String[] args) { T t = new T(); Thread thread= new Thread(t); Thread thread1 = new Thread(t); thread.setName("T1"); thread1.setName("T2"); thread.start(); thread1.start();
} }
class T implements Runnable{ private int money=10000; @Override public void run() { while (true) { synchronized (this) { if (money < 1000) { System.out.println("余额不足"); break; } money -= 1000; System.out.println(Thread.currentThread().getName() + "取出了1000块 当前余额=" + money); } try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }
|