美文网首页回家
每日一面 - java里的wait()和sleep()的区别有哪

每日一面 - java里的wait()和sleep()的区别有哪

作者: 干货满满张哈希 | 来源:发表于2021-01-24 08:16 被阅读0次

    一句话总结:sleep方法是当前线程休眠,让出cpu,不释放锁,这是Thread的静态方法;wait方法是当前线程等待,释放锁,这是Object的方法。同时要注意,Java 14 之后引入的 inline class 是没有 wait 方法的

    Sleep()原理

    public static native void sleep(long millis) throws InterruptedException;
    

    sleep()是Thread中的static方法,也是native实现。就是调用底层的 sleep 函数实现:

    void THREAD_sleep(int seconds) {
    #ifdef windows
        Sleep(1000L * seconds);
    #else
        sleep(seconds);
    #endif
    }
    

    linux的sleep函数参考 sleep: https://man7.org/linux/man-pages/man3/sleep.3.html

    wait(), notify(), notifyAll()

    这些属于基本的Java多线程同步类的API,都是native实现:

    public final native void wait(long timeout) throws InterruptedException;
    public final native void notify();
    public final native void notifyAll();
    

    那么底层实现是怎么回事呢?
    首先我们需要先明确JDK底层实现共享内存锁的基本机制。
    每个Object都有一个ObjectMonitor,这个ObjectMonitor中包含三个特殊的数据结构,分别是CXQ(实际上是Contention List),EntryList还有WaitSet;一个线程在同一时间只会出现在他们三个中的一个中。首先来看下CXQ:


    这里写图片描述

    一个尝试获取Object锁的线程,如果首次尝试(就是尝试CAS更新轻量锁)失败,那么会进入CXQ;进入的方法就是CAS更新CXQ指针指向自己,如果成功,自己的next指向剩余队列;CXQ是一个LIFO队列,设计成LIFO主要是为了:

    1. 进入CXQ队列后,每个线程先进入一段时间的spin自旋状态,尝试获取锁,获取失败的话则进入park状态。这个自旋的意义在于,假设锁的hold时间非常短,如果直接进入park状态的话,程序在用户态和系统态之间的切换会影响锁性能。这个spin可以减少切换;
    2. 进入spin状态如果成功获取到锁的话,需要出队列,出队列需要更新自己的头指针,如果位于队列前列,那么需要操作的时间会减少
      但是,如果全部依靠这个机制,那么理所当然的,CAS更新队列头的操作会非常频繁。所以,引入了EntryList来减少争用:
      这里写图片描述
      假设Thread A是当前锁的Owner,接下来他要释放锁了,那么如果EntryList为null并且cxq不为null,就会从cxq末尾取出一个线程,放入EntryList(注意,EntryList为双向队列),并且标记EntryList其中一个线程为Successor(一般是头节点,这个EntryList的大小可能大于一,一般在notify时,后面会说到),这个Successor接下来会进入spin状态尝试获取锁(注意,在第一次自旋过去后,之后线程一直处于park状态)。如果获取成功,则成为owner,否则,回到EntryList中。
      这种利用两个队列减少争用的算法,可以参考: Michael Scott's "2Q" algorithm
      接下来,进入我们的正题,wait方法。如果一个线程成为owner后,执行了wait方法,则会进入WaitSet:
      Object.wait()底层实现
    这里写图片描述
    void ObjectMonitor::wait(jlong millis, bool interruptible, TRAPS) {
    
        //检查线程合法性
        Thread *const Self = THREAD;
        assert(Self->is_Java_thread(), "Must be Java thread!");
        JavaThread *jt = (JavaThread *) THREAD;
        DeferredInitialize();
        //检查当前线程是否拥有锁
        CHECK_OWNER();
        EventJavaMonitorWait event;
        // 检查中断位
        if (interruptible && Thread::is_interrupted(Self, true) && !HAS_PENDING_EXCEPTION) {
            if (JvmtiExport::should_post_monitor_waited()) {
                JvmtiExport::post_monitor_waited(jt, this, false);
            }
            if (event.should_commit()) {
                post_monitor_wait_event(&event, 0, millis, false);
            }
            TEVENT(Wait - ThrowIEX);
            THROW(vmSymbols::java_lang_InterruptedException());
            return;
        }
        TEVENT(Wait);
        assert(Self->_Stalled == 0, "invariant");
        Self->_Stalled = intptr_t(this);
        jt->set_current_waiting_monitor(this);
    
        //建立放入WaitSet中的这个线程的封装对象
        ObjectWaiter node(Self);
        node.TState = ObjectWaiter::TS_WAIT;
        Self->_ParkEvent->reset();
        OrderAccess::fence();
        //用自旋方式获取操作waitset的lock,因为一般只有owner线程会操作这个waitset(无论是wait还是notify),所以竞争概率很小(除非响应interrupt事件才会有争用),采用spin方式效率高
        Thread::SpinAcquire(&_WaitSetLock, "WaitSet - add");
        //添加到waitset
        AddWaiter(&node);
        //释放锁,代表现在线程已经进入了waitset,接下来要park了
        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(true, Self);                    // exit the monitor
        guarantee(_owner != Self, "invariant");
    
        // 确保没有unpark事件冲突影响本次park,方法就是主动post一次unpark
        if (node._notified != 0 && _succ == Self) {
            node._event->unpark();
        }
    
        // 接下来就是park操作了
        。。。。。。。。。
        。。。。。。。。。
    }
    

    当另一个owner线程调用notify时,根据Knob_MoveNotifyee这个值,决定将从waitset里面取出的一个线程放到哪里(cxq或者EntrySet)
    Object.notify()底层实现

    void ObjectMonitor::notify(TRAPS) {
        //检查当前线程是否拥有锁
        CHECK_OWNER();
        if (_WaitSet == NULL) {
            TEVENT(Empty - Notify);
            return;
        }
        DTRACE_MONITOR_PROBE(notify, this, object(), THREAD);
        //决定取出来的线程放在哪里
        int Policy = Knob_MoveNotifyee;
        //同样的,用自旋方式获取操作waitset的lock
        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;
            Thread *Self = THREAD;
            iterator->_notifier_tid = Self->osthread()->thread_id();
    
            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
                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);
            }
    
            // _WaitSetLock protects the wait queue, not the EntryList.  We could
            // move the add-to-EntryList operation, above, outside the critical section
            // protected by _WaitSetLock.  In practice that's not useful.  With the
            // exception of  wait() timeouts and interrupts the monitor owner
            // is the only thread that grabs _WaitSetLock.  There's almost no contention
            // on _WaitSetLock so it's not profitable to reduce the length of the
            // critical section.
        }
        //释放waitset的lock
        Thread::SpinRelease(&_WaitSetLock);
    
        if (iterator != NULL && ObjectMonitor::_sync_Notifications != NULL) {
            ObjectMonitor::_sync_Notifications->inc();
        }
    }
    

    对于NotifyAll就很好推测了,这里不再赘述;

    最后求个投票,感激不尽。

    相关文章

      网友评论

        本文标题:每日一面 - java里的wait()和sleep()的区别有哪

        本文链接:https://www.haomeiwen.com/subject/tvnpzktx.html