直接看一段代码,经典的生产者消费者模式,用多线程来实现数字的加减:
package test;
/**
* Created by ZhuHao on 2018/10/14
*/
class Resource{
private int num = 0;
private boolean flag = true;
public synchronized void add() throws Exception{
if(this.flag == false){
this.wait();
}
Thread.sleep(10);
this.num++;
System.out.println("[加法操作 - "+ Thread.currentThread().getName() + "] num = " + this.num);
this.flag = false;
this.notifyAll();
}
public synchronized void sub() throws Exception{
if(this.flag == true){
this.wait();
}
Thread.sleep(20);
this.num--;
System.out.println("[减法操作 - "+ Thread.currentThread().getName() + "] num = " + this.num);
this.flag = true;
this.notifyAll();
}
}
class AddThread implements Runnable{
private Resource resource;
public AddThread(Resource resource){
this.resource = resource;
}
@Override
public void run() {
for(int i = 0;i<10;i++){
try {
this.resource.add();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
class SubThread implements Runnable{
private Resource resource;
public SubThread(Resource resource){
this.resource = resource;
}
@Override
public void run() {
for(int i = 0;i<10;i++){
try {
this.resource.sub();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
Resource res = new Resource();
AddThread at = new AddThread(res);
SubThread st = new SubThread(res);
new Thread(at,"加法线程 - A").start();
new Thread(at,"加法线程 - B").start();
new Thread(st,"减法线程 - X").start();
new Thread(st,"减法线程 - Y").start();
}
}
我们期望的结果是num只出现1、0、-1三种结果,然而真实的运行结果却不是这样:
[加法操作 - 加法线程 - A] num = 1
[减法操作 - 减法线程 - Y] num = 0
[加法操作 - 加法线程 - B] num = 1
[减法操作 - 减法线程 - X] num = 0
[减法操作 - 减法线程 - Y] num = -1
[加法操作 - 加法线程 - A] num = 0
[减法操作 - 减法线程 - Y] num = -1
[减法操作 - 减法线程 - X] num = -2
[加法操作 - 加法线程 - B] num = -1
[减法操作 - 减法线程 - X] num = -2
[减法操作 - 减法线程 - Y] num = -3
[加法操作 - 加法线程 - A] num = -2
[减法操作 - 减法线程 - Y] num = -3
[加法操作 - 加法线程 - B] num = -2
[减法操作 - 减法线程 - X] num = -3
[减法操作 - 减法线程 - Y] num = -4
[加法操作 - 加法线程 - A] num = -3
[减法操作 - 减法线程 - Y] num = -4
[减法操作 - 减法线程 - X] num = -5
[加法操作 - 加法线程 - B] num = -4
[减法操作 - 减法线程 - X] num = -5
[减法操作 - 减法线程 - Y] num = -6
[加法操作 - 加法线程 - A] num = -5
[减法操作 - 减法线程 - Y] num = -6
[减法操作 - 减法线程 - X] num = -7
[加法操作 - 加法线程 - B] num = -6
[减法操作 - 减法线程 - X] num = -7
[减法操作 - 减法线程 - Y] num = -8
[加法操作 - 加法线程 - A] num = -7
[减法操作 - 减法线程 - X] num = -8
[加法操作 - 加法线程 - B] num = -7
[减法操作 - 减法线程 - X] num = -8
[加法操作 - 加法线程 - A] num = -7
[加法操作 - 加法线程 - B] num = -6
[加法操作 - 加法线程 - A] num = -5
[加法操作 - 加法线程 - B] num = -4
[加法操作 - 加法线程 - A] num = -3
[加法操作 - 加法线程 - B] num = -2
[加法操作 - 加法线程 - A] num = -1
[加法操作 - 加法线程 - B] num = 0
百思不得其解,试了半天发现是if判断的问题,多线程的情况下似乎条件没被满足也有可能线程被唤醒,将if换成while或者用if...else...将代码块包起来后就能正常的获得结果;
package test;
/**
* Created by ZhuHao on 2018/10/14
*/
class Resource{
private int num = 0;
private boolean flag = true;
public synchronized void add() throws Exception{
while(this.flag == false){
this.wait();
}
Thread.sleep(10);
this.num++;
System.out.println("[加法操作 - "+ Thread.currentThread().getName() + "] num = " + this.num);
this.flag = false;
this.notifyAll();
}
public synchronized void sub() throws Exception{
while(this.flag == true){
this.wait();
}
Thread.sleep(20);
this.num--;
System.out.println("[减法操作 - "+ Thread.currentThread().getName() + "] num = " + this.num);
this.flag = true;
this.notifyAll();
}
}
class AddThread implements Runnable{
private Resource resource;
public AddThread(Resource resource){
this.resource = resource;
}
@Override
public void run() {
for(int i = 0;i<10;i++){
try {
this.resource.add();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
class SubThread implements Runnable{
private Resource resource;
public SubThread(Resource resource){
this.resource = resource;
}
@Override
public void run() {
for(int i = 0;i<10;i++){
try {
this.resource.sub();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
Resource res = new Resource();
AddThread at = new AddThread(res);
SubThread st = new SubThread(res);
new Thread(at,"加法线程 - A").start();
new Thread(at,"加法线程 - B").start();
new Thread(st,"减法线程 - X").start();
new Thread(st,"减法线程 - Y").start();
}
}
运行后结果和期望的一样:
[加法操作 - 加法线程 - A] num = 1
[减法操作 - 减法线程 - X] num = 0
[加法操作 - 加法线程 - B] num = 1
[减法操作 - 减法线程 - Y] num = 0
[加法操作 - 加法线程 - A] num = 1
[减法操作 - 减法线程 - X] num = 0
[加法操作 - 加法线程 - B] num = 1
[减法操作 - 减法线程 - Y] num = 0
[加法操作 - 加法线程 - A] num = 1
[减法操作 - 减法线程 - X] num = 0
[加法操作 - 加法线程 - B] num = 1
[减法操作 - 减法线程 - Y] num = 0
[加法操作 - 加法线程 - A] num = 1
[减法操作 - 减法线程 - X] num = 0
[加法操作 - 加法线程 - B] num = 1
[减法操作 - 减法线程 - Y] num = 0
[加法操作 - 加法线程 - A] num = 1
[减法操作 - 减法线程 - X] num = 0
[加法操作 - 加法线程 - B] num = 1
[减法操作 - 减法线程 - Y] num = 0
[加法操作 - 加法线程 - A] num = 1
[减法操作 - 减法线程 - X] num = 0
[加法操作 - 加法线程 - B] num = 1
[减法操作 - 减法线程 - Y] num = 0
[加法操作 - 加法线程 - A] num = 1
[减法操作 - 减法线程 - X] num = 0
[加法操作 - 加法线程 - B] num = 1
[减法操作 - 减法线程 - Y] num = 0
[加法操作 - 加法线程 - A] num = 1
[减法操作 - 减法线程 - X] num = 0
[加法操作 - 加法线程 - B] num = 1
[减法操作 - 减法线程 - Y] num = 0
[加法操作 - 加法线程 - A] num = 1
[减法操作 - 减法线程 - X] num = 0
[加法操作 - 加法线程 - B] num = 1
[减法操作 - 减法线程 - Y] num = 0
[加法操作 - 加法线程 - A] num = 1
[减法操作 - 减法线程 - X] num = 0
[加法操作 - 加法线程 - B] num = 1
[减法操作 - 减法线程 - Y] num = 0
后来考证了一下,有贴云:
永远在循环(loop)里调用 wait 和 notify,不是在 If 语句
现在你知道wait应该永远在被synchronized的背景下和那个被多线程共享的对象上调用,下一个一定要记住的问题就是,你应该永远在while循环,而不是if语句中调用wait。因为线程是在某些条件下等待的——在我们的例子里,即“如果缓冲区队列是满的话,那么生产者线程应该等待”,你可能直觉就会写一个if语句。但if语句存在一些微妙的小问题,导致即使条件没被满足,你的线程你也有可能被错误地唤醒。所以如果你不在线程被唤醒后再次使用while循环检查唤醒条件是否被满足,你的程序就有可能会出错——例如在缓冲区为满的时候生产者继续生成数据,或者缓冲区为空的时候消费者开始小号数据。所以记住,永远在while循环而不是if语句中使用wait!我会推荐阅读《Effective Java》,这是关于如何正确使用wait和notify的最好的参考资料。
那么我们之前做的while和if...else...语句的操作其实是一样的,就是保护核心逻辑代码不被错误的唤醒执行,碰巧解决了这个问题。