Java—多线程实现生产消费者

程序浅谈 后端 2024-12-25

Java—多线程实现生产消费者

多线程实现生产消费者_version1

说明:该版本总共包含4个类

  1. Producer——生产者,该类实现Runnable接口
  2. Consumer——消费者,该类实现Runnable接口
  3. Resource——公共资源,使用数组模拟公共资源
  4. TestMain——测试类java
代码解读
复制代码
package thread.pcmv1; import java.util.ArrayList; import java.util.List; public class Resource { // 模拟的公共资源 private int num = 1; private final static Object lc = new Object(); private Object objs[] = new Object[1]; public void add() throws InterruptedException { synchronized (lc) { // 模拟当添加的数量大于50个的时候,线程就会进入等待时刻 if (objs[0] != null) { lc.wait(); } objs[0] = "资源" + num; System.out.println(Thread.currentThread().getName() + "-生产编号:" + objs[0]); num++; lc.notify(); } } public void delete() throws InterruptedException { synchronized (lc) { // 模拟当添加的数量大于50个的时候,线程就会进入等待时刻 if (objs[0] == null) { lc.wait(); } objs[0] = null; System.out.println(Thread.currentThread().getName() + "-消费编号:" + objs[0]); lc.notify(); } } }java
代码解读
复制代码
package thread.pcmv1; public class Producer implements Runnable { public Resource r; public Producer(Resource r) { this.r = r; } @Override public void run() { for (int i = 0; i < 50; i++) { try { // 为了演示出生产者和消费者交错的效果 Thread.sleep(10); r.add(); } catch (InterruptedException e) { e.printStackTrace(); } } } }java
代码解读
复制代码
package thread.pcmv1; public class Consumer implements Runnable { public Resource r; public Consumer(Resource r) { this.r = r; } @Override public void run() { for (int i = 0; i < 50; i++) { try { // 为了演示出生产者和消费者交错的效果 Thread.sleep(100); r.delete(); } catch (InterruptedException e) { e.printStackTrace(); } } } }ini
代码解读
复制代码
package thread.pcmv1; /** * 当然这里可以自行添加多个生产者或者消费者 */ public class TestMain { public static void main(String[] args) { Resource r = new Resource(); // 生产者 Producer p = new Producer(r); Thread p1 = new Thread(p); p1.setName("生产者1"); p1.start(); // 消费者 Consumer c = new Consumer(r); Thread c1 = new Thread(c); c1.setName("消费者1"); c1.start(); } }

Java—多线程实现生产消费者

代码说明Resource中使用synchronized代码块儿进行加锁的操作,线程需要等待的时候使用wait()方法进行等待操作,唤醒使用notify()方法进行唤醒,需要注意的是notify()唤醒是对其他等待线程进行随机唤醒操作,所以在TestMain中只设置了一个生产类和一个消费类,这都是有意而为之的。
存在问题: 如果我们的共有资源只有一份的时候并且存在多个消费者和生产者的时候,以上的代码就会出现问题。为什么呢?因为notify()是随机唤醒一个等待线程,可能消费者线程还会唤醒一个消费者线程,同理可能生产线程还会唤醒一个生产线程,所以在只有一份资源的情况下,就可能多次消费或者多次生产的情况,那么怎么解决这个问题呢?看下文version2。

Java—多线程实现生产消费者

多线程实现生产消费者_version2

说明:该版本代码就是用来解决如果资源只有一份的时候出现多次消费和多次生产的问题。
解决方案 :将Resource中的if更改为while。java

代码解读
复制代码
package thread.pcmv1; import java.util.ArrayList; import java.util.List; public class Resource { // 模拟的公共资源 private int num = 1; private final static Object lc = new Object(); private Object objs[] = new Object[1]; public void add() throws InterruptedException { synchronized (lc) { // 模拟当添加的数量大于50个的时候,线程就会进入等待时刻 while (objs[0] != null) { lc.wait(); } objs[0] = "资源" + num; System.out.println(Thread.currentThread().getName() + "-生产编号:" + objs[0]); num++; lc.notify(); } } public void delete() throws InterruptedException { synchronized (lc) { // 模拟当添加的数量大于50个的时候,线程就会进入等待时刻 while (objs[0] == null) { lc.wait(); } objs[0] = null; System.out.println(Thread.currentThread().getName() + "-消费编号:" + objs[0]); lc.notify(); } } }

以上代码虽然会解决多次生产和多次消费的问题,但是还是会有问题。如果共享资源只有一份,并且,并且存在多个消费者和生产者,那么使用notify()唤醒线程的时候,极有可能唤醒同伴线程(消费线程唤醒新的消费线程,等待线程唤醒新的等待线程),又因为我们更改了判断是否等待的关键字,所以会造成一种所有线程都在等待的情况——死锁。(这一切的一切都要归功于notify()线程是随机唤醒一个等待线程的机制,找个所以我们不要这种机制)。
解决方案:使用notifyAll()替换notify()这个方法。notifyAll()会唤醒所有等待的线程,即使唤醒的是同伴线程,那么同伴线程依然会进行等待操作(因为while循环)。非同伴线程则会正常执行。java

代码解读
复制代码
package thread.pcmv1; import java.util.ArrayList; import java.util.List; public class Resource { // 模拟的公共资源 private int num = 1; private final static Object lc = new Object(); private Object objs[] = new Object[1]; public void add() throws InterruptedException { synchronized (lc) { // 模拟当添加的数量大于50个的时候,线程就会进入等待时刻 while (objs[0] != null) { lc.wait(); } objs[0] = "资源" + num; System.out.println(Thread.currentThread().getName() + "-生产编号:" + objs[0]); num++; lc.notifyAll(); } } public void delete() throws InterruptedException { synchronized (lc) { // 模拟当添加的数量大于50个的时候,线程就会进入等待时刻 while (objs[0] == null) { lc.wait(); } objs[0] = null; System.out.println(Thread.currentThread().getName() + "-消费编号:" + objs[0]); lc.notifyAll(); } } }

存在问题: 但是用这种方法解决问题,线程唤醒的代价太大了,每回唤醒线程都需要唤醒所有等待的线程,显然这不是最好的解决方案。

多线程实现生产消费者_version3

说明:使用lock()、unlock()、await()、signal()Condition解决notifyAll()唤醒所有线程的问题,Condition可以指定唤醒线程,大大提升代码的效率,代码如下(因为只更改Resource中的代码,所以其余的代码参考version1,记得在测试类中添加多个生产者和消费者即可):

代码解读
复制代码
package thread.pcmv2; import java.util.ArrayList; import java.util.List; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class Resource { private List<Integer> list = new ArrayList<>(); private int num = 1; Lock lock = new ReentrantLock(); Condition pCon = lock.newCondition(); Condition cCon = lock.newCondition(); public void add() throws InterruptedException { // 添加try-catch的意义就在与lock.unlock()这一步操作 try{ lock.lock(); while (list.size() != 0) { // 开始生产 pCon.await(); } list.add(num); System.out.println(Thread.currentThread().getName() + "-生产编号:" + num); num++; // 开始消费 cCon.signal(); }catch (Exception e) { e.printStackTrace(); }finally { lock.unlock(); } } public void delete() throws InterruptedException { // 添加try-catch的意义就在与lock.unlock()这一步操作 try{ lock.lock(); while (list.size() == 0) { cCon.await(); } // 每回都消费列表中的第一个数据 System.out.println(Thread.currentThread().getName() + "-消费编号:" + list.remove(0)); pCon.signal(); }catch (Exception e) { e.printStackTrace(); }finally { lock.unlock(); } } }

Java—多线程实现生产消费者

转载来源:https://juejin.cn/post/7154172590150385701

Apipost 私有化火热进行中

评论