生产消费模型
通过不同的线程操作同一共享资源,这种现象就是生产消费模型。
1.单线程的生产消费模型示例:
1.1 创建资源共享类
package scxf;
/**
* 资源共享类
*/
public class Resource {
//保存共享资源数组
private Object obj[] = new Object[1];
//记录生产和消费次数
private int num = 1;
//创建同步对象
private static final Object lock = new Object();
//创建生产者
public void add() throws InterruptedException {
synchronized (lock) {
if (obj[0] != null) {
lock.wait();
}
obj[0] = "水" + num;
System.out.println(Thread.currentThread().getName() + "正在注水的是" + obj[0]);
num++;
lock.notify();
}
}
//创建消费者
public void delete() throws InterruptedException {
synchronized (lock) {
if (obj[0] == null) {
lock.wait();
}
System.out.println(Thread.currentThread().getName() + "正在抽水的是:" + obj[0]);
obj[0] = null;
lock.notify();
}
}
}
1.2 创建生产者类,实现Runnable接口
package scxf;
/**
* 生产者类
* 持续注水会产生多次注水或者多次抽水
*/
public class Producre implements Runnable {
//定义资源共享对象
private Resource resource;
//通过无参构造方法加载资源共享对象
public Producre(Resource resource) {
this.resource = resource;
}
@Override
public void run() {
//持续注水
while (true) {
try {
resource.add();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
1.3 创建消费者类,实现Runnable接口
package scxf5;
/**
* 消费者类
* 持续注水会产生多次注水或者多次抽水
*/
public class Consumer implements Runnable {
//定义资源共享对象
private Resource resource;
//通过无参构造方法传入资源共享对象
public Consumer(Resource resource) {
this.resource = resource;
}
@Override
public void run() {
//持续抽水
while (true) {
try {
resource.delete();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
1.4 创建测试类,创建生产和消费线程进行测试
package scxf;
/**
* 结果会产生多次注水或者多次抽水
*/
public class TestMain {
public static void main(String[] args) {
//创建资源共享对象
Resource rs = new Resource();
//创建生产者对象
Producre sc = new Producre(rs);
//创建消费者对象
Consumer xf = new Consumer(rs);
//创建生产者线程
Thread scthread = new Thread(sc);
//创建消费者线程
Thread xfthread = new Thread(xf);
//为生产线程命名
scthread.setName("生产者一:");
//为消费线程命名
xfthread.setName("消费者一:");
//启动生产者线程
scthread.start();
//启动消费者线程
xfthread.start();
}
}
1.5 测试结果:

2.多线程生产消费模型示例:
2.1 创建资源共享类
package scxf6;
/**
* 资源共享类
*/
public class Resource {
//保存共享资源数组
private Object obj[] = new Object[1];
//记录生产和消费次数
private int num = 1;
//创建同步对象
private static final Object lock = new Object();
//创建生产者
public void add() throws InterruptedException {
synchronized (lock) {
while (obj[0] != null) {
lock.wait();
}
obj[0] = "水" + num;
System.out.println(Thread.currentThread().getName() + "正在注水的是" + obj[0]);
num++;
lock.notifyAll();
}
}
//创建消费者
public void delete() throws InterruptedException {
synchronized (lock) {
while (obj[0] == null) {
lock.wait();
}
System.out.println(Thread.currentThread().getName() + "正在抽水的是" + obj[0]);
obj[0] = null;
lock.notifyAll();
}
}
}
2.2 创建资源生产者类,实现runnable接口
package scxf6;
/**
* 生产者类
* 持续注水会产生多次注水或者多次抽水
*/
public class Producre implements Runnable {
//定义资源共享对象
private Resource resource;
//通过无参构造方法加载资源共享对象
public Producre(Resource resource) {
this.resource = resource;
}
@Override
public void run() {
//持续注水
while (true) {
try {
resource.add();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
2.3 创建资源消费者类,实现runnable接口
package scxf6;
/**
* 消费者类
* 持续注水会产生多次注水或者多次抽水
*/
public class Consumer implements Runnable {
//定义资源共享对象
private Resource resource;
//通过无参构造方法传入资源共享对象
public Consumer(Resource resource) {
this.resource = resource;
}
@Override
public void run() {
//持续抽水
while (true) {
try {
resource.delete();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
2.4 创建测试类,创建多条线程进行测试
package scxf6;
/**
* 结果会产生多次注水或者多次抽水
*/
public class TestMain {
public static void main(String[] args) {
//创建资源共享对象
Resource rs = new Resource();
//创建生产者对象
Producre sc = new Producre(rs);
//创建消费者对象
Consumer xf = new Consumer(rs);
//创建生产者线程
Thread scthread1 = new Thread(sc);
Thread scthread2 = new Thread(sc);
//创建消费者线程
Thread xfthread1 = new Thread(xf);
Thread xfthread2 = new Thread(xf);
//为各条线程命名
scthread1.setName("生产者一:");
scthread2.setName("生产者二");
xfthread1.setName("消费者一:");
xfthread2.setName("消费者二:");
//启动生产者线程
scthread1.start();
scthread2.start();
//启动消费者线程
xfthread1.start();
xfthread2.start();
}
}
2.5 测试结果:

总结:
上述单线程生产消费模型,如果在测试类直接添加多个线程,会出现多次抽水或者多次注水现象。原因:唤醒的线程有可能是自己的同伴。
解决方法:将判断是否有水的 if 语句改为 while 语句。
结果新的问题又出现了,产生了死锁,原因:所有的线程都处于等待状态,没有可执行的线程。
解决方法:将notify()换成notifyAll()就可以了。每次都唤醒所有线程,即使唤醒的是自己的同伴,也会重新判断,同伴就继续等待,对方执行操作。
3.使用Condition接口代替等待和唤醒机制
为什么使用Condition接口代替上述示例呢?
多线程生产消费过程为了保证所有线程不被wait(),唤醒的时候使用notifyAll(),虽然可以唤醒全部,保证有执行操作的线程,每次可能都会唤醒自己的同伴,这样自己的同伴要处于等待状态,这样的执行效率太低。
JDK5以后一个同步锁的等待和唤醒就可以辨别当前线程是生成还是消费。
void await()造成当前线程在接到信号或被中断之前一直处于等待状态。
void signal()唤醒一个等待线程。
void signalAll() 唤醒所有等待线程。
注意:如果使用Condition接口,必须使用Lock接口,只有同步使用Lock接口,等待和唤醒才能使用Condition接口
3.1 创建资源共享类,创建Lock接口作为同步锁
package scxf7;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 资源共享类
*/
public class Resource {
//保存共享资源数组
private Object obj[] = new Object[1];
//记录生产和消费次数
private int num = 1;
//创建Lock接口,作为同步锁
private Lock lock = new ReentrantLock();
//负责监视注水
private Condition scCondition = lock.newCondition();
//负责监视抽水
private Condition xfCondition = lock.newCondition();
//创建生产者
public void add() {
try {
lock.lock();
while (obj[0] != null) {
scCondition.await();
}
obj[0] = "水" + num;
System.out.println(Thread.currentThread().getName() + "正在注水的是" + obj[0]);
num++;
xfCondition.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
//创建消费者
public void delete() {
try {
lock.lock();
while (obj[0] == null) {
xfCondition.await();
}
//obj[0] = "水" + num;
System.out.println(Thread.currentThread().getName() + "正在抽水的是" + obj[0]);
obj[0] = null;
scCondition.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
3.2 创建资源共享生产者类,实现Runnable接口
package scxf7;
/**
* 生产者类
* 持续注水会产生多次注水或者多次抽水
*/
public class Producre implements Runnable {
//定义资源共享对象
private Resource resource;
//通过无参构造方法加载资源共享对象
public Producre(Resource resource) {
this.resource = resource;
}
@Override
public void run() {
//持续注水
while (true) {
resource.add();
}
}
}
3.3 创建资源共享消费者类,实现Runnable接口
package scxf7;
/**
* 消费者类
* 持续注水会产生多次注水或者多次抽水
*/
public class Consumer implements Runnable {
//定义资源共享对象
private Resource resource;
//通过无参构造方法传入资源共享对象
public Consumer(Resource resource) {
this.resource = resource;
}
@Override
public void run() {
//持续抽水
while (true) {
resource.delete();
}
}
}
3.4 创建测试类,创建多条线程进行测试
package scxf7;
/**
* 结果会产生多次注水或者多次抽水
*/
public class TestMain {
public static void main(String[] args) {
//创建资源共享对象
Resource rs = new Resource();
//创建生产者对象
Producre sc = new Producre(rs);
//创建消费者对象
Consumer xf = new Consumer(rs);
//创建生产者线程
Thread scthread1 = new Thread(sc);
Thread scthread2 = new Thread(sc);
//创建消费者线程
Thread xfthread1 = new Thread(xf);
Thread xfthread2 = new Thread(xf);
//为各条线程命名
scthread1.setName("生产者一:");
scthread2.setName("生产者二");
xfthread1.setName("消费者一:");
xfthread2.setName("消费者二:");
//启动生产者线程
scthread1.start();
scthread2.start();
//启动消费者线程
xfthread1.start();
xfthread2.start();
}
}
测试结果:

总结:
如果我们不使用Lock就要使用同步代码(synchronized)来实现线程同步。
使用同步代码就要使用 Object 提供的 wait() 、notify() 、notifyAll() 方法。缺点就是Object提供的notifyAll()虽然可以唤醒全部线程,也可能会唤醒同伴,使其进入等待状态,降低了执行效率。
使用 Condition 接口提供的方法 await() 、signal() 、signalAll() 他们只会唤醒对方线程,不会唤醒同伴线程,大大提高执行效率。
Condition 接口在使用时需要 Lock 接口对象 newCondition() 创建 Condition 接口对象,所以使用Lock接口同步方式代替同步代码方式。
sleep 和 wait 的区别?
sleep继承Thread类,wait继承Object类。
sleep依赖系统时钟和CPU调度机制,wait调用notify() 、notifyAll() 方法唤醒线程。
sleep不释放已获取的锁资源,wait释放获取的锁资源。
notify() 与 notifyAll() 的区别?
notify() 随机唤醒一个线程,notifyAll() 唤醒所有线程。
notify() 可能会导致死锁,notifyAll() 不会导致死锁。
本文详细介绍了Java中的生产消费模型,从单线程到多线程,再到使用Condition接口实现更高效的线程同步。通过实例展示了如何避免死锁,以及wait、notify和notifyAll、sleep的区别。
1万+

被折叠的 条评论
为什么被折叠?



