Collaboration between threads (wait / notify / sleep / yield / join)

 

A state, thread

   Java threads in the state can be divided into five categories: New (New State), Runnable (ready), Running (running state), Blocked (blocked), Dead (dead state) .

  New: New state, when the thread is created for the new state, that new Thread (...), when no call start method, the thread is in the new state.

  Runnable: ready state when the start method is called a thread, the thread into the ready state, waiting for CPU resources. System thread in the ready state by the Java runtime thread scheduler ( the Thread Scheduler ) to schedule.

  Running: running, ready to get a thread after execution of the CPU into operation, started the run method.

  Blocked: blocked state, the thread is not executed, for some reason (eg, I / O operations, etc.) so that the executive power CPU, itself into the blocked state.

  Dead: Death state, abnormal thread execution is completed or during the execution occurs, the thread will enter a state of death.

  Conversion relation between the five states, as shown below:

  

  With a basic understanding of these five states, and now we look at how Java is to achieve the conversion of these types of state. 

Using a two, wait / notify / notifyAll method

1, wait method:

void wait() Causes the current thread to wait until another thread invokes the notify() method or the notifyAll() method for this object.
void wait(long timeout) Causes the current thread to wait until either another thread invokes the notify() method or the notifyAll() method for this object, or a specified amount of time has elapsed.
void wait(long timeout, int nanos) Causes the current thread to wait until another thread invokes the notify() method or the notifyAll() method for this object, or some other thread interrupts the current thread, or a certain amount of real time has elapsed.

  The JDK provided a total of three versions of the method,

  Role (1) wait () method is to suspend the currently running thread (that is allowed to enter the blocked state) until the notify or notifyAll method to wake up threads.

  Similar (2) wait (long timeout), and the method wait () method, the only difference is within the specified time, or if there is no wake notify notifAll method will automatically wake.

  (3) As to wait (long timeout, long nanos), intended that more precise control of the scheduled time, but from the view of the current version, which does not looks like a full realize this function, which source (JDK1.8) as follows:

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 >= 500000 || (nanos != 0 && timeout == 0)) {
            timeout++;
        }

        wait(timeout);
    }

From the source point of view, JDK8 the treatment of the nanosecond, only rounding, they still follow milliseconds to process, you may use the nanosecond-level accuracy at some point in the future. Although JDK provides three versions, in fact ultimately call wait (long timeout) method to achieve, wait () method and the wait (0) equivalent, and wait (long timeout, int nanos) from the above source see also through the wait (long timeout) to complete. Let's demonstrate with a simple example of wait () method of use:

package com.paddx.test.concurrent;

public class WaitTest {

    public void testWait(){
        System.out.println("Start-----");
        try {
            wait(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("End-------");
    }

    public static void main(String[] args) {
        final WaitTest test = new WaitTest();
        new Thread(new Runnable() {
            @Override
            public void run() {
                test.testWait();
            }
        }).start();
    }
}

The intent of this code is very simple, that is, after the implementation of the program, let it pause one second, and then executed. Run the code, view the results:

Start-----
Exception in thread "Thread-0"  java.lang.IllegalMonitorStateException
     at java.lang.Object.wait(Native Method)
     at com.paddx.test.concurrent.WaitTest.testWait(WaitTest.java: 8 )
     at com.paddx.test.concurrent.WaitTest$ 1 .run(WaitTest.java: 20 )
     at java.lang.Thread.run(Thread.java: 745 )

  This program is not according to our expected output corresponding results, but an exception is thrown. You may be wondering why throw an exception? And thrown IllegalMonitorStateException what is abnormal? We can look at the description of the JDK IllegalMonitorStateException of:

Thrown to indicate that a thread has attempted to wait on an object 's monitor or to notify other threads waiting on an object' s monitor without owning the specified monitor.

  What this means is probably: thread tries to wait for an object's monitor or to notify other threads waiting on an object's monitor, but does not have a corresponding monitor ownership. Actually, this is the "Java Concurrent Programming: Synchronized and realization of the principle" one article has mentioned, wait method is a local method, which is the bottom of the monitor lock of an object by a named done. So the reason why the above will throw an exception, because there is no ownership of the monitor to get an object when you call wait mode, how to get that monitor object ownership? Java can only be done by Synchronized keyword, modify the code above, an increase Synchronized Keywords:

package com.paddx.test.concurrent;

public class WaitTest {

    public synchronized void testWait(){//增加Synchronized关键字
        System.out.println("Start-----");
        try {
            wait(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("End-------");
    }

    public static void main(String[] args) {
        final WaitTest test = new WaitTest();
        new Thread(new Runnable() {
            @Override
            public void run() {
                test.testWait();
            }
        }).start();
    }
}

 Now and then run the code above, you can see the desired results:

Start-----
End-------

  So, by this example, it should be very clear, use the wait method must be within the range of synchronization, otherwise it will throw an exception IllegalMonitorStateException role wait method is to block the current thread to wait wake notify / notifyAll methods, or wait for the timeout after automatic wake-up.

2、notify/notifyAll方法

void notify() Wakes up a single thread that is waiting on this object's monitor.
void notifyAll() Wakes up all threads that are waiting on this object's monitor.

  With the understanding of the principles of the method wait, notify and notifyAll method method is easy to understand. Now that wait is through the monitor to achieve the objects of the object, so long as up call notify / notifyAll method on the same object, you can wake up a thread waiting on the objects monitor the correspondence. Notify and notifyAll difference is that the former can only wake up a thread on the monitor, no effect on the other thread, notifyAll wakes up all the threads, see the following example is easy to understand the difference between these two:

package com.paddx.test.concurrent;

public class NotifyTest {
    public synchronized void testWait(){
        System.out.println(Thread.currentThread().getName() +" Start-----");
        try {
            wait(0);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() +" End-------");
    }

    public static void main(String[] args) throws InterruptedException {
        final NotifyTest test = new NotifyTest();
        for(int i=0;i<5;i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    test.testWait();
                }
            }).start();
        }

        synchronized (test) {
            test.notify();
        }
        Thread.sleep(3000);
        System.out.println("-----------分割线-------------");
        
        synchronized (test) {
            test.notifyAll();
        }
    }
}

Output:

Thread- 0  Start-----
Thread- 1  Start-----
Thread- 2  Start-----
Thread- 3  Start-----
Thread- 4  Start-----
Thread- 0  End-------
-----------分割线-------------
Thread- 4  End-------
Thread- 3  End-------
Thread- 2  End-------
Thread- 1  End-------

As can be seen from the results: Only thread Thread-0 is awakened when calling notify method, but when you call notifyAll, all the threads are awakened.

  Finally, there are two little note:

  (1) After calling wait method, the thread will relinquish ownership of the monitor object.

  (2) blocked by a thread wait method, the following two conditions must be met in order to be truly execute:

  •     Thread needs to be woken up (or time-out wake-up call to notify / notifyll).
  •     Competition needs to lock (monitor) after the thread wakes up.

Three, sleep / yield / join Analytical Method

   Above we have clear principles and use wait and notify methods, the way we now look at another set of inter-thread cooperation. This set of methods with the most obvious difference is that the above method: these methods are in the Thread class, while the three methods above are located in the Object class. As for why, before we can think about. Now take one sleep / yield / join methods:

1、sleep

  The role of sleep method is to suspend the current thread specified time (in milliseconds), sleep method is the easiest way, in the above example is also used before, easier to understand. The only caveat is the difference between wait approach. The simplest difference is, wait method relies on synchronization, and sleep method can be called directly. The difference lies deeper sleep is a temporary method to let the execution of the CPU, does not release the lock. The wait method will need to release the lock.

package com.paddx.test.concurrent;

public class SleepTest {
    public synchronized void sleepMethod(){
        System.out.println("Sleep start-----");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Sleep end-----");
    }

    public synchronized void waitMethod(){
        System.out.println("Wait start-----");
        synchronized (this){
            try {
                wait(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("Wait end-----");
    }

    public static void main(String[] args) {
        final SleepTest test1 = new SleepTest();

        for(int i = 0;i<3;i++){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    test1.sleepMethod();
                }
            }).start();
        }


        try {
            Thread.sleep(10000);//暂停十秒,等上面程序执行完成
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("-----分割线-----");

        final SleepTest test2 = new SleepTest();

        for(int i = 0;i<3;i++){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    test2.waitMethod();
                }
            }).start();
        }

    }
}

 执行结果:

Sleep start-----
Sleep end-----
Sleep start-----
Sleep end-----
Sleep start-----
Sleep end-----
-----分割线-----
Wait start-----
Wait start-----
Wait start-----
Wait end-----
Wait end-----
Wait end-----

  这个结果的区别很明显,通过sleep方法实现的暂停,程序是顺序进入同步块的,只有当上一个线程执行完成的时候,下一个线程才能进入同步方法,sleep暂停期间一直持有monitor对象锁,其他线程是不能进入的。而wait方法则不同,当调用wait方法后,当前线程会释放持有的monitor对象锁,因此,其他线程还可以进入到同步方法,线程被唤醒后,需要竞争锁,获取到锁之后再继续执行。

2、yield方法
  yield方法的作用是暂停当前线程,以便其他线程有机会执行,不过不能指定暂停的时间,并且也不能保证当前线程马上停止。yield方法只是将Running状态转变为Runnable状态。我们还是通过一个例子来演示其使用:

package com.paddx.test.concurrent;

public class YieldTest implements Runnable {
    @Override
    public void run() {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for(int i=0;i<5;i++){
            System.out.println(Thread.currentThread().getName() + ": " + i);
            Thread.yield();
        }
    }

    public static void main(String[] args) {
        YieldTest runn = new YieldTest();
        Thread t1 = new Thread(runn,"FirstThread");
        Thread t2 = new Thread(runn,"SecondThread");

        t1.start();
        t2.start();

    }
}

运行结果如下:

FirstThread: 0
SecondThread: 0
FirstThread: 1
SecondThread: 1
FirstThread: 2
SecondThread: 2
FirstThread: 3
SecondThread: 3
FirstThread: 4
SecondThread: 4

  这个例子就是通过yield方法来实现两个线程的交替执行。不过请注意:这种交替并不一定能得到保证,源码中也对这个问题进行说明:

/**
      * A hint to the scheduler that the current thread is willing to yield
      * its current use of a processor. The scheduler is free to ignore this
      * hint.
      *
      * <p> Yield is a heuristic attempt to improve relative progression
      * between threads that would otherwise over-utilise a CPU. Its use
      * should be combined with detailed profiling and benchmarking to
      * ensure that it actually has the desired effect.
      *
      * <p> It is rarely appropriate to use this method. It may be useful
      * for debugging or testing purposes, where it may help to reproduce
      * bugs due to race conditions. It may also be useful when designing
      * concurrency control constructs such as the ones in the
      * {@link java.util.concurrent.locks} package.
*/

这段话主要说明了三个问题:

  •   调度器可能会忽略该方法。
  •   使用的时候要仔细分析和测试,确保能达到预期的效果。
  •   很少有场景要用到该方法,主要使用的地方是调试和测试。  

3、join方法

void join() Waits for this thread to die.
void join(long millis) Waits at most millis milliseconds for this thread to die.
void join(long millis, int nanos) Waits at most millis milliseconds plus nanos nanoseconds for this thread to die.

  join方法的作用是父线程等待子线程执行完成后再执行,换句话说就是将异步执行的线程合并为同步的线程。JDK中提供三个版本的join方法,其实现与wait方法类似,join()方法实际上执行的join(0),而join(long millis, int nanos)也与wait(long millis, int nanos)的实现方式一致,暂时对纳秒的支持也是不完整的。我们可以看下join方法的源码,这样更容易理解:

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) {
            while (isAlive()) {
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }

public final synchronized void join(long millis, int nanos)
    throws InterruptedException {

        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (nanos < 0 || nanos > 999999) {
            throw new IllegalArgumentException(
                                "nanosecond timeout value out of range");
        }

        if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
            millis++;
        }

        join(millis);
    }

  大家重点关注一下join(long millis)方法的实现,可以看出join方法就是通过wait方法来将线程的阻塞,如果join的线程还在执行,则将当前线程阻塞起来,直到join的线程执行完成,当前线程才能执行。不过有一点需要注意,这里的join只调用了wait方法,却没有对应的notify方法,原因是Thread的start方法中做了相应的处理,所以当join的线程执行完成以后,会自动唤醒主线程继续往下执行。下面我们通过一个例子来演示join方法的作用:

(1)不使用join方法:
package com.paddx.test.concurrent;

public class JoinTest implements Runnable{
    @Override
    public void run() {

        try {
            System.out.println(Thread.currentThread().getName() + " start-----");
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName() + " end------");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        for (int i=0;i<5;i++) {
            Thread test = new Thread(new JoinTest());
            test.start();
        }

        System.out.println("Finished~~~");
    }
}

执行结果如下:

Thread- 0  start-----
Thread- 1  start-----
Thread- 2  start-----
Thread- 3  start-----
Finished~~~
Thread- 4  start-----
Thread- 2  end------
Thread- 4  end------
Thread- 1  end------
Thread- 0  end------
Thread- 3  end------
(2)使用join方法:
package com.paddx.test.concurrent;

public class JoinTest implements Runnable{
    @Override
    public void run() {

        try {
            System.out.println(Thread.currentThread().getName() + " start-----");
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName() + " end------");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        for (int i=0;i<5;i++) {
            Thread test = new Thread(new JoinTest());
            test.start();
            try {
                test.join(); //调用join方法
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        System.out.println("Finished~~~");
    }
}

执行结果如下:

Thread- 0  start-----
Thread- 0  end------
Thread- 1  start-----
Thread- 1  end------
Thread- 2  start-----
Thread- 2  end------
Thread- 3  start-----
Thread- 3  end------
Thread- 4  start-----
Thread- 4  end------
Finished~~~

 对比两段代码的执行结果很容易发现,在没有使用join方法之间,线程是并发执行的,而使用join方法后,所有线程是顺序执行的。

四、总结

  本文主要详细讲解了wait/notify/notifyAll和sleep/yield/join方法。最后回答一下上面提出的问题:wait/notify/notifyAll方法的作用是实现线程间的协作,那为什么这三个方法不是位于Thread类中,而是位于Object类中?位于Object中,也就相当于所有类都包含这三个方法(因为Java中所有的类都继承自Object类)。要回答这个问题,还是得回过来看wait方法的实现原理,大家需要明白的是,wait等待的到底是什么东西?如果对上面内容理解的比较好的话,我相信大家应该很容易知道wait等待其实是对象monitor,由于Java中的每一个对象都有一个内置的monitor对象,自然所有的类都理应有wait/notify方法。

 

来源于:https://www.cnblogs.com/paddix/p/5381958.html

 

Guess you like

Origin www.cnblogs.com/JonaLin/p/11490488.html