Java高级开发

Java线程通信-线程协调API

2019-12-29  本文已影响0人  依弗布德甘

线程通信

想要实现多个线程之间的协同,如:线程执行顺序,获取某个线程执行结果等,则需要使用线程之间互相通信。

  1. 文件共享
    通过多个线程对同一文件操作的通信方式

  2. 网络共享
    如socket通信,双向的通信连接实现数据的交换

  3. 共享变量
    对同一内存或缓存中的变量进行数据操作

  4. jdk提供的线程协调API
    jdk中对于需要多线程写作完成的任务场景,提供API支持,例如:生成者 - 消费者 (线程阻塞、线程唤醒)
    细分为:suspend/resumewait/notifypark/unpark


线程协调API

  1. suspend和resume API弃用
    suspend:挂起目标线程
    resume:恢复线程执行
    弃用原因:容易出现死锁代码

    1. 在使用synchronized 同步锁的情况下,两个线程一个锁住,另一个线程无法拿到锁,造成死锁

    2.resume(恢复)先执行,suspend(挂起)后执行,将会永远处于Waiting状态,出现死锁

    /** 正常的suspend/resume */
    public void suspendResumeTest() throws Exception {
        // 启动线程
        Thread consumerThread = new Thread(() -> {
            if (baozidian == null) { // 如果没包子,则进入等待
                System.out.println("1、进入等待");
                Thread.currentThread().suspend();
            }
            System.out.println("2、买到包子,回家");
        });
        consumerThread.start();
        // 3秒之后,生产一个包子
        Thread.sleep(3000L);
        baozidian = new Object();
        consumerThread.resume();
        System.out.println("3、通知消费者");
    }

    /** 死锁的suspend/resume。 suspend并不会像wait一样释放锁,故此容易写出死锁代码 */
    public void suspendResumeDeadLockTest() throws Exception {
        // 启动线程
        Thread consumerThread = new Thread(() -> {
            if (baozidian == null) { // 如果没包子,则进入等待
                System.out.println("1、进入等待");
                // 当前线程拿到锁,然后挂起
                synchronized (this) {
                    Thread.currentThread().suspend();
                }
            }
            System.out.println("2、买到包子,回家");
        });
        consumerThread.start();
        // 3秒之后,生产一个包子
        Thread.sleep(3000L);
        baozidian = new Object();
        // 争取到锁以后,再恢复consumerThread
        synchronized (this) {
            consumerThread.resume();
        }
        System.out.println("3、通知消费者");
    }

    /** 导致程序永久挂起的suspend/resume */
    public void suspendResumeDeadLockTest2() throws Exception {
        // 启动线程
        Thread consumerThread = new Thread(() -> {
            if (baozidian == null) {
                System.out.println("1、没包子,进入等待");
                try { // 为这个线程加上一点延时
                    Thread.sleep(5000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 这里的挂起执行在resume后面
                Thread.currentThread().suspend();
            }
            System.out.println("2、买到包子,回家");
        });
        consumerThread.start();
        // 3秒之后,生产一个包子
        Thread.sleep(3000L);
        baozidian = new Object();
        consumerThread.resume();
        System.out.println("3、通知消费者");
        consumerThread.join();
    }


  1. wait和notify
    只能由同一对象锁的持有者线程调用,并且必须在synchronized块里面,否则会抛出 IllegalMonitorStateException

    wait: 当前线程进入等待,加入对象等待集合中,并且释放持有对象锁
    notify/notifyAll:唤醒一个或多个正在等待这个对象锁的线程
    死锁:虽然wait会自动解锁,但是对顺序有要去,如果在notify后执行,线程将永远处于Waiting状态

    /** 正常的wait/notify */
    public void waitNotifyTest() throws Exception {
        // 启动线程
        new Thread(() -> {
                synchronized (this) {
                    while (baozidian == null) { // 如果没包子,则进入等待
                    try {
                        System.out.println("1、进入等待");
                        this.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
            System.out.println("2、买到包子,回家");
        }).start();
        // 3秒之后,生产一个包子
        Thread.sleep(3000L);
        baozidian = new Object();
        synchronized (this) {
            this.notifyAll();
            System.out.println("3、通知消费者");
        }
    }

    /** 会导致程序永久等待的wait/notify */
    public void waitNotifyDeadLockTest() throws Exception {
        // 启动线程
        new Thread(() -> {
            if (baozidian == null) { // 如果没包子,则进入等待
                try {
                    Thread.sleep(5000L);
                } catch (InterruptedException e1) {
                    e1.printStackTrace();
                }
                synchronized (this) {
                    try {
                        System.out.println("1、进入等待");
                        this.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
            System.out.println("2、买到包子,回家");
        }).start();
        // 3秒之后,生产一个包子
        Thread.sleep(3000L);
        baozidian = new Object();
        synchronized (this) {
            this.notifyAll();
            System.out.println("3、通知消费者");
        }
    }


  1. park/unpark
    采用等待“许可/令牌” 与提供“许可/令牌” 的形式来激活线程,对执行顺序无要求

    park:获取或等待“许可“,拿到“许可“则直接跳过等待,多次调用第二次会需要等待
    unpark:为线程提供“许可”,多次调用只产生一条,不会叠加
    死锁:LockSupport.park()在同步代码块中,不会释放锁,其他线程使用该同步锁的情况下,会永远拿不到锁

    /** 正常的park/unpark */
    public void parkUnparkTest() throws Exception {
        // 启动线程
        Thread consumerThread = new Thread(() -> {
            while (baozidian == null) { // 如果没包子,则进入等待
                System.out.println("1、进入等待");
                LockSupport.park();
            }
            System.out.println("2、买到包子,回家");
        });
        consumerThread.start();
        // 3秒之后,生产一个包子
        Thread.sleep(3000L);
        baozidian = new Object();
        LockSupport.unpark(consumerThread);
        System.out.println("3、通知消费者");
    }

    /** 死锁的park/unpark */
    public void parkUnparkDeadLockTest() throws Exception {
        // 启动线程
        Thread consumerThread = new Thread(() -> {
            if (baozidian == null) { // 如果没包子,则进入等待
                System.out.println("1、进入等待");
                // 当前线程拿到锁,然后挂起
                synchronized (this) {
                    LockSupport.park();
                }
            }
            System.out.println("2、买到包子,回家");
        });
        consumerThread.start();
        // 3秒之后,生产一个包子
        Thread.sleep(3000L);
        baozidian = new Object();
        // 争取到锁以后,再恢复consumerThread
        synchronized (this) {
            LockSupport.unpark(consumerThread);
        }
        System.out.println("3、通知消费者");
    }


注意事项 - 伪唤醒

 正确的条件判断 ,使用循环检查条件

 // wait
 synchronized(obj){
  while(<条件判断>){
    obj.wait();
    ...//执行后续操作
  }
 }

 // park
 while(<条件判断>){
   LockSupport.park();
   ...//执行后续操作
 }

上一篇 下一篇

猜你喜欢

热点阅读