• 多线程和并发编程(3)—AQS和ReentrantLock实现的互斥锁


    一、管程模型—MESA模型

    管程是什么?

    管程就是指管理共享变量,以及对共享变量的相关操作。

    在管程的发展史上,先后出现过三种不同的管程模型,分别是Hasen模型、Hoare模型和MESA模型。现在正在广泛使用的是MESA模型。

    MESA模型的核心是需要一个共享变量来表示共享资源的数量,同步等待队列中的线程请求到一个共享资源,相应共享变量要减一,一直到共享变量为0,则请求的线程阻塞在同步等待队列中,如果需要满足某些条件才能竞争共享资源,这些线程会阻塞在条件等待队列中,但条件满足后要么转移到同步等待队列中,要么直接占有共享资源。

    MESA 管程模型

    同步等待队列:竞争共享资源暂没竞争到的线程会阻塞在同步等待队列中。

    条件等待队列:在竞争资源的过程中还未达到某个条件,会阻塞在条件等待队列中,其中需要达到的条件即用条件变量表示,当满足这个条件后就会转移到同步等待队列中。

    二、AQS原理

    在Java中针对管程有两种实现:(1)一种是基于Object的Monitor机制,用于synchronized内置锁的实现;(2)一种是抽象队列同步器AQS,用于JUC包下Lock锁机制的实现;以下重点介绍方案(2)中的AQS。

    1.实现思路

    对于被请求的共享变量如果是空闲的,则将请求共享资源的线程设置为工作线程并且将共享变量减一。对于被请求资源是被占用的情况,则将该线程阻塞起来放到双向同步等待队列中,等共享资源被释放再进行申请。

    2.AQS的组成

    • 共享变量

    AQS内部维护属性volatile int state ,其中state表示资源的可用状态,State三种访问方式:

    1. getState()
    2. setState()
    3. compareAndSetState()

    AQS实现时主要实现以下几种方法:

    1. isHeldExclusively():该线程是否正在独占资源。只有用到condition才需要去实现它。
    2. tryAcquire(int):独占方式。尝试获取资源,成功则返回true,失败则返回false。
    3. tryRelease(int):独占方式。尝试释放资源,成功则返回true,失败则返回false。
    4. tryAcquireShared(int):共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
    5. tryReleaseShared(int):共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false。
    • 同步队列

    AQS的数据结构包括同步等待队列和条件等待队列,同步等待队列是一个双端队列CLH,每个队列元素为一个Node,Node中保存前驱后置节点、当前线程状态、当前线程以及下一个等待线程。每个Node通过获取和重置state参数来进行加锁操作。共享资源是否被占用是通过对state进行修改来实现的,当该共享资源被加锁后,就会修改state为1。条件等待队列是保存不满足条件的线程,每一个条件对于一个条件同步队列。

    img

    同步阻塞队列和条件阻塞队列在一定条件会相互转换,当条件阻塞队列满足条件的情况,就能转移到同步阻塞队列中。即同步阻塞队列中的线程只差竞争到锁,而条件阻塞队列中的线程还需要满足条件才能转移到同步阻塞队列。在Java中通过signal()或signalAll()将条件同步队列中的线程转移到同步等待队列。但不满足条件时,调用await()方法会将同步等待队列中线程转移到条件等待队列中。

    • 编程模型

    常见的使用AQS的编程模式如下:

    public class BlockedQueue{
      final Lock lock = new ReentrantLock();
      final Condition condition1 = lock.newCondition();
      final Condition condition2 = lock.newCondition();
      //Boolean flag = true;
    
      // 入队
      void enq(T x) {
        lock.lock();
        try {
          while (!条件1){
            // 条件1不满足,进入1条件等待队列
            condition1.await();
          }  
          // ...
          //解锁前,唤醒条件2等待队列中线程
          condition2.signal();
        }finally {
          lock.unlock();
        }
      }
      // 出队
      void deq(){
        lock.lock();
        try {
          while (!条件2){
            // 条件2不满足,进入条件2等待队列
            condition2.await();
          }
          // ...
           //解锁前,唤醒条件1等待队列中线程
          condition1.signal();
        }finally {
          lock.unlock();
        }  
      }
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    1. 申请一把Lock锁,即申请一个同步等待队列,每个线程进入业务处理前需要竞争锁资源,没有竞争上锁资源的线程放到同步等待队列中;
    2. 在获取锁之后,会进行条件判断,如果满足条件则会在持有该锁的条件下进行业务逻辑的处理,其他线程无法并发处理,如果没有满足条件则会进入到条件同步队列中,并且暂时释放锁,等待满足条件后再参与锁竞争;

    三、ReentrantLock原理

    ReentrantLock是实现AQS的悲观锁。初始化状态是state为0,当调用lock()方法时候会调用tryAcquire方法将其state设为1并且锁定,之后其他线程调用tryAcquire方法将会失败并加入到同步队列阻塞,直到该线程调用unlock()方法会将此锁释放,调用tryRelease方法释放锁,将state改为0,同时注意,该线程在释放该锁之前,可以重复获得此锁,所以ReentrantLock是可重入的。

    ReentrantLock内部有三个内部类,包括抽象类Sync,和其实现类NonfairSync、FairSync,可以分别实现公平锁和非公平锁,对ReentrantLock的操作基本是对Sync的操作,Sync分为公平实现FairSync和非公平实现NonfairSync,他们都是继承AQS接口。

    image-20230914205718458

    ReentrantLock和synchronized的区别?

    • 相同点:

    (1)synchronied和ReentrantLock都是可重入锁;

    (2)synchronied是Java的关键字,是通过JVM对对象进行加Monitor锁操作实现的;而ReentrantLock通过JDK中的AQS接口来实现,提供多种加锁、解锁方法;

    • 不同点:

    (1)特性:synchronied是非公平锁,ReentrantLock可以实现公平锁和非公平锁;ReentrantLock可以实现可中断、可重试、超时中断、多条件加锁机制;ReentrantLock可以实现等待多条件释放锁,总之ReentrantLock更加灵活、功能更强大;

    (2)实现:Synchronized是JVM自动隐式加解锁,执行完成他会自动释放锁;ReentrantLock通过Lock()和unLock()实现手动加锁和取消加锁。

    参考资料

    1. 管程(Moniter): 并发编程的基本心法:https://developer.aliyun.com/article/904581
    2. Java中的管程模型:https://segmentfault.com/a/1190000021557492

    本文由博客一文多发平台 OpenWrite 发布!

  • 相关阅读:
    Rust7.1 Functional Language Features Iterators and Closures
    华为OD 完全二叉树非叶子部分后序遍历(200分)【java】A卷+B卷
    【OpenCV】- 直方图反向投影
    数据库常用的数据类型和约束条件
    PostgreSQL11.17离线安装过程(X86+Ubuntu)
    栈的实现(c语言)
    架设好传奇登录器显示无法连接服务器,完美登录器使用常见问题解决办法
    Allegro如何将丝印文字Change到任意层面操作指导
    Python 爬虫入门:常见工具介绍
    fatal: bad boolean config value ‘“false”‘ for ‘http.sslverify
  • 原文地址:https://blog.csdn.net/ynkimage/article/details/132890730