• 周五. 9月 30th, 2022

5G编程聚合网

5G时代下一个聚合的编程学习网

热门标签

juc:LockSupport

admin

11月 28, 2021

简介

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方法时

  • 如果有凭证,则会直接消耗掉这个凭证然后正常退出;

发表回复

您的电子邮箱地址不会被公开。