Basic thread blocking primitives for creating locks and other synchronization classes. 用于创建锁定和其他同步类的基本线程阻塞原语(基础?)。
上面这段话是Java Doc对LockSupport的描述,表明了该类在实现锁当中的重要意义。因此我们先来查看一下其中的源码,看看它是如何实现的。
public class LockSupport {
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); }
}
}
/**
* 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调用。另外在LockSupport的java doc中也写到:
This object is recorded while the thread is blocked to permit monitoring and diagnostic tools to identify the reasons that threads are blocked. (Such tools may access blockers using method [getBlocker(Thread).) The use of these forms rather than the original forms without this parameter is strongly encouraged. The normal argument to supply as a
blocker
within a lock implementation isthis
.
大致是说,parkBlocker是当线程被阻塞的时候被记录,以便监视和诊断工具来识别线程被阻塞的原因。
Unsafe类提供了获取某个字段相对 Java对象的“起始地址”的偏移量的方法objectFieldOffset,从而能够获取该字段的值。
那么为什么记录该blocker在对象中的偏移量,而不是直接调用Thread.getBlocker(),这样不是更好,原因其实很好理解,当线程被阻塞(Blocked)的时候,线程是不会响应的。另外通过反射应该也可以拿到。
类中的方法主要分为两类:park
(阻塞线程)和unpark
(解除阻塞)。
首先强调的一点事park方法阻塞的是当前的线程,也就是说在哪个线程中调用,那么哪个线程就被阻塞(在没有获得许可的情况下)。
重点讲其中的几个:
public static void park() {
UNSAFE.park(false, 0L);
}
UNSAFE.park的两个参数,前一个为true的时候表示传入的是绝对时间,false表示相对时间,即从当前时间开始算。后面的long类型的参数就是等待的时间,0L表示永久等待。
根据java doc中的描述,调用park后有三种情况,能使线程继续执行下去:
验证一:
public class UnparkTest {
public static void main(String[] args) throws InterruptedException {
Thread ut = new Thread(new UnparkThread(Thread.currentThread()));
ut.start();
System.out.println("I'm going to call park");
// Thread.sleep(1000L);
LockSupport.park();
System.out.println("oh, I'm running again");
}
}
class UnparkThread implements Runnable {
private final Thread t;
UnparkThread(Thread t) {
this.t = t;
}
@Override
public void run() {
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("I'm in unpark");
LockSupport.unpark(t);
System.out.println("I called unpark");
}
}
结果:
I'm going to call park
I'm in unpark
I called unpark
oh, I'm running again
另外值得一提的是,LockSupport对park和unpark的调用顺序并没有要求,将两个Thread.sleep(1000L);
注释切换一下就可以发现,先调用unpark,再调用park,依旧可以获得许可,让线程继续运行。这一点与Object的 wait 和 notify 要求固定的顺序不同,其实现原理可以看这里。
验证二:
public class LockSupportInterrupt {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(new InterruptThread());
t.start();
Thread.sleep(1000L);
System.out.println("I'm going to interrupt");
t.interrupt();
}
}
class InterruptThread implements Runnable {
@Override
public void run() {
System.out.println("I'm going to park");
LockSupport.park();
System.out.println("I'm going to again");
}
}
运行结果:
I'm going to park
I'm going to interrupt
I'm going to again
LockSupport的park能够能响应interrupt事件,且不会抛出InterruptedException异常。
park的另一个重载方法需要传入blocker对象:
public static void park(Object blocker) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(false, 0L);
setBlocker(t, null);
}
在理解了parkBlocker的作用后,这个方法里的代码就很好理解了。
再看一下setBlocker的代码:
private static void setBlocker(Thread t, Object arg) {
// Even though volatile, hotspot doesn't need a write barrier here.
UNSAFE.putObject(t, parkBlockerOffset, arg);
}
方法是私有的,嗯,为了保证正确性,肯定不能被其他类调用。
另外就是利用了之前提到的偏移量以及unsafe对象将blocker值设置进了线程t当中。
public static void unpark(Thread thread) {
if (thread != null)
UNSAFE.unpark(thread);
}
这就很简单了,判断是否为空,然后调用unsafe的unpark方法。由此更可见unsafe这个类的重要性。
前面提到parkBlocker提供了调试工具上面查找原因,所以我们来看一下在jstack上面是什么情况:
public class JstackTest {
public static void main(String[] args) {
// 给main线程设置名字,好查找一点
Thread.currentThread().setName("jstacktest");
LockSupport.park("block");
}
}
利用park(blocker)来阻塞main线程,传入string作为parkBlocker。
运行之后,在shell里运行:
> jps
37137 Jps
4860
37132 Launcher
37133 JstackTest
可以看到我们的java线程的pid,JstackTest这个类对应的是37133,然后再利用jstack来查看:
> jstack -l 37133
"jstacktest" #1 prio=5 os_prio=31 tid=0x00007f7f07001800 nid=0x2903 waiting on condition [0x0000700000901000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x000000079582f5d0> (a java.lang.String)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at lock.JstackTest.main(JstackTest.java:11)
Locked ownable synchronizers:
- None
省略了一部分,可以看到jstacktest线程的状态是Waiting on condition(等待资源,或等待某个条件的发生),同事可以看到这样一句话:parking to wait for <0x000000079582f5d0> (a java.lang.String)。
<0x000000079582f5d0>的类型是String,也就是之前传入park里的block字符串。而0x000000079582f5d0估计就是其地址(待验证)。
在来看一下java doc上提供的示例:
class FIFOMutex {
private final AtomicBoolean locked = new AtomicBoolean(false);
private final Queue<Thread> waiters
= new ConcurrentLinkedQueue<Thread>();
public void lock() {
boolean wasInterrupted = false;
Thread current = Thread.currentThread();
waiters.add(current);
while (waiters.peek() != current ||
!locked.compareAndSet(false, true)) {
LockSupport.park(this);
if (Thread.interrupted())
wasInterrupted = true;
}
waiters.remove();
if (wasInterrupted)
current.interrupt();
}
public void unlock() {
locked.set(false);
LockSupport.unpark(waiters.peek());
}
}
先进先出锁就是先申请锁的线程最先获得锁的资源,实现上采用了队列再加上LockSupport.park。
到这里,对LockSupport有了简单的认识,如果还想深入了话,就要开始阅读C++里面的代码了。后面有机会再重拾C++。最后谢谢各位园友观看,如果有描述不对的地方欢迎指正,与大家共同进步!