第13页
一个线程调用共享对象的notify()方法后会唤醒一个在该共享变量上调用wait系列方法后被挂起的线程。一个共享变量上可能会有多个线程在等待,具体唤醒哪个等待的线程是随机的
一个并不随机的例子
请考虑代码片段
...
private static volatile Object resourceA = new Object();
public static void main(String[] args) throws InterruptedException {
ExecutorService pool = Executors.newCachedThreadPool();
int concurrencyThreadNum = 10;
CountDownLatch latch = new CountDownLatch(concurrencyThreadNum);
for (int i = 0; i < 10;) {
pool.execute(new Task(++i, resourceA, latch));
}
Thread.sleep(TimeUnit.SECONDS.toMillis(1));
synchronized (resourceA) {
resourceA.notify();
log.info("release lock");
}
latch.await();
log.info("done!");
}
public static class Task implements Runnable {
private final int i;
private final Object lock;
private final CountDownLatch latch;
public Task(int i, Object lock, CountDownLatch latch) {
this.i = i;
this.lock = lock;
this.latch = latch;
}
@Override
public void run() {
synchronized (lock) {
log.info(String.format("thread-%d get lock", i));
try {
log.info(String.format("thread-%d begin wait", i));
lock.wait();
log.info(String.format("thread-%d end wait", i));
Thread.sleep(TimeUnit.SECONDS.toMillis(2));
lock.notify();
} catch (InterruptedException e) {
log.error(e.getMessage());
}
log.info(String.format("thread-%d release lock", i));
latch.countDown();
}
}
}
...
执行结果可能如下:
03:38:31.168 [pool-1-thread-1] INFO love.iris.firestige.beautyofjavaconcurrency.RandomNotify - thread-1 get lock
03:38:31.171 [pool-1-thread-1] INFO love.iris.firestige.beautyofjavaconcurrency.RandomNotify - thread-1 begin wait
03:38:31.171 [pool-1-thread-10] INFO love.iris.firestige.beautyofjavaconcurrency.RandomNotify - thread-10 get lock
03:38:31.171 [pool-1-thread-10] INFO love.iris.firestige.beautyofjavaconcurrency.RandomNotify - thread-10 begin wait
03:38:31.171 [pool-1-thread-9] INFO love.iris.firestige.beautyofjavaconcurrency.RandomNotify - thread-9 get lock
03:38:31.172 [pool-1-thread-9] INFO love.iris.firestige.beautyofjavaconcurrency.RandomNotify - thread-9 begin wait
03:38:31.172 [pool-1-thread-8] INFO love.iris.firestige.beautyofjavaconcurrency.RandomNotify - thread-8 get lock
03:38:31.172 [pool-1-thread-8] INFO love.iris.firestige.beautyofjavaconcurrency.RandomNotify - thread-8 begin wait
03:38:31.172 [pool-1-thread-7] INFO love.iris.firestige.beautyofjavaconcurrency.RandomNotify - thread-7 get lock
03:38:31.172 [pool-1-thread-7] INFO love.iris.firestige.beautyofjavaconcurrency.RandomNotify - thread-7 begin wait
03:38:31.172 [pool-1-thread-6] INFO love.iris.firestige.beautyofjavaconcurrency.RandomNotify - thread-6 get lock
03:38:31.172 [pool-1-thread-6] INFO love.iris.firestige.beautyofjavaconcurrency.RandomNotify - thread-6 begin wait
03:38:31.172 [pool-1-thread-5] INFO love.iris.firestige.beautyofjavaconcurrency.RandomNotify - thread-5 get lock
03:38:31.172 [pool-1-thread-5] INFO love.iris.firestige.beautyofjavaconcurrency.RandomNotify - thread-5 begin wait
03:38:31.173 [pool-1-thread-3] INFO love.iris.firestige.beautyofjavaconcurrency.RandomNotify - thread-3 get lock
03:38:31.173 [pool-1-thread-3] INFO love.iris.firestige.beautyofjavaconcurrency.RandomNotify - thread-3 begin wait
03:38:31.173 [pool-1-thread-4] INFO love.iris.firestige.beautyofjavaconcurrency.RandomNotify - thread-4 get lock
03:38:31.173 [pool-1-thread-4] INFO love.iris.firestige.beautyofjavaconcurrency.RandomNotify - thread-4 begin wait
03:38:31.173 [pool-1-thread-2] INFO love.iris.firestige.beautyofjavaconcurrency.RandomNotify - thread-2 get lock
03:38:31.173 [pool-1-thread-2] INFO love.iris.firestige.beautyofjavaconcurrency.RandomNotify - thread-2 begin wait
03:38:32.161 [main] INFO love.iris.firestige.beautyofjavaconcurrency.RandomNotify - release lock
03:38:32.161 [pool-1-thread-1] INFO love.iris.firestige.beautyofjavaconcurrency.RandomNotify - thread-1 end wait
03:38:34.162 [pool-1-thread-1] INFO love.iris.firestige.beautyofjavaconcurrency.RandomNotify - thread-1 release lock
03:38:34.162 [pool-1-thread-10] INFO love.iris.firestige.beautyofjavaconcurrency.RandomNotify - thread-10 end wait
03:38:36.162 [pool-1-thread-10] INFO love.iris.firestige.beautyofjavaconcurrency.RandomNotify - thread-10 release lock
03:38:36.162 [pool-1-thread-9] INFO love.iris.firestige.beautyofjavaconcurrency.RandomNotify - thread-9 end wait
03:38:38.162 [pool-1-thread-9] INFO love.iris.firestige.beautyofjavaconcurrency.RandomNotify - thread-9 release lock
03:38:38.162 [pool-1-thread-8] INFO love.iris.firestige.beautyofjavaconcurrency.RandomNotify - thread-8 end wait
03:38:40.163 [pool-1-thread-8] INFO love.iris.firestige.beautyofjavaconcurrency.RandomNotify - thread-8 release lock
03:38:40.163 [pool-1-thread-7] INFO love.iris.firestige.beautyofjavaconcurrency.RandomNotify - thread-7 end wait
03:38:42.164 [pool-1-thread-7] INFO love.iris.firestige.beautyofjavaconcurrency.RandomNotify - thread-7 release lock
03:38:42.164 [pool-1-thread-6] INFO love.iris.firestige.beautyofjavaconcurrency.RandomNotify - thread-6 end wait
03:38:44.164 [pool-1-thread-6] INFO love.iris.firestige.beautyofjavaconcurrency.RandomNotify - thread-6 release lock
03:38:44.164 [pool-1-thread-5] INFO love.iris.firestige.beautyofjavaconcurrency.RandomNotify - thread-5 end wait
03:38:46.165 [pool-1-thread-5] INFO love.iris.firestige.beautyofjavaconcurrency.RandomNotify - thread-5 release lock
03:38:46.165 [pool-1-thread-3] INFO love.iris.firestige.beautyofjavaconcurrency.RandomNotify - thread-3 end wait
03:38:48.165 [pool-1-thread-3] INFO love.iris.firestige.beautyofjavaconcurrency.RandomNotify - thread-3 release lock
03:38:48.165 [pool-1-thread-4] INFO love.iris.firestige.beautyofjavaconcurrency.RandomNotify - thread-4 end wait
03:38:50.166 [pool-1-thread-4] INFO love.iris.firestige.beautyofjavaconcurrency.RandomNotify - thread-4 release lock
03:38:50.166 [pool-1-thread-2] INFO love.iris.firestige.beautyofjavaconcurrency.RandomNotify - thread-2 end wait
03:38:52.167 [pool-1-thread-2] INFO love.iris.firestige.beautyofjavaconcurrency.RandomNotify - thread-2 release lock
03:38:52.167 [main] INFO love.iris.firestige.beautyofjavaconcurrency.RandomNotify - done!
Process finished with exit code 0
可以看到,虽然线程池内的线程随机获得了锁。但是当我们notify()的时候,唤醒顺序并不是随机的,而是和进入wait()的顺序一致。notify()唤醒的线程看起来并不是随机的,那么书说错了么?
notify()做了什么?
先说结论,原文的说法没有问题,这里出现顺序一致是特殊情况。那么为什么会有这种情况呢?先上源码,看看notify()都做了什么。
// openjdk8
//./hotspot/src/share/vm/runtime/objectMonitor.cpp
//如果锁没有竞争,那么可以直接把线程从WaitSet出队,并且直接解除阻塞
void ObjectMonitor::notify(TRAPS) {
CHECK_OWNER();
if (_WaitSet == NULL) {
TEVENT (Empty-Notify) ;
return ;
}
DTRACE_MONITOR_PROBE(notify, this, object(), THREAD);
//获取入队策略
int Policy = Knob_MoveNotifyee ;
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) {
if (List == NULL) {
iterator->_next = iterator->_prev = NULL ;
_EntryList = iterator ;
} else {
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) {
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) {
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);
}
}
Thread::SpinRelease (&_WaitSetLock) ;
if (iterator != NULL && ObjectMonitor::_sync_Notifications != NULL) {
ObjectMonitor::_sync_Notifications->inc() ;
}
}
预备知识:
- 监视器队列:
- cxq,持有最近尝试进入监视器的线程(Cxq points to the the set of Recently Arrived Threads attempting entry)
- EntryList,持有准备进入监视器的线程,通常用一个双向链表或者双向环形链表实现
- WaitSet,持有调用wait()而阻塞的线程,双向环形链表实现
- 一个线程任何时间最多只能出现在一个监视器队列
简而言之,notify()的任务就是把WaitSet中的线程移入EntryList或者cxq,在这之后就是竞争监视器锁的过程。
能竞争或者说能给监视器响应的线程只有cxq和EntryList中的线程(Only threads on cxq|EntryList may be responsible for a monitor)。
转移的过程:
抛开特殊情况,如WaitSet为空,或者cxq为空。从WaitSet到EntryList有两个过程:
- WaitSet出队
- EntryList入队
WaitSet出队,使用DequeueWaiter()方法直接按照FIFO出队
void ObjectMonitor::notify(TRAPS) {
...
ObjectWaiter * iterator = DequeueWaiter() ;
...
}
inline ObjectWaiter* ObjectMonitor::DequeueWaiter() {
ObjectWaiter* waiter = _WaitSet;
if (waiter) {
DequeueSpecificWaiter(waiter);
}
return waiter;
}
EntryList入队则分四种策略,由Knob_MoveNotifyee控制。
- Knob_MoveNotifyee = 0,头插到EntryList
if (Policy == 0) {
if (List == NULL) {
iterator->_next = iterator->_prev = NULL ;
_EntryList = iterator ;
} else {
List->_prev = iterator ;
iterator->_next = List ;
iterator->_prev = NULL ;
_EntryList = iterator ;
}
}
- Knob_MoveNotifyee = 1,尾插到EntryList
if (Policy == 1) {
if (List == NULL) {
iterator->_next = iterator->_prev = NULL ;
_EntryList = iterator ;
} else {
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 ;
}
}
- Knob_MoveNotifyee = 2,头插到cxq
if (Policy == 2) {
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 ;
}
}
}
}
- Knob_MoveNotifyee = 3, 尾插到cxq
if (Policy == 3) {
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 ;
}
}
}
回到开头
梳理流程过后我们可以看到,notify()只是将线程从WaitSet向EntryList或者cxq转移,虽然入队的顺序根据jvm中Knob_MoveNotifyee的值来决定,但是在一个运行的JVM中,入队顺序是一致的。真正造成随机性的原因在于被唤醒后的锁竞争过程。
开头的示例代码将全部10个线程加入WaitSet之后,每次只唤醒一个线程,且没有新的线程竞争监视器锁。由于WaitSet是个FIFO的队列,所以日志上表现出notify()的顺序和wait()的顺序一致。