线程通讯方式

JDK提供的线程协调API

suspend/resume(不推荐)、wait/notify、park/unpark


前言

JDK中对于需要多线程协作完成某一任务的场景,提供了对应的API支持。多线程写作的典型场景是:生产者-消费者模型(线程阻塞、线程唤醒)。

示例:线程1去蛋挞店买蛋挞,没有蛋挞,则不再执行。线程2生产蛋挞,通知线程1继续执行。


一、suspend/resume(不推荐,被JDK弃用)

调用suspend挂起目标线程,通过resume可以恢复线程执行。

1、suspendResume()方法是正常执行,线程1(消费者)能买到蛋挞吃下午茶

2、 suspendResumeSynchronizedDead()属于死锁,线程1不能买到蛋挞

 3、suspendResumeOrderDead(): resume 先执行,导致线程1买不到蛋挞,因为suspend比resume后执行

二、wait/notify

wait/notify这些方法只能由同一对象锁的持有者线程调用(写在同步代码块里),否则会抛异常。

wait()导致当前线程等待,加入该对象的等待集合中,并且释放当前持有的对象锁。notify、notifyAll方法唤醒一个/多个正在等待这个对象锁的线程。

注意:虽然notify会释放锁,但是有顺序要求,如果notify被调用之后才调用wait(),线程会永远处于WAITING状态。

示例:

1、在同步代码块中使用wait(),线程挂起,然后生产线程获取到锁通知消费者,然后在执行剩下消费者线程的代码。

 2、线程死锁状态,wait()在notify()后面执行就会存在死锁。消费者线程先睡眠5s,此时生产者已经获取到锁,并已经通知消费者,最后才到消费者线程挂起释放锁,线程处于等待状态,一直等待。。

三、park/unpark

(java.util.concurrent.locks下的LockSupport的方法)

线程调用park是等待“许可”,unpark方法为指定线程提供“许可”。park、unpark方法的调用顺序不做要求,顺序随意。

注意:多次调用unpark后在调用park线程会直接运行。但是不会叠加,就是说,连续多次调用park方法,第一次的park会拿到“许可”直接运行,后续的调用会进入等待。

示例:

1、park后线程等待,等待unpark为线程提供许可,然后消费者开始继续执行。(此处,park和unpark的先后顺序对执行效果没有影响,最后都能买到蛋挞)

2、park、unpark死锁 ,在同步代码块中存在死锁的问题(park不会释放锁,所以会导致死锁)。

消费者线程 中的同步代码块中,park后,并未释放this锁对象,然后生产者获取不到锁,最后死锁。

伪唤醒问题:警告,之前的代码中使用if来判断,是否进入等待状态是错误的!官方建议在循环中检查等待条件,因为处于等待状态的线程可能会收到错误警报和伪唤醒,如果不在循环中检查等待条件,程序就可能在没有满足结束条件的情况下退出。伪唤醒是指线程并非notify、notifyall、unpark等api调用唤醒而是更底层原因导致的。(具体原因我也懂)

wait()方法中有提示,防止伪唤醒的方式。

 完整代码示例:



/**
 * 线程协作通信的三种方式:suspend/resume(不推荐)、wait/notify、park/unpark
 */

import java.util.concurrent.locks.LockSupport;

/**
 * 场景:线程1去蛋挞店买蛋挞,没有蛋挞,则不再执行。线程2生产蛋挞,通知线程1继续执行。
 */
public class ThreadCommunicateDemo {
    /** 蛋挞店 */
    public static Object eggTart = null;


    public void suspendResume() throws InterruptedException {

        Thread consumerThread = new Thread(() -> {
            while(eggTart == null) {
                System.out.println("1、等待购买蛋挞。。。。");
                Thread.currentThread().suspend();
            }
            System.out.println("2、买到蛋挞啦!下午茶开吃");
        });
        consumerThread.start();
        //5s后生产蛋挞
        Thread.sleep(5000L);
        eggTart = new Object();
        consumerThread.resume();
        System.out.println("3、通知consumer");
    }

    /**
     * 在同步代码块中使用suspend/resume,导致死锁例子(suspend并不会像wait一样释放锁)
     */
    public void suspendResumeSynchronizedDead() throws InterruptedException {
        Thread consumerThread = new Thread(() -> {
            while(eggTart == null) {
                System.out.println("1、等待购买蛋挞。。。。");
                synchronized (this){
                    Thread.currentThread().suspend();
                }
            }
            System.out.println("2、买到蛋挞啦!下午茶开吃");
        });
        consumerThread.start();
        //5s后生产蛋挞
        Thread.sleep(5000L);
        eggTart = new Object();
        //争取到锁以后,再恢复consumerThread
        synchronized (this){
            consumerThread.resume();
        }
        System.out.println("3、通知consumer");
    }

    /**
     * resume 比 suspend先执行,导致程序永久挂起
     */
    public void suspendResumeOrderDead() throws InterruptedException {
        Thread consumerThread = new Thread(() -> {
            while(eggTart == null) {
                System.out.println("1、等待购买蛋挞。。。。");
                try {
                    Thread.sleep(5000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Thread.currentThread().suspend();
            }
            System.out.println("2、买到蛋挞啦!下午茶开吃");
        });
        consumerThread.start();
        //5s后生产蛋挞
        Thread.sleep(2000L);
        eggTart = new Object();
        consumerThread.resume();
        System.out.println("3、通知consumer");
    }
    /**
     * wait/notify 正常方法
     */
    public void waitNotify() throws InterruptedException {
        new Thread(() -> {
            while(eggTart == null) {
                synchronized (this){
                    System.out.println("1、等待购买蛋挞。。。。");
                    try {
                        //wait会释放锁
                        this.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
            System.out.println("2、买到蛋挞啦!下午茶开吃");
        }).start();
        //2s后生产蛋挞
        Thread.sleep(2000L);
        eggTart = new Object();
        synchronized (this){
            this.notifyAll();
            System.out.println("3、通知consumer");
        }
    }
    /**
     * wait/notify 死锁,wait()在notify后面执行就会存在死锁
     */
    public void waitNotifyOrderDead() throws InterruptedException {
        new Thread(() -> {
            while(eggTart == null) {
                try {
                    Thread.sleep(5000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (this){
                    System.out.println("1、等待购买蛋挞。。。。");
                    try {
                        this.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
            System.out.println("2、买到蛋挞啦!下午茶开吃");
        }).start();
        //2s后生产蛋挞
        Thread.sleep(2000L);
        eggTart = new Object();
        synchronized (this){
            this.notifyAll();
            System.out.println("3、通知consumer");
        }
    }
    /**
     * park/unpark 正常方法
     */
    public void parkUnpark() throws InterruptedException {
      Thread consumerThread = new Thread(() -> {
            while(eggTart == null) {
                try {
                    Thread.sleep(5000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("1、等待购买蛋挞。。。。");
                LockSupport.park();
            }
            System.out.println("2、买到蛋挞啦!下午茶开吃");
        });
        consumerThread.start();
        //2s后生产蛋挞
        Thread.sleep(2000L);
        eggTart = new Object();
        //unpark需要指定线程,park和unpark没有顺序,先后谁都可以
        LockSupport.unpark(consumerThread);
        System.out.println("3、通知consumer");
    }
    /**
     * park/unpark 死锁,在同步代码块中存在死锁的问题(park不会释放锁,所以会导致死锁)
     */
    public void parkUnparkDead() throws InterruptedException {
        Thread consumerThread = new Thread(() -> {
            while(eggTart == null) {
                System.out.println("1、等待购买蛋挞。。。。");
                // 当前线程拿到锁,然后挂起
                synchronized (this){
                    //park不会释放锁,所以会导致死锁
                    LockSupport.park();
                }
            }
            System.out.println("2、买到蛋挞啦!下午茶开吃");
        });
        consumerThread.start();
        //2s后生产蛋挞
        Thread.sleep(2000L);
        eggTart = new Object();
        // 需要争取到锁以后,再恢复consumerThread
        synchronized (this){
            LockSupport.unpark(consumerThread);
        }
        System.out.println("3、通知consumer");
    }
    public static void main(String[] args) throws InterruptedException {
        ThreadCommunicateDemo threadCommunicateDemo = new ThreadCommunicateDemo();
//        threadCommunicateDemo.suspendResume();
//        threadCommunicateDemo.suspendResumeSynchronizedDead();
//        threadCommunicateDemo.suspendResumeOrderDead();
//        threadCommunicateDemo.waitNotify();
//        threadCommunicateDemo.waitNotifyOrderDead();
        threadCommunicateDemo.parkUnpark();
//        threadCommunicateDemo.parkUnparkDead();
    }
}

补充: 线程封闭:多线程访问共享变量时,涉及到线程间数据同步问题,但是并不是所有时候都需要用到共享数据,所以就有了线程封闭。数据被封闭都自己的线程中,就不需要同步,这中将数据封闭在线程中而避免使用同步的技术就叫做线程封闭。线程封闭的具现:ThreadLocal、局部变量(栈封闭)。

ThreadLocal:它是java里特殊的变量。它是一个线程级别变量,每个线程都有一个ThreadLocal就是每个线程都拥有了自己独立的一个变量,在并发模式下是绝对安全的变量

ThreadLocal原理:会自动在每个线程上创建一个副本,副本之间彼此独立互不影响。可以理解为JVM维护了一个Map<Thread,T>每个线程都要用到这个T的时候,用当前的线程去Map里面取值。

例子:执行结果如下,实现了线程封闭。

 

 

 

总结

1、suspend/resume对调用顺序有要求,也要开发自己注意锁的释放。这个被弃用的API, 容易死锁,也容易导致永久挂起。
2、wait/notify要求再同步关键字里面使用,免去了死锁的困扰,但是一定要先调用wait,再调用notify,否则永久等待了
3、 park/unpark没有顺序要求,但是park并不会释放锁,所有再同步代码中使用要注意
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值