【并发编程】park与unpark、notify与notifyAll

文章放置于:https://github.com/zgkaii/CS-Notes-Kz,欢迎批评指正!

1 线程状态简述

Java线程在运行的生命周期中可能处于如下6种不同的状态,在给定的一个时刻,线程只能处于其中的一个状态。

线程状态 说明
NEW 初始状态,线程刚被创建,但是并未启动(还未调用start方法)。
RUNNABLE 运行状态,JAVA线程将操作系统中的就绪(READY)和运行(RUNNING)两种状态笼统地称为“运行中”。
BLOCKED 阻塞状态,表示线程阻塞于锁。
WAITING 等待状态,表示该线程无限期等待另一个线程执行一个特别的动作。
TIMED_WAITING 超时等待状态,不同于WAITING的是,它可以在指定时间自动返回。
TERMINATED 终止状态,表示当前状态已经执行完毕。

线程在自身的生命周期中,并不是固定地处于某个状态,而是随着代码的执行在不同的状态之间进行切换。

【并发编程基础】线程基础(常用方法、状态)一文中,主要学习了wait()、join()和sleep()等方法,在【并发编程】深入理解synchronized原理一文中,主要探讨了synchronized原理。下面就进行park()/unpark()、wait()/notify()/notifyAll()的学习。

2 wait和notify/notifyAll

2.1 源码简析

wait( ),notify( ),notifyAll( )都是Object基础类中的方法,所以在任何 Java 对象上都可以使用。

public class Object {
   
    // 导致当前线程等待,直到另一个线程调用此对象的notify()方法或notifyAll()方法。
    public final void wait() throws InterruptedException {
   
        wait(0);
    }

    // 导致当前线程等待,直到另一个线程调用此对象的notify()方法或notifyAll()方法,或者已经过了指定的时间。
    public final native void wait(long timeout) throws InterruptedException;

    //  唤醒正在此对象监视器上等待的单个线程。
    public final native void notify();

    //  唤醒等待此对象监视器的所有线程。
    public final native void notifyAll();
}

打开objectMonitor.cpp,查看wait方法:

 	...
   // 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);		  		 // 将当前线程封装成ObjectWatier
   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) ;				 // 添加到_WaitSet节点中
	...

查看AddWaiter()方法:

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对象添加到_WaitSet列表中
    ObjectWaiter* tail = head->_prev;
    assert(tail->_next == head, "invariant check");
    tail->_next = node;
    head->_prev = node;
    node->_next = head;
    node->_prev = tail;
  }
}

查看notify方法源码:

void ObjectMonitor::notify(TRAPS) {
  CHECK_OWNER();
  if (_WaitSet == NULL) {
     TEVENT (Empty-Notify) ;// _WaitSet=NULL表明没有等待状态的线程,直接返回。
     return ;
  }
  DTRACE_MONITOR_PROBE(notify, this, object(), THREAD);

  int Policy = Knob_MoveNotifyee ;

  Thread::SpinAcquire (&_WaitSetLock, "WaitSet - notify") ;
  ObjectWaiter * iterator = DequeueWaiter() ;// 获取一个ObjectWaiter对象
  if (iterator != NULL) {
      ...
     ObjectWaiter * List = _EntryList ;
     if (List != NULL) {
        assert (List->_prev == NULL, "invariant") ;
        assert (List->TState == ObjectWaiter::TS_ENTER, "invariant") ;
        assert (List != iterator, "invariant") ;
     }
	 // 根据不同状态采取不同策略,将从_WaitSet列表中移出来的ObjectWaiter对象加入到_EntryList列表中。
     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) {...} else     // append to EntryList
	 if (Policy == 2) {...} else     // prepend to cxq
	 if (Policy == 3) {				 // append to cxq
         ...     
     } else {
        ParkEvent * ev = iterator->_event ;
        iterator->TState = ObjectWaiter::TS_RUN ;
        OrderAccess::fence() ;		 // 被唤醒的线程又变成run状态。
        ev->unpark() ;
     }
}

查看notifyAll方法源码:

void ObjectMonitor::notifyAll(TRAPS) {
  CHECK_OWNER();
  ObjectWaiter* iterator;
  if (_WaitSet == NULL) {
      TEVENT (Empty-NotifyAll) ;// _WaitSet=NULL表明没有等待状态的线程,直接返回。
      return ;
  }
  DTRACE_MONITOR_PROBE(notifyAll, this, object(), THREAD);

  int Policy = Knob_MoveNotifyee ;
  int Tally = 0 ;
  Thread::SpinAcquire (&_WaitSetLock, "WaitSet - notifyall") ;

  for (;;) {
     iterator = DequeueWaiter () ;// 循环获取所以ObjectWaiter对象
	   ...
     ObjectWaiter * List = _EntryList ;
     if (List != NULL) {
        assert (List->_prev == NULL, "invariant") ;
        assert (List->TState == ObjectWaiter::TS_ENTER, "invariant") ;
        assert (List != iterator, "invariant") ;
     }
	  // 根据不同状态采取不同策略,将从_WaitSet列表中移出来的ObjectWaiter对象加入到_EntryList列表中。	
     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
		...
     } else
     if (Policy == 2) {      // prepend to cxq
		...
     } else
     if (Policy == 3) {      // append to cxq
		...
     } else {
        ParkEvent * ev = iterator->_event ;
        iterator->TState = ObjectWaiter::TS_RUN ;// 被唤醒的线程又变成run状态。
        OrderAccess::fence() ;
        ev->unpark() ;
     }

      ...

可见,wait()与notify()/notifyAll()的实现都跟Monitor有很大关联。

  • 当多线程访问一段同步代码块时,这些都线程会被被封装成一个个ObjectWatier对象,并被放入 _EntryList列表中,也就是被放到 Entry Set(入口区) 中等待获取锁。
  • 如果该线程获取到了锁(acquire),线程就会成为当前锁的 Owner。
  • 获取到锁的线程可也以通过调用 wait 方法将锁释放(release),然后该线程对象会被放入_WaitSet列表中,进入Wait Set (等待区)进行等待(阻塞BLOCKED)。
  • 当获取到锁的对象调用notify/notifyAll方法唤醒等待区被阻塞的线程时,线程重新竞争锁。如果竞争锁成功,那么线程就进入RUNNABLE状态;如果竞争锁失败,这些线程会重新进入到Entry Set区再重新去竞争锁。

wait方法的使用对应上图的第3步,也就是说,调用wait()notify()/notifyAll()方法的对象必须已经获取到锁

如何确保调用对象获取到锁呢?使用sychronized关键字呗!所以说这些方法调用也必须发生在sychronized修饰的同步代码块内

2.2 等待/通知机制

(1)什么是等待/通知机制

等待/通知机制是多个线程间的一种协作机制。谈到线程我们经常想到的是线程间的竞争(race),比如去竞争锁。但这并不是故事的全部,线程间也有协作机制。就好比我们在公司中与同事关系,可能存在在晋升时的竞争,但更多时候是一起合作以完成某些任务。

wait/notify 就是线程间的一种协作机制。

当一个线程调用wait()/wait(long)方法后,进入WAITING状态或者TIMED_WAITING状态(阻塞),并释放锁与CPU资源。只有其他获取到锁的线程执行完他们的指定代码过后,再通过notify()方法将其唤醒。 如果需要,也可以使用notifyAll()来唤醒所有的阻塞线程。

(2)等待/通知使用方法

等待/通知机制就是用于解决线程间通信的问题的,使用到的3个方法的含义如下:

  1. wait:线程不再活动,不再参与调度,释放它对锁的拥有权。它还要等着别的线程执行一个特别的动作,也即是“通知(notify)”在这个对象上等待的线程从WAITING状态中释放出来,重新进入到调度队列(ready queue)中。
  2. notify:唤醒一个等待当前对象的锁的线程。唤醒在此对象监视器上等待的单个线程。
  3. notifyAll:唤醒在此对象监视器上等待的所有线程。

注意:

哪怕只通知了一个等待的线程,被通知线程也不能立即恢复执行,因为它当初中断的地方是在同步块内,而此刻它已经不持有锁,所以它需要再次尝试去获取锁(很可能面临其它线程的竞争),成功后才能在当初调用 wait 方法之后的地方恢复执行。

总结如下:

  • 如果能获取锁,线程就从 WAITING/TIMED_WAITING 状态转换为RUNNABLE 状态;
  • 否则,从 Wait Set 区出来,又进入 Entry Set区,线程就从 WAITING 状态又变成 BLOCKED 状态。

(3)调用wait和notify方法需要注意的细节

  • wait方法与notify方法必须要由同一个锁对象调用。因为对应的锁对象可以通过notify唤醒使用同一个锁对象调用的wait方法后的线程。
  • wait方法与notify方法是属于Object类的方法的。因为锁对象可以是任意对象,而任意对象的所属类都是继承了Object类的。
  • wait方法与notify方法必须要在同步代码块或者是同步函数中使用。因为必须要通过锁对象调用这2个方法。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值