In this article, we mainly illustrate wait(), notify() and join(), which may be a little rough and we can point out many shortcomings.

The profile

  1. Wait () method
  2. Notify () method
  3. The join () method

Let’s start with a wave analysis of object.wait ().

Following up on the question left in the previous article

    1. Why must calls to Object.wait hold an Object lock?
    1. If object.wait () is suspended, will the current lock be released to free the CPU?

Let’s answer the first question first

The bytecode generated by Javap contains “Monitorenter” and “Monitorexit”. We will not extend the lock here, but we will know that there is such a thing. This is why wait needs to acquire the lock in order to acquire the Monitor object.

1. The wait () method

In the HotSpot VIRTUAL machine, ObjectMonitor is used to implement Monitor

//ObjectMonitor :: ObjectMonitor() {_header = NULL; _count = 0; _waiters = 0, _recursions = 0; // Lock reentrant count _object = NULL; _owner = NULL; // point to thread holding ObjectMonitor object _WaitSet = NULL; _WaitSetLock = 0; _Responsible = NULL; _succ = NULL; _cxq = NULL; FreeNext = NULL; _EntryList = NULL; _SpinFreq = 0; _SpinClock = 0; OwnerIsThread = 0; }Copy the code

Read on to answer the second question with an explanation from the WAIT interface

This method causes the current thread (call it <var>T</var>) toplace itself in the wait set for this object and then to relinquish any and all synchronization claims on this object. Basically, you put the object into waitSet and drop all synchronization declarations, which means you give up the CPU.

In the next talent shallow, drew a very rough flow chart 😆, if there is wrong request pointed out.Summarize the above process

  1. The Monitor object that gets the lock.
  2. Checks whether the current thread object has acquired the lock.
  3. Create ObjectWaiter and set its state to TS_WAIT.
  4. Operation _WaitSet to insert the end of the current node node into the queue.
  5. Call the Exit() method to Exit Monitor and release the lock, freeing the CPU.
  6. Call the Park() method to suspend the thread.
  7. When ObjectWaiter is in TS_WAIT state,WaitSet removes the current node and changes the state to TS_RUN.
  8. Call Enter(Self) to re-preempt the lock.
  9. Exits the current wait for Monitor.

2. notify()

To strike while the iron is hot, let’s do a wave analysis of Object.notify().Summarize the above process

  1. Thread A is added to the _WaitSet queue after wait().
  2. Thread C fails to compete for the lock after being started by thread B and is added to the first position in the _CXQ queue.
  3. When thread B notifies (), it fetkes the first thread from _WaitSet and, depending on Policy, places it at the beginning or end of the _EntryList or _CXQ queue.
  4. Wake up ObjectWaiter from _CXQ or _EntryList, depending on QMode.

3. join()

As you can see, the core of the JOIN method is still wait, and synchronized is used to modify the join method because the wait method must acquire the lock.

Thread.join() // Wait for the Thread to terminate Public final void join() throws InterruptedException {join(0); } public final synchronized void join(long millis) throws InterruptedException { long base = System.currentTimeMillis();  long now = 0; if (millis < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (millis == 0) {// check whether the thread isAlive while (isAlive()) {wait(0); } } else { while (isAlive()) { long delay = millis - now; if (delay <= 0) { break; } wait(delay); now = System.currentTimeMillis() - base; }}}Copy the code
Let’s look at the use of a join through an example
static class CreateRunable implements Runnable { public CreateRunable(int i) { this.i = i; } private int i; public int getI() { return i; } public void setI(int i) { this.i = i; } @override public void run() {synchronized (this){system.out.println ("Runable "+ I); } } } public static void main(String[] args) throws InterruptedException { for (int i = 0; i< 20 ; i++){ Thread createThread = new Thread(new CreateRunable(i)); createThread.start(); createThread.join(); } system.out.println ("mian block last execution "); }Copy the code

Through a simple flowchart, you can see that synchronized, the invoked procedure, is modified at the method level, equivalent to synchronized(this), which is an instance of createThread itself. The child thread will call nitifyAll() to wake up the main thread. The main thread can continue executing as long as it obtains the CPU and the lock.

conclusion

The above three methods use simple flow diagrams to describe the source process. We can see the thread wait and release mainly through the preemption of the lock.

Deficiencies, I hope you can point out more, timely correction ~