注意:如无特殊说明,本文源码分析基于的Java JDK版本均为1.8。
假设这样的情景,只要线程A修改了某值value,则线程B则对新的value值进行某些操作,比较容易想到的方法是,线程B不断循环访问value,一旦感知到变化,则执行相应逻辑。
// 线程A
set value = newValue
// 线程B
for(;;){
while(newValue != oldValue){
doSomething(newValue);
}
}
如果value值发生变化的频率较低,则线程B不断自旋获取value的值,过多的无效尝试极大地浪费系统处理资源。
改进的方法是,每一段时间(如1s)去访问一下:
// 线程A
set value = newValue
// 线程B
for(;;){
while(newValue != oldValue){
doSomething(newValue);
}
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
}
改进后,较少了较多的无效尝试,降低了对处理器资源的浪费,但是休眠时间的大小难以确定:
考虑到上述监听机制上述的困境,线程间协作采用的是等待/通知机制。
原理就是,当线程A完成对数据的修改之后,会通过一定的机制通知线程B来获取新的数据值来进行相关业务处理,线程B处理完之后挂起等待后续线程A的通知。
等待/通知机制是所有Java对象均具备的,因为相关方法是定义在所有对象的超类java.lang.Object上的。
相关方法描述如下:
方法名称 | 描述 |
---|---|
notify() | 通知一个在对象上等待的线程,使其从wait()返回,而返回的前提是该线程获取到对象的锁 |
notifyAll() | 通知所有等待在该对象上的线程 |
wait() | 调用wait()方法后,会释放对象的锁,并进入WAITING状态 |
wait(long) | 超时等待一段时间,如果没有通知就超时返回 |
wait(long, int) | 超时时间更细粒度的控制,可以达到毫秒 |
public class WaitAndNotifyDemo {
public static void main(String[] args) {
Object lock = new Object();
// 线程A
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程A等待获取锁");
synchronized (lock) {
try {
System.out.println("线程A获取锁");
Thread.sleep(3000);
System.out.println("线程A将要运行wait()进入等待");
lock.wait();
System.out.println("线程A等待结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
// 线程B
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程B等待获取锁");
synchronized (lock) {
try {
System.out.println("线程B获取锁");
Thread.sleep(3000);
System.out.println("线程B将要运行notify()唤醒其他wait状态的线程");
lock.notify();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}
运行结果为:
线程A等待获取锁
线程A获取锁
线程B等待获取锁
线程A将要运行wait()进入等待
线程B获取锁
线程B将要运行notify()唤醒其他wait状态的线程
线程A等待结束
Process finished with exit code 0
需要注意的是:Thread的sleep()方法仅是让出CPU时间片,让其他线程有机会执行,但是sleep()方法不会释放其持有的对象锁,仅当调用对象的wait()方法才会释放对象锁。
进入到Object的wait()方法:
public final void wait() throws InterruptedException {
wait(0);
}
可以看出,其底层调用的是Object的重载方法wait(long):
public final native void wait(long timeout) throws InterruptedException;
重载方法wait(long)是native方法,方法实现可通过OpenJDK来查看(Object.c)来找到:
static JNINativeMethod methods[] = {
{"hashCode", "()I", (void *)&JVM_IHashCode},
{"wait", "(J)V", (void *)&JVM_MonitorWait},
{"notify", "()V", (void *)&JVM_MonitorNotify},
{"notifyAll", "()V", (void *)&JVM_MonitorNotifyAll},
{"clone", "()Ljava/lang/Object;", (void *)&JVM_Clone},
};
其中,JVM_MonitorWait和JVM_MonitorNotify分别对应于wait()和notify()方法,JVM_MonitorWait方法声明是在jvm.h中,如下所示:
JNIEXPORT void JNICALL
JVM_MonitorWait(JNIEnv *env, jobject obj, jlong ms);
方法实现为:
JVM_ENTRY(void, JVM_MonitorWait(JNIEnv* env, jobject handle, jlong ms))
JVMWrapper("JVM_MonitorWait");
Handle obj(THREAD, JNIHandles::resolve_non_null(handle));
assert(obj->is_instance() || obj->is_array(), "JVM_MonitorWait must apply to an object");
JavaThreadInObjectWaitState jtiows(thread, ms != 0);
if (JvmtiExport::should_post_monitor_wait()) {
JvmtiExport::post_monitor_wait((JavaThread *)THREAD, (oop)obj(), ms);
}
ObjectSynchronizer::wait(obj, ms, CHECK);
JVM_END
JVM_MonitorWait方法最终调用了ObjectSynchronizer的wait方法:
void ObjectSynchronizer::wait(Handle obj, jlong millis, TRAPS) {
if (UseBiasedLocking) {
BiasedLocking::revoke_and_rebias(obj, false, THREAD);
assert(!obj->mark()->has_bias_pattern(), "biases should be revoked by now");
}
if (millis < 0) {
TEVENT (wait - throw IAX) ;
THROW_MSG(vmSymbols::java_lang_IllegalArgumentException(), "timeout value is negative");
}
ObjectMonitor* monitor = ObjectSynchronizer::inflate(THREAD, obj());
DTRACE_MONITOR_WAIT_PROBE(monitor, obj(), THREAD, millis);
monitor->wait(millis, true, THREAD);
/* This dummy call is in place to get around dtrace bug 6254741. Once
that's fixed we can uncomment the following line and remove the call */
// DTRACE_MONITOR_PROBE(waited, monitor, obj(), THREAD);
dtrace_waited_probe(monitor, obj, THREAD);
}
最终,是通过调用ObjectMonitor的wait()方法来实现等待的,其主要代码如下:
void ObjectMonitor::wait(jlong millis, bool interruptible, TRAPS) {
...
// create a node to be put into the queue
// Critically, after we reset() the event but prior to park(), we must check
// for a pending interrupt.
ObjectWaiter node(Self);
node.TState = ObjectWaiter::TS_WAIT ;
Self->_ParkEvent->reset() ;
OrderAccess::fence(); // ST into Event; membar ; LD interrupted-flag
// Enter the waiting queue, which is a circular doubly linked list in this case
// but it could be a priority queue or any data structure.
// _WaitSetLock protects the wait queue. Normally the wait queue is accessed only
// by the the owner of the monitor *except* in the case where park()
// returns because of a timeout of interrupt. Contention is exceptionally rare
// so we use a simple spin-lock instead of a heavier-weight blocking lock.
Thread::SpinAcquire (&_WaitSetLock, "WaitSet - add") ;
AddWaiter (&node) ;
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 (Self) ; // exit the monitor
guarantee (_owner != Self, "invariant") ;
...
if (node._notified != 0 && _succ == Self) {
node._event->unpark();
}
// The thread is on the WaitSet list - now park() it.
...
}
整个调用链路可以总结如下:
Object.wait()-->Object.wait(long)-->JVM_MonitorWait-->ObjectSynchronizer::wait-->ObjectMonitor::wait
说明对象的Object最终调用的是底层native方法ObjectMonitor::wait,下面介绍一下ObjectMonitor::wait的基本步骤:
ObjectWriter类声明为:
class ObjectWaiter : public StackObj {
public:
enum TStates { TS_UNDEF, TS_READY, TS_RUN, TS_WAIT, TS_ENTER, TS_CXQ } ;
enum Sorted { PREPEND, APPEND, SORTED } ;
ObjectWaiter * volatile _next;
ObjectWaiter * volatile _prev;
Thread* _thread;
ParkEvent * _event;
volatile int _notified ;
volatile TStates TState ;
Sorted _Sorted ; // List placement disposition
bool _active ; // Contention monitoring is enabled
public:
ObjectWaiter(Thread* thread);
void wait_reenter_begin(ObjectMonitor *mon);
void wait_reenter_end(ObjectMonitor *mon);
};
可以看出,ObjectWaiter是双向链表结构,保存了当前线程_thread及当前的状态TState等数据,每个等待锁的线程都会被封装成ObjectWaiter对象。
inline void ObjectMonitor::AddWaiter(ObjectWaiter* node) {
assert(node != NULL, "should not dequeue NULL node");
assert(node->_prev == NULL, "node already in list");
assert(node->_next == NULL, "node already in list");
// put node at end of queue (circular doubly linked list)
if (_WaitSet == NULL) {
_WaitSet = node;
node->_prev = node;
node->_next = node;
} else {
ObjectWaiter* head = _WaitSet ;
ObjectWaiter* tail = head->_prev;
assert(tail->_next == head, "invariant check");
tail->_next = node;
head->_prev = node;
node->_next = head;
node->_prev = tail;
}
}
调用此方法前后需要获取和释放_WaitSet列表的_WaitSetLock锁。从注释中可以看到,_WaitSet列表其实是一个双向循环链表。
void ATTR ObjectMonitor::exit(TRAPS) {
Thread * Self = THREAD ;
if (THREAD != _owner) {
if (THREAD->is_lock_owned((address) _owner)) {
// Transmute _owner from a BasicLock pointer to a Thread address.
// We don't need to hold _mutex for this transition.
// Non-null to Non-null is safe as long as all readers can
// tolerate either flavor.
assert (_recursions == 0, "invariant") ;
_owner = THREAD ;
_recursions = 0 ;
OwnerIsThread = 1 ;
} else {
// NOTE: we need to handle unbalanced monitor enter/exit
// in native code by throwing an exception.
// TODO: Throw an IllegalMonitorStateException ?
TEVENT (Exit - Throw IMSX) ;
assert(false, "Non-balanced monitor enter/exit!");
if (false) {
THROW(vmSymbols::java_lang_IllegalMonitorStateException());
}
return;
}
}
...
}
与wait()方法类似,最终也是调用的底层native方法:ObjectMonitor::notify(TRAPS)。
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 ;
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
} else if (Policy == 1) { // append to EntryList
} else if (Policy == 2) { // prepend to cxq
}
...
}
ObjectMonitor::notify(TRAPS)方法的基本步骤为:
若_WaitSet为NULL,即没有需要唤醒的线程,则直接退出;
通过ObjectMonitor::DequeueWaiter方法,获取_WaitSet列表中的第一个ObjectWaiter节点;
inline ObjectWaiter* ObjectMonitor::DequeueWaiter() {
// dequeue the very first waiter
ObjectWaiter* waiter = _WaitSet;
if (waiter) {
DequeueSpecificWaiter(waiter);
}
return waiter;
}
lock.notifyAll()方法最终通过ObjectMonitor的void notifyAll(TRAPS)实现:
void ObjectMonitor::notifyAll(TRAPS) {
...
for (;;) {
iterator = DequeueWaiter () ;
if (iterator == NULL) break ;
TEVENT (NotifyAll - Transfer1) ;
++Tally ;
...
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
} else if (Policy == 1) { // append to EntryList
} else if (Policy == 2) { // prepend to cxq
}
}
}
该方法和notify()方法比较类似,不同的是,notifyAll()通过for循环取出_WaitSet的ObjectWaiter节点,并根据不同策略,加入到_EntryList或则进行自旋操作。
Condition出现在JDK1.5中的J.U.C包中,由Doug Lea李大爷操刀设计并开发完成。
Condition是个接口,其方法有:
像Object的wait()/notify()方法必须在Synchronized中调用类似,Condition的await()/siganl()方法必须在Lock中调用。
Condition接口的实现类是AQS中的ConditionObject。
跟进ConditionObject的源码,查看await()的实现方式:
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter();
int savedState = fullyRelease(node);
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
可以发现,其与ObjectMonitor::wait流程基本类似,都是将当前线程封装成node节点,然后添加到等待队列,最后挂起当前线程。
只不过是ConditionObject实现了Java版的wait()流程,如Object的wait()方法是通过native的park()方法挂起当前线程的,而ConditionObject则使用的则是LockSupport工具类的park()方法。
可以通过Lock接口创建Condition对象,Lock接口中定义了newCondition()方法:
Condition newCondition();
获取方式如下所示:
Lock lock = new ReentrantLock();
Condition c1 = lock.newCondition();
Condition c2 = lock.newCondition();
ReentrantLock唯一实现了Lock接口,看一下ReentrantLock的对newCondition()的实现:
public Condition newCondition() {
return sync.newCondition();
}
发现调用的是内部类的sync的newCondition()方法:
final ConditionObject newCondition() {
return new ConditionObject();
}
可以发现,最终创建的就是AQS中的ConditionObject,由其实现Condition接口的各个方法。
下面,我们通过Condition的await()/signal()来完成一个小Demo:
首先定义一个服务MyService:
public class MyService {
// 实例化一个ReentrantLock对象
private ReentrantLock lock = new ReentrantLock();
// 为线程A注册一个Condition
public Condition conditionA = lock.newCondition();
// 为线程B注册一个Condition
public Condition conditionB = lock.newCondition();
public void awitA(){
try{
lock.lock();
System.out.println(Thread.currentThread().getName() + "进入了awitA方法");
long timeBefore = System.currentTimeMillis();
// 执行condition等待
conditionA.await();
long timeAfter = System.currentTimeMillis();
System.out.println(Thread.currentThread().getName() + "等待了:" + (timeAfter-timeBefore)/1000 + " s");
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void awitB(){
try{
lock.lock();
System.out.println(Thread.currentThread().getName() + "进入了awitB方法");
long timeBefore = System.currentTimeMillis();
// 执行condition等待
conditionB.await();
long timeAfter = System.currentTimeMillis();
System.out.println(Thread.currentThread().getName() + "等待了:" + (timeAfter-timeBefore)/1000 + " s");
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void signallA(){
try{
lock.lock();
System.out.println("启动唤醒程序");
// 唤醒所有注册conditionA的线程
conditionA.signalAll();
}finally {
lock.unlock();
}
}
public void signallB(){
try{
lock.lock();
System.out.println("启动唤醒程序");
// 唤醒所有注册conditionB的线程
conditionB.signalAll();
}finally {
lock.unlock();
}
}
}
然后构建两个线程,均持有MyService对象:
线程A:
public class MyServiceThreadA implements Runnable{
private MyService service;
public MyServiceThreadA(MyService service) {
this.service = service;
}
@Override
public void run() {
service.awitA();
}
}
线程B:
public class MyServiceThreadB implements Runnable{
private MyService service;
public MyServiceThreadB(MyService service) {
this.service = service;
}
@Override
public void run() {
service.awitB();
}
}
主函数为:
public class ApplicationCondition {
public static void main(String[] args) throws InterruptedException {
MyService service = new MyService();
Runnable runnableA = new MyServiceThreadA(service);
Runnable runnableB = new MyServiceThreadB(service);
new Thread(runnableA, "a").start();
new Thread(runnableB, "b").start();
Thread.sleep(2000);
// 唤醒所有持有ConditionA的线程
service.signallA();
Thread.sleep(2000);
// 唤醒所有持有ConditionB的线程
service.signallB();
}
}
运行结果为:
a进入了awitA方法
b进入了awitB方法
启动唤醒程序
a等待了:2 s
启动唤醒程序
b等待了:4 s
Process finished with exit code 0
可以看到,在实现线程协作时,Condition具有更大的灵活性,比如现在有3个线程A、B、C,线程A更新了某数据,需要通知线程B去拿新数据做搞事情,而C线程继续休眠,此种情况采用Object的wait()/notify()是难以实现的,因为notify()唤醒的线程是难以控制和指定的,而Condition却可以轻松完成这一切。
本文主要介绍了线程协作的两种方式,分别为Object的wait()/notify()和Condition的await()/signal()。
两种方式在原理上基本类似,均实现了等待/通知机制,相比使用Object的wait()、notify(),使用Condition的await()、signal()这种方式实现线程间协作更加安全和高效。因此通常来说比较推荐使用Condition,如阻塞队列实际上使用了Condition来模拟线程间协作。
参考文献:
欢迎您扫一扫上面的二维码,关注我的微信公众号!