JUC锁原理分析之LockSupport
Table of Contents
LockSupport工具类
JUC提供了LockSupport工具类,它的主要作用是挂起和唤醒线程,该工具类是创建锁和其他同步类的基础,是一个提供锁机制的工具类。
简而言之,当调用LockSupport.park时,表示当前线程将会等待,直至获得许可,当调LockSupport.unpark时,必须把等待获得许可的线程作为参数进行传递,好让此线程继续运行。
LockSupport类与每个使用它的线程都会关联一个许可证,在默认情况下调用LockSupport类的方法的线程是不持有许可证的。LockSupport是使用Unsafe类实现的,下面主要介绍LockSupport的几个主要函数。
LockSupport主要方法
方法名称 | 描述 |
---|---|
void park() | 如果调用park方法的线程已经拿到了与LockSupport关联的许可证,则调用LockSupport.park()会马上返回,否则调用线程会被禁止参与线程调度,也就是会被阻塞挂起。在三种情况下,当前线程会获得许可继续运行:1. 其他某个线程将当前线程作为目标调用 unpark。2. 当前线程被其他某个线程中断interrupt()方法。3. 该调用不合逻辑的返回。 |
void park(Object blocker) | Thread类中存在volatile Object parkBlocker字段,用来存放park方法传递的blocker对象,相比于无参的park()方法,额外设置了该字段。 |
void parkNanos(long nanos) | 与park方法类似,不同在于此函数表示在许可可用前禁用当前线程,并最多等待指定的等待时间。 |
void parkNanos(Object blocker, long nanos) | 相比于park(Object blocker)方法多了一个超时时间。 |
void parkUntil(Object blocker, long nanos) | 阻塞当前线程知道deadline时间(从1970年开始deadline时间的毫秒数) |
void unpark(Thread thread) | 此函数表示如果给定线程的许可尚不可用,则使其可用。如果线程在 park 上受阻塞,则它将解除其阻塞状态。否则,保证下一次调用 park 不会受阻塞。如果给定线程尚未启动,则无法保证此操作有任何效果。 |
示例说明
-
使用wait/notify实现同步
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
public class WaitAndNotifyDemo { public static void main(String[] args) throws InterruptedException { MyThread myThread = new MyThread(); synchronized (myThread) { try { myThread.start(); // 主线程睡眠3s Thread.sleep(3000); System.out.println("before wait"); // 阻塞主线程 myThread.wait(); System.out.println("after wait"); } catch (InterruptedException e) { e.printStackTrace(); } } } } class MyThread extends Thread { public void run() { synchronized (this) { System.out.println("before notify"); notify(); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("after notify"); } } }
结果
1 2 3 4
before wait before notify after notify after wait
代码具体的流程图如下
使用wait/notify实现同步时,必须先调用wait,后调用notify,如果先调用notify,再调用wait,将起不了作用。具体代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
class MyThread extends Thread { public void run() { synchronized (this) { System.out.println("before notify"); notify(); System.out.println("after notify"); } } } public class WaitAndNotifyDemo { public static void main(String[] args) throws InterruptedException { MyThread myThread = new MyThread(); myThread.start(); // 主线程睡眠3s Thread.sleep(3000); synchronized (myThread) { try { System.out.println("before wait"); // 阻塞主线程 myThread.wait(); System.out.println("after wait"); } catch (InterruptedException e) { e.printStackTrace(); } } } }
1 2 3
before notify after notify before wait
说明: 由于先调用了notify,再调用的wait,此时主线程还是会一直阻塞。
-
使用park/unpark实现线程同步
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
import java.util.concurrent.locks.LockSupport; public class ParkAndUnparkDemo { public static void main(String[] args) throws InterruptedException { MyThread myThread = new MyThread(Thread.currentThread()); myThread.start(); //Thread.sleep(2000); System.out.println("before park"); // 获取许可 LockSupport.park("ParkAndUnparkDemo"); System.out.println("after park"); } static class MyThread extends Thread { private Object object; public MyThread(Object object) { this.object = object; } public void run() { System.out.println("before unpark"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } // 获取blocker System.out.println("Blocker info " + LockSupport.getBlocker((Thread) object)); // 释放许可 LockSupport.unpark((Thread) object); // 休眠500ms,保证先执行park中的setBlocker(t, null); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } // 再次获取blocker System.out.println("Blocker info " + LockSupport.getBlocker((Thread) object)); System.out.println("after unpark"); } } }
运行结果
1 2 3 4 5 6
before park before unpark Blocker info ParkAndUnparkDemo after park Blocker info null after unpark
先执行park,然后在执行unpark,进行同步,并且在unpark的前后都调用了getBlocker,可以看到两次的结果不一样,并且第二次调用的结果为null,这是因为在调用unpark之后,执行了Lock.park(Object blocker)函数中的setBlocker(t, null)函数,所以第二次调用getBlocker时为null。
上例是先调用park,然后调用unpark,现在修改程序,先调用unpark,然后调用park。在主线程中设置等待时间,使得先调用unpark,得到运行结果。
1 2 3 4 5 6
before unpark Blocker info null Blocker info null after unpark before park after park
可以看到,在先调用unpark,再调用park时,仍能够正确实现同步,不会造成由wait/notify调用顺序不当所引起的阻塞。因此park/unpark相比wait/notify更加的灵活。
更多关于park、unpark、sleep、wait、await的区别参考这里