一.概述
本篇文章会对线程等待/唤醒方法进行介绍。涉及到的内容包括:
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()方法的线程告诉虚拟机它乐意让其他线程占用自己的位置。这表明该线程没有在做一些紧急的事情。注意,这仅是一个暗示,并不能保证不会产生任何影响。
让我们列举一下关于以上定义重要的几点:
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