并发编程-LockSupport和wait&notify的区别

基础 专栏收录该内容
8 篇文章 0 订阅

LcokSupport

定义

LockSupport是一个在JUC包里的线程阻塞工具类,所有的方法都是静态方法,主要用途是让线程在任意位置阻塞,广泛应用在AQS和各种JUC的锁中

常用的方法和基本原理了解

public static void park(Object blocker); // 暂停当前线程
public static void parkNanos(Object blocker, long nanos); // 暂停当前线程,不过有超时时间的限制
public static void parkUntil(Object blocker, long deadline); // 暂停当前线程,直到某个时间
public static void park(); // 无期限暂停当前线程
public static void parkNanos(long nanos); // 暂停当前线程,不过有超时时间的限制
public static void parkUntil(long deadline); // 暂停当前线程,直到某个时间
public static void unpark(Thread thread); // 恢复当前线程

可以看到这里的方法主要就是park和unpark也就是阻塞和唤醒的作用
park指的就是停车
unpark指的就是放行
这里的blocker指的一般就是当前的线程对象,方便dump时分析问题,
我们可以尝试先在代码使用下这些方法

static class ParkThread implements Runnable{

        @Override
        public void run() {
            System.out.println( Thread.currentThread().getName() + "开始线程阻塞");
            LockSupport.park();
            System.out.println(Thread.currentThread().getName() + "结束线程阻塞");
        }
    }
    public static void main(String[] args) {
        ParkThread parkThread = new ParkThread();
        Thread t1=new Thread(parkThread);
        Thread t2=new Thread(parkThread);
        t1.start();
        LockSupport.unpark(t1);
        t2.start();
        LockSupport.unpark(t2);
    }

这样代码运行是没有问题,那么如果先执行unpark方法,后执行park方法会怎么样呢

static class ParkThread implements Runnable{

        @Override
        public void run() {
            System.out.println( Thread.currentThread().getName() + "开始线程阻塞");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            LockSupport.park();
            System.out.println(Thread.currentThread().getName() + "结束线程阻塞");
        }
    }
    public static void main(String[] args) {
        ParkThread parkThread = new ParkThread();
        Thread t1=new Thread(parkThread);
        Thread t2=new Thread(parkThread);
        t1.start();
        LockSupport.unpark(t1);
        t2.start();
        LockSupport.unpark(t2);
    }

运行后会发现,线程并没有阻塞,而是和之前一样执行成功了,这个就是和notify和wait最大的区别了,因为notify的唤醒顺序是不能颠倒的.

那么LockSupport是怎么实现这个功能的呢?这里是基本介绍,我就不和大家分析源码了,给大家描述一下流程:

源码里其实是通过一个_counter变量来控制的
_counter可以作为一个凭证
unpark方法的时候把_counter置为1
park方法判断如果_counter=1的话就不堵塞,并且把_counter设置为0,
换句话说当_counter=1的时候,调用park方法是不会阻塞的, 所以调用多次unpark方法后,调用一次park方法,不会堵塞
但是调用多次unpark方法后,调用多次park方法就会堵塞

应用

在之前分析AQS源码ReentrantLock加锁解锁源码详细解析的时候,我们看到过LockSupport的实际应用场景,其实在Condition和CountDownLatch等常用的并发类中用的也都是LockSupport

notify&wait

定义

notify和wait都是object的方法,一般被用作线程间的协作,也可以实现线程的等待和唤醒,功能和LockSupport较为类似

应用示例

private static Object obj = new Object();

    public static void main(String[] args) throws InterruptedException {
        new Thread(new WaitThread(), "wait-1").start();
        Thread.sleep(100);
        new Thread(new NotifyThread(), "notify-1").start();
    }

    static class WaitThread implements Runnable{

        @Override
        public void run() {
            synchronized (obj){
                System.out.println("start wait");
                try {
                    obj.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("end wait");
            }

        }
    }
    static class NotifyThread implements Runnable{

        @Override
        public void run() {
            synchronized (obj){
                System.out.println("start notify");
                obj.notify();
                System.out.println("end notify");
            }
        }
    }

需要注意的是notify和wait必须在同步代码块中使用,否则就会出现IllegalMonitorStateException

start wait
start notify
Exception in thread "notify-1" java.lang.IllegalMonitorStateException
	at java.lang.Object.notify(Native Method)
	at com.example.skill.lock.NotifyTest$NotifyThread.run(NotifyTest.java:40)
	at java.lang.Thread.run(Thread.java:748)

基本原理

wait方法:

  • 把当前线程封装成一个node
  • 通过objectmonitor::addwaiter方法将node添加到_WaitSet列表中
  • 通过ObjectMonitor:exit方法释放当前的ObjectMonitor对象,这样其他竞争线程就可以获取该ObjectMonitor对象
  • 最终还是代调用了park方法挂起线程

notify方法则是随机唤醒等待池中的一个线程

两者之间的区别

相同点:

  • wait&notift和LockSupport都可以用作线程之间的协作
  • 低层其实都是调用了park方法来挂起线程

不同点:

  • wait&notify必须在同步代码块中使用,LockSupport则可以在任意场合使用
  • wait&notify不能颠倒顺序使用,LockSupport可以颠倒park和unpark的顺序
  • wait&notify不能唤醒指定的线程,但是LockSupport可以
  • notifyAll方法可以唤醒所有等待的线程,但是LockSupport只能唤醒单个线程

扩展1(如何使用wait&notify实现一个阻塞队列)

 public static void main(String[] args) throws InterruptedException {
        NotifyBlockQueue notifyBlockQueue = new NotifyBlockQueue(5);
        for (int i = 0; i < 10; i++) {
            final int a = i;
            new Thread(() -> {
                try {
                    notifyBlockQueue.put(a);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        }
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                try {
                    System.out.println("从队列取出" + notifyBlockQueue.get());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();

        }

    }
    static class NotifyBlockQueue{
        //模拟队列
        private LinkedList linkedList= new LinkedList();
        //最大容量
        private int maxSize;

        public NotifyBlockQueue(int maxSize){
            this.maxSize = maxSize;
        }

        //队列是否满了
        public boolean isFull(){
            return linkedList.size() == maxSize;
        }

        //队列是否为空
        public boolean isEmpty(){
            return linkedList.isEmpty();
        }
        public synchronized void put(Object value) throws InterruptedException {
            //如果队列满了就一直等待
            while (isFull()){
                this.wait();
            }
            linkedList.add(value);
            System.out.println("开始往队列放入" + value);
            this.notifyAll();
        }
        public synchronized Object get() throws InterruptedException {
            while (isEmpty()){
                this.wait();
            }
            Object o = linkedList.removeFirst();
            this.notifyAll();
            return o;
        }
    }
  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 打赏
    打赏
  • 扫一扫,分享海报

参与评论 您还未登录,请先 登录 后发表或查看评论
©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页

打赏作者

紫枫231

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值