java ReentrantLock结合条件队列 实现生产者-消费者模式 以及ReentratLock和Synchronized对比
1 package reentrantlock; 2 3 import java.util.ArrayList; 4 5 public class ProviderAndConsumerTest { 6 7 static ProviderAndConsumer providerAndConsumer = new ProviderAndConsumer(); 8 9 public static void main(String[] args) throws InterruptedException { 10 11 // new Thread(new GetRunnable(), "消费者002").start(); 12 // Thread.sleep(1000); 13 // new Thread(new PutRunnable(), "生产者001").start(); 14 15 ArrayList<Thread> provider = new ArrayList<>(); 16 for (int i = 0; i < 3; i++){ 17 provider.add(new Thread(new PutRunnable(), "生产者00"+ (i+1))); 18 } 19 20 ArrayList<Thread> consumer = new ArrayList<>(); 21 for (int i = 0; i < 3; i++){ 22 consumer.add(new Thread(new GetRunnable(), " 消费者--99"+ (i+1))); 23 } 24 25 26 for (Thread i : 27 consumer) { 28 i.start(); 29 } 30 31 // 先让消费者线程全部饥饿,进入消费者条件队列中 32 Thread.sleep(10000); 33 34 for (Thread i : 35 provider) { 36 i.start(); 37 } 38 39 40 41 42 } 43 44 static class PutRunnable implements Runnable { 45 46 47 @Override 48 public void run() { 49 for (int i = 0; i < 3; i++){ 50 providerAndConsumer.put(" (" +Thread.currentThread().getName() + "_data_" + i + ")"); 51 try { 52 // 调整睡眠时间,等同于调整生产者生产数据的频率,但是不准,因为跟生产者内部逻辑执行时间有很大关系 53 Thread.sleep(500,1); 54 } catch (InterruptedException e) { 55 e.printStackTrace(); 56 } 57 } 58 } 59 } 60 61 static class GetRunnable implements Runnable{ 62 63 @Override 64 public void run() { 65 while(true){ 66 providerAndConsumer.get(); 67 try { 68 // 调整睡眠时间,等同于调整消费者消费数据的频率,但是不准,因为跟消费者内部执行时间有很大关系 69 Thread.sleep(10,1); 70 } catch (InterruptedException e) { 71 e.printStackTrace(); 72 } 73 } 74 } 75 } 76 }
1 package reentrantlock; 2 3 import java.util.concurrent.locks.Condition; 4 import java.util.concurrent.locks.ReentrantLock; 5 6 public class ProviderAndConsumer { 7 8 ReentrantLock reentrantLock = new ReentrantLock(); 9 Condition notEmpty = reentrantLock.newCondition(); 10 Condition notFull = reentrantLock.newCondition(); 11 int maxSize = 3; 12 int putIndex = 0; 13 int getIndex = 0; 14 int realDataCount = 0; 15 Object[] queue = new Object[maxSize]; 16 17 boolean isConsumerWait = false; 18 boolean isProviderWait = false; 19 20 21 public void put(Object data){ 22 System.out.println(Thread.currentThread().getName() + "线程,-----尝试加锁-----"); 23 reentrantLock.lock(); 24 System.out.println(Thread.currentThread().getName() + "线程,-----加锁成功-----"); 25 try { 26 27 while (realDataCount == queue.length){ 28 System.out.println(Thread.currentThread().getName() + "线程,-----数据满了,只好等待----" + 29 "哈哈要进生产者条件队列啦"); 30 isProviderWait = true; 31 notFull.await(); 32 } 33 34 queue[putIndex] = data; 35 realDataCount++; 36 37 putIndex++; 38 if (putIndex == queue.length) putIndex = 0; 39 System.out.println(Thread.currentThread().getName() + "线程,成功生产一个数据=" + data.toString()); 40 41 if (isConsumerWait){ 42 System.out.println(Thread.currentThread().getName() + "线程," + 43 " 未 激活消费者条件队列节点前,获取消费者队列长度" 44 + reentrantLock.getWaitQueueLength(notEmpty)); 45 } 46 notEmpty.signal(); 47 if (isConsumerWait){ 48 int length = reentrantLock.getWaitQueueLength(notEmpty); 49 if (length == 0){ 50 isConsumerWait = false; 51 } 52 System.out.println(Thread.currentThread().getName() + "线程," + 53 " 已 激活消费者条件队列节点后,获取消费者队列长度" 54 + reentrantLock.getWaitQueueLength(notEmpty)); 55 } 56 // 调整睡眠时间,等同于调整生产者持有锁的时间 57 Thread.sleep(1000); 58 } catch (InterruptedException e) { 59 e.printStackTrace(); 60 } finally { 61 System.out.println(Thread.currentThread().getName() + "线程,成功解锁"); 62 reentrantLock.unlock(); 63 } 64 65 } 66 67 68 public void get(){ 69 System.out.println("\t\t" + Thread.currentThread().getName() + "线程,尝试加锁"); 70 reentrantLock.lock(); 71 System.out.println("\t\t" + Thread.currentThread().getName() + "线程,-----加锁成功----"); 72 73 try { 74 75 while (realDataCount == 0){ 76 System.out.println("\t\t" + Thread.currentThread().getName() + "线程,-----没有数据,只好等待----"+ 77 "哈哈 要进消费者条件队列啦"); 78 isConsumerWait = true; 79 notEmpty.await(); 80 } 81 82 System.out.println("\t\t" + Thread.currentThread().getName() + "线程,成功消费一个数据=" + queue[getIndex]); 83 realDataCount--; 84 85 getIndex++; 86 if (getIndex == queue.length) getIndex = 0; 87 88 if (isProviderWait){ 89 System.out.println("\t\t" + Thread.currentThread().getName() + "线程," + 90 " 未 激活生产者条件队列节点前,获取生产者队列长度" 91 + reentrantLock.getWaitQueueLength(notFull)); 92 } 93 notFull.signal(); 94 if (isProviderWait){ 95 int length = reentrantLock.getWaitQueueLength(notFull); 96 if (length == 0){ 97 isProviderWait = false; 98 } 99 System.out.println("\t\t" + Thread.currentThread().getName() + "线程," + 100 " 已 激活生产者条件队列节点后,获取生产者队列长度" 101 + length); 102 } 103 104 } catch (InterruptedException e) { 105 e.printStackTrace(); 106 } finally { 107 System.out.println("\t\t" + Thread.currentThread().getName() + "线程,成功解锁"); 108 reentrantLock.unlock(); 109 } 110 } 111 112 113 114 115 116 117 }
跑通上面的例子可以得到一些总结:
1、在生产者-消费者模式下,消费者线程和生产者线程都在抢占cpu,谁抢到cpu谁就得到执行。抢不到的会进入AQS队列。
此时如果有多个线程在抢占不到cpu进入AQS队列时,进入AQS队列的顺序是不可预知的(比如恰好经历线程切换),
但可以保证的是: 一旦在AQS队列里,顺序就是绝对从前往后的。
换句话说,先尝试获取锁的线程在获取不到锁需要进入AQS队列时,不一定就比其他紧接着(也就是说几乎同时,但相对靠后)
尝试获取锁同样获取不到锁需要进入AQS队列要进入队列早。(比如恰好经历线程切换)
2、那什么时候会进入条件队列呢?什么时候出条件对列呢?
当消费者线程获取到cpu,但此时,没有任何数据可供消费,那么当前的这个消费者线程就会释放(完全释放,因为是可重入)锁并让出cpu资源(线程挂起)然后进入消费者条件队列
当接下来有生产者线程得到cpu执行并生产出数据后,生产者线程就会唤醒“消费者条件队列”中的第一个线程,并把第一个线程加入到AQS队列的尾部
对于“生产者条件对列”,跟上面是一个道理。
3、生产者-消费者模式中的数据缓存区,有一个生产者生产指针,指向下一个生产数据应该放的位置,有一个消费者消费指针,指向下一个要消费的数据的位置。
4、永远记住,锁只是一种资源控制方式,任何行为都不能控制cpu在何时切换,以及cpu切换到哪个线程。
ReentratLock和Synchronized对比:
相同点:
1、都是独占锁。synchronized属于隐式锁,编码简单。ReentrantLock需要手动控制加锁解锁,必须配对使用,控制灵活,但是相对技术要求高。
2、都具有可重入性。synchronized可重入简单,用在复杂场景中(比如递归)不需要考虑锁释放的问题。ReentrantLock手动控制,必须配对,在复杂场景中,对技术要求高,否则带来锁不能正确释放的问题。
不同点:
1、灵活性。synchronized属于非公平锁,cpu切换到哪个线程,哪个线程就获得执行。ReentrantLock可灵活配置公平锁、非公平锁。
2、是否可被中断。synchronized不可被中断,一直阻塞。ReentrantLock可被中断,更好的用于解决死锁问题。
3、ReentrantLock使用场景更多。可设置超时时间。可结合条件队列实现 等待-通知机制(例如生产者-消费者模式)