全文概要
本文主要介绍wait()/wait(long timeOut)/notify()/notifyAll()的基本使用情况,包括以下内容:
- wait()和notify()联合使用,完成线程之间的通信交互;
- 通过一个案例说明wait(long timeOut)的用法;
- 通过一个案例说明notifyAll()的用法;
- 总结下notify/wait使用场景
wait()/notify()使用案例
本案例是通过使用wait()和notify()完成两个线程之间的顺序输出。线程1先循环5次,接着线程2循环5次,接着线程1再循环5次,最后线程2循环5次。
- 代码案例
package com.tml.javaCore.thread;
/**
* <p>
* 介绍两个线程之间的通讯
* wait()/notify()
*
* @author Administrator
*
*/
public class ThreadCommunication {
public static void main(String[] args) {
ThreadCommunication communication = new ThreadCommunication();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
try {
communication.print();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "thread_01");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
try {
communication.print();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "thread_02");
t1.start();
t2.start();
}
private void print() throws InterruptedException {
synchronized (this) {
for (int i = 0; i < 10; i++) {
this.notify();//唤醒被当前对象加锁的线程,释放对象锁,但是不会让出CPU的执行权
if (i % 5 == 0) {
this.wait();//让当前已经获取对象锁的线程阻塞,同时释放掉对象锁和让出CPU的执行权
}
Thread.sleep(500);
System.out.println(Thread.currentThread().getName() + " is printing: " + i);
}
}
}
}
- 输出结果
thread_01 is printing: 0
thread_01 is printing: 1
thread_01 is printing: 2
thread_01 is printing: 3
thread_01 is printing: 4
thread_02 is printing: 0
thread_02 is printing: 1
thread_02 is printing: 2
thread_02 is printing: 3
thread_02 is printing: 4
thread_01 is printing: 5
thread_01 is printing: 6
thread_01 is printing: 7
thread_01 is printing: 8
thread_01 is printing: 9
thread_02 is printing: 5
thread_02 is printing: 6
thread_02 is printing: 7
thread_02 is printing: 8
thread_02 is printing: 9
thread_01 is printing: 1
thread_01 is printing: 2
thread_01 is printing: 3
thread_01 is printing: 4
thread_02 is printing: 0
thread_02 is printing: 1
thread_02 is printing: 2
thread_02 is printing: 3
thread_02 is printing: 4
thread_01 is printing: 5
thread_01 is printing: 6
thread_01 is printing: 7
thread_01 is printing: 8
thread_01 is printing: 9
thread_02 is printing: 5
thread_02 is printing: 6
thread_02 is printing: 7
thread_02 is printing: 8
thread_02 is printing: 9
- 结果说明
- 线程1首先获取对象锁,拥有CPU的执行权,当执行到this.notify()时,线程1会唤醒在communication对象上休眠的线程2,让其进入就绪状态;
- 换句话说线程1会释放communication的对象锁,但是线程1不会放弃CPU的执行权,只有当synchronized中的代码执行完毕后或者由于其他原因导致线程阻塞才会释放CPU的执行权;
- 当线程1循环到第五次的时候,会执行this.wait(),此时线程1会释放对象锁,同时让出CPU的执行权限,也就是说线程1进入阻塞状态,只有当调用communication对象的notify()/notifyAll()方法才会让线程1进入就绪状态;
- 由于线程2此时是就绪状态,立马获取空闲的CPU资源,当线程2执行到this.notify()时,线程2会唤醒在communication对象上休眠的线程1,让其进入就绪状态,后续逻辑以此类推,直至线程的任务完成。
wait(long timeOut)使用案例
- 代码案例
package com.tml.javaCore.thread;
/**
* <p>线程之间的通信
* wait(long timeOut)
* @author Administrator
*
*/
public class WaitDemo {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Runnable() {
public void run() {
synchronized (this) {
System.out.println(Thread.currentThread().getName() + " is executing!");
//模拟死循环,t1线程不释放对象锁
while(true){
}
}
}
}, "thread_01");
t1.start();
System.out.println(Thread.currentThread().getName() + " start!");
synchronized (t1) {
System.out.println(Thread.currentThread().getName() + " wait!");
t1.wait(3000);//由于t1线程死循环,无法释放对象锁,当前主线程阻塞,约3秒后,t1线程释放对象锁,唤醒主线程
}
System.out.println(Thread.currentThread().getName() + " end!");
}
}
- 输出结果
main start!
thread_01 is executing!
main wait!
main end!
thread_01 is executing!
main wait!
main end!
- 结果说明
- 主线程获取CPU的执行权,调用t1.start()时,让t1线程进入就绪状态;
- 当主线程获取对象锁t1时,会调用t1.wait(3000),同时释放对象锁,线程1获取空闲的CPU资源后,开始执行run()方法,但是run()方法中有一个死循环,也就是线程1不会释放对象锁,同时由于同步代码块中的逻辑没有完成,因此线程1也不会释放CPU的执行权,这就导致了主线程无法执行后续代码;
- 当约3秒后,线程1释放对象锁,主线程获取CPU资源,主线程的逻辑完成;
- 调用t1.wait(3000)为啥是让主线程等待?原因就是所谓的等待是让正在执行任务的线程等待,因为主线程调用t1.wait(3000),因此正在执行的线程是主线程。
notifyAll()使用案例
- 代码案例
package com.tml.javaCore.thread;
/**
* <p>线程之间的通信
* wait()/notifyAll()
* @author Administrator
*
*/
public class NotifyAllDemo {
//锁对象
private static Object object = new Object();
public static void main(String[] args) {
NotifyAllDemo demo = new NotifyAllDemo();
//使用匿名内部类启动线程1
new Thread(new Runnable() {
@Override
public void run() {
demo.threadPrint();
}
}, "thread_01").start();;
//使用匿名内部类启动线程2
new Thread(new Runnable() {
@Override
public void run() {
demo.threadPrint();
}
}, "thread_02").start();
//使用匿名内部类启动线程3
new Thread(new Runnable() {
@Override
public void run() {
demo.threadPrint();
}
}, "thread_03").start();
try {
//主线程休眠3秒
Thread.sleep(3000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
synchronized (object) {
System.out.println(Thread.currentThread().getName() + " :is ready to notifyAll!");
//唤醒object上休眠的三个线程
object.notifyAll();
System.out.println(Thread.currentThread().getName() + " :is finished!");
}
}
private void threadPrint(){
synchronized (object) {
try {
System.out.println(Thread.currentThread().getName() + " :is ready to wait!");
object.wait();//当前线程进入阻塞状态,同时释放对象锁
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " :is finished!");
}
}
}
- 输出结果
thread_01 :is ready to wait!
thread_02 :is ready to wait!
thread_03 :is ready to wait!
main :is ready to notifyAll!
main :is finished!
thread_03 :is finished!
thread_02 :is finished!
thread_01 :is finished!
thread_02 :is ready to wait!
thread_03 :is ready to wait!
main :is ready to notifyAll!
main :is finished!
thread_03 :is finished!
thread_02 :is finished!
thread_01 :is finished!
- 结果说明
- 主线程中使用匿名内部类的方式启动三个线程,紧接着主线程休眠3秒;
- 三个线程由于使用了相同的对象锁,随机一个线程获取对象锁,当该线程执行到object.wait()时,会释放对象锁,紧接着剩下两个线程以此类推;
- 当三个线程都进入阻塞状态时,主线程在休眠三秒后重新拥有CPU的执行权,当主线程获取对象锁执行到object.notifyAll()时,会将object对象上休眠的三个线程都唤醒;
- 三个线程唤醒后,依次获取到对象锁,完成各自的任务。
总结
- wait()/notify()/notifyAll()等方法,必须写在synchronized代码块或者synchronized方法中,否则会抛出java.lang.IllegalMonitorStateException运行时异常;
- wait()/notify()/notifyAll()等方法为啥都是定义在Object类中而不是定义在Thread类中?因为这些方法都是依赖对象同步锁,而java中所有的对象都可以作为同步锁,因此这些方法必须定义在超类Object中,同时这些方法为final的,不可以被重写。