是一种在多线程环境下出现的问题,本文将一步步进行解释和解决.
下面是使用synchronized关键字进行加锁,实现线程不安全问题的生产者与消费者案例.
/**
* @author 嘿嘿嘿1212
* @version 1.0
* @date 2020/1/31 19:48
*/
//主方法
public class TestProductorAndConsumer {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Producer pro = new Producer(clerk);
Consumer cus = new Consumer(clerk);
new Thread(pro,"生产者A").start();
new Thread(cus,"消费者B").start();
}
}
//仓库类
class Clerk {
private int product = 0;
//进货
public synchronized void get() {
if (product >= 10) {
System.out.println("仓库已满");
} else {
System.out.println(Thread.currentThread().getName() + ":" + ++product);
}
}
//卖货
public synchronized void sale() {
if (product <= 0) {
System.out.println("缺货");
} else {
System.out.println(Thread.currentThread().getName() + ":" + --product);
}
}
}
//生产者类producer
class Producer implements Runnable {
private Clerk clerk;
public Producer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
clerk.get();
}
}
}
//消费者类
class Consumer implements Runnable {
private Clerk clerk;
public Consumer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
clerk.sale();
}
}
}
然后就会出现下面图片类似的情况,在没有使用等待唤醒机制的情况下,会存在俩个线程无法同步进行的问题,造成生成者不断生成,而消费者一直消费,两者无法统一进行,这是不合理的,也会造成性能的损坏.
在采用等待唤醒机制,下面是使用Object的sleep()和wait()方法进行实现.
/**
* @author 嘿嘿嘿1212
* @version 1.0
* @date 2020/1/31 19:48
*/
//主方法
public class TestProductorAndConsumer {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Producer pro = new Producer(clerk);
Consumer cus = new Consumer(clerk);
new Thread(pro, "生产者A").start();
new Thread(cus, "消费者B").start();
}
}
//仓库类
class Clerk {
private int product = 0;
//进货
public synchronized void get() {
if (product >= 10) {
System.out.println("仓库已满");
try {
//在仓库已满时才进行等待
this.wait();
} catch (InterruptedException e) {
}
} else {
System.out.println(Thread.currentThread().getName() + ":" + ++product);
//当仓库内存在货物时,通知线程可以进行卖货了
this.notifyAll();
}
}
//卖货
public synchronized void sale() {
if (product <= 0) {
System.out.println("缺货");
try {
//在仓库缺货时才进行等待
this.wait();
} catch (InterruptedException e) {
}
} else {
System.out.println(Thread.currentThread().getName() + ":" + --product);
//通知线程可以进行进货了,存在空位
this.notifyAll();
}
}
}
//生产者类
class Producer implements Runnable {
private Clerk clerk;
public Producer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
clerk.get();
}
}
}
//消费者类
class Consumer implements Runnable {
private Clerk clerk;
public Consumer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
clerk.sale();
}
}
}
这种方式在俩个线程并生产者与消费者各为一个线程时是不存在虚假唤醒问题的.但是还是存在问题
在生产者的代码中加上0.2秒的网络延时
@Override
public void run() {
for (int i = 0; i < 20; i++) {
//存在延时
try {
Thread.sleep(200);
} catch (InterruptedException e) {
}
clerk.get();
}
}
就会可能出现如下图这种情况(将货物空位调整为1,方便查看
),在咋一看没有什么问题?(注意是有概率出现该错误
)
然而问题在于该程序并没有结束,还是持续运行!
为什么会出现这种情况呢?
因为在消费者或者是生产者二者所剩余的次数不一致时出现的问题,如上图消费者
已经使用完了他的消费次数,而生成者
并未使用完,而当生产者生成到仓库满了时就会进行等待唤醒,然而消费者线程已经使用完了(也就是该线程已经死亡
)没有线程会将生成者线程唤醒
,所以就会出现无限等待
的情况.
如何解决这个无限等待问题呢?
解决的办法非常简单,那就是在方法的最后都进行唤醒所有线程的方法.
具体代码如下:
//仓库类
class Clerk {
private int product = 0;
//进货
public synchronized void get() {
if (product >= 1) {
System.out.println("仓库已满");
try {
//在仓库已满时才进行等待
this.wait();
} catch (InterruptedException e) {
}
}
System.out.println(Thread.currentThread().getName() + ":" + ++product);
//当仓库内存在货物时,通知线程可以进行卖货了
this.notifyAll();
}
//卖货
public synchronized void sale() {
if (product <= 0) {
System.out.println("缺货");
try {
//在仓库缺货时才进行等待
this.wait();
} catch (InterruptedException e) {
}
}
System.out.println(Thread.currentThread().getName() + ":" + --product);
//通知线程可以进行进货了,存在空位
this.notifyAll();
}
}
由于一旦出现库满或者缺货都会进行等待唤醒,所以去除else就可以了,不会造成缺货还进行卖货操作
(俩个线程情况下)
然而在多个生成者和消费者的情况下会出现虚假唤醒问题.(就是会出现缺货还进行卖货操作等)
将主线程的方法修改为如下:
public class TestProductorAndConsumer {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Producer pro = new Producer(clerk);
Consumer cus = new Consumer(clerk);
new Thread(pro, "生产者A").start();
new Thread(cus, "消费者B").start();
new Thread(pro, "生产者C").start();
new Thread(cus, "消费者D").start();
}
}
为什么会出现这种情况呢?
因为在缺货情况下,俩条线程同时进行一条进行等待唤醒,而另一个线程则还在进行卖货操作.
如:当线程B消费时货物还有1个
造成D线程被异常的唤醒,而再进行消费时D线程会以同样的方式唤醒B线程,当俩个线程同时进行等待唤醒时,才不会再继续下去
那么如何解决虚假唤醒问题呢?
其实在jdk的API文档中就说明了该问题,官方推荐使用whlie对wait()进行包裹
这样就不会出现虚假唤醒问题了
下面是测试的代码:
/**
* @author 嘿嘿嘿1212
* @version 1.0
* @date 2020/1/31 19:48
*/
//主方法
public class TestProductorAndConsumer {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Producer pro = new Producer(clerk);
Consumer cus = new Consumer(clerk);
new Thread(pro, "生产者A").start();
new Thread(cus, "消费者B").start();
new Thread(pro, "生产者C").start();
new Thread(cus, "消费者D").start();
}
}
//仓库类
class Clerk {
private int product = 0;
//进货
public synchronized void get() {
while (product >= 1) {
System.out.println("仓库已满");
try {
//在仓库已满时才进行等待
this.wait();
} catch (InterruptedException e) {
}
}
System.out.println(Thread.currentThread().getName() + ":" + ++product);
//当仓库内存在货物时,通知线程可以进行卖货了
this.notifyAll();
}
//卖货
public synchronized void sale() {
while (product <= 0) {
System.out.println("缺货");
try {
//在仓库缺货时才进行等待
this.wait();
} catch (InterruptedException e) {
}
}
System.out.println(Thread.currentThread().getName() + ":" + --product);
//通知线程可以进行进货了,存在空位
this.notifyAll();
}
}
//生产者类
class Producer implements Runnable {
private Clerk clerk;
public Producer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
//存在延时
try {
Thread.sleep(200);
} catch (InterruptedException e) {
}
clerk.get();
}
}
}
//消费者类
class Consumer implements Runnable {
private Clerk clerk;
public Consumer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
clerk.sale();
}
}
}