一句话总结:sleep方法是当前线程休眠,让出cpu,不释放锁,这是Thread的静态方法;wait方法是当前线程等待,释放锁,这是Object的方法。同时要注意,Java 14 之后引入的 inline class 是没有 wait 方法的
Sleep()原理
public static native void sleep(long millis) throws InterruptedException;
sleep()是Thread中的static方法,也是native实现。就是调用底层的 sleep 函数实现:
void THREAD_sleep(int seconds) { #ifdef windows Sleep(1000L * seconds); #else sleep(seconds); #endif }
linux的sleep函数参考 sleep: https://man7.org/linux/man-pages/man3/sleep.3.html
wait(), notify(), notifyAll()
这些属于基本的Java多线程同步类的API,都是native实现:
public final native void wait(long timeout) throws InterruptedException; public final native void notify(); public final native void notifyAll();
那么底层实现是怎么回事呢? 首先我们需要先明确JDK底层实现共享内存锁的基本机制。 每个Object都有一个ObjectMonitor,这个ObjectMonitor中包含三个特殊的数据结构,分别是CXQ(实际上是Contention List),EntryList还有WaitSet;一个线程在同一时间只会出现在他们三个中的一个中。首先来看下CXQ:
一个尝试获取Object锁的线程,如果首次尝试(就是尝试CAS更新轻量锁)失败,那么会进入CXQ;进入的方法就是CAS更新CXQ指针指向自己,如果成功,自己的next指向剩余队列;CXQ是一个LIFO队列,设计成LIFO主要是为了:
- 进入CXQ队列后,每个线程先进入一段时间的spin自旋状态,尝试获取锁,获取失败的话则进入park状态。这个自旋的意义在于,假设锁的hold时间非常短,如果直接进入park状态的话,程序在用户态和系统态之间的切换会影响锁性能。这个spin可以减少切换;
- 进入spin状态如果成功获取到锁的话,需要出队列,出队列需要更新自己的头指针,如果位于队列前列,那么需要操作的时间会减少 但是,如果全部依靠这个机制,那么理所当然的,CAS更新队列头的操作会非常频繁。所以,引入了EntryList来减少争用:
假设Thread A是当前锁的Owner,接下来他要释放锁了,那么如果EntryList为null并且cxq不为null,就会从cxq末尾取出一个线程,放入EntryList(注意,EntryList为双向队列),并且标记EntryList其中一个线程为Successor(一般是头节点,这个EntryList的大小可能大于一,一般在notify时,后面会说到),这个Successor接下来会进入spin状态尝试获取锁(注意,在第一次自旋过去后,之后线程一直处于park状态)。如果获取成功,则成为owner,否则,回到EntryList中。 这种利用两个队列减少争用的算法,可以参考: Michael Scott's "2Q" algorithm 接下来,进入我们的正题,wait方法。如果一个线程成为owner后,执行了wait方法,则会进入WaitSet: Object.wait()底层实现
void ObjectMonitor::wait(jlong millis, bool interruptible, TRAPS) { //检查线程合法性 Thread *const Self = THREAD; assert(Self->is_Java_thread(), "Must be Java thread!"); JavaThread *jt = (JavaThread *) THREAD; DeferredInitialize(); //检查当前线程是否拥有锁 CHECK_OWNER(); EventJavaMonitorWait event; // 检查中断位 if (interruptible && Thread::is_interrupted(Self, true) && !HAS_PENDING_EXCEPTION) { if (JvmtiExport::should_post_monitor_waited()) { JvmtiExport::post_monitor_waited(jt, this, false); } if (event.should_commit()) { post_monitor_wait_event(&event, 0, millis, false); } TEVENT(Wait - ThrowIEX); THROW(vmSymbols::java_lang_InterruptedException()); return; } TEVENT(Wait); assert(Self->_Stalled == 0, "invariant"); Self->_Stalled = intptr_t(this); jt->set_current_waiting_monitor(this); //建立放入WaitSet中的这个线程的封装对象 ObjectWaiter node(Self); node.TState = ObjectWaiter::TS_WAIT; Self->_ParkEvent->reset(); OrderAccess::fence(); //用自旋方式获取操作waitset的lock,因为一般只有owner线程会操作这个waitset(无论是wait还是notify),所以竞争概率很小(除非响应interrupt事件才会有争用),采用spin方式效率高 Thread::SpinAcquire(&_WaitSetLock, "WaitSet - add"); //添加到waitset AddWaiter(&node); //释放锁,代表现在线程已经进入了waitset,接下来要park了 Thread::SpinRelease(&_WaitSetLock); if ((SyncFlags & 4) == 0) { _Responsible = NULL; } intptr_t save = _recursions; // record the old recursion count _waiters++; // increment the number of waiters _recursions = 0; // set the recursion level to be 1 exit(true, Self); // exit the monitor guarantee(_owner != Self, "invariant"); // 确保没有unpark事件冲突影响本次park,方法就是主动post一次unpark if (node._notified != 0 && _succ == Self) { node._event->unpark(); } // 接下来就是park操作了 。。。。。。。。。 。。。。。。。。。 }
当另一个owner线程调用notify时,根据Knob_MoveNotifyee这个值,决定将从waitset里面取出的一个线程放到哪里(cxq或者EntrySet)Object.notify()底层实现
void ObjectMonitor::notify(TRAPS) { //检查当前线程是否拥有锁 CHECK_OWNER(); if (_WaitSet == NULL) { TEVENT(Empty - Notify); return; } DTRACE_MONITOR_PROBE(notify, this, object(), THREAD); //决定取出来的线程放在哪里 int Policy = Knob_MoveNotifyee; //同样的,用自旋方式获取操作waitset的lock Thread::SpinAcquire(&_WaitSetLock, "WaitSet - notify"); ObjectWaiter *iterator = DequeueWaiter(); if (iterator != NULL) { TEVENT(Notify1 - Transfer); guarantee(iterator->TState == ObjectWaiter::TS_WAIT, "invariant"); guarantee(iterator->_notified == 0, "invariant"); if (Policy != 4) { iterator->TState = ObjectWaiter::TS_ENTER; } iterator->_notified = 1; Thread *Self = THREAD; iterator->_notifier_tid = Self->osthread()->thread_id(); ObjectWaiter *List = _EntryList; if (List != NULL) { assert(List->_prev == NULL, "invariant"); assert(List->TState == ObjectWaiter::TS_ENTER, "invariant"); assert(List != iterator, "invariant"); } if (Policy == 0) { // prepend to EntryList if (List == NULL) { iterator->_next = iterator->_prev = NULL; _EntryList = iterator; } else { List->_prev = iterator; iterator->_next = List; iterator->_prev = NULL; _EntryList = iterator; } } else if (Policy == 1) { // append to EntryList if (List == NULL) { iterator->_next = iterator->_prev = NULL; _EntryList = iterator; } else { // CONSIDER: finding the tail currently requires a linear-time walk of // the EntryList. We can make tail access constant-time by converting to // a CDLL instead of using our current DLL. ObjectWaiter *Tail; for (Tail = List; Tail->_next != NULL; Tail = Tail->_next); assert(Tail != NULL && Tail->_next == NULL, "invariant"); Tail->_next = iterator; iterator->_prev = Tail; iterator->_next = NULL; } } else if (Policy == 2) { // prepend to cxq // prepend to cxq if (List == NULL) { iterator->_next = iterator->_prev = NULL; _EntryList = iterator; } else { iterator->TState = ObjectWaiter::TS_CXQ; for (;;) { ObjectWaiter *Front = _cxq; iterator->_next = Front; if (Atomic::cmpxchg_ptr(iterator, &_cxq, Front) == Front) { break; } } } } else if (Policy == 3) { // append to cxq iterator->TState = ObjectWaiter::TS_CXQ; for (;;) { ObjectWaiter *Tail; Tail = _cxq; if (Tail == NULL) { iterator->_next = NULL; if (Atomic::cmpxchg_ptr(iterator, &_cxq, NULL) == NULL) { break; } } else { while (Tail->_next != NULL) Tail = Tail->_next; Tail->_next = iterator; iterator->_prev = Tail; iterator->_next = NULL; break; } } } else { ParkEvent *ev = iterator->_event; iterator->TState = ObjectWaiter::TS_RUN; OrderAccess::fence(); ev->unpark(); } if (Policy < 4) { iterator->wait_reenter_begin(this); } // _WaitSetLock protects the wait queue, not the EntryList. We could // move the add-to-EntryList operation, above, outside the critical section // protected by _WaitSetLock. In practice that's not useful. With the // exception of wait() timeouts and interrupts the monitor owner // is the only thread that grabs _WaitSetLock. There's almost no contention // on _WaitSetLock so it's not profitable to reduce the length of the // critical section. } //释放waitset的lock Thread::SpinRelease(&_WaitSetLock); if (iterator != NULL && ObjectMonitor::_sync_Notifications != NULL) { ObjectMonitor::_sync_Notifications->inc(); } }
对于NotifyAll就很好推测了,这里不再赘述;