wait(), notify(), notifyAll(),join(),sleep(),yield()等方法介绍

一.概述

本篇文章会对线程等待/唤醒方法进行介绍。涉及到的内容包括:
1. wait(), notify(), notifyAll(),join(),sleep(),yield()等方法介绍

2. wait()和notify()

3. wait(long timeout)和notify()

4. wait() 和 notifyAll()

5.wait()和join() 

6. 为什么notify(), wait()等函数定义在Object中,而不是Thread中?

7.wait()和sleep(),yield()的区别

二.详细分析

1. wait(), notify(), notifyAll(),join(),sleep(),yield()等方法介绍

在Object.java中,定义了wait(), notify()和notifyAll()等方法。

wait()的作用是让当前线程进入等待状态,同时,wait()也会让当前线程释放它所持有的锁。而notify()和notifyAll()的作用,则是唤醒当前对象上的等待线程;notify()是唤醒单个线程,而notifyAll()是唤醒所有的线程。

notify()唤醒在此对象锁上等待的单个线程。
notifyAll()唤醒在此对象锁上等待的所有线程。
wait()让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法”,当前线程被唤醒(进入“就绪状态”)。
wait(long timeout) 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量”,当前线程被唤醒(进入“就绪状态”)。
wait(long timeout, int nanos) 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量”,当前线程被唤醒(进入“就绪状态”)。

在Thread.java中,定义了join(),sleep(),yield()等方法。

join方法把指定的线程添加到当前线程中,可以不给参数直接thread.join(),也可以给一个时间参数,单位为毫秒thread.join(100)。事实上join方法是通过wait方法来实现的。比如线程A中加入了线程B.join方法,则线程A默认执行wait()方法,释放资源进入等待状态,此时线程B获得资源,执行结束后释放资源,线程A重新获取自CPU,继续执行,由此实现线程的顺序执行。

sleep()方法导致了程序暂停执行指定的时间,让出cpu给其他线程,但是它的监控状态依然保持者,当指定的时间到了又会自动苏醒,并返回到可运行状态,不是运行状态。sleep()中指定的时间是线程不会运行的最短时间。因此,sleep()方法不能保证该线程睡眠到期后就开始执行。在调用sleep()方法的过程中,线程不会释放对象锁。

yield意味着放手,放弃,投降。一个调用yield()方法的线程告诉虚拟机它乐意让其他线程占用自己的位置。这表明该线程没有在做一些紧急的事情。注意,这仅是一个暗示,并不能保证不会产生任何影响。

让我们列举一下关于以上定义重要的几点:

  • Yield是一个静态的原生(native)方法
  • Yield告诉当前正在执行的线程把运行机会交给线程池中拥有相同优先级的线程
  • Yield不能保证使得当前正在运行的线程迅速转换到可运行的状态
  • 它仅能使一个线程从运行状态转到可运行状态,而不是等待或阻塞状态

2. wait()和notify()示例

/**
 * @author zhaoj
 * @version MyThread.java, v 0.1 2019-04-18 10:45
 */
public class MyThread extends Thread {
    private Object object;

    public MyThread(String name, Object object) {
        super(name);
        this.object = object;
    }

    public void run() {
        synchronized (object) {
            try {
                System.out.println(Thread.currentThread().getName() + " wait");
                object.wait();
                System.out.println(Thread.currentThread().getName() + " continue");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }


    }
}

/**
 * @author zhaoj
 * @version ThreadTest.java, v 0.1 2019-04-18 10:44
 */
public class ThreadTest {
    private static Object object = new Object();

    public static void main(String[] args) throws InterruptedException {
        MyThread t1 = new MyThread("t1", object);
        t1.start();
        //休眠1秒让线程t1先进入等待状态,防止主线程先执行
        Thread.sleep(1000);
        synchronized (object) {
            try {
                System.out.println(Thread.currentThread().getName() + " call notify()");
                // 主线程通过notify()唤醒线程t1。
                object.notify();
                System.out.println(Thread.currentThread().getName() + " continue");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

执行结果:

t1 wait
main call notify()
main continue
t1 continue

运行结果说明:

(1)主线程main通过new MyThread("t1", object)创建t1线程,然后再通过t1.start()开启t1线程(进入就绪状态,即可运行状态),随后主线程进入休眠1秒(此时不休眠有可能主线程先获得到对象锁object,t1线程就一直处于等待状态了,为了演示效果,让主线程进入休眠,确保t1线程可以先获得对象锁),进入阻塞状态;

(2)t1线程获得到对象锁object(进入运行状态),执行了run方法中的wait()方法,进入等待(阻塞状态),同时释放了对象锁object,等待主线程的唤醒;

(3)主线程休眠结束(进入可运行状态),获得到对象锁object(运行状态),执行唤醒notify(),此时t1线程进入就绪状态(即可运行状态),此时主线程还拥有对象锁,继续执行下面的操作,直至所有操作执行结束,释放对象锁object,此时主线程等待t1线程执行;

(4)t1线程又获得对象锁object(运行状态),继续执行后面的操作,直至所有操作执行结束,然后释放对象锁object,此时t1线程执行完毕(即死亡状态),主线程同时也结束等待,执行完毕(死亡状态)。

3. wait(long timeout)和notify()

wait(long timeout)会让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量”,当前线程被唤醒(进入“就绪状态”,即可运行状态)。

下面的示例就是演示wait(long timeout)在等待超时情况下,线程被唤醒的情况:

/**
 * @author zhaoj
 * @version MyThread.java, v 0.1 2019-04-18 10:45
 */
public class MyThread extends Thread {
    private Object object;

    public MyThread(String name, Object object) {
        super(name);
        this.object = object;
    }

    public void run() {
        synchronized (object) {
            try {
                System.out.println(Thread.currentThread().getName() + " wait");
                object.wait(5000);
                System.out.println(Thread.currentThread().getName() + " continue");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}



/**
 * @author zhaoj
 * @version ThreadTest.java, v 0.1 2019-04-18 10:44
 */
public class ThreadTest {
    private static Object object = new Object();

    public static void main(String[] args) throws InterruptedException {
        MyThread t1 = new MyThread("t1", object);
        t1.start();
        //休眠1秒让线程t1先进入等待状态,防止主线程先执行
        Thread.sleep(1000);
        synchronized (object) {
            try {
                System.out.println(Thread.currentThread().getName() + " I come in");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

运行结果:

t1 wait
main I come in
t1 continue

运行结果说明:

(1)主线程main通过new Thread("t1",object)创建线程t1,然后主线程main执行了t1.start(),t1线程进入了"就绪状态(可运行状态)",此时主线程进入了休眠(阻塞状态),为了确保t1线程先能够获得对象锁,才进行休眠的;

(2)t1线程获得对象锁oject(运行状态),执行object.wait(5000),等待5秒,进入"阻塞状态",释放对象锁;

(3)主线程main休眠结束(就绪状态,即可运行状态),主线程main获得对象锁object(运行状态),执行后续的操作完成,释放对象锁,此时t1线程还处于等待状态(阻塞状态),主线程此时等待t1线程执行完成主;

(4)5秒后,object.wait(5000)等待时间超时,t1线程重新进入“就绪状态”(即可运行状态),由于主线程执行操作结束,t1重新获得对象锁oject(运行状态),执行后续操作完成,释放对象锁object,执行结束(死亡状态),主线程等待t1线程执行结束,自己也结束等待状态(进入死亡状态)。

4. wait() 和 notifyAll()

上面的示例是notify()唤醒单个线程在对象锁上的等待,下面的示例是notifyAll()唤醒所有线程在对象锁上的等待:

/**
 * @author zhaoj
 * @version MyThread.java, v 0.1 2019-04-18 10:45
 */
public class MyThread extends Thread {
    private Object object;

    public MyThread(String name, Object object) {
        super(name);
        this.object = object;
    }

    public void run() {
        synchronized (object) {
            try {
                System.out.println(Thread.currentThread().getName() + " wait");
                object.wait();
                System.out.println(Thread.currentThread().getName() + " continue");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}


/**
 * @author zhaoj
 * @version ThreadTest.java, v 0.1 2019-04-18 10:44
 */
public class ThreadTest {
    private static Object object = new Object();

    public static void main(String[] args) throws InterruptedException {
        MyThread t1 = new MyThread("t1", object);
        MyThread t2 = new MyThread("t2", object);
        MyThread t3 = new MyThread("t3", object);
        t1.start();
        t2.start();
        t3.start();
        //休眠1秒让线程t1先进入等待状态,防止主线程先执行
        Thread.sleep(1000);
        synchronized (object) {
            try {
                System.out.println(Thread.currentThread().getName() + " start");
                //主线程通过notifyAll()唤醒所有该对象锁上的等待线程
                object.notifyAll();
                System.out.println(Thread.currentThread().getName() + " continue");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

运行结果:

t1 wait
t2 wait
t3 wait
main start
main continue
t3 continue
t2 continue
t1 continue

运行结果说明:

(1)主线程main通过new Thread()创建了线程t1,t2,t3,然后执行t1.start(),t2.start(),t3.start()开启三个线程,t1,t2,t3分别进入“就绪状态”,即可运行状态,主线程main进入休眠(阻塞状态),t1,t2,t3分别争抢对象锁oject的拥有权,谁先获得对象锁object,谁先执行操作(object.wait()),进入等待状态(阻塞状态),根据结果可以看出t1先获得,其次是t2,最后是t3;

(2)主线程休眠结束(可运行状态),获得对象锁object(运行状态),执行object.notifyAll()唤醒所有该对象锁object上的等待线程t1,t2,t3,此时主线程main还未执行完所有操作,继续执行,执行完所有动作后,释放对象锁object,等待t1,t2,t3执行;

(3)t1,t2,t3再次争抢对象锁object,先获得锁的先执行,根据执行结果可以看出t3先获得,其次t2,最后t1,当所有线程执行结束,均进入死亡状态。

5.wait()和join() 

先看一下join()的源码:

    /**
     * Waits for this thread to die.
     *
     * 

An invocation of this method behaves in exactly the same * way as the invocation * *

* {@linkplain #join(long) join}{@code (0)} *
* * @throws InterruptedException * if any thread has interrupted the current thread. The * interrupted status of the current thread is * cleared when this exception is thrown. */ public final void join() throws InterruptedException { join(0); } /** * Waits at most {@code millis} milliseconds for this thread to * die. A timeout of {@code 0} means to wait forever. * *

This implementation uses a loop of {@code this.wait} calls * conditioned on {@code this.isAlive}. As a thread terminates the * {@code this.notifyAll} method is invoked. It is recommended that * applications not use {@code wait}, {@code notify}, or * {@code notifyAll} on {@code Thread} instances. * * @param millis * the time to wait in milliseconds * * @throws IllegalArgumentException * if the value of {@code millis} is negative * * @throws InterruptedException * if any thread has interrupted the current thread. The * interrupted status of the current thread is * cleared when this exception is thrown. */ 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; } } }

从上面的源码我们可以看出,join方法实际上是通过调用wait()方法, 来实现同步的效果的。例如,A线程中调用了B线程的join()方法,则相当于A线程调用了B线程的wait()方法,在调用了B线程的wait()方法后,A线程就会进入阻塞状态,因为它相当于放弃了CPU的使用权。需要注意的是,jdk规定,join(0)的意思不是A线程等待B线程0秒,而是A线程等待B线程无限时间,直到B线程执行完毕,即join(0)等价于join()。

下面看个小例子:

/**
 * @author zhaoj
 * @version ThreadNum.java, v 0.1 2019-04-18 11:34
 */
public class ThreadNum extends Thread {
    public ThreadNum(String name) {
        super(name);
    }

    @Override
    public void run() {
        synchronized (this) {
            try {
                System.out.println(Thread.currentThread().getName() + " start");
                Thread.sleep(5000);
                System.out.println(Thread.currentThread().getName() + " end");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}


/**
 * @author zhaoj
 * @version ThreadNumTest.java, v 0.1 2019-04-18 11:37
 */
public class ThreadNumTest {
    public static void main(String[] args) throws InterruptedException {
        ThreadNum t1 = new ThreadNum("t1");
        ThreadNum t2 = new ThreadNum("t2");
        ThreadNum t3 = new ThreadNum("t3");
        t1.start();
        t1.join();
        t2.start();
        t2.join();
        t3.start();
        t3.join();
        System.out.println(Thread.currentThread().getName()+" continue");
    }
}

执行结果:

t1 start
t1 end
t2 start
t2 end
t3 start
t3 end
main continue

运行结果分析:

(1)主线程main通过new Thread()创建了线程t1,t2,t3;

(2)然后执行t1.start()开启线程t1(t1进入就绪状态,即可运行状态),然后执行t1.join()方法,此时相当于主线程main调用了t1线程的wait()方法,主线程main进入等待状态(阻塞状态),直到线程t1执行结束(死亡状态),才继续往下执行

(3)t2,t3同理,这样就可以实现三个线程按顺序执行。

(4)可能有人会有疑问,主线程main执行了t1.join(),而join()的实现是wait()方法,哪为什么不是t1线程进入等待状态(阻塞状态)呢?这里请注意理解wait()方法的作用,上面有提到,wait()方法让当前线程处于“等待(阻塞)状态”,当前线程正是主线程main,所以其实是主线程main调用了t1线程的wait()方法,而不是t1线程内部调用了wait()方法,两者是有很大的区别的(打个不恰当的例子,a有一辆车,b有一辆车,a不开自己的车,开b的车,对b本身并无影响)。有人或许有还有疑问,那既然相当于主线程main调用了t1线程的wait()方法,那没人其他线程唤醒主线程main,主线程main不应该一直等待吗?对于这个问题,请看清join()的源码,while (isAlive())  { wait(0);}这段代码的意思是当线程t1还在执行(即线程存活),则继续等待,否则继续往下执行,也就是说主线程main调用了t1线程的wait()方法,t1线程只要存活着,主线程就一直等待,t1线程执行结束(即死亡状态),随之等待也就结束。

另外join(long millis)是通过在内部使用wait(long millis)方法来实现的,所有它其实是具有释放锁的特点的,在执行完;而sleep(long millis)是不释放锁的,也就是如果有synchronized同步代码块,其他线程仍然获得不到锁,无法访问共享数据。

6. 为什么notify(), wait()等函数定义在Object中,而不是Thread中?

Object中的wait(), notify()等方法,和synchronized一样,会对“对象的同步锁”进行操作。(关于锁的内容,本篇暂不深入,后期会单独出一篇锁相关的内容)

wait()会使"当前线程"等待,因为线程进入等待状态,所以线程应该释放它所持有的"同步锁",否则其它线程获取不到该"同步锁"而无法运行!
OK,线程调用wait()方法之后,会释放它所持有的“同步锁”;而且,根据前面的介绍,我们知道:等待线程可以被notify()或notifyAll()唤醒。现在,请思考一个问题:notify()是依据什么唤醒等待线程的?或者说,wait()等待线程和notify()之间是通过什么关联起来的?答案是:依据“对象的同步锁”。

负责唤醒等待线程的那个线程(我们称为“唤醒线程”),它只有在获取“该对象的同步锁”(这里的同步锁必须和等待线程的同步锁是同一个),并且调用notify()或notifyAll()方法之后,才能唤醒等待线程。虽然,等待线程被唤醒;但是,它不能立刻执行,因为唤醒线程还持有“该对象的同步锁”。必须等到唤醒线程释放了“对象的同步锁”之后,等待线程才能获取到“对象的同步锁”进而继续运行。

总之,notify(), wait()依赖于“同步锁”,而“同步锁”是对象锁持有,并且每个对象有且仅有一个!这就是为什么notify(), wait()等函数定义在Object类,而不是Thread类中的原因。

7.wait()和sleep(),yield()的区别

类型 来源 是否释放锁 作用
wait() Oject 使当前线程等待
sleep() Thread 使当前线程休眠
yield() Thread 使当前线程让出CPU使用权

 

通过上面的表格我们可以看出:wait()方法是Oject对象所有,使当前线程进入等待状态,并释放所有用的对象锁;sleep()是Thread类所有,使当前线程休眠,如果拥有锁,将继续持有该锁,不会释放;yield()也是Thread类所有,使当前线程让出CPU使用权,但是并不能保证其他线程就一定会执行,由于让出使用权并不是等待或者阻塞状态,如果该线程拥有对象锁,其他该对象锁上等待线程也不会拥有该锁,直到该线程执行结束才会释放锁。

以上如有不正确之处,欢迎留言批评指正。

参考文献:

http://www.cnblogs.com/skywang12345/p/3479224.html;

https://blog.csdn.net/qq_38162448/article/details/81676235;

http://www.importnew.com/14958.html;

https://blog.csdn.net/striveb/article/details/83585380

 

你可能感兴趣的:(java基础篇)