https://blog.csdn.net/secsf/article/details/78560013
LockSupport提供park()和unpark()方法实现阻塞线程和解除线程阻塞,实现的阻塞和解除阻塞是基于”许可(permit)”作为关联,permit相当于一个信号量(0,1),默认是0. 线程之间不再需要一个Object或者其它变量来存储状态,不再需要关心对方的状态.
源码:
public class LockSupport {
private LockSupport() {} // Cannot be instantiated.
private static void setBlocker(Thread t, Object arg) {
// Even though volatile, hotspot doesn't need a write barrier here.
UNSAFE.putObject(t, parkBlockerOffset, arg);
}
public static void unpark(Thread thread) {
if (thread != null)
UNSAFE.unpark(thread);
}
public static void park(Object blocker) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(false, 0L);
setBlocker(t, null);
}
public static void parkNanos(Object blocker, long nanos) {
if (nanos > 0) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(false, nanos);
setBlocker(t, null);
}
}
public static void parkUntil(Object blocker, long deadline) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(true, deadline);
setBlocker(t, null);
}
public static Object getBlocker(Thread t) {
if (t == null)
throw new NullPointerException();
return UNSAFE.getObjectVolatile(t, parkBlockerOffset);
}
public static void park() {
UNSAFE.park(false, 0L);
}
public static void parkNanos(long nanos) {
if (nanos > 0)
UNSAFE.park(false, nanos);
}
public static void parkUntil(long deadline) {
UNSAFE.park(true, deadline);
}
static final int nextSecondarySeed() {
int r;
Thread t = Thread.currentThread();
if ((r = UNSAFE.getInt(t, SECONDARY)) != 0) {
r ^= r << 13; // xorshift
r ^= r >>> 17;
r ^= r << 5;
}
else if ((r = java.util.concurrent.ThreadLocalRandom.current().nextInt()) == 0)
r = 1; // avoid zero
UNSAFE.putInt(t, SECONDARY, r);
return r;
}
// Hotspot implementation via intrinsics API
private static final sun.misc.Unsafe UNSAFE;
private static final long parkBlockerOffset;
private static final long SEED;
private static final long PROBE;
private static final long SECONDARY;
static {
try {
UNSAFE = sun.misc.Unsafe.getUnsafe();
Class<?> tk = Thread.class;
parkBlockerOffset = UNSAFE.objectFieldOffset
(tk.getDeclaredField("parkBlocker"));
SEED = UNSAFE.objectFieldOffset
(tk.getDeclaredField("threadLocalRandomSeed"));
PROBE = UNSAFE.objectFieldOffset
(tk.getDeclaredField("threadLocalRandomProbe"));
SECONDARY = UNSAFE.objectFieldOffset
(tk.getDeclaredField("threadLocalRandomSecondarySeed"));
} catch (Exception ex) { throw new Error(ex); }
}
}
从源码中得到初步信息:
1.不能被实例化(构造函数是私有的)
2.方法都是静态方法
成员变量:
UNSAFE
parkBlockerOffset
SEED
PROBE
SECONDARY
UNSAFE:
JDK内部用的工具类, 可以直接操控内存,被JDK广泛用于自己的包中.它通过暴露一些Java意义上说“不安全”的功能给Java层代码,来让JDK能够更多的使用Java代码来实现一些原本是平台相关的、需要使用native语言(例如C或C++)才可以实现的功能。该类不应该在JDK核心类库之外使用。
parkBlockerOffset:
Class<?> tk = Thread.class;
parkBlockerOffset = UNSAFE.objectFieldOffset
(tk.getDeclaredField("parkBlocker"));
解释起来就是:挂起线程对象的偏移地址,对应的是Thread类的parkBlocker.
/**
* The argument supplied to the current call to
* java.util.concurrent.locks.LockSupport.park.
* Set by (private) java.util.concurrent.locks.LockSupport.setBlocker
* Accessed using java.util.concurrent.locks.LockSupport.getBlocker
*/
volatile Object parkBlocker;
可以看出这个对象是被LockSupport的setBlocker和getBlocker调用
这个对象是用来记录线程被阻塞时被谁阻塞的,用于线程监控和分析工具来定位原因的。
偏移量又拿来做什么呢?
parkBlockerOffset = UNSAFE.objectFieldOffset
(tk.getDeclaredField("parkBlocker"));
原来偏移量就算Thread这个类里面变量parkBlocker在内存中的偏移量:
JVM的实现可以自由选择如何实现Java对象的“布局“,也就是在内存里Java对象的各个部分放在哪里,包括对象的实例字段和一些元数据之类。 sun.misc.Unsafe里关于对象字段访问的方法把对象布局抽象出来,它提供了objectFieldOffset()方法用于获取某个字段相对 Java对象的“起始地址”的偏移量,也提供了getInt、getLong、getObject之类的方法可以使用前面获取的偏移量来访问某个Java 对象的某个字段。
获取当前线程,通过偏移量的方式设置parkBlocker的值,将调取unsafe.park把线程挂起,线程被恢复后,修改blocker为null
为什么要用偏移量来获取对象?干吗不要直接写个get,set方法?
parkBlocker就是在线程处于阻塞的情况下才被赋值。线程都已经被阻塞了,如果不通过这种内存的方法,而是直接调用线程内的方法,线程是不会回应调用的。
SEED, PROBE, SECONDARY:
/** The current seed for a ThreadLocalRandom */
@sun.misc.Contended("tlr")
long threadLocalRandomSeed;
/** Probe hash value; nonzero if threadLocalRandomSeed initialized */
@sun.misc.Contended("tlr")
int threadLocalRandomProbe;
/** Secondary seed isolated from public ThreadLocalRandom sequence */
@sun.misc.Contended("tlr")
int threadLocalRandomSecondarySeed;
都是Thread类中的内存偏移地址,主要用于ThreadLocalRandom类进行随机数生成,它要比Random性能好很多,可阅读了解详情
核心函数
因为LockSupport的核心方法都是基于Unsafe类中的park和unpark方法
public native void park(boolean isAbsolute, long time);
public native void unpark(Thread thread);
park: 阻塞线程,线程在一下三种情况下会被堵塞:
1.调用unpark方法,释放该线程的许可
2.该线程被中断
3.到期时间。
isAbsolute为true。
isAbsolute为false。当time为0时,表示无限等待,直到unpark发生。
unpark: 释放线程的堵塞,即修改信号量为1, 同时park返回。
1.调用一次unpark,
如果没有可用线程,则给定许可(permit就变成1(不会累计))
如果有线程被阻塞,接触锁,同时park返回.
如果给定线程没有启动,则该操作不能保证有任何效果.
2.调用park,则会检测permit是否为1;
如果为1则将permit变成0;
如果不为1,则堵塞线程,直到permit变为1.
3.park()和unpark()不会有“Thread.suspend和Thread.resume所可能引发的死锁” 问题,由于许可的存在,调用 park 的线程和另一个试图将其 unpark 的线程之间的竞争将保持活性。
4.如果调用线程被中断,则park方法会返回.
5.park 方法还可以在其他任何时间"毫无理由"地返回,因此通常必须在重新检查返回条件的循环里调用此方法。从这个意义上说,park 是“忙碌等待”的一种优化,它不会浪费这么多的时间进行自旋,但是必须将它与 unpark 配对使用才更高效
6.unpark方法可以先于park调用。比如线程1调用unpark给线程2发了一个“许可“,那么当线程2调用park时,发现已经有“许可”了,那么它会马上再继续运行
public static void park();
public static void park() {
UNSAFE.park(false, 0L);// // 获取许可,设置时间为无限长,直到可以获取许可
}
调用了park方法后,会禁用当前线程,除非许可可用,在一下三种情况发生之前,线程处于阻塞状态:
1.其他某个线程将当前线程作为目标调用 unpark
2.其他某个线程中断当前线程
3.该调用不合逻辑地(即毫无理由地)返回
当前线程会获取许可,可以继续运行
public static void park(Object blocker);
public static void park(Object blocker) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(false, 0L);
setBlocker(t, null);
}
先获取当前线程,设置当前线程的parkBlocker字段(parkBlocker), 调用Unsafe类的park方法,最后再次调用setBlocker,为什么呢?
因为当前线程首先设置好parkBlocker字段后再调用Unsafe的park方法,之后,当前线程已经被阻塞,等待unpark方法被调用, unpark方法被调用,该线程获得许可后,可以继续进行下面的代码,第二个setBlocker参数parkBlocker字段设置为null,这样就完成了整个park方法的逻辑. 如果没有第二个setBlocker,那么之后没有调用park(Object blocker),而直接调用getBlocker方法,得到的还是前一个park(Object blocker)设置的blocker,显然是不符合逻辑的。 所以,park(Object) 方法里必须要调用setBlocker方法两次。
private static void setBlocker(Thread t, Object arg) {
UNSAFE.putObject(t, parkBlockerOffset, arg);
}
public static void parkNanos(Object blocker, long nanos)
public static void parkNanos(Object blocker, long nanos) {
if (nanos > 0) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(false, nanos);
setBlocker(t, null);
}
}
在解除堵塞之前禁用当前线程, 并最多等待指定的等待时间
public static void parkUntil(Object blocker, long deadline)
public static void parkUntil(Object blocker, long deadline) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(true, deadline);
setBlocker(t, null);
}
指定的时限前禁用当前线程,除非许可可用
public static void unpark(Thread thread)
public static void unpark(Thread thread) {
if (thread != null)
UNSAFE.unpark(thread);
}
线程在 park 上受阻塞,将解除其阻塞状态。否则,预发许可,下一次调用 park 不会受阻塞
LockSupport的park(),unPark()与Thread的wait(),notify()区别
1.面向的主体不一样。LockSuport主要是针对Thread进进行阻塞处理,可以指定阻塞队列的目标对象,每次可以指定具体的线程唤醒。Object.wait()是以对象为纬度,阻塞当前的线程和唤醒单个(随机)或者所有线程。
2.实现机制不同。虽然LockSuport可以指定monitor的object对象,但和object.wait(),两者的阻塞队列并不交叉。
代码:
public class LockSupportTest {
public static void main(String[] args) throws Exception {
TestLockSupport lockSupport = new TestLockSupport();
lockSupport.start();
// TestThread thread = new TestThread();
// thread.start();
}
}
class TestLockSupport extends Thread {
public void run() {
System.out.println( "TestLockSupport.run()" );
LockSupport.park( );
}
}
class TestThread extends Thread {
public void run() {
System.out.println( "TestThread.run()" );
synchronized (this) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
使用jstack打印堆栈信息
LockSupport:
"Thread-0" #10 prio=5 os_prio=0 tid=0x000000001d6a5800 nid=0x2ad0 waiting on con
dition [0x000000001e1cf000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:304)
at TestLockSupport.run(LockSupportTest.java:16)
可以看出当前线程状态时等待的,用的是Unsafe.park挂起的
Thread:
"Thread-0" #10 prio=5 os_prio=0 tid=0x000000001d321000 nid=0x8a8 in Object.wait(
) [0x000000001e2af000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x000000076b257698> (a TestThread)
at java.lang.Object.wait(Object.java:502)
at TestThread.run(LockSupportTest.java:23)
- locked <0x000000076b257698> (a TestThread)
可以看出是用Object.wait实现的
从上面可以看出:
LockSupport阻塞和解除阻塞线程直接操作的是Thread,而Object的wait/notify它并不是直接对线程操作,它是被动的方法,它需要一个object来进行线程的挂起或唤醒.
Thead在调用wait之前, 当前线程必须先获得该对象的监视器(synchronized),被唤醒之后需要重新获取到监视器才能继续执行.而LockSupport可以随意进行park或者unpark.
结论:
Thread的notify也是在实际使用过程只需要斟酌的事情,notifyAll唤醒所有线程;notify唤醒单个线程,如果有两个被阻塞线程….
LockSupport的park也可以响应Thread的interrupt中断,后面会有例子详讲.
案例
先park,后unpark
public class LockSupportTest {
public static void main(String[] args) throws Exception {
TestThread thread = new TestThread( Thread.currentThread() );
thread.start();
System.out.println( "before park" );
// 等待获取许可
LockSupport.park( "Park" );
System.out.println( "after park" );
}
}
class TestThread extends Thread {
private Object object;
public TestThread(Object object) {
this.object = object;
}
public void run() {
System.out.println( "before unpark" );
// 休眠,保证setBlocker(t, blocker)
try {
Thread.sleep( 500 );
} 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();
}
System.out.println( "Blocker info: " + LockSupport.getBlocker( (Thread) object ) );
System.out.println( "after unpark" );
}
}
结果:
before park
before unpark
Blocker info Park
after park
Blocker info null
after unpark
先执行park,然后在执行unpark,进行同步,并且在unpark的前后都调用了getBlocker,可以看到两次的结果不一样,第二次调用的结果为null,这是因为在调用unpark之后,执行了Lock.park(Object blocker) 方法中的setBlocker(t, null) 方法,所以第二次调用getBlocker时为null
先unpark,后park
public class LockSupportTest {
public static void main(String[] args) throws Exception {
TestThread thread = new TestThread( Thread.currentThread() );
long start = System.nanoTime();
thread.start();
try {
Thread.sleep( 1000 );
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println( "before park" );
// 等待获取许可
LockSupport.park( "Park" );
System.out.println( "after park:" + (System.nanoTime() - start) );
}
}
class TestThread extends Thread {
private Object object;
public TestThread(Object object) {
this.object = object;
}
public void run() {
System.out.println( "before unpark" );
LockSupport.unpark( (Thread) object );
System.out.println( "after unpark" );
}
}
before unpark
after unpark
before park
after park:3000046392
先执行unpark,在调用park,直接就没被阻塞, 因此park/unpark相比wait/notify更加的灵活
interrupt与unpark
public class LockSupportTest {
public static void main(String[] args) throws Exception {
TestThread thread = new TestThread();
thread.start();
try {
Thread.sleep( 1000 );
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.interrupt();
}
}
class TestThread extends Thread {
public void run() {
System.out.println( Thread.currentThread().isInterrupted() );
LockSupport.park();
System.out.println( Thread.currentThread().isInterrupted() );
}
}
false
true
可以看到子线程被阻塞后,主线程调用子线程中断,子线程则继续执行,并没有抛出InterruptedException, 这里依赖的是Interrupted status, 如果线程被中断而退出阻塞该状态会被修改为true, 可见interrupt起到的作用与unpark一样.
总结
简而言之:
1.实现机制和wait/notify有所不同,面向的是线程
2.不需要依赖监视器
3.与wait/notify没有交集
4.使用起来方便灵活