线程wait和notify深入学习和分析

我们都知道简单的生产者和消费者的实现就是通过Object内的wait和notify实现的。至于例子暂时不写了。

一、前言

常见面试题:

为什么wait和notify方法是Object类中的方法,而不是在Thread里,毕竟通常我们都是在多线程的时候在使用wait和notify方法。

解释:

1.首先我们知道生产者和消费者模型里,我们使用的是一个Object lock = new Object()。然后在新起的线程内使用的是lock.wait()和lock.notify()。并且我们配合使用的还有一个东西synchronize关键字,保证同步的。它们有可能是有联系的,毕竟我们从来没见过在同步之外去使用wait和notify。

2.看wait方法的注释:

/**

     * <p>
     * The current thread must own this object's monitor.
     * <p>
     */
    public final native void wait(long timeout) throws InterruptedException;

我截图了最关键的一句话。当前线程必须拥有这个object的monitor。monitor我们在分析synchronize的时候已经有所了解。它就是一个监视器,存在于对象内。那么这就能解释了,为什么它在Object内?因为它依赖于对象,而对象的最高父类就是Object。

3.为什么需要配合synchronize使用呢?其实synchronize的底层代码中使用了monitorentermonitorexit指令,看见没?这就解释了必须配合它才能使用wait和notify。你不进入怎么拥有这个对象的monitor啊~~~~~~~~~~~~~

二、分析和学习

需要了解wait和notify,jdk源码已经没啥用了。因为都是native方法,必须去学习HotSpot源码,还是那句话,HotSpot源码只能大致看看,不可能了解很细,扣的很细的话会另学习效率非常低,不是专业人士读起来非常费劲。

1.monitor

monitor是个很抽象的东西,在HotSpot中,monitor的实现是ObjectMonitor。其实我后来读过之后有个感受,就是它和AQS很像,超级像,AQS就是C.U.T中的一个等待队列,用于存放获取锁失败的线程,后续唤醒也是从AQS中根据一定规则进行唤醒动作。而ObjectMonitor的结构差不多也是如此。

  // initialize the monitor, exception the semaphore, all other fields
  // are simple integers or pointers
  ObjectMonitor() {
    _header       = NULL;
    _count        = 0;
    _waiters      = 0,
    _recursions   = 0;
    _object       = NULL;
    _owner        = NULL;
    _WaitSet      = NULL;
    _WaitSetLock  = 0 ;
    _Responsible  = NULL ;
    _succ         = NULL ;
    _cxq          = NULL ;
    FreeNext      = NULL ;
    _EntryList    = NULL ;
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;
    _previous_owner_tid = 0;
  }

ObjectMonitor中存在两个队列,_WaitSet和_EntryList,都是用来保存ObjectWaiter对象,而_owner就是用来保存获取到ObjectMonitor对象的线程。下图是网络盗图一张(但是我对于此图的真实性不太相信,有些东西解释的不清楚,只是copy)


private:
  ObjectWaiter * volatile _cxq ;    // LL of recently-arrived threads blocked on entry.
                                    // The list is actually composed of WaitNodes, acting
									// as proxies for Threads.
protected:
  ObjectWaiter * volatile _EntryList ;     // Threads blocked on entry or reentry.
protected:
  ObjectWaiter * volatile _WaitSet; // LL of threads wait()ing on the monitor

1.1处于wait状态的线程会加入到Wait队列。

1.2等待锁状态的会加入到EntryList中。

1.3最近刚被阻塞的线程进入到_cxq,字面解释。

2.wait方法的HotSpot实现

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);   //1.构造ObjectWaiter对象
   node.TState = ObjectWaiter::TS_WAIT ; //线程状态是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) ;   //2.加入到ObjectWaiter中
   Thread::SpinRelease (&_WaitSetLock) ;

   exit (true, Self) ;                    // exit the monitor
							//3.释放掉当前的锁,保证后续线程能够获取到锁
   
       if (node._notified == 0) {
         if (millis <= 0) {
            Self->_ParkEvent->park () ;
         } else {
            ret = Self->_ParkEvent->park (millis) ; //4.挂起自己
         }
       }
	   ....
}

AddWaiter:(在AddWaiter中注意到是放入到_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;
  }
}

ObjectWaiter:它是一个双向链表结构,保存的是当前线程和状态信息,每个等待锁的线程都会被封装成ObjectWaiter对象。

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;
  jlong         _notifier_tid;
  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);
};

Exit方法释放锁并唤醒其他线程

if (QMode == 2 && _cxq != NULL) {
          // QMode == 2 : cxq has precedence over EntryList.
          // Try to directly wake a successor from the cxq.
          // If successful, the successor will need to unlink itself from cxq.
          w = _cxq ;
          assert (w != NULL, "invariant") ;
          assert (w->TState == ObjectWaiter::TS_CXQ, "Invariant") ;
          ExitEpilog (Self, w) ;
          return ;
      }
if (QMode == 3 && _cxq != NULL) {
          // Aggressively drain cxq into EntryList at the first opportunity.
          // This policy ensure that recently-run threads live at the head of EntryList.
          // Drain _cxq into EntryList - bulk transfer.
          // First, detach _cxq.
          // The following loop is tantamount to: w = swap (&cxq, NULL)
          w = _cxq ;
          for (;;) {
             assert (w != NULL, "Invariant") ;
             ObjectWaiter * u = (ObjectWaiter *) Atomic::cmpxchg_ptr (NULL, &_cxq, w) ;
             if (u == w) break ;
             w = u ;
          }
          assert (w != NULL              , "invariant") ;

          ObjectWaiter * q = NULL ;
          ObjectWaiter * p ;
          for (p = w ; p != NULL ; p = p->_next) {
              guarantee (p->TState == ObjectWaiter::TS_CXQ, "Invariant") ;
              p->TState = ObjectWaiter::TS_ENTER ;
              p->_prev = q ;
              q = p ;
          }

          // Append the RATs to the EntryList
          // TODO: organize EntryList as a CDLL so we can locate the tail in constant-time.
          ObjectWaiter * Tail ;
          for (Tail = _EntryList ; Tail != NULL && Tail->_next != NULL ; Tail = Tail->_next) ;
          if (Tail == NULL) {
              _EntryList = w ;
          } else {
              Tail->_next = w ;
              w->_prev = Tail ;
          }

          // Fall thru into code that tries to wake a successor from EntryList
      }
if (QMode == 4 && _cxq != NULL) {
          // Aggressively drain cxq into EntryList at the first opportunity.
          // This policy ensure that recently-run threads live at the head of EntryList.

          // Drain _cxq into EntryList - bulk transfer.
          // First, detach _cxq.
          // The following loop is tantamount to: w = swap (&cxq, NULL)
          w = _cxq ;
          for (;;) {
             assert (w != NULL, "Invariant") ;
             ObjectWaiter * u = (ObjectWaiter *) Atomic::cmpxchg_ptr (NULL, &_cxq, w) ;
             if (u == w) break ;
             w = u ;
          }
          assert (w != NULL              , "invariant") ;

          ObjectWaiter * q = NULL ;
          ObjectWaiter * p ;
          for (p = w ; p != NULL ; p = p->_next) {
              guarantee (p->TState == ObjectWaiter::TS_CXQ, "Invariant") ;
              p->TState = ObjectWaiter::TS_ENTER ;
              p->_prev = q ;
              q = p ;
          }

          // Prepend the RATs to the EntryList
          if (_EntryList != NULL) {
              q->_next = _EntryList ;
              _EntryList->_prev = q ;
          }
          _EntryList = w ;

          // Fall thru into code that tries to wake a successor from EntryList
      }
if (QMode == 1) {
         // QMode == 1 : drain cxq to EntryList, reversing order
         // We also reverse the order of the list.
         ObjectWaiter * s = NULL ;
         ObjectWaiter * t = w ;
         ObjectWaiter * u = NULL ;
         while (t != NULL) {
             guarantee (t->TState == ObjectWaiter::TS_CXQ, "invariant") ;
             t->TState = ObjectWaiter::TS_ENTER ;
             u = t->_next ;
             t->_prev = u ;
             t->_next = s ;
             s = t;
             t = u ;
         }
         _EntryList  = s ;
         assert (s != NULL, "invariant") ;
      } else {
         // QMode == 0 or QMode == 2
         _EntryList = w ;
         ObjectWaiter * q = NULL ;
         ObjectWaiter * p ;
         for (p = w ; p != NULL ; p = p->_next) {
             guarantee (p->TState == ObjectWaiter::TS_CXQ, "Invariant") ;
             p->TState = ObjectWaiter::TS_ENTER ;
             p->_prev = q ;
             q = p ;
         }
      }

具体的解释一下:

QMode=2:取_cxq排头位置的ObjectWaiter对象,调用ExitEpilog方法,唤醒ObjectWaiter所记录的线程,然后return。

QMode=3:取_cxq中的线程放入EntryList尾部。

QMode=4:取_cxq中的线程放入EntryList头部。

基本分析这几个,可以看见只是唤醒的动作有所区别,主要目的还是在唤醒线程,策略的不同可能导致的结果就是唤醒的线程的不同。

整理一下wait()四点:

1.包装成ObjectWaiter对象,并且状态是WAIT。

2.ObjectWaiter对象被放入到_WaitSet中,注意_WaitSet是一个环形双向链表。

3.释放锁(monitor),执行Exit方法

    根据QMode(QMode是什么,咱不知道)不同,将ObjectWaiter从_cxq或者EntryList中唤醒。那么被唤醒的线程会继续执行挂起之前的代码,就会通过CAS去竞争锁,此时因为当前线程已经释放了锁,可以去竞争,至于哪一个去竞争成功,不知道。

4.挂起当前线程。

3.notify方法的HotSpot实现

void ObjectMonitor::notify(TRAPS) {
 
	...
 
  ObjectWaiter * iterator = DequeueWaiter() ;
  
  ...
 
} 
采用DequeueWaiter方法取出WaitSet中的第一个ObjectWaiter节点,具体看注释,very first waiter。其实虽说notify是随机唤醒一个线程,其实在HotSpot中还是有顺序的,只要我们找到一个点,那么就可以指定唤醒哪一个线程了。当然了,这样做,得不偿失
inline ObjectWaiter* ObjectMonitor::DequeueWaiter() {
  // dequeue the very first waiter
  ObjectWaiter* waiter = _WaitSet;
  if (waiter) {
    DequeueSpecificWaiter(waiter);
  }
  return waiter;
}

取出第一个ObjectWaiter节点之后,根据策略将节点加入EntryList或者_cxq中。

Policy=0:放入EntryList的排头位置

Policy=1:放入EntryList的尾部。

Policy=2:EntryList为空就放入EntryList,否则放入_cxq排头位置

Policy=3: 放入_cxq的尾部,注意放入的时候做过并发兼容,正常来说,只要紧跟着Tail放入Tail-next即可,但是考虑并发的话,需要有个循环,取Tail-next,然后放入,和J.C.U.T包里有些操作类似,读过的小伙伴肯定眼前一亮吧。

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);
     }
整理一下notify()两点:

1.从_WaitSet中取出第一个ObjectWaiter节点。

2.根据Policy的不同,将这个线程放入_EntryList或者_cxq中的起始或者尾部位置。

3.notifyAll()方法的HotSpot实现

void ObjectMonitor::notifyAll(TRAPS) {
...
  for (;;) {
     iterator = DequeueWaiter () ;
     if (iterator == NULL) break ;
     TEVENT (NotifyAll - Transfer1) ;
     ++Tally ;
...
}

notifyAll的方法和notify方法类似,区别就是有一个for循环,将_WaitSet里面的ObjectWaiter节点全部取出来,然后也是根据Policy规则放入_EntryList和_cxq中。

4.根据例子去完善自己的理解

public class Test {

    public static void main(String[] args) throws Exception{
        new Thread(new Thread1()).start();
        try{
            Thread.sleep(2000);
        }catch (Exception e){
            e.printStackTrace();
        }
        new Thread(new Thread2()).start();
    }

}
class Thread1 implements Runnable{

    @Override
    public void run() {

        System.out.println("Thread1 start");

        synchronized (Test.class){

            System.out.println("Thread1 in lock");
            try {
                Thread.sleep(5000);   //1
                System.out.println("Thread1 start wait");
                Test.class.wait();
            }catch (Exception e){
                e.printStackTrace();
            }
            System.out.println("Thread1 end wait and process on"); //5
        }
    }
}
class Thread2 implements Runnable{

    @Override
    public void run() {

        System.out.println("Thread2 start");  //2

        synchronized (Test.class){
            System.out.println("Thread2 in lock");  //3
            try {
                System.out.println("Thread2 start notify");
                Test.class.notify();
                for(int i = 0;i<100;i++){   //4
                    System.out.print(i+"_");
                    if(i==50){
                        System.out.println("\n");
                    }
                }
                System.out.println("\n");
                System.out.println("Thread2 end notify");
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
}
Thread1 start
Thread1 in lock
//发生停顿
Thread2 start
//发生停顿
Thread1 start wait
Thread2 in lock
Thread2 start notify
0_1_2_3_4_5_6_7_8_9_10_11_12_13_14_15_16_17_18_19_20_21_22_23_24_25_26_27_28_29_30_31_32_33_34_35_36_37_38_39_40_41_42_43_44_45_46_47_48_49_50_

51_52_53_54_55_56_57_58_59_60_61_62_63_64_65_66_67_68_69_70_71_72_73_74_75_76_77_78_79_80_81_82_83_84_85_86_87_88_89_90_91_92_93_94_95_96_97_98_99_

Thread2 end notify
Thread1 end wait and process on

在只有两个线程的时候,wait和notify的结果比较清晰。要注意的几点:

1.Thread1进行wait之后,就释放掉了当前的对象锁,然后Thread2此时就可以获取到对象锁。

2.可以发现Thread2在执行完notify之后仍然在继续运行余下的代码,当结束之后Thread1才继续执行它剩下的代码。也就是说,此时的锁仍然在Thread2,Thread1没有获取到锁。所以执行不了。因此notify只是起到一个通知的作用。

参考博客:

占小狼

无名氏

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值