前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >了解过LockSupport吗

了解过LockSupport吗

作者头像
贪挽懒月
发布2021-07-13 14:30:31
3090
发布2021-07-13 14:30:31
举报
文章被收录于专栏:JavaEEJavaEE

1. 是什么?

LockSupport是JUC包下的一个类,是用来创建锁和其他同步类的基本线程阻塞原语。

相信大多数人看了这句话也还是不太明白它到底是啥东西,那你还记得等待唤醒机制吗?之前实现等待唤醒机制可以用wait/notify,可以用await/signal,这个LockSupport就是它们的改良版。

2. 等待唤醒机制:

先来回顾一下等待唤醒机制。

先看看用wait/notify实现:

  • wait/notify:
代码语言:javascript
复制
    private static Object lockObj = new Object();

    private static void waitNotify() {
        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (lockObj){
                System.out.println("线程" + Thread.currentThread().getName() + "进来了");
                try { lockObj.wait(); } catch (InterruptedException e) { e.printStackTrace(); }
                System.out.println("线程" + Thread.currentThread().getName() + "被唤醒");
            }
        }, "A").start();

        new Thread(() -> {
            synchronized (lockObj){
               System.out.println("线程" + Thread.currentThread().getName() + "进来了");
               lockObj.notify();
               System.out.println("线程" + Thread.currentThread().getName() + "唤醒另一个线程");
            }
        }, "B").start();
    }

这段代码就很简单了,线程A先wait,线程B去notify,线程B执行完了A就被唤醒了,这就是最开始学的等待唤醒机制。

假如我现在注释掉synchronized,如下:

代码语言:javascript
复制
new Thread(() -> {
     //synchronized (lockObj){
         System.out.println("线程" + Thread.currentThread().getName() + "进来了");
         try { lockObj.wait(); } catch (InterruptedException e) { e.printStackTrace(); }
         System.out.println("线程" + Thread.currentThread().getName() + "被唤醒");
     //}
}, "A").start();

再次运行,结果报错了,如下:

异常信息

得出第一个结论:wait/notify必须中同步代码块中才能使用。

如果先执行notify,再执行wait,情况如何呢?让A线程睡3秒钟,使B先执行,先notify,然后A中wait,执行结果如下:

运行结果

可以发现”线程A被唤醒“这句话一直没有打印出来。

得出第二个结论:先notify再wait的话,程序无法被唤醒。

再来看看用await/notify实现:

  • await/signal:
代码语言:javascript
复制
private static Lock lock = new ReentrantLock();
private static Condition condition = lock.newCondition();
private static void awaitSignal(){
        new Thread(() -> {
            lock.lock();
            try {
                System.out.println("线程" + Thread.currentThread().getName() + "进来了");
                condition.await();
                System.out.println("线程" + Thread.currentThread().getName() + "被唤醒");
            } catch (Exception e){
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }, "A").start();

        new Thread(() -> {
            try {
                lock.lock();
                System.out.println("线程" + Thread.currentThread().getName() + "进来了");
                condition.signal();
                System.out.println("线程" + Thread.currentThread().getName() + "唤醒另一个线程");
            } catch (Exception e){
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }, "B").start();
}

这个是用await/signal实现的等待唤醒机制。

假如注释掉lock和unlock这两个操作,再次执行,还是会抛之前那个异常,即IllegalMonitorStateException。

得出第一个结论:await/notify必须伴随lock/unlock出现。

假如先signal,再await,情况也是和之前用wait/notify一样,await的线程一直没被唤醒。

得出第二个结论:必须先await再signal。

所以不管是wait/notify还是await/signal,都有两个限制条件:

  • 线程要先获得并持有锁,必须中锁块中执行;
  • 必须先等待后唤醒。

3. LockSupport怎么用?

LockSupport主要就是用park(等待)和unpark(唤醒)方法来实现等待唤醒。它的原理就是使用了一种名为permit(许可证)的概念来实现等待唤醒功能,每个线程都有一个许可证,许可证只有两个值,一个是0,一个是1。默认值是0,表示没有许可证,就会被阻塞。那谁来发放许可证呢,就是unpark方法。这两个方法底层其实是UNSAFE类的park和unpark方法,调用park时,将permit的值设置为0,调用unpark时,将permit的值设置为1。

用法如下:

代码语言:javascript
复制
private static void lockSupportTest(){
        Thread a = new Thread(() -> {
            System.out.println("线程" + Thread.currentThread().getName() + "进来了");
            LockSupport.park(); // 等待
            System.out.println("线程" + Thread.currentThread().getName() + "被唤醒");
        }, "A");
        a.start();

        Thread b = new Thread(() -> {
            System.out.println("线程" + Thread.currentThread().getName() + "进来了");
            LockSupport.unpark(a); // 唤醒
        }, "B");
        b.start();
}

首先它不需要再同步块中使用,这是第一个优点。那么先unpark再park会不会报错呢?要知道另两种方式先唤醒再等待的话,都会导致线程无法被唤醒的。假如我先unpark,再park,其实也是可以的,相当于提前发放了通行证,先给A线程unpark了,那么A线程执行的时候,就相当于没有park这一行。

LockSupport总结:是一个线程阻塞唤醒的工具类,所有方法都是静态方法,可以让线程在任意位置阻塞,其底层调用的是UNSAFE类的native方法。每调用一次unpark方法,permit就会变成1,每调一次park方法,就会消耗掉一个许可证,即permit就变成0,每个线程都有一个permit,permit最多也就一个,多次调用unpark也不会累加。因为这是根据是否有permit去判断是否要阻塞线程的,所以,先unpark再park也可以,跟顺序无关,只看是否有permit。如果先unpark了两次,再park两次,那么线程还是会被阻塞,因为permit不会累加,unpark两次,permit的值还是1,第一次park的时变成0了,所以第二次park就会阻塞线程。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档