zoukankan      html  css  js  c++  java
  • 【Java并发】- 1.对Object类中wait和notify方法的解析

    1.对wait方法的解析

    Object类中有三个wait方法

        public final void wait() throws InterruptedException {
            wait(0);
        }
        
        public final native void wait(long timeout) throws InterruptedException;
    
        public final void wait(long timeout, int nanos) throws InterruptedException {
            if (timeout < 0) {
                throw new IllegalArgumentException("timeout value is negative");
            }
    
            if (nanos < 0 || nanos > 999999) {
                throw new IllegalArgumentException(
                                    "nanosecond timeout value out of range");
            }
    
            if (nanos > 0) {
                timeout++;
            }
    
            wait(timeout);
        }
    

    wait方法在何时使用?

    wait方法必须在合适的地方使用,不然会产生InterruptedException异常,那么合适的地方在哪?

    关于wait的方法的使用,wait方法必须获取对象的锁(monitor)才能执行该对象的wait方法。(获取锁对象指的是程序执行了monitorenter指令之后,执行monitorexit指令之前的状态).当调用wait方法后就会将该对象的锁给释放掉。与Thread方法不同的是,sleep方法只是使当前线程陷入休眠的状态

    如下wait在获取锁对象后才能使用

        public static void main(String[] args) throws InterruptedException {
            Object o = new Object();
            synchronized (o){
                o.wait();
            }
        }
    

    具体来说获取wait方法必须写在被synchronized关键字修饰的代码块中。

    The current thread must own this object’s monitor. The thread
    releases ownership of this monitor and waits until another thread
    notifies threads waiting on this object’s monitor to wake up
    either through a call to the {@code notify} method or the
    {@code notifyAll} method. The thread then waits until it can
    re-obtain ownership of the monitor and resumes execution.

    以上是官方文档中的描述。

    线程执行wait方法后何时被唤醒?

    线程执行wait方法进入等待后会进入对象的一个等待集合,在以下四种情况下会被唤醒

    • 1.当其他线程调用类notify方法,而恰好当前线程被选择为唤醒线程
    • 2.其他线程调用了notifyAll()方法
    • 3.线程被其他线程中断
    • 4.wait到达了等待的最大时间。如果该时间被设置为0在这种情况不成立,线程会一直等待下去

    在Javadoc中特别声明了,在使用wait方法时,最好将wait放在一个loop循环中,因为wait有很小的机率在不通过以上四种方法的
    情况下被唤醒,该唤醒被称为伪唤醒。如果不把wait写入loop中,那么有可能导致线程异常执行。
    即要把wait方法写入一个for或者while循环中,在线程被唤醒后进行一次判断,检测线程是否达到了被唤醒的条件,防止线程被伪唤醒。

    在Javadoc文档中如下指出:

    A thread can also wake up without being notified, interrupted, or
    timing out, a so-called spurious wakeup. While this will rarely
    occur in practice, applications must guard against it by testing for
    the condition that should have caused the thread to be awakened, and
    continuing to wait if the condition is not satisfied. In other words,
    waits should always occur in loops, like this one:

    synchronized (obj) {
    while (<condition does not hold>)
    obj.wait(timeout);
    … // Perform action appropriate to condition
    }

    2.对notify方法的解析

    Object中存在的notify方法如下

    public final native void notify();
    
    public final native void notifyAll();
    

    notify()它会随机唤醒该对象等待集合(wait set)中的任意一个线程,被唤醒的线程不会立即执行,只能等待当前线程放弃对象的monitor后 才能和其他线程抢夺对象的执行权,执行当抢到执行权后线程才能执行

    获取对象monitor有三种情况

    • 1.执行了synchronized修饰的代码块
    • 2.执行了synchronized修饰的对象的方法
    • 3.对应class对象,执行了被synchronized修饰的静态方法

    notifyAll(),唤醒所有在该对象monitor的等待集合中的所有线程,其他说明与notify相同,二者不同点就是唤醒的线程的数量不同

    wait和notify(notifyAll)方法总是一起使用。

    在某一时刻对同一个对象只有唯一的一个线程能获取对象的锁

    3.对wait和notify两个方法的底层实现的简单分析

    该源码参考的是Java8的源码,该源码位于hotspot-mastersrcsharevm untime目录下objectMonitor.cpp文件中。hotspot源码在github中可以找到

    对wait方法源码的分析(重要的部分我做了注释,我在代码下面不再详细展开。。)

    该c++中的wait对应了Java中public final native void wait(long timeout) throws InterruptedException;

    void ObjectMonitor::wait(jlong millis, bool interruptible, TRAPS) {
       Thread * const Self = THREAD ;//获取正在执行的线程
       assert(Self->is_Java_thread(), "Must be Java thread!");//断言这是一个Java线程
       
       。。。(这是我认为在分析中不重要的部分我省略掉了,下同)
    
       // 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 对象,因为执行wait方法后的线程会进入阻塞状态
       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) ;//把节点加入到waitSet等待集合。该方法源码下面给出
    
    	。。。
    	
    	exit (Self) ;                    // exit the monitor退出monitor
    	
    	。。。
    

    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;
      }
    }
    

    从wait方法的底层代码,我们可以看出线程执行完wait方法后,先将自身存入waitset在释放了monitor对象,和wait方法文档中描述是一致的。

    对notify方法底层源码的简单分析

    // Consider:
    // If the lock is cool (cxq == null && succ == null) and we're on an MP system
    // then instead of transferring a thread from the WaitSet to the EntryList
    // we might just dequeue a thread from the WaitSet and directly unpark() it.
    
    void ObjectMonitor::notify(TRAPS) {
      CHECK_OWNER();
      if (_WaitSet == NULL) {//进行了简单的判断,如果waitset集合为空,说明等待集合不存在线程。
      //但是notify方法是唤醒waitset找到线程故直接退出方法
         TEVENT (Empty-Notify) ;
         return ;
      }
    
    	。。。
    
    	ObjectWaiter * iterator = DequeueWaiter() ;//获取了等待队列的第一个线程对象DequeueWaiter()方法源码在下面会给出
      if (iterator != NULL) {//判断,然后通过Policy == 0中Policy的值判断线程调度策略,从而判断该唤醒哪一个线程。
         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
             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
    
    。。。
    

    DequeueWaiter()方法源码——获取waitset集合第一个线程对象

    inline ObjectWaiter* ObjectMonitor::DequeueWaiter() {
      // dequeue the very first waiter
      ObjectWaiter* waiter = _WaitSet;
      if (waiter) {
        DequeueSpecificWaiter(waiter);
      }
      return waiter;
    }
    

    通过对notify方法源码的分析,可以知道notify方法唤醒线程不是随机的,而是由线程的调度策略来决定。

    4.练习:启动两个线程使用wait和notify使线程对x分别加一减一。最后输出10101010…

    目标类:

    public class TestDemo {
        private int counter = 0;
    
        public synchronized void increaseCounter() {
            while (counter != 0){//此处就是wait方法的Java Doc文档中提到的wait假唤醒的情况,虽然不是真正的假唤醒,但产生了与假唤醒相同的情况故加while就解决
                //此处的要点就是执行wait的线程被唤醒会从中断的位置开始执行。故在多线程情况下要判断被唤醒后是否满足条件,当满足条件然后才可以执行代码
                //如果不加该循环条件,那么如果有多个线程来执行时就会发生错误导致打印101010失败。
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            counter++;
            System.out.println(counter);
            notifyAll();
        }
    
        public synchronized void decreaseCounter() {
            while (counter == 0){
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            counter--;
            System.out.println(counter);
            notifyAll();
        }
    }
    

    增加数字的线程:

    public class IncreaseThread extends Thread {
        private TestDemo testDemo;
    
        public IncreaseThread(TestDemo demo){
            this.testDemo = demo;
        }
    
        @Override
        public void run() {
            for (int i = 0; i < 30; i++) {
                testDemo.increaseCounter();
            }
        }
    }
    

    减少数字的线程:

    public class DecreaseThread extends Thread {
        private TestDemo testDemo;
    
        public DecreaseThread(TestDemo demo){
            this.testDemo = demo;
        }
    
        @Override
        public void run() {
            for (int i = 0; i < 30; i++) {
                testDemo.decreaseCounter();
            }
        }
    }
    

    启动类,调用上面的三个类

    public class Client {
    
        public static void main(String[] args) {
            TestDemo demo = new TestDemo();
    
            IncreaseThread increaseThread = new IncreaseThread(demo);
            IncreaseThread increaseThread1 = new IncreaseThread(demo);
            DecreaseThread decreaseThread = new DecreaseThread(demo);
            DecreaseThread decreaseThread1 = new DecreaseThread(demo);
            increaseThread.start();
            increaseThread1.start();
            decreaseThread.start();
            decreaseThread1.start();
        }
    }
    

    输出的结果
    在这里插入图片描述

  • 相关阅读:
    Fidder
    常见正则表达式使用
    HTML5 Boilerplate
    微信公众号开发--微信机器人
    sublime text插件推荐
    个人博客开发-笔记
    css之图像替换
    关于浮动与清除浮动
    css定位机制
    Java 集合类实现原理
  • 原文地址:https://www.cnblogs.com/wf614/p/13168595.html
Copyright © 2011-2022 走看看