前几篇分析过wait和notify方法,这两个方法是用来在两个线程之间进行通信的(生产者消费者模型的基本实现)。
a.wait()-> b.execute()->b.notify()->a.execute()。
我们要知道,在实现两个线程之间能够切换运行时,wait和notify方法其实是运用了一个中间变量,就是Object对象来实现的。
今天我们要学习的LockSupport则不是这样,它面向的是线程,和wait不同。
在分析之前,先来个小例子
public class Test {
public static void main(String[] args) throws Exception {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("starting");
LockSupport.park(this);
System.out.println("oh,I am alive");
}
});
thread.start();
Thread.sleep(3000);
System.out.println("main over");
LockSupport.unpark(thread);
}
}
starting
main over
oh,I am alive
如果不执行unpark方法的话,则线程thread是不会执行“oh..”的打印的,那么这是为什么呢???它是怎么实现的呢?和wait的不同在哪儿呢?
一.jdk源码探究
// Hotspot implementation via intrinsics API
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long parkBlockerOffset;
1.unsafe方法在AtomicInteger中我印象很深,它是一个能直接操作内存的方法,但是Java是不推荐你直接去使用的,因为它不稳定而且不安全。
2.parkBlockOffset是偏移量,它是parkBlock对象在内存中的地址,而parkBlock则是记录线程被谁阻塞的。下面的代码是这个Offset怎么来的,通过反射调用得到的。
static {
try {
parkBlockerOffset = unsafe.objectFieldOffset
(java.lang.Thread.class.getDeclaredField("parkBlocker"));
} catch (Exception ex) { throw new Error(ex); }
}
3.park方法()
/**
* Disables the current thread for thread scheduling purposes unless the
* permit is available.
*
* <p>If the permit is available then it is consumed and the call returns
* immediately; otherwise
* the current thread becomes disabled for thread scheduling
* purposes and lies dormant until one of three things happens:
*
* <ul>
* <li>Some other thread invokes {@link #unpark unpark} with the
* current thread as the target; or
*
* <li>Some other thread {@linkplain Thread#interrupt interrupts}
* the current thread; or
*
* <li>The call spuriously (that is, for no reason) returns.
* </ul>
*
* <p>This method does <em>not</em> report which of these caused the
* method to return. Callers should re-check the conditions which caused
* the thread to park in the first place. Callers may also determine,
* for example, the interrupt status of the thread upon return.
*
* @param blocker the synchronization object responsible for this
* thread parking
* @since 1.6
*/
public static void park(Object blocker) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
unsafe.park(false, 0L);
setBlocker(t, null);
}
关注一下流程:
3.1:记录当前线程等待的对象(阻塞对象)。
3.2:阻塞当前线程。
3.3:线程被唤醒之后,将当前线程等待对象置为null。
但是我们需要关注一下这个park()方法的注释:
* Disables the current thread for thread scheduling purposes unless the
* permit is available.
3.3在permit允许的情况下,禁止当前线程被调度,也就是阻塞当前线程。
If the permit is available then it is consumed and the call returns
* immediately; otherwise
* the current thread becomes disabled for thread scheduling
* purposes and lies dormant until one of three things happens:
3.4如果permit可用,那么它会被消费掉,并且立即返回。否则还是禁用,直到下面3种情况发生。(这个待会在讨论)。
先来看看这个permit是什么吧???
3.5关于许可(permit)
park和unpark的设计核心就是这个许可概念。LockSupport和每个使用它的线程都会与一个许可(permit)关联,(其实这个wait/notify一样,它们都和monitor对象关联)。permit相当于一个信号量,默认是0。调用一次unpark方法,就变成1。调用一次park方法就消费掉,变成0。可以这么理解,park就是等待一个许可,unpark是为某个线程提供一个许可,让其去消费。
那么关于park和unpark的顺序。基于上面的例子,我们先park住了线程thread,然后主线程unpark了。然后thread继续执行。其实也可以先unpark,提供一个许可出去,那么执行park的线程预先已经得到一个许可,可以立马执行。这一点比较绕口。但是是正确的。
3.6关于3种情况
* <ul>
* <li>Some other thread invokes {@link #unpark unpark} with the
* current thread as the target; or
*
* <li>Some other thread {@linkplain Thread#interrupt interrupts}
* the current thread; or
*
* <li>The call spuriously (that is, for no reason) returns.
3.6.1 其他线程调用unpark方法唤醒当前线程。
3.6.2 其他线程执行interrupt方法
3.6.3 未知原因返回(这特么的只能说,什么鬼?????)
public class TestA {
public static void main(String[] args) throws Exception{
T2 t2 = new T2();
t2.start();
Thread.sleep(3000);
t2.interrupt();
}
}
class T2 extends Thread {
@Override
public void run() {
System.out.println("t2 into park:" + Thread.currentThread().isInterrupted());
LockSupport.park();
System.out.println("t2 wake up:"+Thread.currentThread().isInterrupted());
}
}
t2 into park:false
t2 wake up:true
* <p>This method does <em>not</em> report which of these caused the
* method to return. Callers should re-check the conditions which caused
* the thread to park in the first place. Callers may also determine,
* for example, the interrupt status of the thread upon return.
3.6.4 因此如果你想使用interrupt的话,最好还是判断下状态,因为park不会告诉你它是由于什么原因被唤醒的。
3.6.5 另外使用park(object block)方法的话,可以在线程堆栈中看见这个对象
* @param blocker the synchronization object responsible for this
* thread parking
* @since 1.6
public class TestA {
public static void main(String[] args) throws Exception{
T2 t2 = new T2();
t2.start();
Thread.sleep(3000);
System.out.println("main over");
}
}
class T2 extends Thread {
@Override
public void run() {
System.out.println("into lock");
Object s = "lockObject";
LockSupport.park(s);
System.out.println("over from lock");
}
}
into lock
main over
打印信息中多出的wait for信息,很重要,能够帮助我们分析很多东西。如果只使用park()方法的话,只能出现一些简略信息,没什么帮助。
4.unpark()方法
/**
* Makes available the permit for the given thread, if it
* was not already available. If the thread was blocked on
* {@code park} then it will unblock. Otherwise, its next call
* to {@code park} is guaranteed not to block. This operation
* is not guaranteed to have any effect at all if the given
* thread has not been started.
*
* @param thread the thread to unpark, or {@code null}, in which case
* this operation has no effect
*/
public static void unpark(Thread thread) {
if (thread != null)
unsafe.unpark(thread);
}
看到unpark方法的注释可以更好的对 “关于许可” 的的解释更有利了。
1.如果给定线程的许可不可用,那么使其可用。
2.如果给定线程已经调用过park(),那么则解除阻塞状态。
3.否则,保证下一次调用 park 不会受阻塞。如果给定线程尚未启动,则无法保证此操作有任何效果。
public class TestA {
public static void main(String[] args) throws Exception{
T2 t2 = new T2();
t2.start();
LockSupport.unpark(t2); //相当于先给许可,此时许可的值从0变成1
System.out.println("main over");
}
}
class T2 extends Thread {
@Override
public void run() {
try{
System.out.println("into lock");
Thread.sleep(3000);
LockSupport.park(); //t2消费掉当前许可,许可的值从1变成0
System.out.println("over from lock");
}catch (Exception e){
e.printStackTrace();
}
}
}
main over
into lock
over from lock
5.其他方法带有参数
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);
}
}
5.1最长时间不超过Nanos秒,然后阻塞线程。
public static void parkUntil(Object blocker, long deadline) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
unsafe.park(true, deadline);
setBlocker(t, null);
}
5.2阻塞当前线程直到deadline的时间。
二.JVM内的源码理解和分析
park()
void Parker::park(bool isAbsolute, jlong time) {
//如果CAS操作成功,返回,说明park成功
if (Atomic::xchg(0, &_counter) > 0) return;
Thread* thread = Thread::current();
assert(thread->is_Java_thread(), "Must be JavaThread");
JavaThread *jt = (JavaThread *)thread;
//如果线程在执行park之前已经被打断了,直接返回,
//这就解释了jdk源码注释中park的返回问题
if (Thread::is_interrupted(thread, false)) {
return;
}
//构造当前线程对象的ThreadBlockInVM
ThreadBlockInVM tbivm(jt);
// Don't wait if cannot get lock since interference arises from
// unblocking. Also. check interrupt before trying wait
if (Thread::is_interrupted(thread, false) || pthread_mutex_trylock(_mutex) != 0) {
return;
}
int status ;
//检查是否大于0,大于0可以park,返回
if (_counter > 0) { // no wait needed
_counter = 0;
status = pthread_mutex_unlock(_mutex);
assert (status == 0, "invariant") ;
// Paranoia to ensure our locked and lock-free paths interact
// correctly with each other and Java-level accesses.
OrderAccess::fence();
return;
}
//根据等待时间的不同进行等待然后进行count的值的设置
if (time == 0) {
_cur_index = REL_INDEX; // arbitrary choice when not timed
status = pthread_cond_wait (&_cond[_cur_index], _mutex) ;
} else {
_cur_index = isAbsolute ? ABS_INDEX : REL_INDEX;
status = os::Linux::safe_cond_timedwait (&_cond[_cur_index], _mutex, &absTime) ;
if (status != 0 && WorkAroundNPTLTimedWaitHang) {
pthread_cond_destroy (&_cond[_cur_index]) ;
pthread_cond_init (&_cond[_cur_index], isAbsolute ? NULL : os::Linux::condAttr());
}
}
_counter = 0 ;
status = pthread_mutex_unlock(_mutex) ;
assert_status(status == 0, status, "invariant") ;
..
}
park方法的核心就是在更新count的值,将count的值+1返回。
unpark()
void Parker::unpark() {
int s, status ;
status = pthread_mutex_lock(_mutex);
assert (status == 0, "invariant") ;
s = _counter;
_counter = 1; //置为1
if (s < 1) {
// thread might be parked
if (_cur_index != -1) {
// thread is definitely parked
if (WorkAroundNPTLTimedWaitHang) {
//唤醒线程
status = pthread_cond_signal (&_cond[_cur_index]);
assert (status == 0, "invariant");
status = pthread_mutex_unlock(_mutex);
assert (status == 0, "invariant");
} else {
//解锁
status = pthread_mutex_unlock(_mutex);
assert (status == 0, "invariant");
status = pthread_cond_signal (&_cond[_cur_index]);
assert (status == 0, "invariant");
}
} else {
pthread_mutex_unlock(_mutex);
assert (status == 0, "invariant") ;
}
} else {
pthread_mutex_unlock(_mutex);
assert (status == 0, "invariant") ;
}
}
总结:HotSpot Parker用condition和mutex维护了一个_counter变量,park时,变量_counter置为0,unpark时,变量_counter置为1。