zoukankan      html  css  js  c++  java
  • jdk提供的线程协调API suspend/resume wait/notify park/unpark

    线程通信(如 线程执行先后顺序,获取某个线程执行的结果等)有多种方式:

    • 文件共享 线程1 --写入--> 文件 < --读取-- 线程2
    • 网络共享
    • 变量共享 线程1 --写入--> 主内存共享变量 < --读取-- 线程2
    • jdk提供的线程协调API suspend/resume wait/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     }

    转载:https://www.cnblogs.com/junjiedeng/p/11679841.html

  • 相关阅读:
    接口实际上是定义一个规范、标准
    类必须实现接口中的方法,否则其为一抽象类
    JAVA的核心概念:接口(interface)
    子类的方法必须覆盖父类的抽象方法
    Abstract可以将子类的共性最大限度的抽取出来,放在父类中,以提高程序的简洁性
    如果将一个类设置为abstract,则此类必须被继承使用
    在JAVA中利用public static final的组合方式对常量进行标识
    final可以修饰类、属性、方法
    覆盖不适用于静态方法
    静态方法不需要有对象,可以使用类名调用
  • 原文地址:https://www.cnblogs.com/zhanvo/p/11795229.html
Copyright © 2011-2022 走看看