在多线程的情况下,对资源的使用条件做判断时,我们需要使用while而不是if
例子:
现在有一个资源类“蛋糕”,两个操作,“买蛋糕”和“做蛋糕”,这里有两种线程,一百个“蛋糕店线程“,一百个“顾客线程”。当蛋糕数量为0时,“蛋糕店线程“执行“做蛋糕操作”,当蛋糕数量为1时,顾客执行“买蛋糕操作”。
代码:
/**
*
* @ClassName: Cake
* @Description: 蛋糕资源类
* @author: fuling
* @date: 2020年9月9日 下午2:04:58
*/
class Cake{
public int count;//蛋糕的数量
/**
*
* @Title: makeCake
* @Description: 做蛋糕操作(synchronized)
* @return: void
* @throws InterruptedException
*/
public synchronized void makeCake() throws InterruptedException {
//如果当前蛋糕数量大于0,则等待
if(count > 0) {
wait();
}
System.out.println(Thread.currentThread().getName() + ":正在做蛋糕。。。目前蛋糕数量:" + count);
count++;
notifyAll();
}
/**
*
* @Title: buyCake
* @Description: 买蛋糕操作(synchronized)
* @return: void
* @throws InterruptedException
*/
public synchronized void buyCake() throws InterruptedException {
//如果当前蛋糕数量为0,则等待
if(count == 0) {
wait();
}
System.out.println(Thread.currentThread().getName() + ":正在买蛋糕。。。目前蛋糕数量:" + count);
count--;
notifyAll();
}
}
/**
*
* @ClassName: LockDemo
* @Description: 启动类
* @author: fuling
* @date: 2020年9月9日 下午2:03:21
*/
public class LockDemo {
public static void main(String[] args) {
//蛋糕资源类
Cake cake = new Cake();
//100个蛋糕店线程
for(int i = 0; i < 100; i++) {
new Thread(()->{
try {
cake.makeCake();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}, "蛋糕店线程" + (i + 1)).start();
}
//100个顾客线程
for(int i = 0; i < 100; i++) {
new Thread(()->{
try {
cake.buyCake();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}, "顾客线程" + (i + 1)).start();
}
}
}
我们的想法是,“蛋糕店线程”做一个蛋糕,“顾客线程“买一个蛋糕这样交替进行,我们需要实现蛋糕数量按0,1,0,1变化
想实现的结果:
蛋糕店线程81:正在做蛋糕。。。目前蛋糕数量:0
顾客线程60:正在买蛋糕。。。目前蛋糕数量:1
蛋糕店线程67:正在做蛋糕。。。目前蛋糕数量:0
顾客线程58:正在买蛋糕。。。目前蛋糕数量:1
蛋糕店线程68:正在做蛋糕。。。目前蛋糕数量:0
顾客线程61:正在买蛋糕。。。目前蛋糕数量:1
蛋糕店线程71:正在做蛋糕。。。目前蛋糕数量:0
顾客线程64:正在买蛋糕。。。目前蛋糕数量:1
蛋糕店线程72:正在做蛋糕。。。目前蛋糕数量:0
顾客线程66:正在买蛋糕。。。目前蛋糕数量:1
蛋糕店线程70:正在做蛋糕。。。目前蛋糕数量:0
顾客线程72:正在买蛋糕。。。目前蛋糕数量:1
蛋糕店线程73:正在做蛋糕。。。目前蛋糕数量:0
顾客线程79:正在买蛋糕。。。目前蛋糕数量:1
蛋糕店线程74:正在做蛋糕。。。目前蛋糕数量:0
顾客线程87:正在买蛋糕。。。目前蛋糕数量:1
执行结果:
蛋糕店线程1:正在做蛋糕。。。目前蛋糕数量:0
顾客线程1:正在买蛋糕。。。目前蛋糕数量:1
蛋糕店线程100:正在做蛋糕。。。目前蛋糕数量:0
蛋糕店线程99:正在做蛋糕。。。目前蛋糕数量:1
蛋糕店线程98:正在做蛋糕。。。目前蛋糕数量:2
蛋糕店线程97:正在做蛋糕。。。目前蛋糕数量:3
蛋糕店线程94:正在做蛋糕。。。目前蛋糕数量:4
蛋糕店线程96:正在做蛋糕。。。目前蛋糕数量:5
蛋糕店线程95:正在做蛋糕。。。目前蛋糕数量:6
蛋糕店线程93:正在做蛋糕。。。目前蛋糕数量:7
蛋糕店线程92:正在做蛋糕。。。目前蛋糕数量:8
蛋糕店线程91:正在做蛋糕。。。目前蛋糕数量:9
蛋糕店线程88:正在做蛋糕。。。目前蛋糕数量:10
蛋糕店线程90:正在做蛋糕。。。目前蛋糕数量:11
问题分析:
“蛋糕店线程”执行“做蛋糕”操作后notify唤醒的可能是另一个“蛋糕店线程”,使得”蛋糕“数量出现大于1的情况!其根源在于两点:1.notify是随机唤醒线程!2.线程被唤醒之后没有再次检查蛋糕的数量。假设有这样一个场景:”蛋糕店线程1“首先抢夺到锁,但此时蛋糕数量为1,所以”蛋糕店线程1“等待,蛋糕店线程2“,蛋糕店线程3“亦如此,直到“顾客线程”抢夺到锁,执行“买蛋糕操作”后蛋糕数量为0,随机唤醒一个线程,假设唤醒的是”蛋糕店线程1“,则”蛋糕店线程1“执行”做蛋糕操作“后,蛋糕数量为1,然后随机唤醒一个线程,注意,这里接下来有可能唤醒的是另一个”蛋糕店线程“,比如“蛋糕店线程2”,而不是“顾客线程”,又由于if之后没有继续判断当前蛋糕数量,“蛋糕店线程2”被唤醒后直接执行后续”做蛋糕操作“,使得蛋糕数量为2!
解决方法:
使用while代替if,每次线程被唤醒之后都可以再次检查蛋糕数量
代码:
/**
*
* @ClassName: Cake
* @Description: 蛋糕资源类
* @author: fuling
* @date: 2020年9月9日 下午2:04:58
*/
class Cake{
public int count;//蛋糕的数量
/**
*
* @Title: makeCake
* @Description: 做蛋糕操作(synchronized)
* @return: void
* @throws InterruptedException
*/
public synchronized void makeCake() throws InterruptedException {
//如果当前蛋糕数量大于0,则等待
while(count > 0) {
wait();
}
System.out.println(Thread.currentThread().getName() + ":正在做蛋糕。。。目前蛋糕数量:" + count);
count++;
notifyAll();
}
/**
*
* @Title: buyCake
* @Description: 买蛋糕操作(synchronized)
* @return: void
* @throws InterruptedException
*/
public synchronized void buyCake() throws InterruptedException {
//如果当前蛋糕数量为0,则等待
while(count == 0) {
wait();
}
System.out.println(Thread.currentThread().getName() + ":正在买蛋糕。。。目前蛋糕数量:" + count);
count--;
notifyAll();
}
}
执行结果:
蛋糕店线程1:正在做蛋糕。。。目前蛋糕数量:0
顾客线程1:正在买蛋糕。。。目前蛋糕数量:1
蛋糕店线程99:正在做蛋糕。。。目前蛋糕数量:0
顾客线程5:正在买蛋糕。。。目前蛋糕数量:1
蛋糕店线程33:正在做蛋糕。。。目前蛋糕数量:0
顾客线程6:正在买蛋糕。。。目前蛋糕数量:1
蛋糕店线程3:正在做蛋糕。。。目前蛋糕数量:0
顾客线程8:正在买蛋糕。。。目前蛋糕数量:1
蛋糕店线程30:正在做蛋糕。。。目前蛋糕数量:0
顾客线程7:正在买蛋糕。。。目前蛋糕数量:1
蛋糕店线程34:正在做蛋糕。。。目前蛋糕数量:0
顾客线程11:正在买蛋糕。。。目前蛋糕数量:1
蛋糕店线程64:正在做蛋糕。。。目前蛋糕数量:0
顾客线程3:正在买蛋糕。。。目前蛋糕数量:1
蛋糕店线程100:正在做蛋糕。。。目前蛋糕数量:0
顾客线程12:正在买蛋糕。。。目前蛋糕数量:1