1、LockSupport基本介绍与基本使用
LockSupport是JDK中比较底层的类,用来创建锁和其他同步工具类的基本线程阻塞原语。java锁和同步器框架的核心 AQS: AbstractQueuedSynchronizer,就是通过调用 LockSupport .park()和 LockSupport .unpark()实现线程的阻塞和唤醒 的。
LockSupport 很类似于二元信号量(只有1个许可证可供使用),如果这个许可还没有被占用,当前线程获取许可并继 续 执行;如果许可已经被占用,当前线 程阻塞,等待获取许可。
public static void main(String[] args)
{
LockSupport.park();
System.out.println("block.");
}运行该代码,可以发现主线程一直处于阻塞状态。因为 许可默认是被占用的 ,调用park()时获取不到许可,所以进入阻塞状态。
如下代码:先释放许可,再获取许可,主线程能够正常终止。LockSupport许可的获取和释放,一般来说是对应的,如果多次unpark,只有一次park也不会出现什么问题,结果是许可处于可用状态。
public static void main(String[] args)
{
Thread thread = Thread.currentThread();
LockSupport.unpark(thread);//释放许可
LockSupport.park();// 获取许可
System.out.println("b");
}LockSupport是不可重入 的,如果一个线程连续2次调用 LockSupport .park(),那么该线程一定会一直阻塞下去。
public static void main(String[] args) throws Exception
{
Thread thread = Thread.currentThread();
LockSupport.unpark(thread);
System.out.println("a");
LockSupport.park();
System.out.println("b");
LockSupport.park();
System.out.println("c");
}这段代码打印出a和b,不会打印c,因为第二次调用park的时候,线程无法获取许可出现死锁。
下面我们来看下LockSupport对应中断的响应性
public static void t2() throws Exception
{
Thread t = new Thread(new Runnable()
{
private int count = 0;
@Override
public void run()
{
long start = System.currentTimeMillis();
long end = 0;
while ((end - start) <= 1000)
{
count++;
end = System.currentTimeMillis();
}
System.out.println("after 1 second.count=" + count);
//等待或许许可
LockSupport.park();
System.out.println("thread over." + Thread.currentThread().isInterrupted());
}
});
t.start();
Thread.sleep(2000);
// 中断线程
t.interrupt();
System.out.println("main over");
}最终线程会打印出thread over.true。这说明 线程如果因为调用park而阻塞的话,能够响应中断请求(中断状态被设置成true),但是不会抛出InterruptedException 。
2、LockSupport源码分析
部分源码:
package java.util.concurrent.locks;
import java.util.concurrent.*;
import sun.misc.Unsafe;public class LockSupport {
private LockSupport() {} // Cannot be instantiated.
// Hotspot implementation via intrinsics API
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long parkBlockerOffset;
static {
try {
parkBlockerOffset = unsafe.objectFieldOffset
(java.lang.Thread.class.getDeclaredField("parkBlocker"));
} catch (Exception ex) { throw new Error(ex); }
}
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) {
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);
}
}这个类提供的都是静态方法,且无法被实例化。
在LockSupport中有两个私有的成员变量:
private static final Unsafe unsafe = Unsafe.getUnsafe(); private static final long parkBlockerOffset; |
unsafe:是JDK内部用的工具类。它通过暴露一些Java意义上说“不安全”的功能给Java层代码,来让JDK能够更多的使用Java代码来实现一些原本是平台相关的、需要使用native语言(例如C或C++)才可以实现的功能。该类不应该在JDK核心类库之外使用。
parkBlokcerOffset:parkBlocker的偏移量,从字面上理解是这么个东东。但是parkBlocker又是干嘛的?偏移量又是做什么的呢?让我们来看看Thread类的实现:
/** * 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;
问题1:parkBlocker又是干嘛的?
原来java.lang.Thread的实现当中有这么一个对象。从注释上看,这个对象被LockSupport的setBlocker和getBlocker调用。查看LockSupport的JAVADOC会发现这么一段解释:
大致意思是,这个对象是用来记录线程被阻塞时被谁阻塞的。用于线程监控和分析工具来定位原因的。主要调用了LockSupport的getBlocker方法。
原来,parkBlocker是用于记录线程是被谁阻塞的。可以通过LockSupport的getBlocker获取到阻塞的对象。用于监控和分析线程用的。
问题2:偏移量又是做什么的?
static {
try {
UNSAFE = sun.misc.Unsafe.getUnsafe();
Class<?> tk = Thread.class;
parkBlockerOffset = UNSAFE.objectFieldOffset
(tk.getDeclaredField("parkBlocker"));
....
} catch (Exception ex) { throw new Error(ex); }
}这个要往后看一下,原来偏移量就算Thread这个类里面变量parkBlocker在内存中的偏移量。回顾上一节我们Unsafe案例中直接通过内存偏移量给变量赋值的案例。
问题3:为什么要用偏移量来获取对象?干吗不要直接写个get,set方法。多简单?
仔细想想就能明白,这个parkBlocker就是在线程处于阻塞的情况下才会被赋值。线程都已经阻塞了,如果不通过这种内存的方法,而是直接调用线程内的方法,线程是不会回应调用的。