文章目录
一、等待/通知机制
1.1 wait/notify机制的由来
如果某一线程通过while轮询机制来检测某一条件,轮询时间间隔很小,会更浪费CPU资源;如果轮询时间间隔很大,可能会取不到想要的数据,所以就需要一种机制来减少CPU的资源浪费,而且还可以在实现多个线程之间的通信,这就是wait/notify机制的由来。
1.2 什么是等待/通知机制
比如生产者和消费者模型,消费者等待生产者生产资源,这是等待,生产者生产好资源通知等待的消费者去消费,这是通知。
1.3 等待/通知机制的实现
- wait()方法
当前执行代码的线程进行等待;将当前线程置入"预执行队列中";并且wait所在的代码行出停止执行,直到接到通知或者被中断为止。在调用wait()之前,线程必须获得该对象的对象级别锁,即只能在同步方法或者同步代码块中调用该方法。在执行wait()方法后,当前线程释放锁,在从wait()返回前,当前线程与其他线程正常去抢占锁,如果调用wait()时没有适当的锁,则会抛出IllegalMonitorStateException,他是RuntimeException的一个子类,不需要try/catch。
- notify()方法
在同步方法或者同步代码块中调用,调用notify()方法时,如果没有持锁,会抛出IllegalMonitorStateException,该方法用来通知那些正在等待的线程,如果有多个线程在等待,则线程规划器会随便挑出一个wait状态的线程,对其发出通知notify,执行notify后,当前线程不会立马释放锁,而是等待执行notify()方法的线程将程序执行完,才会释放锁,wait的线程才能获得锁,没有得到锁的线程会继续阻塞在wait状态。
- notifyAll()方法
是所有处于wait状态的线程都别唤起,进入可运行状态。此时,优先级最高的线程最先执行,但也可能是随机执行,这取决于JVM的实现。
1.4 线程之间状态的切换
- 新建线程对象,调用它的start()方法,系统会为此分配cpu,使其处于Runnnable(可运行)状态,这是一个准备运行的阶段,如果线程抢到了cpu资源,此线程就处于运行状态。
- Runnable状态和Running状态可相互切换,因为线程运行一段时间后,如果有优先级更高的线程抢占了cpu,线程就会从Running状态转变为Runnable状态。
- 线程进入Runnable状态大致分为以下几种状态:
- 调用sleep()方法后经过的时间大于指定的休眠的时间。
- 线程调用的阻塞IO已返回,阻塞方法执行完毕。
- 线程成功的获得了试图同步的监视器
- 线程正在等待某个通知,其它线程发起了通知
- 处于挂起的线程调用了resume恢复方法
- 出现阻塞状态的情况
- 线程调用sleep方法,主动放弃占用的处理器资源
- 线程调用了阻塞式IO方法,在该方法返回前,该线程被阻塞
- 线程试图获得一个同步监视器,但是该同步监视器被其他线程所持有
- 线程等待某个通知
- 程序调用了suspend方法将该线程挂起,此方法容易导致死锁,尽量避免使用该方法。
- run()方法运行完毕后线程进入销毁阶段,整个线程执行完毕
1.5 锁对象拥有的队列
就绪队列:就绪队列存储了将要获得锁的线程,一个线程被唤醒后,才能进入就绪队列,等待cpu的调度。wait()方法执行后,自动释放锁。
阻塞队列:存储了被阻塞的线程。一个线程被wait后,进入阻塞队列。notify()方法执行后,不自动释放锁。
1.6注意问题
notify()方法过早执行,则会先执行notify()方法所在的同步代码块中的内容,wait线程就无法被唤醒。
如果资源到达临界值,还有线程准备操作资源,就会出错。
二、生产者与消费者模型
2.1 一生产者与一消费者
生产者:
package wait;
public class produce extends Thread {
Object lock;
public produce(Object lock) {
this.lock = lock;
}
public void produceResource() throws InterruptedException {
synchronized (lock) {
while(true){
if (resource.flag == true) {
System.out.println("生产者有资源,等待消费者消费");
lock.wait();
}
System.out.println(Thread.currentThread().getName() + "没资源,准备生产资源");
resource.flag = true;
lock.notify();// 通知消费者消费
}
}
}
@Override
public void run() {
try {
produceResource();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
消费者:
package wait;
public class cost extends Thread {
Object lock;
public cost(Object lock) {
this.lock = lock;
}
public void costResource() throws InterruptedException {
synchronized (lock) {
while(true){
if (resource.flag == false) {
System.out.println("消费者没有资源消费,等待生产者生产");
lock.wait();
}
System.out.println(Thread.currentThread().getName() + "有资源了,消费者开始消费");
resource.flag = false;
lock.notify();// 通知生产者生产
}
}
}
@Override
public void run() {
try {
costResource();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
资源:
public class resource {
public static boolean flag = false;
}
测试代码:
package wait;
public class test {
public static void main(String[] args) {
Object lock = new Object();
cost produce = new cost(lock);
produce cost = new produce(lock);
produce.setName("消费者");
cost.setName("生产者");
produce.start();
cost.start();
}
}
结果:
消费者没有资源消费,等待生产者生产
生产者没资源,准备生产资源
生产者有资源,等待消费者消费
消费者有资源了,消费者开始消费
消费者没有资源消费,等待生产者生产
生产者没资源,准备生产资源
生产者有资源,等待消费者消费
消费者有资源了,消费者开始消费
......
可以看到一个生产者与一个消费者的模拟中,生产者生产一次,消费者消费一次,有序的进行着。
2.2 生产者与消费者:操作栈
import java.util.ArrayList;
import java.util.List;
public class Stack_Thread {
private List list = new ArrayList();
synchronized public void push() throws InterruptedException {
while (true) {
if (list.size() == 1) {
//System.out.println("有资源,等待消费者消费");
this.wait();
}
//没资源生产
String resource = "res";
list.add(resource);
System.out.println("push = " + list.size());
//System.out.println(Thread.currentThread().getName() + "生产好资源,通知消费者消费" + list.size());
this.notify();
}
}
synchronized public void pop() throws InterruptedException {
while (true) {
//有资源消费
if (list.size() == 0) {
//System.out.println("没资源,等待生产者生产");
this.wait();
}
list.remove(0);
System.out.println("pop = " + list.size());
//System.out.println(Thread.currentThread().getName() + "消费者消费结束,等待生产者生产"+ list.size());
this.notify();
}
}
}
生产者:
public class produce extends Thread{
Stack_Thread thread = new Stack_Thread();
public produce(Stack_Thread thread){
this.thread = thread;
}
@Override
public void run() {
try {
thread.push();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
消费者:
public class cost extends Thread {
Stack_Thread thread = new Stack_Thread();
public cost(Stack_Thread thread){
this.thread = thread;
}
@Override
public void run() {
try {
thread.pop();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果:
pop = 0
push = 1
pop = 0
push = 1
pop = 0
push = 1
pop = 0
push = 1
pop = 0
push = 1
说明:一个wait()方法对应一个notify()方法,wait()方法在前,notify()方法在后。
2.4 一个生产者与多个消费者
测试程序:
public class test {
public static void main(String[] args) {
Stack_Thread a = new Stack_Thread();
cost cost1 = new cost(a);
cost cost2 = new cost(a);
cost cost3 = new cost(a);
produce produce = new produce(a);
cost1.start();
cost2.start();
cost3.start();
produce.start();
}
}
结果:
push = 1
pop = 0
Exception in thread "Thread-0" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
at java.util.ArrayList.rangeCheck(ArrayList.java:657)
at java.util.ArrayList.remove(ArrayList.java:496)
at Stack_Thread.pop(Stack_Thread.java:30)
at cost.run(cost.java:10)
原因:
if (list.size() == 1) {
//System.out.println("有资源,等待消费者消费");
this.wait();
}
用if条件作为语句判断,当条件发生时,即size为1时,不能够得到及时的响应,多执行了notify()方法,所以多个呈wait状态的线程被唤醒,继而执行list.remove(0)代码而出现异常。把if语句改成while判断,
解决方法:
if换成while条件:
iwhile(list.size() == 1) {
//System.out.println("有资源,等待消费者消费");
this.wait();
}
结果:
push = 1
pop = 0
程序出现假死的情况,出现假死的原因是线程唤醒的是同类的wait线程,解决方法是使用notifyAll()方法,唤醒异类。
"C:\Program Files\Java\jdk1.8.0_181\bin\java.exe" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2018.2.4\lib\idea_rt.jar=52549:C:\Program Files\JetBrains\IntelliJ IDEA 2018.2.4\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_181\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\rt.jar;D:\untitled2\out\production\untitled2" test
push = 1
pop = 0
push = 1
pop = 0
push = 1
正常运行。
2.5 多生产与一消费
public class test {
public static void main(String[] args) {
Stack_Thread a = new Stack_Thread();
cost cost1 = new cost(a);
produce produce1 = new produce(a);
produce produce2 = new produce(a);
produce produce3 = new produce(a);
cost1.start();
produce1.start();
produce2.start();
produce3.start();
}
}
2.6 多生产与多消费
public class test {
public static void main(String[] args) {
Stack_Thread a = new Stack_Thread();
cost cost1 = new cost(a);
cost cost2 = new cost(a);
cost cost3 = new cost(a);
produce produce1 = new produce(a);
produce produce2 = new produce(a);
produce produce3 = new produce(a);
cost1.start();
cost2.start();
cost3.start();
produce1.start();
produce2.start();
produce3.start();
}
}