一文解读LockSupport

简介: 一文解读LockSupport

LockSupport是什么?

LockSupport是JDK1.5中新增的一个类,用于实现线程的阻塞和唤醒。LockSupport中的park()和unpark()方法可以分别实现阻塞线程和唤醒线程的功能。和其他的线程控制方法相比,LockSupport有以下优点:可以阻塞和唤醒指定的线程,而不是像wait()和notify()方法一样只能阻塞和唤醒当前线程;不需要获取锁才能操作,而是将操作权限直接分配给调用线程;唤醒操作可以先于阻塞操作,而不像wait()方法只能等待notify()方法的唤醒。

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

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

LockSupport类中的park等待和unpark唤醒

LockSupport类使用了一种名为Pemit(许可)的概念来做到阻塞和唤醒线程的功能,每个线程都有一个许可(permit),但与Semaphore不同的是,许可的累加上限是1。

permit许可证默认没有不能放行,所以一开始调park()方法当前线程就会阻塞,直到别的线程给当前线程的发放permit,park方法才会被唤醒。

调用unpark(thread)方法后,就会将thread线程的许可证permit发放,会自动唤醒park线程,即之前阻塞中的LockSuppot.pak()方法会立即返回。

3种让线程等待和唤醒的方法

方式1:object中的wait()==》notify()

使用object中的wait()方法让线程等待,使用Object中的notify()方法唤醒线程

1. public class Demo3 {
2. public static void main(String[] args) {
3. Object obj = new Object();
4. new Thread(() -> {
5. synchronized (obj){
6.                 System.out.println(Thread.currentThread().getName()+"进来了...");
7. try {
8.                     obj.wait();
9.                 } catch (InterruptedException e) {
10.                     e.printStackTrace();
11.                 }
12.                 System.out.println(Thread.currentThread().getName()+"醒过来了...");
13.             }
14.         }, "t1").start();
15. 
16. new Thread(() -> {
17. synchronized (obj){
18.                 System.out.println(Thread.currentThread().getName()+"通知...");
19.                 obj.notify();
20.             }
21.         }, "t2").start();
22.     }
23. }

Object 类中的 wait、notify、notifyAll 用于线程等待和唤醒的方法,都必须在 synchronized 内部执行。必须要先 wait 后 notify或者 notifyAll ,等待中的线程才会被唤醒,否则无法唤醒。

方式2:Juc包中Condition==》await()

使用Juc包中Condition的await()方法让线程等待,使用signal()方法唤醒线程

1. public class Demo3 {
2. static Lock lock=new ReentrantLock();
3. static Condition condition=lock.newCondition();
4. public static void main(String[] args) {
5. new Thread(() -> {
6.            lock.lock();
7.            System.out.println(Thread.currentThread().getName()+"进入");
8. try {
9.                condition.await();
10.            } catch (InterruptedException e) {
11.                e.printStackTrace();
12.            }finally {
13.                lock.unlock();
14.            }
15.            System.out.println(Thread.currentThread().getName()+"被唤醒");
16. 
17.        }, "t1").start();
18. new Thread(() -> {
19.            lock.lock();
20.            condition.signal();
21.            System.out.println(Thread.currentThread().getName()+"通知");
22.            lock.unlock();
23.        }, "t2").start();
24.     }
25. }

Condition接口中的 await、signal、signalAll 用于线程等待和唤醒的方法,都必须在 lock 块内部执行。必须要先 await 后 signal或者 signalAll ,等待中的线程才会被唤醒,否则无法唤醒。

方式3:LockSupport类

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

线程先要获得并持有锁,必须在锁块(synchronized或lock)中

必须要先等待后唤醒,线程才能够被唤醒

LockSupport类可以阻塞当前线程以及唤醒指定被阻塞的线程

1. public class Demo3 {
2. public static void main(String[] args) {
3. Thread t1 = new Thread(() -> {
4.             System.out.println(Thread.currentThread().getName() + "进入");
5.             LockSupport.park();
6.             System.out.println(Thread.currentThread().getName() + "被唤醒");
7.         }, "t1");
8.         t1.start();
9. //3秒以后线程2去唤醒t1
10. try { TimeUnit.MICROSECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
11. new Thread(() -> {
12.             System.out.println(Thread.currentThread().getName()+"通知");
13.             LockSupport.unpark(t1);
14.         }, "t2").start();
15.     };
16. }

LockSupport优势
正常+无锁块要求
错误的先唤醒后等待,LockSupport照样支持

1. public class Demo3 {
2. public static void main(String[] args) {
3. Thread t1 = new Thread(() -> {
4.             System.out.println(Thread.currentThread().getName() + "进入");
5.             LockSupport.park();
6.             LockSupport.park();//锁俩次
7.             System.out.println(Thread.currentThread().getName() + "被唤醒");
8.         }, "t1");
9.         t1.start();
10. //3秒以后线程2去唤醒t1
11. try { TimeUnit.MICROSECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
12. new Thread(() -> {
13.             System.out.println(Thread.currentThread().getName()+"通知");
14.             LockSupport.unpark(t1);
15.             LockSupport.unpark(t1);//给俩次通行证
16.         }, "t2").start();
17.     };
18. }

上面的代码也会出现线程阻塞

为什么唤醒两次后阻塞两次,但最终结果还会阻塞线程?
因为凭证的数量最多为1,连续调用两次unpark和调用一次 unpark效果一样,只会增加一个凭证;而调用两次park却需要消费两个凭证,不够,不能放行

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

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

  • 调用一次 unpark 就加 1 变成 1
  • 调用一次 park 会消费 permit ,也就是将 1 变成 0,同时 park 立即返回。
  • 如果再次调用 park 会变成阻塞(因为 permit 为 0 会阻塞在这里,一直到 permit 为 1),这时候调用 unpark 会把 permit 置为 1
  • 每个线程都有一个相关的 permit,permit 最多只有一个,重复调用 unpark 不会积累凭证。

简单来说:

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

  • 当调用 park 方法时
  • 如果有凭证,则会直接消耗掉这个凭证然后正常退出
  • 如果没有凭证,就必须阻塞等待凭证可用
  • 当调用 unpark 方法时
  • 它会增加一个凭证,但凭证最多只能有一个,无法累加。

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


相关文章
|
Java API
LockSupport与线程中断
LockSupport与线程中断
|
安全 Java
JUC第十一讲:JUC锁LockSupport详解
JUC第十一讲:JUC锁LockSupport详解
139 0
|
Java API
【JUC基础】09. LockSupport
LockSupport是一个线程阻塞工具,可以在线程任意位置让线程阻塞。线程操作阻塞的方式其实还有Thread.suspend()和Object.wait()。而LockSupport与suspend()相比,弥补了由于resume()方法而导致线程被挂起(类似死锁)的问题,也弥补了wait()需要先获得某个对象锁的问题,也不会抛出InterruptedException异常。
|
安全 Java C++
JUC在深入面试题——三种方式实现线程等待和唤醒(wait/notify,await/signal,LockSupport的park/unpark)
JUC在深入面试题——三种方式实现线程等待和唤醒(wait/notify,await/signal,LockSupport的park/unpark)
225 1
JUC在深入面试题——三种方式实现线程等待和唤醒(wait/notify,await/signal,LockSupport的park/unpark)
|
Java 程序员 API
JUC - 线程中断与线程等待、唤醒(LockSupport)
JUC - 线程中断与线程等待、唤醒(LockSupport)
JUC - 线程中断与线程等待、唤醒(LockSupport)
|
安全
【JUC】JDK1.8源码分析之LockSupport(一)
  最开始打算分析ReentrantLock,但是分析到最后,发现离不开LockSuport的支持,所以,索性就先开始分析LockSupport,因为它是锁中的基础,是一个提供锁机制的工具类,所以先对其进行分析。
126 0
【JUC】JDK1.8源码分析之LockSupport(一)
|
安全
JUC CyclicBarrier和Semphore
JUC CyclicBarrier和Semphore
105 0
Juc23_LockSupport概述、阻塞方法park、唤醒方法unpark(thread)、解决的痛点、带来的面试题
①. 什么是LockSupport? ②. 阻塞方法 ③. 唤醒方法(注意这个permit最多只能为1) ④. LockSupport它的解决的痛点 ⑤. LockSupport 面试题目
174 0
Juc23_LockSupport概述、阻塞方法park、唤醒方法unpark(thread)、解决的痛点、带来的面试题