zoukankan      html  css  js  c++  java
  • juc:LockSupport

    简介

    LockSupport是juc里的类,提供了线程等待唤醒机制(wait/notify)功能

    LockSupport中的park和unpark的作用分别是阻塞线程和解除阻塞线程。

    有三种线程等待唤醒的方法:

    • 使用Object中的wait方法让线程等待,notify方法唤醒线程
    • 使用JUC中的Condition的await方法让线程等待,使用signal方法唤醒线程
    • 使用LockSupport阻塞当前线程以及唤醒指定被阻塞的线程

    LockSupport的Api如下:

    image-20210716160207950

    wait && notify

    案例:

        static final Object obj = new Object();
    
        public static void main(String[] args) throws InterruptedException {
            new Thread(()->{
                synchronized (obj){
                    System.out.println(Thread.currentThread().getName() +" start");
                    try {
                        obj.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() +"被唤醒");
                }
            }, "A").start();
    
            TimeUnit.SECONDS.sleep(2);
    
            new Thread(()->{
                synchronized (obj){
                    System.out.println(Thread.currentThread().getName()+" start");
                    obj.notify();
                }
            }, "B").start();
        }
    

    image-20210716161615336

    waitnotify方法都是在synchronized代码体中执行的,如果没有经过synchronized修饰,直接使用则会抛出java.lang.IllegalMonitorStateException异常。

    对此,jdk官方文档给出的解释:

    image-20210716161808282

    第二段话翻译过来就是:当前线程必须拥有此对象监视器。该线程释放对此监视器的所有权并等待,直到其他线程通过调用 notify 方法,或 notifyAll 方法通知在此对象的监视器上等待的线程醒来。然后该线程将等到重新获得对监视器的所有权后才能继续执行。

    Condition await && signal

    案例:

        static final Object obj = new Object();
        static final ReentrantLock lock = new ReentrantLock();
        static final Condition condition = lock.newCondition();
    
        public static void main(String[] args) throws InterruptedException {
            new Thread(()->{
                lock.lock();
                try {
                    System.out.println(Thread.currentThread().getName() +" start");
                    try {
                        condition.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() +"被唤醒");
                } finally {
                    lock.unlock();
                }
            }, "A").start();
    
            TimeUnit.SECONDS.sleep(2);
    
            new Thread(()->{
                lock.lock();
                try {
                    System.out.println(Thread.currentThread().getName() +" start");
                    condition.signal();
                } finally {
                    lock.unlock();
                }
            }, "B").start();
        }
    

    运行结果:

    image-20210716162453897

    传统的Object和Condition 实现等待唤醒通知的约束:

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

    LockSupport

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

    LockSupport类使用了一种名为Permit (许可)的概念来做到阻塞和唤醒线程的功能,每个线程都有一个许可(permit).

    permit只有两个值1和零,默认是零。

    可以把许可看成是一种(0,1)信号量(Semaphore).但与Semaphore不同的是,许可的累加上限是1。

    阻塞: park()/park(Object blocker):阻塞当前线程/阻塞传入的具体线程

    唤醒:unpark(Thread thread):唤醒处于阻塞状态的线程

    案例1:

        public static void main(String[] args) throws InterruptedException {
            Thread a = new Thread(() -> {
                System.out.println(Thread.currentThread().getName() +" start");
                LockSupport.park();
                System.out.println(Thread.currentThread().getName() +"被唤醒");
            }, "A");
            a.start();
    
            TimeUnit.SECONDS.sleep(1);
    
            Thread b = new Thread(() -> {
                System.out.println(Thread.currentThread().getName() +" start");
                LockSupport.unpark(a);
            }, "B");
            b.start();
        }
    

    案例2:

        public static void main(String[] args) throws InterruptedException {
            Thread a = new Thread(() -> {
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() +" start");
                LockSupport.park();
                System.out.println(Thread.currentThread().getName() +"被唤醒");
            }, "A");
            a.start();
    
            Thread b = new Thread(() -> {
                System.out.println(Thread.currentThread().getName() +" start");
                LockSupport.unpark(a);
            }, "B");
            b.start();
        }
    

    案例3:

        public static void main(String[] args) throws InterruptedException {
            Thread a = new Thread(() -> {
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() +" start");
                LockSupport.park();
                LockSupport.park();
                System.out.println(Thread.currentThread().getName() +"被唤醒");
            }, "A");
            a.start();
    
            Thread b = new Thread(() -> {
                System.out.println(Thread.currentThread().getName() +" start");
                LockSupport.unpark(a);
                LockSupport.unpark(a);
            }, "B");
            b.start();
        }
    

    使用LockSupport:

    线程不需要先在锁块中获取锁

    可以先执行unpark,再执行park

    "通行证"最多一个

    总结

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

    LockSupport是一个线程阻塞工具类,所有的方法都是静态方法,可以让线程在任意位置阻塞,阻塞之后也有对应的唤醒方法。归根结底,LockSupport调用的Unsafe中的native代码。

    LockSupport 提供park()和unpark()方法实现阻塞线程和解除线程阻塞的过程.

    LockSupport和每个使用它的线程都有一个许可(permit)关联。permit相当于1,0的开关,默认是0

    调用一次unpark就加1变成1,

    调用一次park会消费permit,也就是将1变成0,同时park立即返回。

    如再次调用park会变成阻塞(因为permit为零了会阻塞在这里,一直到permit变为1),这时调用unpark会把permit置为1。

    每个线程都有一个相关的permit,permit最多只有一个,重复调用unpark也不会积累凭证。

    形象的理解

    线程阻塞需要消耗凭证(permit),这个凭证最多只有1个。

    当调用park方法时

    • 如果有凭证,则会直接消耗掉这个凭证然后正常退出;
  • 相关阅读:
    【原创】tyvj1038 忠诚 & 计蒜客 管家的忠诚 & 线段树(单点更新,区间查询)
    [转载]线段树模板
    并查集相当6的一篇文章~~~
    觉得一篇讲SPFA还不错的文章
    【原创】POJ 3259 Wormholes(Bellman-Ford) && 简介Bellman-Ford算法
    【原创】谈谈数据结构课后作业......尴尬不已...《图》 后面的迷宫问题
    MyBatis源码分析(七):动态代理(Mybatis核心机制)
    1026: [SCOI2009]windy数
    Spring Boot 官方文档学习(一)入门及使用
    Spring Hibernate JPA 联表查询 复杂查询
  • 原文地址:https://www.cnblogs.com/wwjj4811/p/15020956.html
Copyright © 2011-2022 走看看