关于多线程加锁时, 遇到了启动三个线程却只能第一个线程运行的bug, 正在写这篇随笔的时候, 不知道什么原因
1 package com.neuedu.test; 2 3 import java.util.concurrent.Callable; 4 import java.util.concurrent.FutureTask; 5 import java.util.concurrent.TimeUnit; 6 import java.util.concurrent.locks.Lock; 7 import java.util.concurrent.locks.ReentrantLock; 8 9 /** 10 * @author 陈龙 11 * @date 2020-09-27 16:12 12 */ 13 14 15 //在这里尝试编写生产者消费者模型--以面包作为资源中心 16 public class WrongCode { 17 18 //为保证代码安全设置的安全锁 19 public static Lock lock = new ReentrantLock(); 20 //所有线程所共用的资源 21 private static Bread commonBread = new Bread(); 22 23 public static class Bread { 24 private Integer leftNumber = 100; 25 26 public Integer getLeftNumber() { 27 return leftNumber; 28 } 29 30 public void setLeftNumber(Integer leftNumber) { 31 this.leftNumber = leftNumber; 32 } 33 } 34 35 public static class Producer { 36 private Bread bread = commonBread; 37 38 public void make(Bread bread) { 39 40 try { 41 lock.lock(); 42 TimeUnit.SECONDS.sleep(1); 43 if (bread.getLeftNumber() < 200) { 44 bread.setLeftNumber(bread.getLeftNumber()+1); 45 System.out.println(Thread.currentThread().getName() + "生产了一个面包, 目前剩余" + bread.getLeftNumber() + "个"); 46 } 47 48 } catch (Exception e) { 49 e.printStackTrace(); 50 } finally { 51 lock.unlock(); 52 } 53 54 } 55 } 56 57 public static class Consumer { 58 private Bread bread = commonBread; 59 60 public void eat(Bread bread) { 61 62 try { 63 lock.lock(); 64 TimeUnit.SECONDS.sleep(2); 65 if (bread.getLeftNumber() > 0) { 66 bread.setLeftNumber(bread.getLeftNumber()-1); 67 System.out.println(Thread.currentThread().getName() + "消费了一个面包, 目前剩余" + bread.getLeftNumber() + "个"); 68 69 } 70 } catch (Exception e) { 71 e.printStackTrace(); 72 } finally { 73 lock.unlock(); 74 } 75 76 } 77 } 78 79 80 public static void main(String[] args) { 81 Producer producer = new Producer(); 82 Consumer consumer = new Consumer(); 83 84 Thread myProducer01 = new Thread(new FutureTask<Void>(new Callable<Void>() { 85 @Override 86 public Void call() throws Exception { 87 while (true) { 88 producer.make(commonBread); 89 } 90 } 91 }), "生产者1号"); 92 Thread myProducer02 = new Thread(new FutureTask<Void>(new Callable<Void>() { 93 @Override 94 public Void call() throws Exception { 95 while (true) { 96 producer.make(commonBread); 97 } 98 } 99 }), "生产者2号"); 100 Thread myConsumer01 = new Thread(new FutureTask<Void>(new Callable<Void>() { 101 @Override 102 public Void call() throws Exception { 103 while (true) { 104 consumer.eat(commonBread); 105 } 106 } 107 }), "消费者1号"); 108 109 110 myConsumer01.start(); 111 myProducer01.start(); 112 myProducer02.start(); 113 } 114 115 116 117 118 119 }
原因分析:
应该是多余多个线程之间共用锁的问题没有处理好, 没有搞清楚锁的应该声明的位置以及加锁的位置
排错进行:
阶段一:
首先是意识到, 消费者和生产者理论上可以同时进行工作, 所以想到了不能共用同一个锁,于是想把主类中声明的锁对象删除掉然后在生产者和消费者类中各自声明一个锁对象
正改动的时候, 又突然意识到, 虽然消费者和生产者可以同时进行, 但是, 对于同一个数据不能同时进行加减操作, 意思就是, 就算消费者和生产者同时完成了生产和消费, 但是在改操作数的时候还是只能一个一个进行
最终的改动方案:
将生产者的生产方法中加锁的步骤和消费者的消费方法中加锁的步骤取消掉, 在改动数据的地方(即setLeftNum()方法上,最小加锁单元原则)加上锁, 发现程序就可以按照自己想要的方式运行了
遗留的问题:
可是, 还是不知道原来的代码问题出在什么地方了.
改动后的代码如下:
package com.neuedu.test; import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * @author 陈龙 * @date 2020-09-27 16:12 */ //在这里尝试编写生产者消费者模型--以面包作为资源中心 public class WrongCode { //为保证代码安全设置的安全锁 public static Lock lock = new ReentrantLock(); //----->要搞清楚什么样的事件才是需要共用同一个锁的,消费者和生产者理论上是可以同时进行的, 所以不能共用同一把锁 //所有线程所共用的资源 private static Bread commonBread = new Bread(); public static class Bread { private Integer leftNumber = 100; public Integer getLeftNumber() { return leftNumber; } public void setLeftNumber(Integer leftNumber) { lock.lock(); this.leftNumber = leftNumber; lock.unlock(); } } public static class Producer { private Bread bread = commonBread; public void make(Bread bread) { try { TimeUnit.SECONDS.sleep(1); if (bread.getLeftNumber() < 200) { bread.setLeftNumber(bread.getLeftNumber()+1); System.out.println(Thread.currentThread().getName() + "生产了一个面包, 目前剩余" + bread.getLeftNumber() + "个"); } } catch (Exception e) { e.printStackTrace(); } finally { } } } public static class Consumer { private Bread bread = commonBread; public void eat(Bread bread) { try { TimeUnit.SECONDS.sleep(2); if (bread.getLeftNumber() > 0) { bread.setLeftNumber(bread.getLeftNumber()-1); System.out.println(Thread.currentThread().getName() + "消费了一个面包, 目前剩余" + bread.getLeftNumber() + "个"); } } catch (Exception e) { e.printStackTrace(); } finally { } } } public static void main(String[] args) { Producer producer = new Producer(); Consumer consumer = new Consumer(); Thread myProducer01 = new Thread(new FutureTask<Void>(new Callable<Void>() { @Override public Void call() throws Exception { while (true) { producer.make(commonBread); } } }), "生产者1号"); Thread myProducer02 = new Thread(new FutureTask<Void>(new Callable<Void>() { @Override public Void call() throws Exception { while (true) { producer.make(commonBread); } } }), "生产者2号"); Thread myConsumer01 = new Thread(new FutureTask<Void>(new Callable<Void>() { @Override public Void call() throws Exception { while (true) { consumer.eat(commonBread); } } }), "消费者1号"); System.out.println("1"); myConsumer01.start(); System.out.println("2"); myProducer01.start(); System.out.println("3"); myProducer02.start(); } }
再次思考:
后来觉得可能是因为两个线程抢占锁的问题,
初次判断应该是在while循环中的那个线程抢占锁资源的概率很大, 而在阻塞中等待锁被释放的线程抢占到锁的概率较小,
所以, 虽然三个线程都开启了, 但是只有一个线程能够获得锁,并一直运行.
-----这也只是初步猜想, 具体什么原因也还会不知道