关于wait与notify和notifyAll方法的总结:
- 当调用wait时,首先要确保调用了wait方法的线程已经持有了对象的锁。
- 当调用了wait后,该线程就会释放掉这个对象的锁,然后进入到等待状态(wait set)
- 当线程调用了wait后进入到等待状态时,它就可以等待其他线程调用相同对象的notify或是notifyAll方法来使得自己被唤醒
- 一旦这个线程被其他线程唤醒后,该线程就会与其他线程一同开始竞争这个对象的锁(公平竞争);只有当线程获取到了这个对象的锁后,线程才会继续往下执行
- 调用wait方法的代码片段需要放在一个synchronized块或是synchronized方法中,这样才可以确保线程在调用wait方法前已经获取到了对象的锁
- 当调用对象的notify方法时,它会随机唤醒该对象等待集合(wait set)中的任意一个线程,当某个线程被唤醒后,它就会与其他线程一同竞争对象的锁
- 当调用对象的notifyAll方法时,它会唤醒该对象等待集合(wait set)中的所有线程,这些线程都会被唤醒后,又会开始竞争对象的锁
- 在某一时刻,只有唯一一个线程可以拥有对象的锁
写段代码验证下
MyObject类
public class MyObject {
private int counter;
public synchronized void increase() {
//着重注意这里的if
if (counter != 0) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
counter++;
System.out.println(counter);
notify();
}
public synchronized void decrease() {
//着重注意这里的if
if (counter == 0) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
counter--;
System.out.println(counter);
notify();
}
}
IncreaseThread类
public class IncreaseThread extends Thread {
private MyObject myObject;
public IncreaseThread(MyObject myObject) {
this.myObject = myObject;
}
@Override
public void run() {
for (int i = 0; i < 30; i++) {
try {
Thread.sleep((long) Math.random() * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
myObject.increase();
}
}
}
DecreaseThread类
public class DecreaseThread extends Thread {
private MyObject myObject;
public DecreaseThread(MyObject myObject) {
this.myObject = myObject;
}
@Override
public void run() {
for (int i = 0; i < 30; i++) {
try {
Thread.sleep((long) Math.random() * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
myObject.decrease();
}
}
}
client类,测试类
public class Client {
public static void main(String[] args) {
MyObject myObject = new MyObject();
Thread increaseThread = new IncreaseThread(myObject);
Thread decreaseThread = new DecreaseThread(myObject);
increaseThread.start();
decreaseThread.start();
}
}
在测试类中,当IncreaseThread、DecreaseThread类的实例对象都只有一个时,控制台依次输出1、0、1、0。。。
但是,当我们对client测试类进行修改,增加两个对象后,控制台输出的值是不是和之前的一样呢?
测试类修改后代码
public class Client {
public static void main(String[] args) {
MyObject myObject = new MyObject();
Thread increaseThread = new IncreaseThread(myObject);
Thread increaseThread2 = new IncreaseThread(myObject);
Thread decreaseThread = new DecreaseThread(myObject);
Thread decreaseThread2 = new DecreaseThread(myObject);
increaseThread.start();
increaseThread2.start();
decreaseThread.start();
decreaseThread2.start();
}
}
控制台除了输出1、0之外还会输出其他不可控制的值。
这是为什么呢?
这里主要看MyObject
类中的increase
、decrease
方法的实现。
public class MyObject {
private int counter;
public synchronized void increase() {
//着重注意这里的if
if (counter != 0) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
counter++;
System.out.println(counter);
notify();
}
public synchronized void decrease() {
//着重注意这里的if
if (counter == 0) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
counter--;
System.out.println(counter);
notify();
}
}
这里使用A1、A2替代increaseThread、increaseThread2对象,用B1、B2替代decreaseThread、decreaseThread2。
四个线程A1、A2、B1、B2同时开始执行。假设一开始B1线程运行,此时counter == 0,执行wait,释放当前锁,假设下一个获得锁的是B2,则因为counter==0,再次执行wait;假设下一个获得锁的是A1,则因为counter等于0,执行counter++,那么counter等于1,然后执行notify,唤醒其他线程,假如唤醒的是B1,则B1线程往下先执行count–,此时counter等于0,然后再执行notify,(重点来了)假如此时唤醒的是B2,则继续往下执行counter–,此时counter等于-1。
经过上面的解释,相信你已经明白为什么会出现增加线程会出现不可控制的情况了。
那么,问题的原因已经找到,如何解决呢?
//将这里的if改为while即可,increase()和decrease()中的if都要改
if (counter == 0) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//修改后
while (counter == 0) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
那么为什么这么修改就可以了呢?
我们从唤醒的假如是B2开始说。**(重点来了)**那么wait语句结束之后,会再次判断当前的counter是否等于0,如果不等于0,则执行counter,如果等于0,则继续执行wait。因为此时的counter是等于0的,所以执行执行wait。所以控制台输出还是1、0、1、0。。。。