• 并发编程——重入锁ReentrantLock


    目录

    显式地同步功能——锁

    synchronized和ReentrantLock的区别

    ReentrantLock实现原理

    ReentrantLock公平锁和非公平锁的差异

    ReentrantLock时序图

    独占式同步状态获取流程


    显式地同步功能——锁

    Java SE 5之后,并发包中新增 了Lock接口(以及相关实现类)用来实现锁功能,它提供了与synchronized关键字类似的同步功能,只是在使用时需要显式地获取和释放锁。

    一个简单的Lock使用方式的示例:

    // ReentrantLock实现简单的独占锁
    public class LockDemo {
        private Lock lock = new ReentrantLock();
        private int count = 0;
        public void doSth () {
            lock.lock();
            try {
                Thread.sleep(1);
                count++;
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
        public static void main(String[] args) throws InterruptedException {
            final LockDemo lockDemo = new LockDemo();
            
            for (int i = 0; i < 1000; i++) {
                new Thread(()->{lockDemo.doSth();}).start();
            }
    ​
            Thread.sleep(3000);
            System.out.println("result:"+lockDemo.count);
        }
    }

    ReentrantLock是独占锁,就是能够防止多个线程同时访问共享资源的锁。当然,也有共享锁,比如ReadWriteLock,这种读写锁的实现允许多个线程同时读取数据,但只允许一个线程写入数据。这在读多写少的场景中可以提高性能和吞吐量。

    public class RwLockDemo {
        private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
        private Map cacheMap = new HashMap<>();
        private Lock read = readWriteLock.readLock();
        private Lock write = readWriteLock.writeLock();
    ​
       public Object get(String key) {
           System.out.println("start read ...");
           read.lock();
           try {
               return cacheMap.get(key);
           } finally {
               read.unlock();
           }
       }
    ​
       public Object put(String key,Integer value){
           System.out.println("start write ...");
           write.lock();
           try {
               return cacheMap.put(key, value);
           } finally {
               write.unlock();
           }
       }
    ​
       public static void main(String[] args) throws InterruptedException {
           final RwLockDemo rwLockDemo = new RwLockDemo();
           rwLockDemo.put("a",1);
           rwLockDemo.put("b",2);
           System.out.println(rwLockDemo.cacheMap.get("a"));
       }
    }
    synchronized和ReentrantLock的区别
    • 锁的实现:synchronized是java语言的关键字,基于JVM实现。ReentrantLock基于JDK的API实现的(lock()和unlock()配合finally语句块实现的。)

    • 性能:JDK1.6以前,synchronized重量级锁性能略差;1.6以后加入自适应自旋、锁消除等,两者性能就差不多了。

    • 功能特点:ReentrantLock提供了些高级功能,如等待可中断、可实现公平锁、可实现选择性通知。

    区别synchronizedReentrantLock
    锁实现机制对象头监视器模式依赖AQS
    灵活性不灵活支持响应中断、超时、尝试获取锁
    释放锁形式自动释放锁显式调用unlock()
    支持锁类型非公平锁公平锁和非公平锁
    条件队列单条件队列多条件队列
    可重入支持支持支持
    ReentrantLock实现原理

    ReentrantLock的一些关键实现原理:

    1. CAS操作ReentrantLock的实现依赖于CAS(Compare-And-Swap)操作,这是一种原子操作,用于在多线程环境中修改共享变量。CAS操作允许一个线程比较内存中的值与预期值,如果相等,则更新为新的值。

    2. 锁状态ReentrantLock内部维护了一个锁状态(lock state),这个状态可以告诉锁当前是被哪个线程占用的,以及该线程占用了几次锁。这允许同一个线程多次获取同一把锁而不会发生死锁。当一个线程首次获取锁时,锁状态设置为1,每次再次获取锁时,状态递增。

    3. AQS(AbstractQueuedSynchronizer)ReentrantLock的实现基于AQS,这是一个用于构建锁和其他同步器的抽象框架。AQS提供了队列、状态管理、等待线程管理等功能。ReentrantLock通过扩展AQS并根据自身需求实现其中的一些方法来实现锁的功能。

    4. 公平性和非公平性ReentrantLock支持公平性和非公平性。在公平模式下,等待线程按照FIFO顺序获取锁。在非公平模式下,等待线程有可能插队,当锁释放时,可能不是等待时间最长的线程获得锁。这个选择可以通过ReentrantLock的构造函数来指定。

    5. 可中断性ReentrantLock允许等待锁的线程响应中断信号,通过使用lockInterruptibly()方法可以实现这一点。

    6. 条件变量ReentrantLock还提供了条件变量(Condition),允许线程在某些条件下等待和唤醒。条件变量是ReentrantLock的一部分,可以通过newCondition()方法创建。

    ReentrantLock公平锁和非公平锁的差异

    ReentrantLock可以用于实现公平锁和非公平锁,它们的主要区别在于线程获取锁的顺序和策略。

    1. 公平锁(Fair Lock)

      • 公平锁确保线程按照请求锁的顺序获得锁资源,即按照FIFO(先进先出)的顺序分配锁。

      • 当一个线程请求锁但锁已经被其他线程占用时,该线程会进入等待队列,并按照请求锁的顺序排队等待。

      • 当锁释放时,等待队列中的线程中最早请求锁的线程将获得锁,以确保公平性。

      • 优点:公平锁能够避免线程饥饿,即某个线程无限期地等待获取锁的情况。每个线程最终都有机会获取锁。

      • 缺点:因为需要维护等待队列和按照顺序分配锁,公平锁的性能可能相对较差,特别是在高并发情况下。

      • 使用方式:通过在创建 ReentrantLock 实例时传递 true 参数来创建公平锁,例如:ReentrantLock fairLock = new ReentrantLock(true);

    2. 非公平锁(Non-Fair Lock)

      • 非公平锁不考虑线程请求锁的顺序,当锁可用时,任何等待的线程都有机会立即获得锁。

      • 当锁被释放时,如果有等待的线程,JVM会从等待线程中随机选择一个线程来获得锁。

      • 优点:非公平锁的性能通常比公平锁更好,因为它减少了维护等待队列和按照顺序分配锁的开销。

      • 缺点:非公平锁可能导致某些线程一直无法获取锁,因为它们不会按照请求锁的顺序获得锁,可能会导致线程饥饿的情况。

      • 使用方式:默认情况下,ReentrantLock 创建的是非公平锁,可以不传递参数或传递 false 来明确使用非公平锁。

    选择公平锁还是非公平锁取决于应用的需求和性能考虑。如果你需要确保线程以公平的方式获得锁资源,可以使用公平锁。如果性能更为重要,而且你不担心某些线程可能被饿死,那么非公平锁可能是更好的选择。一般来说,非公平锁在高并发场景下表现更好,但可能会导致某些线程长时间等待。

    ReentrantLock时序图

    • 入口:ReentrantLock.java中的lock()方法调用了继承自AbstractQueuedSynchronizer的抽象静态类Sync的lock()方法,默认情况下创建的是非公平锁。(部分源码)

      private final Sync sync;
      abstract static class Sync extends AbstractQueuedSynchronizer {}
      public void lock() {
              sync.lock();
          }
    • 获取同步状态:非公平锁直接CAS尝试抢占,如果锁没有被抢占,直接抢占成功;大部分时候是抢占失败的,调用acquire()独占式获取同步状态会调用继承自AQS进行重写的acquire()判断tryAcquire()和addWaiter。

      public final void acquire(int arg) 
          if (!tryAcquire(arg) &&
              acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
              selfInterrupt();
      }
    • 判断结果,同步失败,线程进入同步队列挂起状态等待

    独占式同步状态获取流程

    就是acquire(int arg)方法调用流程。如下所示:

  • 相关阅读:
    【基础篇】第1章 Elasticsearch 引言
    uni-app使用canvas适配手机宽高进行渲染
    Istio服务网格详解
    上班干,下班学!这份 Java 面试八股文涵盖 20 多个技术点,还有优质面经分享,别再说卷不过别人了~
    章节十六:复习与反爬虫
    《智谋故事》收纳
    Android自定义公共引入库(多个项目引入同一自定义框架类库)
    【计算机网络笔记五】应用层(二)HTTP报文
    产品推荐 - 基于复旦微 JFM7K325T FPGA 的高性能 PCIe 总线数据预处理载板(100%国产化)
    ResponseBodyAdvice接口使用导致的报错及解决
  • 原文地址:https://blog.csdn.net/Elaine2391/article/details/133365142