Loading

多线程 - 线程通信 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恢复线程执行。
如下正常情况:

import java.util.concurrent.locks.LockSupport;

public class ThreadInteration {
    public static Object baozidian =null;
    public static void main(String[] args) throws InterruptedException {
        new ThreadInteration().suspendResumeTest();
    }
    /**
     * 使用弃用的API suspend和resume 来挂起目标线程和恢复线程执行
     * 这两个api容易写出死锁的代码。
     * 1,使用同步锁的时候,因为suspend不会释放锁,这样会导致死锁。
     * 2,suspend 和 resume 的执行顺序颠倒,会导致死锁。
     */
    //正常suspend 和 resume
    public void suspendResumeTest() throws InterruptedException {
        Thread consumerThread =new Thread(()->{
            if (baozidian==null){
                System.out.println("1 进入等待,线程被挂起");
                Thread.currentThread().suspend();
                System.out.println("线程被唤醒了");
            }
            System.out.println("3 买到包子了,回家!");
        });
        consumerThread.start();
        Thread.sleep(3000L);
        //生产者创建
        baozidian=new Object();
        consumerThread.resume();
        System.out.println("2 通知消费者,消费者线程被唤醒");
    }
}    

死锁情况:

import java.util.concurrent.locks.LockSupport;

public class ThreadInteration {
    public static Object baozidian =null;
    public static void main(String[] args) throws InterruptedException {
        new ThreadInteration().suspendResumeDeadLockTest();
//        new ThreadInteration().suspendResumeDeadLockTest2();
    }
    
    /**使用同步锁导致死锁,suspend和resume不会像wait一样释放锁**/
    public void suspendResumeDeadLockTest() throws InterruptedException {
        //创建线程
        Thread consumerThread=new Thread(()->{
            if (baozidian==null){//如果没有包子,就进入等待
                //当前线程拿到锁,线程被挂起
                synchronized (this){
                    System.out.println("1 进入等待,线程被挂起");
                    Thread.currentThread().suspend();
                    System.out.println("线程被唤醒了");
                }
                
            }
            System.out.println("3 买完包子,回家");
        });
        consumerThread.start();
        Thread.sleep(2000L);
        //产生包子
        baozidian=new Object();
        //争取到锁后,再恢复consumerThread()。
        synchronized (this){
            consumerThread.resume();
        }
        System.out.println("2 通知消费者,消费者线程被唤醒");
    }
    
    /** 由于suspend/resume的调用顺序,导致程序永久死锁 **/
    public void suspendResumeDeadLockTest2() throws InterruptedException {
        Thread consumerThread=new Thread(()->{
            if (baozidian==null){
                System.out.println("1 进入线程,线程被挂起");
                try {
                    Thread.sleep(5000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //这里的suspend是运行在resume之后
                Thread.currentThread().suspend();
                System.out.println("线程被唤醒了");
            }
            System.out.println("3 买完包子,回家");
        });
        consumerThread.start();
        Thread.sleep(2000L);
        baozidian=new Object();
        consumerThread.resume();
        System.out.println("2 通知消费者,消费者线程被唤醒");
        consumerThread.join();
    }
}

API - 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

这个是许可模式,同步代码中不能主动释放锁,许可模式是一个标记位,不能叠加

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 通知消费者,消费者线程被唤醒");
    }
}

伪唤醒

/**
     * 伪唤醒:前面使用了if (baozidian==null) 来判断是否进入等待状态,是错误的。是指并非由notify/unpack来唤醒的,由更底层的原因被唤醒。
     * 官方建议使用while (baozidian==null) 来判断是否进入等待状态。
     * 因为:处于底层的线程可能会收到错误警报和伪唤醒,如果不在循环中检查,程序可能会在没有满足条件的情况下退出
     * 解决方案就是将上面的if全部改成while
     */
    public void waitNotifyGoodTest() throws InterruptedException {
        new Thread(()->{
            synchronized (this){
                //将while放入同步锁中判断
                while (baozidian==null){
                    System.out.println("1 进入等待,线程将会被挂起");
                    try {
                        this.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("线程被唤醒了");
            
            }
            System.out.println("3 买到包子,回家");
        }).start();
    
        Thread.sleep(2000L);
        baozidian=new Object();
        synchronized (this){
            this.notify();
        }
        System.out.println("2 通知消费者,消费者线程被唤醒");
    }
posted @ 2019-10-15 19:39  Changing_now  阅读(343)  评论(0编辑  收藏  举报