Java线程通信-线程协调API

线程通信

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

  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、通知消费者");
    }


注意事项 - 伪唤醒

  • 伪唤醒
    指线程并非因notify、notifyall、unpark等api调用而唤醒,是更底层的原样导致的。

  • 在线程代码中使用if语句来判断是否进入等待状态,是错误的

  • 官方建议应该在循环中检查等待条件,原因是处于等待状态的线程可能会收到错误的报警和伪唤醒,导致程序没有满足条件的情况下自动唤醒

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

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

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

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  •   在多线程环境中,多个线程之间互相协作,以达到高效实现程序功能的目的,比如某些多线程程序要求线程执行有先后顺序、...
    小胡_鸭阅读 414评论 0 1
  •   谈到并发我们就会想到多线程,要想实现多个线程之间的协同,如:线程执行先后顺序、获取某个线程执行的结果等等。都涉...
    TodoCoder阅读 562评论 0 4
  • 线程通信的方法 程序在使用多线程执行任务时,经常需要线程之间协同工作。此时,我们需要了解线程通信的手段。 线程通信...
    叠最厚的甲阅读 744评论 0 0
  • 线程通信 线程通信指的是多个线程在运行的期间,相互之间的数据交互协作。 1.通信方式 实现多个线程直接的协作,涉及...
    JuneWool阅读 296评论 0 0
  • 通信的方式 要想实现多个线程之间的协同,如:线程执行先后顺序、获取某个线程执行的结果等等。涉及到线程之间的相互通信...
    薛尧笔记阅读 84评论 0 0