JUC进阶02——LockSupport

什么是LockSupport

LockSupport 是用来创建锁和其他同步类的基本线程阻塞原语。

LockSupport类中的park()方法可以让当前线程阻塞,直到被唤醒或被中断。unpark()方法可以唤醒一个被阻塞的线程。

LockSupport的底层实现依赖于Java虚拟机中的Unsafe类中的native代码,相对java.util.concurrent.locks.Lock接口更加底层和高效。LockSupport的使用场景包括创建锁和其他同步类、实现等待/通知机制、实现锁的公平性等。

线程的等待唤醒机制

三种让线程等待/唤醒的方法

  • 使用 Object 类中的 wait() 方法让线程等待,notify() 方法唤醒线程
  • 使用 ava.util.concurrent.locks.Condition 接口中 await() 方法让线程等待, signal() 方法唤醒线程
  • 使用 LockSupport 类中的 park() 方法让线程等待,unpark() 方法唤醒线程

使用 Object 类中的 wait() 和 nofity() 实现线程的等待和唤醒

java 复制代码
public class LockSupportDemo {
    public static void main(String[] args) throws InterruptedException {
        LockSupportDemo supportDemo = new LockSupportDemo();
        new Thread(supportDemo::threadWait,"t1").start();

        TimeUnit.SECONDS.sleep(1);

        new Thread(supportDemo::threadWake,"t2").start();
    }

    /**
     * 阻塞
     */
    public void threadWait(){
        synchronized (this){
            
            try {
                System.out.println("线程:"+Thread.currentThread().getName()+" 被阻塞");
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            
            System.out.println("线程:"+Thread.currentThread().getName()+" 被唤醒");
        }
    }

    /**
     * 唤醒
     */
    public void threadWake(){
        synchronized (this){
            System.out.println("线程:"+Thread.currentThread().getName()+" 准备唤醒 t1");
            this.notify();
            System.out.println("线程:"+Thread.currentThread().getName()+" 完成唤醒 t1");
        }
    }
}

执行结果

线程:t1 被阻塞
线程:t2 准备唤醒 t1
线程:t2 完成唤醒 t1
线程:t1 被唤醒

使用 Condition 接口中的 await() 和 signal() 实现线程的等待和唤醒

java 复制代码
public class LockSupportDemo {
    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();

    public static void main(String[] args) throws InterruptedException {
        LockSupportDemo supportDemo = new LockSupportDemo();
        new Thread(supportDemo::threadWait,"t1").start();

        TimeUnit.SECONDS.sleep(1);

        new Thread(supportDemo::threadWake,"t2").start();
    }

    /**
     * 阻塞
     */
    public void threadWait(){
        lock.lock();
        System.out.println("线程:"+Thread.currentThread().getName()+" 被阻塞");
        try {
            condition.await();
            System.out.println("线程:"+Thread.currentThread().getName()+" 被唤醒");

        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }

    }

    /**
     * 唤醒
     */
    public void threadWake(){
        lock.lock();
        try {
            System.out.println("线程:"+Thread.currentThread().getName()+" 准备唤醒 t1");
            condition.signal();
            System.out.println("线程:"+Thread.currentThread().getName()+" 完成唤醒 t1");
        }finally {
            lock.unlock();
        }
    }
}
线程:t1 被阻塞
线程:t2 准备唤醒 t1
线程:t2 完成唤醒 t1
线程:t1 被唤醒

上述两个对象Object和Condition使用的限制条件:

  • 线程需要先获得并持有锁,必须在锁块(synchronized或lock)中
  • 必须要先等待后唤醒,线程才能够被唤醒

LockSupport类中的park等待和unpark唤醒

LockSupport 这个类为每个使用它的线程关联一个许可(在java.util.concurrent.Semaphore Semaphore类的意义上),通过 Permit(许可) 的机制来做到 线程的阻塞和唤醒。每个线程都有一个许可(Permit)许可证只能有一个,累加上限是1(这个和Semaphore不同)。

LockSupport 主要方法:

  • park/park(Object blocker)-------阻塞当前线程/阻塞传入的具体线程
    • permit(许可) 默认是没有,不能放行,所以一开始调用了park()方法会阻塞当前线程,当别的线程调用了 unpark(当前线程) 方法将当前线程的 permit(许可) 发放 后才会唤醒当前线程,之前阻塞中的LockSupport.park()方法会立即返回。
  • unpark(Thread thread) --------- 唤醒指定的处于阻塞状态的线程
java 复制代码
public class LockSupportDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            System.out.println("调用 LockSupport.park() 方法,线程t1 被阻塞");
            LockSupport.park();

            System.out.println("发放了许可证,线程 t1 被唤醒");
        });
        t1.start();

        
        new Thread(()->{
            System.out.println("线程 t2 准备给 t1 发放许可证");
            //此处指定要唤醒的线程
            LockSupport.unpark(t1);
            System.out.println("线程 t2 准备给 t1 许可证发放完成");
        },"t2").start();

    }

   
}
scss 复制代码
执行结果
调用 LockSupport.park() 方法,线程t1 被阻塞
线程 t2 准备给 t1 发放许可证
线程 t2 准备给 t1 许可证发放完成
发放了许可证,线程 t1 被唤醒

小提示:使用LockSupport 不必一定要先 t1 线程先阻塞再让 t2 发放许可去唤醒,也可以是 t2 线程 先发放许可,然后 t1 线程持有许可,阻塞后被直接唤醒放行

代码演示:先发放许可,在阻塞时直接放行

csharp 复制代码
public class LockSupportDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("调用 LockSupport.park() 方法,线程t1 被阻塞");
            LockSupport.park();

            System.out.println("发放了许可证,线程 t1 被唤醒");
        });
        t1.start();

        /*
           先发放许可证,也是可以实现 t1`阻塞后被唤醒的
         */
        new Thread(()->{
            System.out.println("线程 t2 准备给 t1 发放许可证");
            LockSupport.unpark(t1);
            System.out.println("线程 t2 准备给 t1 许可证发放完成");
        },"t2").start();
    }
}
scss 复制代码
线程 t2 准备给 t1 发放许可证
线程 t2 准备给 t1 许可证发放完成
调用 LockSupport.park() 方法,线程t1 被阻塞
发放了许可证,线程 t1 被唤醒

注意!!!

线程阻塞后唤醒需要消耗凭证(permit),这个凭证最多只有一个

  • 当带哦用park()时,如果有凭证,则会消耗掉这个凭证(permit)然后正常通行,如果没有凭证(permit),则必须阻塞等待凭证可用;
  • 当调用 unpark时,它会增加一个凭证,但是凭证最多只有一个,累加是无效的,不论调用多少次unpark(),都只会有一个凭证

代码示例:

java 复制代码
public class LockSupportDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            System.out.println("调用 LockSupport.park() 方法,线程t1 第一次被阻塞");
            LockSupport.park();
            System.out.println("调用 LockSupport.park() 方法,线程t1 第二次被阻塞");
            //permit只有一个,第一次唤醒已经消耗完了,再次park被阻塞
            LockSupport.park();
            System.out.println("发放了许可证,线程 t1 被唤醒");
        });
        t1.start();

        TimeUnit.SECONDS.sleep(2);
        new Thread(()->{
            System.out.println("线程 t2 准备给 t1 发放许可证");
            LockSupport.unpark(t1);
            LockSupport.unpark(t1);
            LockSupport.unpark(t1);
            System.out.println("线程 t2 准备给 t1 许可证发放完成");
        },"t2").start();
    }
}

执行结果:从控制台看到程序运行到 "调用 LockSupport.park() 方法,线程t1 第二次被阻塞" 再次被阻塞

总结

  • LockSupport是用来创建锁和其他同步类的基本线程阻塞原语,所有的方法都是静态方法,可以让线程再任意位置阻塞,阻塞后也有对应的唤醒方法。归根结底,LockSupport时调用Unsafe中的native代码
  • LockSupport提供park()和unpark()方法实现阻塞线程和解除线程阻塞的过程,LockSupport和每个使用它的线程都有一个许可(Peimit)关联,每个线程都有一个相关的permit,peimit最多只有一个,重复调用unpark也不会积累凭证
  • LockSupport可以突破wait/notify的原有调用顺序:因为unpark获得了一个凭证,之后再调用park方法,就可以名正言顺的凭证消费,故不会阻塞,先发放了凭证后续可以畅通无阻
  • 唤醒三次后阻塞两次,最终结果还会阻塞线程:permit(许可)的数量最多为1,连续调用三次unpark和调用一次unpark效果一样,只会增加一个permit(许可),而调用两次park却需要消费两个permit(许可),permit(许可)不够,不能放行
相关推荐
也无晴也无风雨40 分钟前
深入剖析输入URL按下回车,浏览器做了什么
前端·后端·计算机网络
2401_857610034 小时前
多维视角下的知识管理:Spring Boot应用
java·spring boot·后端
代码小鑫4 小时前
A027-基于Spring Boot的农事管理系统
java·开发语言·数据库·spring boot·后端·毕业设计
颜淡慕潇5 小时前
【K8S问题系列 | 9】如何监控集群CPU使用率并设置告警?
后端·云原生·容器·kubernetes·问题解决
独泪了无痕6 小时前
WebStorm 如何调试 Vue 项目
后端·webstorm
怒放吧德德7 小时前
JUC从实战到源码:JMM总得认识一下吧
java·jvm·后端
代码小鑫7 小时前
A025-基于SpringBoot的售楼管理系统的设计与实现
java·开发语言·spring boot·后端·毕业设计
前端SkyRain7 小时前
后端SpringBoot学习项目-项目基础搭建
spring boot·后端·学习
梦想画家8 小时前
理解Rust 生命周期、所有权和借用机制
开发语言·后端·rust
编程乐趣8 小时前
推荐一个.NetCore开源的CMS项目,功能强大、扩展性强、支持插件的系统!
后端