LockSupport类的字段和方法
总体描述

通过上面图片可以看到,LockSupport具有的字段:parkBlockerOffset、UNSAFE、SEED、PROBE、SECONDARY ;具有的方法:getBlocker(Thread t)、park()、park(Object blocker)、parkNanos(long nanos)、parkNanos(Object blocker, long nanos)、parkUntil(long deadline)、parkUntil(Object blocker, long deadline)、setBlocker(Thread t, Object arg)、unpark(Thread thread);此外,还有一块静态代码块。
字段

它的字段主要是一个引用类型UNSAFE对象,这个类前面提到过;而其它4个字段其实是Thread类中四个属性的内存偏移地址。可以看到在静态代码块中获取了Thread对象的四个属性的偏移地址,那就是说LockSupport类中需要用到这四个偏移地址。以parkBlocker字段为例,示意图如下:
方法
getBlocker和setBlocker


可以看到这两个方法其实就是分别给上图的Thread对象的blocker字段取值和赋值操作,并且这种操作是通过unsafe这个类操作的,也就是直接操作的内存,与这个Thread类对象是否处于运行状态没有关系。
park类型的方法

首先是park()方法,其实可以看到它调用的是unsafe类的park方法,而这个方法是一个native方法,首先需要先了解unsafe的park和unpark方法:
而unsafe类的park和unpark底层是cpp实现,具体怎么实现可以参考:
https://juejin.cn/post/6844903703623761933#heading-3
此外,park方法还有一个重载方法:
可以其实就是设置线程对象的blocker字段,而这个blocker字段是用来标识当前线程等待的对象(简称为阻塞对象),该对象主要用于问题排查和系统监控。
由于在Java 5之前,当线程阻塞时(使用synchronized关键字)在一个对象上时,通过线程dump能够查看到该线程的阻塞对象。方便问题定位,而Java 5退出的Lock等并发工具却遗漏了这一点,致使在线程dump时无法提供阻塞对象的信息。因此,在Java 6中,LockSupport新增了含有阻塞对象的park方法。用以替代原有的park方法。
可见调用了两次setBlocker方法,这个方法是直接操作内存的。当执行到“UNSAFE.park”之后,当前线程就阻塞了,无法执行到下一个setBlocker,而当线程t被唤醒(比如调用unpark)就会接着执行第二个setBlocker方法,并把阻塞对象设置为null,线程t重新进入到运行状态。如下代码所示:
class LockSupportDemo {public static void main(String[] args) throws InterruptedException {Thread a = new Thread(new Runnable() {@Overridepublic void run() {LockSupport.park("线程a的blocker数据");System.out.println("我是被线程b唤醒后的操作");}});a.start();//让当前主线程睡眠1秒,保证线程a在线程b之前执行Thread.sleep(1000);Thread b = new Thread(new Runnable() {@Overridepublic void run() {String before = (String) LockSupport.getBlocker(a);System.out.println("阻塞时从线程a中获取的blocker------>" + before);LockSupport.unpark(a);//这里睡眠是,保证线程a已经被唤醒了try {Thread.sleep(1000);String after = (String) LockSupport.getBlocker(a);System.out.println("唤醒时从线程a中获取的blocker------>" + after);} catch (InterruptedException e) {e.printStackTrace();}}});b.start();}}

而上述例子,完全知道了blocker可以在线程阻塞的时候获取数据,也就证明了当我们对线程进行问题排查和系统监控的时候blocker的有着非常重要的作用。
unpark方法

LockSupport类的unpark方法其实调用的是unsafe类的unpark方法,只需要一个需要unpark的线程对象即可。
LockSupport的优点
这里说的LockSupport的优点主要是对比Object类的wait/notify的优点。
简单
相比wait/notify,parl/unpark其实更加简单易用,因为wait/notify必须在同步代码块中使用,比如下面的代码是错误的,会抛出异常:
public class TestObjWait {public static void main(String[] args) throws Exception {final Object obj = new Object();Thread t = new Thread(() -> {int sum = 0;for (int i = 0; i < 10; i++) {sum += i;}try {obj.wait();} catch (Exception e) {e.printStackTrace();}System.out.println(sum);});t.start();//睡眠一秒钟,保证线程A已经计算完成,阻塞在wait方法Thread.sleep(1000);obj.notify();}}

至于为什么会抛出异常,其实就是wait/notify机制的实现原理,可以参考:https://www.yuque.com/docs/share/4bddd325-236c-491e-b2eb-012a2422afaf?# 《wait-notify机制》
修改为下面这样才是正确的:
public class TestObjWait {public static void main(String[] args) throws Exception {final Object obj = new Object();Thread t = new Thread(() -> {int sum = 0;for (int i = 0; i < 10; i++) {sum += i;}try {synchronized (obj) {obj.wait();}} catch (Exception e) {e.printStackTrace();}System.out.println(sum);});t.start();//睡眠一秒钟,保证线程A已经计算完成,阻塞在wait方法Thread.sleep(1000);synchronized (obj) {obj.notify();}}}

而park/unpark其实就没有这个限制了,如下代码所示:
public class TestObjWait {public static void main(String[] args) throws Exception {Thread t = new Thread(() -> {int sum = 0;for (int i = 0; i < 10; i++) {sum += i;}LockSupport.park();System.out.println(sum);});t.start();//睡眠一秒钟,保证线程A已经计算完成,阻塞在wait方法Thread.sleep(1000);LockSupport.unpark(t);}}
灵活
相比wait/notify机制,park/unpark更加灵活。因为如果先调用notify,然后才调用wait。如下代码:
public class TestObjWait {public static void main(String[] args) throws Exception {final Object obj = new Object();Thread A = new Thread(new Runnable() {@Overridepublic void run() {int sum = 0;for(int i=0;i<10;i++){sum+=i;}try {synchronized (obj){obj.wait();}}catch (Exception e){e.printStackTrace();}System.out.println(sum);}});A.start();//睡眠一秒钟,保证线程A已经计算完成,阻塞在wait方法//Thread.sleep(1000);synchronized (obj){obj.notify();}}}
上面的代码有的时候能够正常打印结果并退出程序,但有的时候线程无法打印结果阻塞住了。原因就在于:主线程调用完notify后,线程t才进入wait方法,导致线程t一直阻塞住,从而导致整个程序无法退出。
而这个其实就是之前所说的“通知遗漏”,程序不停止的原因就是线程t一直在wait:
但是,使用park/unpark却不会出现通知遗漏,因为先unpark后park不会导致线程一直处于等待状态(具体实现还得是底层的c++),如:
public class TestObjWait {public static void main(String[] args) throws Exception {final Object obj = new Object();Thread t = new Thread(() -> {int sum = 0;for(int i=0;i<10;i++){sum+=i;}LockSupport.park();System.out.println(sum);});t.start();//睡眠一秒钟,保证线程A已经计算完成,阻塞在wait方法//Thread.sleep(1000);LockSupport.unpark(t);}}


几个方法的对比
Thread.sleep()和Object.wait()的区别
Thread.sleep()和Condition.await()的区别
Thread.sleep()和LockSupport.park()的区别

这里特别注意LockSupport.park()是不会释放锁资源的。
Object.wait()和LockSupport.park()的区别




