目录:
- 1、LockSupport工具定位:
- 2、LockSupport工具定义:
- 3、LockSupport常用方法:
- 3.1、阻塞方法
- 3.2、唤醒线程
- 3.3、demo分析blocker
- 3.4、分析LockSupport.parkNanos(long)、LockSupport.parkUnitl(long):
- 4 、源码分析:
- 4.1、park()分析
- 4.2、unpark()分析
1、LockSupport工具定位
构建同步组件的基础工具,协助AQS完成相应线程的阻塞或者唤醒的工作。
2、LockSupport工具定义
LockSupport定义了一组以park开头的方法来阻塞当前线程,unpark来唤醒被阻塞的线程。
3、LockSupport常用方法
3.1、阻塞方法
- 1、void park():阻塞当前线程。如果调用unpark方法或者当前线程被中断,可以从park()方法中返回;Thread.interrupte()详解一下?<<<<<<传送门
- 2、void park(Object blocker):功能同方法1,入参增加一个Object对象,用来记录导致线程阻塞的阻塞对象,方便进行问题排查;
- 3、void parkNanos(long nanos):阻塞当前线程,最长不超过nanos纳秒,增加了超时返回的特性;
- 4、void parkNanos(Object blocker, long nanos):功能同方法3,入参增加一个Object对象,用来记录导致线程阻塞的阻塞对象,方便进行问题排查;
- 5、void parkUntil(long deadline):阻塞当前线程,知道deadline(绝对时间);
- 6、void parkUntil(Object blocker, long deadline):功能同方法5,入参增加一个Object对象,用来记录导致线程阻塞的阻塞对象,方便进行问题排查;
3.2、唤醒线程
- 1、void unpark(Thread thread):唤醒处于阻塞状态的指定线程;
3.3、demo分析blocker:
/**
* @program: jvmproject
* @description: LockSupport场景和方法
* @author: biudefu
* @create: 2019-08-24
**/
public class LockSupportMain {
private static final Object u = new Object();
public static void main(String[] args) throws InterruptedException {
System.out.println("main线程 启动!");
Thread t1 = new Thread(() -> {
new LockSupportMain().blockCurrencyThread();
}, "LockSupport_T1");
t1.start();
Thread.sleep(3 * 1000L);
Thread t2 = new Thread(() -> {
new LockSupportMain().blockCurrencyThreadByObject(u);
}, "LockSupport_T2");
t2.start();
Thread.sleep(3*1000);
System.out.println("main线程 退出!");
}
public void blockCurrencyThreadByObject(Object obj) {
System.out.println("LockSupport.park() ---> 阻塞当前线程!");
LockSupport.park(obj);
System.out.println("LockSupport.park() <--- 被唤醒!");
}
public void blockCurrencyThread() {
System.out.println("Thread name : " + Thread.currentThread().getName() + " call LockSupport.park() ---> 阻塞当前线程!");
LockSupport.park();
System.out.println("Thread name : " + Thread.currentThread().getName() + " call LockSupport.park() <--- 被唤醒!");
}
}
"LockSupport_T2" #12 prio=5 os_prio=31 tid=0x00007fe92e86c000 nid=0x5703 waiting on condition [0x0000700006b93000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x000000076aca4428> (a java.lang.Object)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at com.lyc.jvm.concurrent.locksupport.LockSupportMain.blockCurrencyThreadByObject(LockSupportMain.java:63)
at com.lyc.jvm.concurrent.locksupport.LockSupportMain.lambda$main$1(LockSupportMain.java:30)
at com.lyc.jvm.concurrent.locksupport.LockSupportMain$$Lambda$2/793589513.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
Locked ownable synchronizers:
- None
"LockSupport_T1" #11 prio=5 os_prio=31 tid=0x00007fe92e018000 nid=0x5603 waiting on condition [0x0000700006a90000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:304)
at com.lyc.jvm.concurrent.locksupport.LockSupportMain.blockCurrencyThread(LockSupportMain.java:69)
at com.lyc.jvm.concurrent.locksupport.LockSupportMain.lambda$main$0(LockSupportMain.java:20)
at com.lyc.jvm.concurrent.locksupport.LockSupportMain$$Lambda$1/1915910607.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
Locked ownable synchronizers:
- None
在jstack中可以看到- parking to wait for <0x000000076aca4428> (a java.lang.Object)
,方便进行问题排查。
3.4、分析LockSupport.parkNanos(long)、LockSupport.parkUnitl(long):
public static void main(String[] args) throws InterruptedException {
System.out.println("main线程 启动!");
//@003 LockSupport.parkNanos(long)与LockSupport.parkUntil(long);
Thread t3 = new Thread(()->{
blockUseNanos(10*1000*1000*1000l);
},"LockSupport_T3_Nanos");
Thread t4 = new Thread(()->{
DateFormat dateFormat2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
try {
Date myDate2 = dateFormat2.parse("2019-08-26 06:43:00");
blockUseUntil(myDate2.getTime());
} catch (ParseException e) {
e.printStackTrace();
}
},"LockSupport_T3_Until");
t3.start();
t4.start();
System.out.println("main线程 退出!");
}
private static void blockUseNanos(long l) {
System.out.println(DateUtil.getNowYYMMDDHHMMSS()+"LockSupport.parkNanos(long) ---> 阻塞当前线程!times:"+(l/1000/1000/1000)+"s");
LockSupport.parkNanos(l);
System.out.println(DateUtil.getNowYYMMDDHHMMSS()+"LockSupport.parkNanos(long) <--- 被唤醒!");
}
private static void blockUseUntil(long l) {
System.out.println(DateUtil.getNowYYMMDDHHMMSS()+"LockSupport.parkUntil(long) ---> 阻塞当前线程!唤醒时间:" + DateUtil.timesConvertDate(l));
LockSupport.parkUntil(l);
System.out.println(DateUtil.getNowYYMMDDHHMMSS()+"LockSupport.parkUntil(long) <--- 被唤醒!");
}
main线程 启动!
main线程 退出!
2019-08-26 06:48:49,LockSupport.parkNanos(long) ---> 阻塞当前线程!times:10s
2019-08-26 06:48:49,LockSupport.parkUntil(long) ---> 阻塞当前线程!唤醒时间:2019-08-26 06:49:00,
2019-08-26 06:48:59,LockSupport.parkNanos(long) <--- 被唤醒!
2019-08-26 06:49:00,LockSupport.parkUntil(long) <--- 被唤醒!
4、LockSupport依赖底层sun.misc.Unsafe
4.1、Unsafe.park和Unsafe.unpark的底层实现原理:
Unsafe类中函数基本都是Native属性, 在虚拟机源代码/hotspot/src/share/vm/prims/unsafe.cpp
Unsafe类Native与c++语言函数之间对应关系:
// These are the methods for 1.8.0
static JNINativeMethod methods_18[] = {
{CC"getObject", CC"("OBJ"J)"OBJ"", FN_PTR(Unsafe_GetObject)},
{CC"putObject", CC"("OBJ"J"OBJ")V", FN_PTR(Unsafe_SetObject)},
{CC"getObjectVolatile",CC"("OBJ"J)"OBJ"", FN_PTR(Unsafe_GetObjectVolatile)},
{CC"putObjectVolatile",CC"("OBJ"J"OBJ")V", FN_PTR(Unsafe_SetObjectVolatile)},
DECLARE_GETSETOOP(Boolean, Z),
DECLARE_GETSETOOP(Byte, B),
DECLARE_GETSETOOP(Short, S),
DECLARE_GETSETOOP(Char, C),
DECLARE_GETSETOOP(Int, I),
DECLARE_GETSETOOP(Long, J),
DECLARE_GETSETOOP(Float, F),
DECLARE_GETSETOOP(Double, D),
DECLARE_GETSETNATIVE(Byte, B),
DECLARE_GETSETNATIVE(Short, S),
DECLARE_GETSETNATIVE(Char, C),
DECLARE_GETSETNATIVE(Int, I),
DECLARE_GETSETNATIVE(Long, J),
DECLARE_GETSETNATIVE(Float, F),
DECLARE_GETSETNATIVE(Double, D),
{CC"getAddress", CC"("ADR")"ADR, FN_PTR(Unsafe_GetNativeAddress)},
{CC"putAddress", CC"("ADR""ADR")V", FN_PTR(Unsafe_SetNativeAddress)},
{CC"allocateMemory", CC"(J)"ADR, FN_PTR(Unsafe_AllocateMemory)},
{CC"reallocateMemory", CC"("ADR"J)"ADR, FN_PTR(Unsafe_ReallocateMemory)},
{CC"freeMemory", CC"("ADR")V", FN_PTR(Unsafe_FreeMemory)},
{CC"objectFieldOffset", CC"("FLD")J", FN_PTR(Unsafe_ObjectFieldOffset)},
{CC"staticFieldOffset", CC"("FLD")J", FN_PTR(Unsafe_StaticFieldOffset)},
{CC"staticFieldBase", CC"("FLD")"OBJ, FN_PTR(Unsafe_StaticFieldBaseFromField)},
{CC"ensureClassInitialized",CC"("CLS")V", FN_PTR(Unsafe_EnsureClassInitialized)},
{CC"arrayBaseOffset", CC"("CLS")I", FN_PTR(Unsafe_ArrayBaseOffset)},
{CC"arrayIndexScale", CC"("CLS")I", FN_PTR(Unsafe_ArrayIndexScale)},
{CC"addressSize", CC"()I", FN_PTR(Unsafe_AddressSize)},
{CC"pageSize", CC"()I", FN_PTR(Unsafe_PageSize)},
{CC"defineClass", CC"("DC_Args")"CLS, FN_PTR(Unsafe_DefineClass)},
{CC"allocateInstance", CC"("CLS")"OBJ, FN_PTR(Unsafe_AllocateInstance)},
{CC"monitorEnter", CC"("OBJ")V", FN_PTR(Unsafe_MonitorEnter)},
{CC"monitorExit", CC"("OBJ")V", FN_PTR(Unsafe_MonitorExit)},
{CC"tryMonitorEnter", CC"("OBJ")Z", FN_PTR(Unsafe_TryMonitorEnter)},
{CC"throwException", CC"("THR")V", FN_PTR(Unsafe_ThrowException)},
{CC"compareAndSwapObject", CC"("OBJ"J"OBJ""OBJ")Z", FN_PTR(Unsafe_CompareAndSwapObject)},
{CC"compareAndSwapInt", CC"("OBJ"J""I""I"")Z", FN_PTR(Unsafe_CompareAndSwapInt)},
{CC"compareAndSwapLong", CC"("OBJ"J""J""J"")Z", FN_PTR(Unsafe_CompareAndSwapLong)},
{CC"putOrderedObject", CC"("OBJ"J"OBJ")V", FN_PTR(Unsafe_SetOrderedObject)},
{CC"putOrderedInt", CC"("OBJ"JI)V", FN_PTR(Unsafe_SetOrderedInt)},
{CC"putOrderedLong", CC"("OBJ"JJ)V", FN_PTR(Unsafe_SetOrderedLong)},
{CC"park", CC"(ZJ)V", FN_PTR(Unsafe_Park)},
{CC"unpark", CC"("OBJ")V", FN_PTR(Unsafe_Unpark)}
};
其中:park----->Unsafe_Park,unpark----->Unsafe_Unpark。
UNSAFE_ENTRY(void, Unsafe_Park(JNIEnv *env, jobject unsafe, jboolean isAbsolute, jlong time))
UnsafeWrapper("Unsafe_Park");
EventThreadPark event;
#ifndef USDT2
HS_DTRACE_PROBE3(hotspot, thread__park__begin, thread->parker(), (int) isAbsolute, time);
#else /* USDT2 */
HOTSPOT_THREAD_PARK_BEGIN(
(uintptr_t) thread->parker(), (int) isAbsolute, time);
#endif /* USDT2 */
JavaThreadParkedState jtps(thread, time != 0);
thread->parker()->park(isAbsolute != 0, time);
#ifndef USDT2
HS_DTRACE_PROBE1(hotspot, thread__park__end, thread->parker());
#else /* USDT2 */
HOTSPOT_THREAD_PARK_END(
(uintptr_t) thread->parker());
#endif /* USDT2 */
if (event.should_commit()) {
oop obj = thread->current_park_blocker();
event.set_klass((obj != NULL) ? obj->klass() : NULL);
event.set_timeout(time);
event.set_address((obj != NULL) ? (TYPE_ADDRESS) cast_from_oop(obj) : 0);
event.commit();
}
UNSAFE_END
调用关系:thread->parker()->park(isAbsolute != 0, time)方法。
查看下thread和Parker关系/hotspot/src/share/vm/prims/runtime/thread.hpp(L1740)
:
// JSR166 per-thread parker
private:
Parker* _parker;
public:
Parker* parker() { return _parker; }
可以看出, 每个thread类中都包含一个Parker。
Parker定义在/hotspot/src/share/vm/runtime/park.hpp(L56)
, 定义如下:
class Parker : public os::PlatformParker {
private:
volatile int _counter ;
Parker * FreeNext ;
JavaThread * AssociatedWith ; // Current association
public:
Parker() : PlatformParker() {
_counter = 0 ;
FreeNext = NULL ;
AssociatedWith = NULL ;
}
protected:
~Parker() { ShouldNotReachHere(); }
public:
// For simplicity of interface with Java, all forms of park (indefinite,
// relative, and absolute) are multiplexed into one call.
void park(bool isAbsolute, jlong time);
void unpark();
// Lifecycle operators
static Parker * Allocate (JavaThread * t) ;
static void Release (Parker * e) ;
private:
static Parker * volatile FreeList ;
static volatile int ListLock ;
};
Parker类实际上在Linux系统下是用Posix线程库pthread中的mutex(互斥量),condition(条件变量)来实现的,mutex和condition保护了一个_counter的变量,当park时,这个变量被设置为0,当unpark时,这个变量被设置为1。
4.1、分析park()过程:
以Linux系统实现为例:
void Parker::park(bool isAbsolute, jlong time) {
// Ideally we'd do something useful while spinning, such
// as calling unpackTime().
// Optional fast-path check:
// Return immediately if a permit is available.
// We depend on Atomic::xchg() having full barrier semantics
// since we are doing a lock-free update to _counter.
//这里通过原子操作来完成_counter清零操作。 若_counter之前>0, 那么说明之前该线程被unpark()过, 就可以直接返回而不被阻塞。
if (Atomic::xchg(0, &_counter) > 0) return;
Thread* thread = Thread::current();
assert(thread->is_Java_thread(), "Must be JavaThread"); //判断一定的是java线程
JavaThread *jt = (JavaThread *)thread; //类强制转化
// Optional optimization -- avoid state transitions if there's an interrupt pending.
// Check interrupt before trying to wait
//进入睡眠等待前先检查是否有中断信号, 若有中断信号也直接返回。
if (Thread::is_interrupted(thread, false)) {
return;
}
// Next, demultiplex/decode time arguments
timespec absTime;
//如果是按参数小于0,或者绝对时间,那么可以直接返回
if (time < 0 || (isAbsolute && time == 0) ) { // don't wait at all
return;
}
//如果时间大于0,判断阻塞超时时间或阻塞截止日期,同时将时间赋值给absTime
if (time > 0) {
unpackTime(&absTime, isAbsolute, time);
}
// Enter safepoint region
// Beware of deadlocks such as 6317397.
// The per-thread Parker:: mutex is a classic leaf-lock.
// In particular a thread must never block on the Threads_lock while
// holding the Parker:: mutex. If safepoints are pending both the
// the ThreadBlockInVM() CTOR and DTOR may grab Threads_lock.
ThreadBlockInVM tbivm(jt);
// Don't wait if cannot get lock since interference arises from
// unblocking. Also. check interrupt before trying wait
//再次检查, 如果有中断信号。直接返回; 或者申请互斥锁失败,则直接返回pthread_mutex_trylock返回0。任何其他返回值都表示错误。
//函数pthread_mutex_trylock是POSIX 线程pthread_mutex_lock的非阻塞版本。
if (Thread::is_interrupted(thread, false) || pthread_mutex_trylock(_mutex) != 0) {
return;
}
//此时已经通过_mutex将该代码进行了互斥操作, 那么直接对_counter都是安全的
int status ;
如果count>0, 说明之前原子操作赋值为0没有成功。 而_counter> 0, 线程可以直接不阻塞而返回
if (_counter > 0) { // no wait needed
//将_counter直接清零
_counter = 0;
//释放锁并返回, 返回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(); //这个函数是HotSpot VM对JMM的内存屏障一个具体的实现函数;
return;
}
#ifdef ASSERT
// Don't catch signals while blocked; let the running threads have the signals.
// (This allows a debugger to break into the running thread.)
sigset_t oldsigs;
sigset_t* allowdebug_blocked = os::Linux::allowdebug_blocked_signals();
pthread_sigmask(SIG_BLOCK, allowdebug_blocked, &oldsigs);
#endif
OSThreadWaitState osts(thread->osthread(), false /* not Object.wait() */);
jt->set_suspend_equivalent();
// cleared by handle_special_suspend_equivalent_condition() or java_suspend_self()
assert(_cur_index == -1, "invariant");
//若没有超时时间,那么本线程将进入睡眠状态并释放cpu、释放对_mutex的锁定,等待其他线程调用pthread_cond_signal唤醒该线程;唤醒后会获取对_mutex的锁定的锁定
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;
//开始真正的阻塞,超时等待,或者其他线程pthread_cond_signal唤醒该线程
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());
}
}
_cur_index = -1;
assert_status(status == 0 || status == EINTR ||
status == ETIME || status == ETIMEDOUT,
status, "cond_timedwait");
#ifdef ASSERT
pthread_sigmask(SIG_SETMASK, &oldsigs, NULL);
#endif
//该线程被唤醒了, 同时也对_mutex加锁了, 置位_counter是线程安全的
_counter = 0 ;
//解锁_mutex
status = pthread_mutex_unlock(_mutex) ;
assert_status(status == 0, status, "invariant") ;
// Paranoia to ensure our locked and lock-free paths interact
// correctly with each other and Java-level accesses.
OrderAccess::fence(); //内存屏障
// If externally suspended while waiting, re-suspend
if (jt->handle_special_suspend_equivalent_condition()) {
jt->java_suspend_self();
}
}
Parker::park主要做了如下事情:
- 检查_counter>0(别的线程调用过unpark), 则原子操作清零。线程不用睡眠并返回。
- 检查该线程是否有中断信号, 有的话,清掉并返回。
- 尝试通过pthread_mutex_trylock对_mutex加锁来达到线程互斥。
- 检查_counter是否>0, 若成立,说明第一步原子清零操作失败。检查park是否设置超时时间, 若设置了通过safe_cond_timedwait进行超时等待; 若没有设置,调用pthread_cond_wait进行阻塞等待。 这两个函数都在阻塞等待时都会放弃cpu的使用。 直到别的线程调用pthread_cond_signal唤醒
- 直接_counter=0清零。
- 通过pthread_mutex_unlock释放mutex的加锁。
需要了解下: safe_cond_timedwait/pthread_cond_wait在执行之前肯定已经获取了锁_mutex, 在睡眠前释放了锁, 在被唤醒之前, 首先再取唤醒锁。
4.3、unpark()过程:
void Parker::unpark() {
int s, status ;
//首先是互斥获取锁
status = pthread_mutex_lock(_mutex);
assert (status == 0, "invariant") ;
s = _counter;
//只要把这个状态置为1就行了,就是说多次调用unpack()没啥意义
_counter = 1;
//s只能为0,说明没有人调用unpark
if (s < 1) {
// thread might be parked
if (_cur_index != -1) {
// thread is definitely parked
//线程已经处于parker状态了
if (WorkAroundNPTLTimedWaitHang) {
//pthread_cond_signal可以唤醒pthread_cond_wait()被&_cond[_cur_index]阻塞的线程
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") ;
}
}
unpark()主要做了如下事情:
- 首先获取锁_mutex。
- 对_counter置为1, 而不管之前什么值, 这里说明无论多少函数调用unpark(), 都是无效的, 只会记录一次。
- 检查线程是否已经被阻塞了, 若已经阻塞了,调用pthread_cond_signal唤醒唤醒。
- 释放对_mutex的锁定。
参考资料:
《深入理解Java虚拟机-2nd》
《Java 并发编程实战》
《Java 并发编程的艺术》