『每日一问』LockSupport怎么实现线程等待、唤醒

wait/notify实现线程通信

实现线程通信呢,比较传统的办法就是使用synchronized关键字获取对象锁之后,结合Object自带的wait/notify方法来实现。
一个简单例子如下:

	public static void main(String[] args) throws InterruptedException {
        ObjectTar objectTar = new ObjectTar();
        new WaitThread("WaitThread", objectTar).start();
        Thread.sleep(1000);
        new NotifyThread("NotifyThread", objectTar).start();
    }

    @Data
    private static class ObjectTar {
        private String name;
    }


   private static class WaitThread extends Thread {

        final ObjectTar objectTar;

        WaitThread(String name, ObjectTar tar) {
            super(name);
            this.objectTar = tar;
        }

        @Override
        public void run() {
            synchronized (objectTar) {
                System.out.println(getName() + "获取锁,准备执行自己的业务逻辑");
                if (StringUtils.isEmpty(objectTar.getName())) {
                    System.out.println(getName() + "发现目前的状况不能满足自己的要求,于是执行objectTar.wait()");
                    try {
                        objectTar.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(getName() + "从objectTar.wait()返回,完成自己的业务逻辑," + objectTar.getName());
            }
        }
    }

    private static class NotifyThread extends Thread {

        final ObjectTar objectTar;

        NotifyThread(String name, ObjectTar tar) {
            super(name);
            this.objectTar = tar;
        }

        @Override
        public void run() {
            synchronized (objectTar) {
                System.out.println(getName() + "获取锁,完成了objectTar的name初始化");
                objectTar.setName("黄焖鸡");
                objectTar.notifyAll();
                System.out.println(getName() + "执行完notify之后,继续执行自己的业务逻辑");
            }
        }
    }

打印

WaitThread获取锁,准备执行自己的业务逻辑
WaitThread发现目前的状况不能满足自己的要求,于是执行objectTar.wait()
NotifyThread获取锁,完成了objectTar的name初始化
NotifyThread执行完notify之后,继续执行自己的业务逻辑
WaitThread从objectTar.wait()返回,完成自己的业务逻辑,黄焖鸡

wait和notify的通知实现机制如下图,因为这不是本文猪脚,所以就不展开说了:)
在这里插入图片描述
通过上边的示例呢,可以发现使用wait/notify来实现通信机制有三个比较明显的缺点

  1. wait/notify都是Object中的方法,使用这两个方法之前必须是先获取到了这个对象的锁,这也就限制了其适用场合,那就是必须在同步代码块中。
  2. notify是随机唤醒一个线程,notifyAll是唤醒所有线程,没有办法去指定一个线程唤醒。
  3. 如果notify执行在前,wait执行在后,进入wait的判断逻辑如果有问题,那么线程可能会一直在阻塞状态
使用LockSupport实现线程通信

一个简单的示例实现wait/notify相同的效果

    public static void main(String[] args) throws InterruptedException {
        ObjectTar objectTar = new ObjectTar();
        ParkThread parkThread = new ParkThread("ParkThread", objectTar);
        parkThread.start();
        Thread.sleep(1000);
   		//注意这里传入了parkThread
        new UnParkThread("UnParkThread", objectTar, parkThread).start();
    }

    @Data
    private static class ObjectTar {
        private String name;
    }


    private static class ParkThread extends Thread {

        final ObjectTar objectTar;

        ParkThread(String name, ObjectTar tar) {
            super(name);
            this.objectTar = tar;
        }

        @Override
        public void run() {
            System.out.println(getName() + "获取锁,准备执行自己的业务逻辑");
            if (StringUtils.isEmpty(objectTar.getName())) {
                System.out.println(getName() + "发现目前的状况不能满足自己的要求,于是执行LockSupport.park()");
                LockSupport.park();
            }
            System.out.println(getName() + "从LockSupport.park()返回,完成自己的业务逻辑," + objectTar.getName());
        }
    }

    private static class UnParkThread extends Thread {

        final ObjectTar objectTar;

        Thread parkThread;

        UnParkThread(String name, ObjectTar tar, Thread parkThread) {
            super(name);
            this.objectTar = tar;
            this.parkThread = parkThread;
        }

        @Override
        public void run() {
            System.out.println(getName() + "获取锁,完成了objectTar的name初始化");
            objectTar.setName("黄焖鸡");
            //注意unpark传入了一个线程对象
            LockSupport.unpark(parkThread);
            System.out.println(getName() + "执行完LockSupport.unpark()之后,继续执行自己的业务逻辑");
        }
    }

打印

1:ParkThread获取锁,准备执行自己的业务逻辑
2:ParkThread发现目前的状况不能满足自己的要求,于是执行LockSupport.park()
3:UnParkThread获取锁,完成了objectTar的name初始化
4:ParkThread从LockSupport.park()返回,完成自己的业务逻辑,黄焖鸡      
5:UnParkThread执行完LockSupport.unpark()之后,继续执行自己的业务逻辑

注意4、5的顺序是不一定的,这点和wait/notify是不一样的。

知识点

  • LockSupport.park(),park是有停车的意思,那么这个方法就是相当于线程“停车”了,那就是“阻塞”了呗
  • LockSupport.unpark(Thread thread),unpark那就是“不停车”了,滚蛋吧,参数有个线程,可以指定具体的线程从“阻塞”中释放出来
和wait/notify的区别

从上边的示例中呢,我们可以看出来下边的区别了:

  1. LockSupport实现线程之间的通信,不需要借助Object来达到目的
  2. LockSupport可以实现对指定线程从“阻塞”状态中释放
  3. 还有一点其实例子中没有表现出来,那就是LockSupport不需要担心阻塞和唤醒的操作顺序。什么意思呢,就是说我们代码中如果不是先执行park来阻塞线程,然后等另一个线程unpark来唤醒这个阻塞线程,而是先unpark,而后执行park,那么park是不会阻塞的,会接着执行后续业务逻辑;这点和wait/notify是不一样的,wait/notify实现线程的阻塞唤醒是有严格顺序要求的,必须先wait,而后才能notify。如果先notify,那么另一个线程wait之后可能永久阻塞住【当然这种情况在进入wait处业务逻辑判断没问题的时候是不会出现的】。
原理浅析

park和unpark都是native方法,底层都是C实现的。这里就简单来说下:

  • 每个线程有一个Park实例,里边有个字段 “volatile int _counter”,它等于0的时候表示没有获取“凭证”,线程要阻塞。他等于1的时候,表示线程获取的“凭证”,也就是相当传统意义上的获取了锁标记,也就可以执行接下来的业务逻辑。
  • park方法每执行一遍,都会消费一个“凭证”,相当于“_counter- - ”
  • unpark方法如果连续执行,且期间没有park来消费“凭证”的话,只会生成一个凭证。也就是说不管unpark执行多少遍,每个线程都只有一个“凭证”。
总结

LockSupport是JDK中用来实现线程阻塞和唤醒的通信工具。相比于wait/notify,它更加灵活,不依赖对象的同时,可以指定任何线程进行唤醒,并且不担心阻塞和唤醒的操作顺序,但是需要注意,连续的多次唤醒的效果和一次唤醒是一样的。

JDK并发包下的锁和其他同步工具的底层中大量使用了LockSupport来实现线程之间的通信,掌握它的原理和用法对我们更好的理解这些JUC底层实现是有帮助的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

高级摸鱼工程师

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

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

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

打赏作者

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

抵扣说明:

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

余额充值