您的位置:首页 > 编程语言 > Java开发

LockSupport源码分析(JDK 1.7)

2015-11-24 17:59 441 查看
LockSupport是用来创建锁和其他同步类的基本线程阻塞基本体(primitives)。通过调用LockSupport的park方法可以申请一个许可,如果当前许可可用的话,那么则立即返回,否则则阻塞等待许可。直到另外一个线程调用unpark方法对被阻塞的线程的许可进行释放。(默认许可阻塞)

park方法还支持Blocker对象参数,在调用park方法对当前线程进行阻塞时候,可以把Blocker对象参数传递过来,LockSupport的park方法里会调用setBlocker方法把该对象参数存入到Thread里的parkBlocker变量里。我们可以通过getBlocker获取线程的parkBlocker变量,从而得知该线程正在被阻塞的原因。

public class LockSupport {
private LockSupport() {} // 禁止创建对象

// Unsafe实际上调用的都是本地方法native
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long parkBlockerOffset;

static {
try {
//获取Thread的成员变量parkBlocker的偏移量
parkBlockerOffset = unsafe.objectFieldOffset
(java.lang.Thread.class.getDeclaredField("parkBlocker"));
} catch (Exception ex) { throw new Error(ex); }
}
//设置Blocker对象到Thread对象里面
private static void setBlocker(Thread t, Object arg) {
// Even though volatile, hotspot doesn't need a write barrier here.
unsafe.putObject(t, parkBlockerOffset, arg);
}

//获取先前park时候存入的线程的Blocker,如果未存入或者当前线程并不在park里,则为null
public static Object getBlocker(Thread t) {
if (t == null)
throw new NullPointerException();
return unsafe.getObjectVolatile(t, parkBlockerOffset);
}

//对于给定的Thread释放它所占有的许可。如果当前线程已经占有许可,则释放。如果为占有也释放,那么下次调用该线程的unpark方法时候
//会导致park直接获取了。因为上次已经释放了
public static void unpark(Thread thread) {
if (thread != null)
unsafe.unpark(thread);
}

//挂起当前线程,且把Blocker存到线程中
public static void park(Object blocker) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
//false代表非绝对时间(即相对时间)
unsafe.park(false, 0L);
setBlocker(t, null);
}
}


以上是LockSupport源码的分析,除了上面说提到的Park(Object obj),还有两个Park重载方法,分别提供了可设定线程挂起的绝对时间和相对时间方法。

下面是个LockSupport的使用例子

利用LockSupport实现了一个先进先出(FIFO)阻塞线程队列功能,即每一条线程入队后都只能排队依次等待执行

class FIFOMutex {
private final AtomicBoolean locked = new AtomicBoolean(false);
private final Queue waiters = new ConcurrentLinkedQueue();

public void lock() {
boolean wasInterrupted = false;
//首先获取当前线程,然后存入waiters 队列头部
Thread current = Thread.currentThread();
waiters.add(current);

// 先循环判断是否满足线程挂起条件
while (waiters.peek() != current ||
!locked.compareAndSet(false, true)) {
//把当前的FIFOMutex 对象传递到 LockSupport.park(this),LockSupport会把this给赋值到当前Thread的变量里
LockSupport.park(this);
if (Thread.interrupted()) // ignore interrupts while waiting
wasInterrupted = true;
}
//上面那段while循环主要是负责阻塞挂起线程。当挂起条件不满足后则跳出循环
//删除队列头部已被阻塞完成的线程
waiters.remove();
if (wasInterrupted)          // reassert interrupt status on exit
current.interrupt();
}
//释放线程
public void unlock() {
locked.set(false);
LockSupport.unpark(waiters.peek());
}
}}

//TestFIFOMutex是FIFOMutex的测试例子
import java.util.Date;

public class TestFIFOMutex {

private static FIFOMutex mutex = new FIFOMutex();

public static void main(String[] args) {
new Thread(new Runnable(){
@Override
public void run() {
sleep(1000 * 2);
System.out.println(new Date().toGMTString()+" :t1 准备lock");
mutex.lock();
System.out.println(new Date().toGMTString()+" :t1 完成lock");
}
}).start();

new Thread(new Runnable(){
@Override
public void run() {
sleep(1000 * 3);
System.out.println(new Date().toGMTString()+" :t2 准备lock");
mutex.lock();
System.out.println(new Date().toGMTString()+" :t2 完成lock");
}
}).start();

new Thread(new Runnable(){
@Override
public void run() {
sleep(1000 * 5);
System.out.println(new Date().toGMTString()+" :t3 准备 unlock释放");
mutex.unlock();
System.out.println(new Date().toGMTString()+" :t3 释放完毕unlock");
}
}).start();
}

public static void sleep(long num){
try {
Thread.sleep(num);
} catch (InterruptedException e) {
e.printStackTrace();
}
}

}

//TestFIFOMutex执行结果
24 Nov 2015 14:33:46 GMT :t1 准备lock
24 Nov 2015 14:33:46 GMT :t1 完成lock
24 Nov 2015 14:33:47 GMT :t2 准备lock
24 Nov 2015 14:33:49 GMT :t3 准备 unlock释放
24 Nov 2015 14:33:49 GMT :t2 完成lock
24 Nov 2015 14:33:49 GMT :t3 释放完毕unlock


通过上面这个测试,可以观察到t2 完成lock必须得等到t3进行unlock。因为t2线程已经给park挂起了,必须得等待unpark唤醒
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  JUC LockSuppor AQS