图解 ReentrantLock 公平锁和非公平锁实现

概述

ReentrantLock 是 Java 并发中十分常用的一个类,具备类似 synchronized 锁的作用。但是相比 synchronized, 它具备更强的能力,同时支持公平锁和非公平锁。

公平锁: 指多个线程按照申请锁的顺序来获取锁,线程直接进入队列中排队,队列中的第一个线程才能获得锁。

非公平锁: 多个线程加锁时直接尝试获取锁,能抢到锁到直接占有锁,抢不到才会到等待队列的队尾等待。

那 ReentrantLock 中具体是怎么实现公平和非公锁的呢?它们之间又有什么优缺点呢?本文就带大家一探究竟。

RenentrantLock 原理概述

上面是 RenentrantLock 的类结构图。

  • RenentrantLock 实现了 Lock 接口,Lock 接口提供了锁的通用 api,比如加锁 lock,解锁 unlock 等操作。

  • RenentrantLock 底层加锁是通过 AQS 实现的,两个内部类 FairSync 服务于公平锁,NofaireSync 服务于非公平锁的实现,他们统一继承自 AQS。

ReentrantLock 类 API:

  • public void lock():获得锁

如果锁没有被另一个线程占用,则将锁定计数设置为 1

如果当前线程已经保持锁定,则保持计数增加 1

如果锁被另一个线程保持,则当前线程被禁用线程调度,并且在锁定已被获取之前处于休眠状态

  • public void unlock():尝试释放锁

如果当前线程是该锁的持有者,则保持计数递减

如果保持计数现在为零,则锁定被释放

如果当前线程不是该锁的持有者,则抛出异常

非公平锁实现

演示

@Testpublic void testUnfairLock() throws InterruptedException {
      // 无参构造函数,默认创建非公平锁模式    ReentrantLock reentrantLock = new ReentrantLock();
    for (int i = 0; i < 10; i++) {
          final int threadNum = i;        new Thread(() -> {
              reentrantLock.lock();            try {
                  System.out.println("线程" + threadNum + "获取锁");                Thread.sleep(1000);            } catch (InterruptedException e) {
                  e.printStackTrace();            } finally {
                  // finally中解锁                reentrantLock.unlock();                System.out.println("线程" + threadNum +"释放锁");            }        }).start();        Thread.sleep(999);    }
    Thread.sleep(100000);}

运行结果:

线程0获取锁线程0释放锁线程1获取锁线程1释放锁线程3获取锁线程3释放锁线程2获取锁线程2释放锁线程5获取锁线程5释放锁线程4获取锁线程4释放锁....
  • 默认构造函数创建的是非公平锁

  • 运行结果可以看到线程 3 优先于线程 2 获取锁(这个结果是人为造的,很难模拟出来)。

加锁原理

  1. 构造函数创建锁对象

public ReentrantLock() {
     sync = new NonfairSync();}
  • 默认构造函数,创建了 NonfairSync,非公平锁同步器,它是继承自 AQS.

  1. 第一个线程加锁时,不存在竞争,如下图:

// ReentrantLock.NonfairSync#lockfinal void lock() {
      // 用 cas 尝试(仅尝试一次)将 state 从 0 改为 1, 如果成功表示【获得了独占锁】    if (compareAndSetState(0, 1))        // 设置当前线程为独占线程        setExclusiveOwnerThread(Thread.currentThread());    else        acquire(1);//失败进入}

  • cas 修改 state 从 0 到 1,获取锁

  • 设置锁对象的线程为当前线程

  1. 第二个线程申请加锁时,出现锁竞争,如下图:

  • Thread-1 执行,CAS 尝试将 state 由 0 改为 1,结果失败(第一次),进入 acquire 逻辑

// AbstractQueuedSynchronizer#acquirepublic final void acquire(int arg) {
      // tryAcquire 尝试获取锁失败时, 会调用 addWaiter 将当前线程封装成node入队,acquireQueued 阻塞当前线程,    // acquireQueued 返回 true 表示挂起过程中线程被
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值