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(许可)不够,不能放行
相关推荐
潘多编程24 分钟前
Spring Boot微服务架构设计与实战
spring boot·后端·微服务
2402_8575893629 分钟前
新闻推荐系统:Spring Boot框架详解
java·spring boot·后端
2401_8576226630 分钟前
新闻推荐系统:Spring Boot的可扩展性
java·spring boot·后端
Amagi.2 小时前
Spring中Bean的作用域
java·后端·spring
2402_857589362 小时前
Spring Boot新闻推荐系统设计与实现
java·spring boot·后端
J老熊2 小时前
Spring Cloud Netflix Eureka 注册中心讲解和案例示范
java·后端·spring·spring cloud·面试·eureka·系统架构
Benaso3 小时前
Rust 快速入门(一)
开发语言·后端·rust
sco52823 小时前
SpringBoot 集成 Ehcache 实现本地缓存
java·spring boot·后端
原机小子3 小时前
在线教育的未来:SpringBoot技术实现
java·spring boot·后端
吾日三省吾码3 小时前
详解JVM类加载机制
后端