• 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("		" + Thread.currentThread().getName() + "线程,尝试加锁");
     70         reentrantLock.lock();
     71         System.out.println("		" + Thread.currentThread().getName() + "线程,-----加锁成功----");
     72 
     73         try {
     74 
     75             while (realDataCount == 0){
     76                 System.out.println("		" + Thread.currentThread().getName() + "线程,-----没有数据,只好等待----"+
     77                         "哈哈 要进消费者条件队列啦");
     78                 isConsumerWait = true;
     79                 notEmpty.await();
     80             }
     81 
     82             System.out.println("		" + 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("		" + 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("		" + Thread.currentThread().getName() + "线程," +
    100                         " 已 激活生产者条件队列节点后,获取生产者队列长度"
    101                         + length);
    102             }
    103 
    104         } catch (InterruptedException e) {
    105             e.printStackTrace();
    106         } finally {
    107             System.out.println("		" + 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使用场景更多。可设置超时时间。可结合条件队列实现 等待-通知机制(例如生产者-消费者模式)

  • 相关阅读:
    javascript高级程序设计---Event对象三
    javascript高级程序设计---Event对象二
    javascript高级程序设计---Event对象
    javascript高级程序设计---CSS操作
    javascript高级程序设计---Element对象
    javascript高级程序设计---document节点
    javascript高级程序设计---NodeList和HTMLCollection
    javascript高级程序设计---DOM
    Javascript高级程序设计——客户端检测
    学习javascript系列之变量
  • 原文地址:https://www.cnblogs.com/cnblogszs/p/10361385.html
Copyright © 2020-2023  润新知