浅谈线程间的协作机制-Object的wait()/notify()及Condition的await()/signal()

注意:如无特殊说明,本文源码分析基于的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();
    }
}

改进后,较少了较多的无效尝试,降低了对处理器资源的浪费,但是休眠时间的大小难以确定:

  1. 休眠时间太大,不能在休眠期间及时感知数据的变化,实时性较差;
  2. 休眠时间过小,实时性提高的同时,增加了无效尝试的次数,造成了系统处理资源的浪费。

考虑到上述监听机制上述的困境,线程间协作采用的是等待/通知机制。

原理就是,当线程A完成对数据的修改之后,会通过一定的机制通知线程B来获取新的数据值来进行相关业务处理,线程B处理完之后挂起等待后续线程A的通知。

等待/通知机制是所有Java对象均具备的,因为相关方法是定义在所有对象的超类java.lang.Object上的。

相关方法描述如下:

方法名称 描述
notify() 通知一个在对象上等待的线程,使其从wait()返回,而返回的前提是该线程获取到对象的锁
notifyAll() 通知所有等待在该对象上的线程
wait() 调用wait()方法后,会释放对象的锁,并进入WAITING状态
wait(long) 超时等待一段时间,如果没有通知就超时返回
wait(long, int) 超时时间更细粒度的控制,可以达到毫秒

wait()/notify()

入门实例

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()方法才会释放对象锁。

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的基本步骤:

  • 将调用wait()方法的线程封装为ObjectWaiter类的对象node;

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对象。

  • 通过ObjectMonitor::AddWaiter方法将node添加到_WaitSet列表中;
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列表其实是一个双向循环链表。

  • 通过ObjectMonitor::exit方法释放当前的ObjectMonitor对象,这样其它竞争线程就可以获取该ObjectMonitor对象;
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;
     }
   }
 
   ...
}
  • 最终通过底层的park()方法挂起当前线程。

notify()方法

与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;
}
  • 根据不同的策略,将取出来的ObjectWaiter节点,加入到_EntryList或则通过Atomic::cmpxchg_ptr指令进行自旋操作cxq。

notifyAll()方法

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或则进行自旋操作。

小结

  1. wait()方法会释放所占有的ObjectMonitor对象;
  2. notify()和notifyAll()并不会释放所占有的ObjectMonitor对象,只是将相应的等待线程从_WaitSet转移到_EntryList中,然后等待竞争锁;
  3. 真正释放ObjectMonitor对象的时机是,退出同步代码块或同步方法时,即执行monitorexit指令时;
  4. 一旦释放ObjectMonitor对象后,_EntityList中ObjectWaiter节点所保存的线程即可以参与竞争ObjectMonitor对象了;
  5. wait()/notify()基本就是C/C++版的AQS(啥是AQS,可参见我的另外一篇文章)。

Condition

Condition出现在JDK1.5中的J.U.C包中,由Doug Lea李大爷操刀设计并开发完成。

Condition是个接口,其方法有:

浅谈线程间的协作机制-Object的wait()/notify()及Condition的await()/signal()_第1张图片

  • await()方法对应于Object的wait();
  • signal()方法对应于Object的notify();
  • signalAll()方法对应于Object的notifyAll();

像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()方法。

构建Condition对象

可以通过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来模拟线程间协作。

参考文献:

  • https://www.cnblogs.com/dolphin0520/p/3920385.html
  • http://www.importnew.com/30150.html
  • https://blog.csdn.net/qq_38293564/article/details/80432875
  • https://www.cnblogs.com/superfj/p/7543927.html

浅谈线程间的协作机制-Object的wait()/notify()及Condition的await()/signal()_第2张图片

欢迎您扫一扫上面的二维码,关注我的微信公众号!

你可能感兴趣的:(Java多线程)