java ---线程wait/notify/sleep/yield/join
一、线程的状态
Java中线程中状态可分为五种:New(新建状态),Runnable(就绪状态),Running(运行状态),Blocked(阻塞状态),Dead(死亡状态)。
New:新建状态,当线程创建完成时为新建状态,即new Thread(...),还没有调用start方法时,线程处于新建状态。
Runnable:就绪状态,当调用线程的的start方法后,线程进入就绪状态,等待CPU资源。处于就绪状态的线程由Java运行时系统的线程调度程序(thread scheduler)来调度。
Running:运行状态,就绪状态的线程获取到CPU执行权以后进入运行状态,开始执行run方法。
Blocked:阻塞状态,线程没有执行完,由于某种原因(如,I/O操作等)让出CPU执行权,自身进入阻塞状态。
Dead:死亡状态,线程执行完成或者执行过程中出现异常,线程就会进入死亡状态。
这五种状态之间的转换关系如下图所示:
有了对这五种状态的基本了解,现在我们来看看Java中是如何实现这几种状态的转换的。
二、wait/notify/notifyAll方法的使用
1、wait方法:
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. |
JDK中一共提供了这三个版本的方法,
(1)wait()方法的作用是将当前运行的线程挂起(即让其进入阻塞状态),直到notify或notifyAll方法来唤醒线程.
(2)wait(long timeout),该方法与wait()方法类似,唯一的区别就是在指定时间内,如果没有notify或notifAll方法的唤醒,也会自动唤醒。
(3)至于wait(long timeout,long nanos),本意在于更精确的控制调度时间,不过从目前版本来看,该方法貌似没有完整的实现该功能,其源码(JDK1.8)如下:
1 public final void wait(long timeout, int nanos) throws InterruptedException {
2 if (timeout < 0) {
3 throw new IllegalArgumentException("timeout value is negative");
4 }
5
6 if (nanos < 0 || nanos > 999999) {
7 throw new IllegalArgumentException(
8 "nanosecond timeout value out of range");
9 }
10
11 if (nanos >= 500000 || (nanos != 0 && timeout == 0)) {
12 timeout++;
13 }
14
15 wait(timeout);
16 }
从源码来看,JDK8中对纳秒的处理,只做了四舍五入,所以还是按照毫秒来处理的,可能在未来的某个时间点会用到纳秒级别的精度。虽然JDK提供了这三个版本,其实最后都是调用wait(long timeout)方法来实现的,wait()方法与wait(0)等效,而wait(long timeout,int nanos)从上面的源码可以看到也是通过wait(long timeout)来完成的。下面我们通过一个简单的例子来演示wait()方法的使用:
1 package com.paddx.test.concurrent;
2
3 public class WaitTest {
4
5 public void testWait(){
6 System.out.println("Start-----");
7 try {
8 wait(1000);
9 } catch (InterruptedException e) {
10 e.printStackTrace();
11 }
12 System.out.println("End-------");
13 }
14
15 public static void main(String[] args) {
16 final WaitTest test = new WaitTest();
17 new Thread(new Runnable() {
18 @Override
19 public void run() {
20 test.testWait();
21 }
22 }).start();
23 }
24 }
这段代码的意图很简单,就是程序执行以后,让其暂停一秒,然后再执行。运行上述代码,查看结果:
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 ) |
这段程序并没有按我们的预期输出相应结果,而是抛出了一个异常。大家可能会觉得奇怪为什么会抛出异常?而抛出的IllegalMonitorStateException异常又是什么?我们可以看一下JDK中对IllegalMonitorStateException的描述:
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. |
这句话的意思大概就是:线程试图等待对象的监视器或者试图通知其他正在等待对象监视器的线程,但本身没有对应的监视器的所有权。其实这个问题在《Java并发编程:Synchronized及其实现原理》一文中有提到过,wait方法是一个本地方法,其底层是通过一个叫做监视器锁的对象来完成的。所以上面之所以会抛出异常,是因为在调用wait方式时没有获取到monitor对象的所有权,那如何获取monitor对象所有权?Java中只能通过Synchronized关键字来完成,修改上述代码,增加Synchronized关键字:
1 package com.paddx.test.concurrent;
2
3 public class WaitTest {
4
5 public synchronized void testWait(){//增加Synchronized关键字
6 System.out.println("Start-----");
7 try {
8 wait(1000);
9 } catch (InterruptedException e) {
10 e.printStackTrace();
11 }
12 System.out.println("End-------");
13 }
14
15 public static void main(String[] args) {
16 final WaitTest test = new WaitTest();
17 new Thread(new Runnable() {
18 @Override
19 public void run() {
20 test.testWait();
21 }
22 }).start();
23 }
24 }
现在再运行上述代码,就能看到预期的效果了:
Start----- End------- |
所以,通过这个例子,大家应该很清楚,wait方法的使用必须在同步的范围内,否则就会抛出IllegalMonitorStateException异常,wait方法的作用就是阻塞当前线程等待notify/notifyAll方法的唤醒,或等待超时后自动唤醒。
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. |
有了对wait方法原理的理解,notify方法和notifyAll方法就很容易理解了。既然wait方式是通过对象的monitor对象来实现的,所以只要在同一对象上去调用notify/notifyAll方法,就可以唤醒对应对象monitor上等待的线程了。notify和notifyAll的区别在于前者只能唤醒monitor上的一个线程,对其他线程没有影响,而notifyAll则唤醒所有的线程,看下面的例子很容易理解这两者的差别:
1 package com.paddx.test.concurrent;
2
3 public class NotifyTest {
4 public synchronized void testWait(){
5 System.out.println(Thread.currentThread().getName() +" Start-----");
6 try {
7 wait(0);
8 } catch (InterruptedException e) {
9 e.printStackTrace();
10 }
11 System.out.println(Thread.currentThread().getName() +" End-------");
12 }
13
14 public static void main(String[] args) throws InterruptedException {
15 final NotifyTest test = new NotifyTest();
16 for(int i=0;i<5;i++) {
17 new Thread(new Runnable() {
18 @Override
19 public void run() {
20 test.testWait();
21 }
22 }).start();
23 }
24
25 synchronized (test) {
26 test.notify();
27 }
28 Thread.sleep(3000);
29 System.out.println("-----------分割线-------------");
30
31 synchronized (test) {
32 test.notifyAll();
33 }
34 }
35 }
输出结果如下:
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------- |
从结果可以看出:调用notify方法时只有线程Thread-0被唤醒,但是调用notifyAll时,所有的线程都被唤醒了。
最后,有两点点需要注意:
(1)调用wait方法后,线程是会释放对monitor对象的所有权的。
(2)一个通过wait方法阻塞的线程,必须同时满足以下两个条件才能被真正执行:
- 线程需要被唤醒(超时唤醒或调用notify/notifyll)。
- 线程唤醒后需要竞争到锁(monitor)。
三、sleep/yield/join方法解析
上面我们已经清楚了wait和notify方法的使用和原理,现在我们再来看另外一组线程间协作的方法。这组方法跟上面方法的最明显区别是:这几个方法都位于Thread类中,而上面三个方法都位于Object类中。至于为什么,大家可以先思考一下。现在我们逐个分析sleep/yield/join方法:
1、sleep
sleep方法的作用是让当前线程暂停指定的时间(毫秒),sleep方法是最简单的方法,在上述的例子中也用到过,比较容易理解。唯一需要注意的是其与wait方法的区别。最简单的区别是,wait方法依赖于同步,而sleep方法可以直接调用。而更深层次的区别在于sleep方法只是暂时让出CPU的执行权,并不释放锁。而wait方法则需要释放锁。
1 package com.paddx.test.concurrent;
2
3 public class SleepTest {
4 public synchronized void sleepMethod(){
5 System.out.println("Sleep start-----");
6 try {
7 Thread.sleep(1000);
8 } catch (InterruptedException e) {
9 e.printStackTrace();
10 }
11 System.out.println("Sleep end-----");
12 }
13
14 public synchronized void waitMethod(){
15 System.out.println("Wait start-----");
16 synchronized (this){
17 try {
18 wait(1000);
19 } catch (InterruptedException e) {
20 e.printStackTrace();
21 }
22 }
23 System.out.println("Wait end-----");
24 }
25
26 public static void main(String[] args) {
27 final SleepTest test1 = new SleepTest();
28
29 for(int i = 0;i<3;i++){
30 new Thread(new Runnable() {
31 @Override
32 public void run() {
33 test1.sleepMethod();
34 }
35 }).start();
36 }
37
38
39 try {
40 Thread.sleep(10000);//暂停十秒,等上面程序执行完成
41 } catch (InterruptedException e) {
42 e.printStackTrace();
43 }
44 System.out.println("-----分割线-----");
45
46 final SleepTest test2 = new SleepTest();
47
48 for(int i = 0;i<3;i++){
49 new Thread(new Runnable() {
50 @Override
51 public void run() {
52 test2.waitMethod();
53 }
54 }).start();
55 }
56
57 }
58 }
执行结果:
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状态。我们还是通过一个例子来演示其使用:
1 package com.paddx.test.concurrent;
2
3 public class YieldTest implements Runnable {
4 @Override
5 public void run() {
6 try {
7 Thread.sleep(100);
8 } catch (InterruptedException e) {
9 e.printStackTrace();
10 }
11 for(int i=0;i<5;i++){
12 System.out.println(Thread.currentThread().getName() + ": " + i);
13 Thread.yield();
14 }
15 }
16
17 public static void main(String[] args) {
18 YieldTest runn = new YieldTest();
19 Thread t1 = new Thread(runn,"FirstThread");
20 Thread t2 = new Thread(runn,"SecondThread");
21
22 t1.start();
23 t2.start();
24
25 }
26 }
运行结果如下:
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方法的源码,这样更容易理解:
1 public final void join() throws InterruptedException {
2 join(0);
3 }
4
5 public final synchronized void join(long millis)
6 throws InterruptedException {
7 long base = System.currentTimeMillis();
8 long now = 0;
9
10 if (millis < 0) {
11 throw new IllegalArgumentException("timeout value is negative");
12 }
13
14 if (millis == 0) {
15 while (isAlive()) {
16 wait(0);
17 }
18 } else {
19 while (isAlive()) {
20 long delay = millis - now;
21 if (delay <= 0) {
22 break;
23 }
24 wait(delay);
25 now = System.currentTimeMillis() - base;
26 }
27 }
28 }
29
30 public final synchronized void join(long millis, int nanos)
31 throws InterruptedException {
32
33 if (millis < 0) {
34 throw new IllegalArgumentException("timeout value is negative");
35 }
36
37 if (nanos < 0 || nanos > 999999) {
38 throw new IllegalArgumentException(
39 "nanosecond timeout value out of range");
40 }
41
42 if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
43 millis++;
44 }
45
46 join(millis);
47 }
大家重点关注一下join(long millis)方法的实现,可以看出join方法就是通过wait方法来将线程的阻塞,如果join的线程还在执行,则将当前线程阻塞起来,直到join的线程执行完成,当前线程才能执行。不过有一点需要注意,这里的join只调用了wait方法,却没有对应的notify方法,原因是Thread的start方法中做了相应的处理,所以当join的线程执行完成以后,会自动唤醒主线程继续往下执行。下面我们通过一个例子来演示join方法的作用:
(1)不使用join方法:
1 package com.paddx.test.concurrent;
2
3 public class JoinTest implements Runnable{
4 @Override
5 public void run() {
6
7 try {
8 System.out.println(Thread.currentThread().getName() + " start-----");
9 Thread.sleep(1000);
10 System.out.println(Thread.currentThread().getName() + " end------");
11 } catch (InterruptedException e) {
12 e.printStackTrace();
13 }
14 }
15
16 public static void main(String[] args) {
17 for (int i=0;i<5;i++) {
18 Thread test = new Thread(new JoinTest());
19 test.start();
20 }
21
22 System.out.println("Finished~~~");
23 }
24 }
执行结果如下:
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方法:
1 package com.paddx.test.concurrent;
2
3 public class JoinTest implements Runnable{
4 @Override
5 public void run() {
6
7 try {
8 System.out.println(Thread.currentThread().getName() + " start-----");
9 Thread.sleep(1000);
10 System.out.println(Thread.currentThread().getName() + " end------");
11 } catch (InterruptedException e) {
12 e.printStackTrace();
13 }
14 }
15
16 public static void main(String[] args) {
17 for (int i=0;i<5;i++) {
18 Thread test = new Thread(new JoinTest());
19 test.start();
20 try {
21 test.join(); //调用join方法
22 } catch (InterruptedException e) {
23 e.printStackTrace();
24 }
25 }
26
27 System.out.println("Finished~~~");
28 }
29 }
执行结果如下:
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方法。
java ---线程wait/notify/sleep/yield/join的更多相关文章
- Java 并发编程:线程间的协作(wait/notify/sleep/yield/join)
Java并发编程系列: Java 并发编程:核心理论 Java并发编程:Synchronized及其实现原理 Java并发编程:Synchronized底层优化(轻量级锁.偏向锁) Java 并发编程 ...
- 【转】Java 并发编程:线程间的协作(wait/notify/sleep/yield/join)
一.线程的状态 Java中线程中状态可分为五种:New(新建状态),Runnable(就绪状态),Running(运行状态),Blocked(阻塞状态),Dead(死亡状态). New:新建状态,当线 ...
- 线程间的协作(wait/notify/sleep/yield/join)(五)
一.线程的状态 Java中线程中状态可分为五种:New(新建状态),Runnable(就绪状态),Running(运行状态),Blocked(阻塞状态),Dead(死亡状态). New:新建状态,当线 ...
- JAVA 多线程随笔 (二) sleep, yield, join, wait 和notify
这里先说明一下锁对象,如果一个类比如Person里的方法都有synchronized来修饰,那么每一个方法的锁对象就是Person的一个实例person. 锁对象也可以针对某个特定的实例, 比如syn ...
- Java多线程系列 基础篇10 wait/notify/sleep/yield/join
1.Object类中的wait()/notify()/notifyAll() wait(): 让当前线程处于Waiting状态并释放掉持有的对象锁,直到其他线程调用此对象的线程notify()/not ...
- Java线程编程中isAlive()和join()的使用详解
一个线程如何知道另一线程已经结束?Thread类提供了回答此问题的方法. 有两种方法可以判定一个线程是否结束.第一,可以在线程中调用isAlive().这种方法由Thread定义,它的通常形式如下: ...
- java 多线程之wait(),notify,notifyAll(),yield()
wait(),notify(),notifyAll()不属于Thread类,而是属于Object基础类,也就是说每个对像都有wait(),notify(),notifyAll()的功能.因为都个对像都 ...
- Java线程状态、线程start方法源码、多线程、Java线程池、如何停止一个线程
下面将依次介绍: 1. 线程状态.Java线程状态和线程池状态 2. start方法源码 3. 什么是线程池? 4. 线程池的工作原理和使用线程池的好处 5. ThreadPoolExecutor中的 ...
- java线程总结(1/5)
前言 闲来无事正值面试,看面试中有线程之问题,特此总结一番. 正文 一.线程和进程的区别:1.每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销.2.线程可以看成时轻量级的进程 ...
随机推荐
- Nunit-Writing Tests
Nunit 测试可以被任意支持attributes的.net语言使用 Attributes被用于去标识测试类和测试方法,然后通过不同的方式修改他们的行为 Assertions针对一个或多个约束,测试一 ...
- 基础知识《七》---Java多线程详解
- Find celebrity
Suppose you are at a party with n people (labeled from 0 to n - 1) and among them, there may exist o ...
- POJ 1661
http://poj.org/problem?id=1661 这是一道DP的题目,求最优解 上面的这一个题是对于那个重左边开始上的函数的解释 题目要求的是从最高掉下来的小时间,那么我们就可以求从最低处 ...
- struts2 中属性驱动(其实就是struts2 action 中处理 request 的参数【old 方式servlet api 封装数据到javabean中(or beanutils)】),这里属性驱动是新方式
1.属性驱动 a\ 一般的set public class UserAction extends ActionSupport { private String username; private S ...
- 为什么学习c++?该怎么学?
本人最近刚开始学习C++,准备记录下学习C++的历程.以下都是记录欢迎指教. 第一堂课,我们的老师告诉我们为什么学习C++,学习C++有啥用?这我也想了.但是我不了解C++,所以肯定是想不了多少的. ...
- Zookeeper集群服务部署
Zookeeper是一个分布式.开源的分布式应用程序协调服务,是Google的Chubby的开源实现,也是和Hadoop.Hbase相互配合的重要组件,作用就是为分布式应用程序提供一致性服务,包括配置 ...
- 1.nodejs权威指南--基础知识
1. 基础知识 1.1 全局作用域及函数 1.1.1 全局作用域 在nodejs中,定义了一个global对象,代表nodejs中的全局命名空间,任何全局变量.函数或对象都是该对象的一个属性值 1.1 ...
- Java for LeetCode 220 Contains Duplicate III
Given an array of integers, find out whether there are two distinct indices i and j in the array suc ...
- WIZnet官方网盘
之前使用过 WIZnet 的TCP/IP 解决方案,资源较少, 偶然发现此网盘,不敢独享,访问 请戳此处.