ReentrantLock原理

本文详细探讨了ReentrantLock与synchronized的区别,包括可打断、公平锁支持、多条件队列以及超时设置等特性。ReentrantLock基于AQS(AbstractQueuedSynchronizer)实现,内部维护同步队列和条件队列,通过CAS和park/unpark操作来管理线程。此外,还介绍了ReentrantLock的公平与非公平加锁流程,以及条件变量的await和signal方法的工作原理。
摘要由CSDN通过智能技术生成

ReentrantLock与synchronized对比

  • synchronized是Java关键字,是jvm层面实现的锁,而ReentrantLock是一个Java类,基于aqs来实现,本质上是通过cas自旋+park实现锁机制,更容易在应用层面去扩展。
  • 可打断
  • 对于synchronized来说,如果一个线程无法获得锁阻塞后,它只能一直等待,其他线程无法打断它。
  • 而对于ReentrantLock它支持可打断,一个线程无法获得锁阻塞后 可以被其他线程打断继续执行。(因为park的线程是可以被打断的,block的线程无法被打断)
  • 支持公平锁
  • 对于synchronized来说它是不公平的,先申请锁的线程不一定先获得锁。
  • ReentrantLock 默认也是非公平锁,可以通过参数设置为公平锁。它是基于aqs自己实现调度,保证公平性。
  • 支持多个条件队列
  • 对于synchronized来说,它只有一个条件队列,owner线程无论是什么原因无法运行,都只能进入waitset队列。这样通过notifyAll唤醒所有线程存在虚假唤醒问题。
  • 而ReentrantLock 支持多个条件队列,可以调用每个条件变量的 await 和 signal 方法完成等待和唤醒。
  • 因为原因a等待的线程放在等待队列a,因为原因b等待的线程放在等待队列b。可以使用条件变量的 signalAll方法唤醒当前条件变量对应的等待队列中所有线程。
  • 如下表示使用条件变量进行等待和唤醒的示例 子线程1通过条件变量1阻塞,子线程2通过条件变量2阻塞,主线程通过条件变量1只能唤醒子线程1,无法唤醒子线程2,从而解决虚假唤醒问题。
ReentrantLock lock = new ReentrantLock();
Condition condition1 = lock.newCondition(); //条件变量1
Condition condition2 = lock.newCondition(); //条件变量2

Thread t = new Thread(()->{
   
    lock.lock(); //加锁
    System.out.println("子线程1获得锁");
    try {
   
        condition1.await(); //通过条件变量1让 子线程陷入等待状态

        System.out.println("子线程1被唤醒");
        lock.unlock();//解锁

    } catch (InterruptedException e) {
   }

});
t.start();

Thread t2 = new Thread(()->{
   
    lock.lock(); //加锁
    System.out.println("子线程2获得锁");
    try {
   
        condition2.await(); //通过条件变量2让 子线程2陷入等待状态

        System.out.println("子线程2被唤醒");
        lock.unlock();//解锁

    } catch (InterruptedException e) {
   }

});
t2.start();

//主线程先于子线程获取锁
Thread.sleep(1000);
lock.lock(); //加锁
System.out.println("主线程获得锁");
condition1.signal(); //通过条件变量1唤醒子线程
lock.unlock();
  • 可设置超时时间
  • 一个线程获取不到锁陷入阻塞状态后,为了避免死等,可以让其他线程将其打断,这是一种被动的方式。
  • 除此之外,还可以设置超时时间,表示一个线程获取不到锁陷入阻塞状态后,如果持续了一段时间依然没有获得锁,那么退出该线程。
  • 通过设置超时时间可以很方便解决死锁问题,因为任意线程等待一段时间都会终止,就不会出现相互等待锁的情况。
  • 底层基于可超时的park方法+可打断实现。

aqs基本原理

  • AbstractQueuedSynchronizer(AQS)是抽象的队列式的同步器,AQS定义了一套多线程访问共享资源的同步器框架。
  • ReentrantLock底层就是基于aqs来实现的。
  • aqs本质上是通过park加cas自旋实现锁机制的。用一个变量state标记锁状态,以cas修改锁标记的方式进行加锁,如果多次cas尝试都加锁失败就通过park方法阻塞。
  • 我们知道对于synchronize的互斥锁来说,只要加锁失败就会陷入阻塞状态,而对于aqs,acas加锁失败不会立即陷入阻塞,而是灵活控制线程行为。
  • aqs通过自旋操作,也可以实现类似synchronize中轻量级锁和偏向锁的功能。
  • aqs的具体实现原理如下
    aqs内部维护了一个state状态,一个同步队列和若干条件队列。
volatile int state //标识锁状态
volatile Node head; //同步队列头节点
volatile Node tail; //同步队列尾节点
owner; //指向当前获得锁的线程
//多个条件队列  类似于waitset,获得锁的线程不满足某个条件陷入等待。线程同样通过park阻塞
  • 对于state变量来说标记了锁的状态,对于读写锁来说,它可以同时标记读锁和写锁两种锁的状态。另外通过state的计数器还可以记录加锁数量实现可重入锁。
  • 对于同步队列来说,当一个线程通过cas加锁失败,为了避免无限cas重试,会将其存放在同步队列并调用park方法阻塞。同步队列中的线程维护一个先后关系,占有锁的线程位于队头,当占有锁的线程释放锁时会负责唤醒第二个节点的线程。即队列每一个线程都由前一个线程负责唤醒。
  • 对于条件队列来说,默认不存在,只有设置条件变量才会用到条件队列,条件队列与同步队列结构类似,都会维护先后关系,由上一个线程负责唤醒下一个线程。条件队列存放占有锁的线程不满足某个条件陷入等待状态,也是通过park阻塞。当条件队列中线程唤醒后,不会立刻执行,而是在同步队列先排队申请锁。
  • 由于park方法不会释放锁,因此在将活动线程放入条件队列前,要把当前线程占有的锁全部释放。
  • aqs基本方法如下
//申请锁 失败会阻塞
public final void acquire(int arg) 

//申请可打断的锁
public final void acquireInterruptibly(int arg)

//尝试申请带超时的锁
public final boolean tryAcquireNanos(int arg, long nanosTimeout)

//释放锁
public final boo
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: ReentrantLock是Java中的一个锁类,它是一个可重入锁,允许同一个线程多次获得同一个锁。在使用ReentrantLock时,我们需要显式地获取锁和释放锁,可以通过lock()和unlock()方法来完成这些操作。 ReentrantLock采用了一种非公平的获取锁的方式,这意味着当多个线程同时请求锁时,ReentrantLock并不保证锁的获取顺序与请求锁的顺序相同。这种方式的好处是可以减少线程竞争,从而提高系统的并发性能。 另外,ReentrantLock还支持Condition条件变量,可以使用它来实现线程的等待和通知机制,以及更加灵活的线程同步和通信。 总之,ReentrantLock是Java中一个非常强大的锁类,可以帮助我们实现高效的线程同步和并发控制。但是,使用ReentrantLock也需要注意一些问题,比如需要正确地使用try-finally块来释放锁,避免死锁等问题。 ### 回答2: ReentrantLock是Java中的一种可重入锁,它提供了与synchronized关键字相似的功能,但具有更强大的扩展性和灵活性。 ReentrantLock内部使用一个同步器Sync来实现锁机制。Sync是ReentrantLock的核心组件,它有两个实现版本,分别是NonfairSync和FairSync。 NonfairSync是默认的实现版本,它采用非公平方式进行线程获取锁的竞争,即线程请求锁的时候,如果锁可用,则直接将锁分配给请求的线程,而不管其他线程是否在等待。 FairSync是公平版本,它按照线程请求锁的顺序来分配锁,当锁释放时,会优先分配给等待时间最长的线程。 ReentrantLock在实现上使用了Java的锁机制和条件变量来管理线程的等待与唤醒。当一个线程调用lock方法获取锁时,如果锁可用,线程会立即获得锁;如果锁被其他线程占用,调用线程就会被阻塞,进入等待队列。 当一个线程占用了锁之后,可以多次重复地调用lock方法,而不会引起死锁。这就是ReentrantLock的可重入性。每次重复调用lock都需要记住重入次数,每次成功释放锁时,重入次数减1,直到次数为0,锁才会被完全释放。 与synchronized相比,ReentrantLock提供了更多的高级功能。例如,可以选择公平或非公平版本的锁,可以实现tryLock方法来尝试获取锁而不会阻塞线程,可以使用lockInterruptibly方法允许线程在等待时可以被中断等等。 总之,ReentrantLock通过灵活的接口和可重入特性,提供了一种强大的同步机制,使多个线程可以安全地访问共享资源,并且具有更大的灵活性和扩展性。它在并发编程中的应用非常广泛。 ### 回答3: ReentrantLock是一种与synchronized关键字相似的线程同步工具。与synchronized相比,ReentrantLock提供了更灵活的锁操作,在并发环境中能更好地控制线程的互斥访问。 ReentrantLock原理主要包含以下几个方面: 1. 线程控制:ReentrantLock内部维护了一个线程的等待队列,每个线程通过调用lock()方法来竞争锁资源。当一个线程成功获取到锁资源时,其他线程会被阻塞在等待队列中,直到锁被释放。 2. 重入性:ReentrantLock允许同一个线程多次获取锁资源,而不会发生死锁。这种机制称为重入性。在线程第一次获取到锁资源后,锁的计数器会加1,当该线程再次获取锁时,计数器会再次加1。而在释放锁时,计数器会递减。只有当计数器减为0时,表示锁已完全释放。 3. 公平性和非公平性:ReentrantLock可以根据需要选择公平锁或非公平锁。在公平锁模式下,等待时间最久的线程会优先获取到锁资源。而在非公平锁模式下,锁资源会被直接分配给新到来的竞争线程,不考虑等待时间。 4. 条件变量:ReentrantLock提供了Condition接口,可以创建多个条件变量,用于对线程的等待和唤醒进行管理。与传统的wait()和notify()方法相比,Condition提供了更加灵活的等待和通知机制,可以更加精确地控制线程的流程。 总的来说,ReentrantLock是通过使用等待队列、重入性、公平性和非公平性、条件变量等机制,来实现线程的互斥访问和同步。它的灵活性和粒度更高,可以更好地适应各种复杂的并发场景。但由于使用ReentrantLock需要手动进行锁的获取和释放,使用不当可能会产生死锁等问题,因此在使用时需要仔细思考和设计。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值