线程通信(如 线程执行先后顺序,获取某个线程执行的结果等)有多种方式:
- 文件共享 线程1 --写入--> 文件 < --读取-- 线程2
- 网络共享
- 变量共享 线程1 --写入--> 主内存共享变量 < --读取-- 线程2
- jdk提供的线程协调API
suspend/resumewait/notify park/unpark。
线程协作 - JDK API
线程协作的典型场景:生产者-消费者 模型(线程阻塞、线程唤醒)
如:线程1去卖包子,没有包子,则不再执行,线程2生产包子,通知线程1继续执行
API - 被弃用 suspend/resume
suspend挂起目标线程,resume恢复线程执行。
如下正常情况:
1 import java.util.concurrent.locks.LockSupport; 2 3 public class ThreadInteration { 4 public static Object baozidian =null; 5 public static void main(String[] args) throws InterruptedException { 6 new ThreadInteration().suspendResumeTest(); 7 } 8 /** 9 * 使用弃用的API suspend和resume 来挂起目标线程和恢复线程执行 10 * 这两个api容易写出死锁的代码。 11 * 1,使用同步锁的时候,因为suspend不会释放锁,这样会导致死锁。 12 * 2,suspend 和 resume 的执行顺序颠倒,会导致死锁。 13 */ 14 //正常suspend 和 resume 15 public void suspendResumeTest() throws InterruptedException { 16 Thread consumerThread =new Thread(()->{ 17 if (baozidian==null){ 18 System.out.println("1 进入等待,线程被挂起"); 19 Thread.currentThread().suspend(); 20 System.out.println("线程被唤醒了"); 21 } 22 System.out.println("3 买到包子了,回家!"); 23 }); 24 consumerThread.start(); 25 Thread.sleep(3000L); 26 //生产者创建 27 baozidian=new Object(); 28 consumerThread.resume(); 29 System.out.println("2 通知消费者,消费者线程被唤醒"); 30 } 31 }
死锁情况:
1 import java.util.concurrent.locks.LockSupport; 2 3 public class ThreadInteration { 4 public static Object baozidian =null; 5 public static void main(String[] args) throws InterruptedException { 6 new ThreadInteration().suspendResumeDeadLockTest(); 7 // new ThreadInteration().suspendResumeDeadLockTest2(); 8 } 9 10 /**使用同步锁导致死锁,suspend和resume不会像wait一样释放锁**/ 11 public void suspendResumeDeadLockTest() throws InterruptedException { 12 //创建线程 13 Thread consumerThread=new Thread(()->{ 14 if (baozidian==null){//如果没有包子,就进入等待 15 //当前线程拿到锁,线程被挂起 16 synchronized (this){ 17 System.out.println("1 进入等待,线程被挂起"); 18 Thread.currentThread().suspend(); 19 System.out.println("线程被唤醒了"); 20 } 21 22 } 23 System.out.println("3 买完包子,回家"); 24 }); 25 consumerThread.start(); 26 Thread.sleep(2000L); 27 //产生包子 28 baozidian=new Object(); 29 //争取到锁后,再恢复consumerThread()。 30 synchronized (this){ 31 consumerThread.resume(); 32 } 33 System.out.println("2 通知消费者,消费者线程被唤醒"); 34 } 35 36 /** 由于suspend/resume的调用顺序,导致程序永久死锁 **/ 37 public void suspendResumeDeadLockTest2() throws InterruptedException { 38 Thread consumerThread=new Thread(()->{ 39 if (baozidian==null){ 40 System.out.println("1 进入线程,线程被挂起"); 41 try { 42 Thread.sleep(5000L); 43 } catch (InterruptedException e) { 44 e.printStackTrace(); 45 } 46 //这里的suspend是运行在resume之后 47 Thread.currentThread().suspend(); 48 System.out.println("线程被唤醒了"); 49 } 50 System.out.println("3 买完包子,回家"); 51 }); 52 consumerThread.start(); 53 Thread.sleep(2000L); 54 baozidian=new Object(); 55 consumerThread.resume(); 56 System.out.println("2 通知消费者,消费者线程被唤醒"); 57 consumerThread.join(); 58 } 59 }
API - wait/notify
使用wait/notify存在的问题,如果有需求需要提前通知唤醒,然后才执行挂起这里是解决不了的。举个例子来说:
大卡车在通过关卡的时候,会被拦下。然后经过关卡警卫人员确认放行才能通过,但是我们这里可能会提前指定某某某车辆可以通过这个关卡。
import java.util.concurrent.locks.LockSupport; public class ThreadInteration { public static Object baozidian =null; public static void main(String[] args) throws InterruptedException { new ThreadInteration().waitNotifyTest(); // new ThreadInteration().waitNotifyDeadLockTest(); } /** * API推荐的 wait/notify 机制来挂起线程和唤醒线程 * 这些方法一定要是在同一锁对象的持有者线程调用。也就是写在同步代码块里面,否则会抛出IllegalMonitorStateException. * wait方法就是将线程等待,调用wait就是把对象加入到 等待集合 中。并且放弃当前持有的锁对象 * notify/notify唤醒一个或者所有正在等待这个对象锁的进程。 * * wait虽然会释放锁,但是对调用的顺序有要求。如果notify先与wait调用,线程会一直处于waiting状态。 */ public void waitNotifyTest() throws InterruptedException { new Thread(()->{ if (baozidian==null){ System.out.println("1 进入等待,线程将会被挂起"); synchronized (this){//顺序1 获取到锁 try { this.wait();//顺序2 线程挂起,释放了锁 } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("线程被唤醒了"); } System.out.println("3 买到包子,回家"); }).start(); Thread.sleep(2000L); baozidian=new Object(); synchronized (this){//顺序3 主线程拿到了锁 this.notify();//顺序4 主线程进行唤醒 } System.out.println("2 通知消费者,消费者线程被唤醒"); } public void waitNotifyDeadLockTest() throws InterruptedException { new Thread(()->{ if (baozidian==null){ try { Thread.sleep(5000L);//顺序1 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("1 进入等待,线程被挂起"); synchronized (this){ try { this.wait();//顺序4 先唤醒了,再进行休眠。导致死锁 } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("线程被唤醒了"); } System.out.println("3 买到包子,回家"); }).start(); Thread.sleep(2000L);//顺序2 baozidian=new Object(); synchronized (this){ this.notify();//顺序3 } System.out.println("2 通知消费者,消费者线程被唤醒"); } }
API - park/unpark
Park、Unpark就能解决我们上边提到的提前通知的问题。我们线程依然可以正常的被唤醒。
这个是许可模式,同步代码中不能主动释放锁,许可模式是一个标记位,不能叠加 import java.util.concurrent.locks.LockSupport; public class ThreadInteration { public static Object baozidian =null; public static void main(String[] args) throws InterruptedException { new ThreadInteration().parkUnparkTest(); // new ThreadInteration().parkUnparkDeadLockTest(); } /** * 线程调用 park 表示线程等待"许可",unpack 表示为指定的线程提供"许可" * park和 unpark 对调用顺序没有要求。 * 多次调用 unpack 之后,再调用 pack ,线程会立即执行。 * 但是这个"许可"不是叠加的,是一个标志位。 * 例如多次调用了 unpack 这个时候也只有一个"许可",这个时候调用一次 park 就会拿到"许可"直接运行。后面的 park 还是得继续等待 */ public void parkUnparkTest() throws InterruptedException { Thread consumerThread=new Thread(()->{ if (baozidian==null){ // try { // Thread.sleep(5000L); // } catch (InterruptedException e) { // e.printStackTrace(); // } System.out.println("1 进入等待,线程被挂起"); LockSupport.park(); System.out.println("线程被唤醒了"); } System.out.println("3 买到包子,回家"); }); consumerThread.start(); Thread.sleep(2000L); baozidian=new Object(); LockSupport.unpark(consumerThread); System.out.println("2 通知消费者,消费者线程被唤醒"); } /** park/unpark 不能自动释放锁**/ public void parkUnparkDeadLockTest() throws InterruptedException { Thread consumerThread=new Thread(()->{ if (baozidian==null){ System.out.println("1 进入等待,线程被挂起"); synchronized (this){//这个时候park获取了锁,然后挂起了。没有及时释放锁导致后面的unpark获取不到锁,就执行不了unpark LockSupport.park(); } System.out.println("线程被唤醒了"); } System.out.println("3 买到包子,回家"); }); consumerThread.start(); Thread.sleep(2000L); baozidian=new Object(); synchronized (this){ LockSupport.unpark(consumerThread); } System.out.println("2 通知消费者,消费者线程被唤醒"); } }
伪唤醒
伪唤醒这里我也找了很多的相关文章,最终也没有觉得可以很直观的理解到底是怎么被伪唤醒。怎么说吧,这种事情也不容易遇到;就相当于诈尸一下,本来挂起睡得好好的,突然就活了。。。
1 /** 2 * 伪唤醒:前面使用了if (baozidian==null) 来判断是否进入等待状态,是错误的。是指并非由notify/unpack来唤醒的,由更底层的原因被唤醒。 3 * 官方建议使用while (baozidian==null) 来判断是否进入等待状态。 4 * 因为:处于底层的线程可能会收到错误警报和伪唤醒,如果不在循环中检查,程序可能会在没有满足条件的情况下退出 5 * 解决方案就是将上面的if全部改成while 6 */ 7 public void waitNotifyGoodTest() throws InterruptedException { 8 new Thread(()->{ 9 synchronized (this){ 10 //将while放入同步锁中判断 11 while (baozidian==null){ 12 System.out.println("1 进入等待,线程将会被挂起"); 13 try { 14 this.wait(); 15 } catch (InterruptedException e) { 16 e.printStackTrace(); 17 } 18 } 19 System.out.println("线程被唤醒了"); 20 21 } 22 System.out.println("3 买到包子,回家"); 23 }).start(); 24 25 Thread.sleep(2000L); 26 baozidian=new Object(); 27 synchronized (this){ 28 this.notify(); 29 } 30 System.out.println("2 通知消费者,消费者线程被唤醒"); 31 }