多线程wait()与notify()和notifyAll()方法的总结
wait和sleep的区别
-
在调用wait方法时,线程必须持有调用对象的锁,当调用wait方法后,线程就会释放掉改对象的锁(monitor)。
-
在调用Thread类的sleep方法时,线程是不会释放掉对象的锁的。
wait()与notify()和notifyAll()方法的总结
- 当调用wait时,首先需要确保调用了wait方法的线程已经持有了对象的锁。
- 当调用wait后,该线程就会释放掉这个对象的锁,然后进入到等待状态(wait set)。
- 当线程调用了wait后进入到等待状态时,它就可以等待其他线程调用相同对象的notify或notifyAll方法来使得自己被唤醒。
- 一旦这个线程被其他线程唤醒后,改线程就会与其他线程一同开始竞争这个对象的锁(公平竞争),只有当该线程获取到了这个对象锁后,线程才会继续往下执行。
- 调用wait方法的代码片段需要放在一个synchronized块或是synchronized方法中,这样才可以确保线程在wait方法前已经获取到了对象的锁。
- 当调用对象的notifty方法时,它会随机唤醒该对象等待集合(wait set)中的任意一个线程,当某个线程被唤醒后,它就会与其他线程一同竞争对象的锁。
- 当调用对象的notifyAll方法时,它会唤醒该对象等待集合(wait set)中的所有线程,这些线程被唤醒后,又会开始竞争对象的锁。
- 在某一时刻,只有唯一一个线程可以拥有对象的锁。
wait()与notify()的使用
两个线程去操作counter,打印出10101010…
package com.learn.thread.wait;
class Counter {
private int counter;
public synchronized void increase() {
if (counter != 0) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
counter++;
System.out.print(counter);
// 随机唤醒了一个
notify();
}
public synchronized void decrease() {
if (counter == 0) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
counter--;
System.out.print(counter);
notify();
}
}
class IncreaseThread extends Thread {
private Counter counter;
public IncreaseThread(Counter counter) {
this.counter = counter;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep((long) (Math.random() * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
counter.increase();
}
}
}
class DecreaseThread implements Runnable {
private Counter counter;
public DecreaseThread(Counter counter) {
this.counter = counter;
}
@Override
public void run() {
for (int i = 0; i < 10; ++i) {
try {
Thread.sleep((long) (Math.random() * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
counter.decrease();
}
}
}
public class MyCounter {
public static void main(String[] args) {
Counter counter = new Counter();
Thread increase = new IncreaseThread(counter);
DecreaseThread decrease = new DecreaseThread(counter);
increase.start();
new Thread(decrease).start();
}
}
再增加两个线程,一个增加一个减少
public class MyCounter {
public static void main(String[] args) {
Counter counter = new Counter();
Thread increase = new IncreaseThread(counter);
DecreaseThread decrease = new DecreaseThread(counter);
increase.start();
new Thread(decrease).start();
new IncreaseThread(counter).start();
new Thread(new DecreaseThread(counter)).start();
}
}
显然,程序输出有问题。为什么会出现这种问题呢?
- 分析:
在初始时刻,counter=0,增加的线程进入到increase方法,程序直接counter++,counter值变为1,控制台打印出1,调用notify()方法(唤醒某个MyCounter对象上的wait的线程)。假设此时没有wait的线程,则该线程退出。
两个减少线程其中之一执行了decrease方法,判断counter是否等于0,此时counter=1,执行counter–,counter=0,控制台打印出0,调用notify()方法。
接下来又有一个减少线程来执行decrease,判断counter是否等于0,此时counter=0,线程wait(),释放对象的锁,其他三个线程(一个减少,两个增加)开始竞争对象锁,假设减少线程获取到这把锁,线程来执行decrease,判断counter是否等于0,此时counter=0,线程wait(),释放对象的锁。现在有两个减少线程T1,T2在等待。一个增加线程获取到这把锁,判断counter是否等于0,此时counter=0,程序执行counter++,counter值变为1,控制台打印出1,调用notify()方法,唤醒等待集合中的一个线程,一个减少线程T1被唤醒,线程继续向下执行,counter–,控制台打印出0,调用notify()方法,恰好此时等待线程T2被唤醒,线程继续向下执行,counter–,控制台打印出-1,调用notify()方法。程序输出已经错误。 - 修改:将wait方法用循环包起来,wait的判断条件应该用循环
使用循环,当程序被唤醒后,继续进行判断,是否满足条件。
public synchronized void decrease() {
while (counter == 0) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
counter--;
System.out.print(counter);
notify();
}
}
package com.learn.thread.wait;
class Counter {
private int counter;
// wait()一定是在synchronized代码块或synchronized方法中调用
public synchronized void increase() {
while (counter != 0) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
counter++;
System.out.print(counter);
// 随机唤醒了一个
notify();
}
// wait()一般放在while循环中,线程下次醒过来后会继续进行循环,判断条件是否满足,
// 满足就重新等待。防止程序直接向下执行。
public synchronized void decrease() {
while (counter == 0) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
counter--;
System.out.print(counter);
notify();
}
}
class IncreaseThread extends Thread {
private Counter counter;
public IncreaseThread(Counter counter) {
this.counter = counter;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep((long) (Math.random() * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
counter.increase();
}
}
}
class DecreaseThread implements Runnable {
private Counter counter;
public DecreaseThread(Counter counter) {
this.counter = counter;
}
@Override
public void run() {
for (int i = 0; i < 10; ++i) {
try {
Thread.sleep((long) (Math.random() * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
counter.decrease();
}
}
}
public class MyCounter {
public static void main(String[] args) {
Counter counter = new Counter();
Thread increase = new IncreaseThread(counter);
DecreaseThread decrease = new DecreaseThread(counter);
increase.start();
new Thread(decrease).start();
new IncreaseThread(counter).start();
new Thread(new DecreaseThread(counter)).start();
}
}