Java多线程并发之ReentrantLock怎么使用

其他教程   发布日期:2025年04月15日   浏览次数:107

本篇内容主要讲解“Java多线程并发之ReentrantLock怎么使用”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Java多线程并发之ReentrantLock怎么使用”吧!

    ReentrantLock

    公平锁和非公平锁

    这个类是接口

    1. Lock
    的实现类,也是悲观锁的一种,但是它提供了
    1. lock
    1. unlock
    方法用于主动进行锁的加和拆。在之前使用的
    1. sychronized
    关键字是隐式加锁机制,而它是显示加锁,同时,这个类的构造方法提供了公平和非公平的两种机制。

    什么是公平和非公平呢?就是多线程对共享资源进行争夺的时候,会出现一个线程或几个线程完全占有共享资源,使得某些线程在长时间处于等待状态。公平就是要等待时间过长的线程先获得锁。

    而在

    1. ReentrantLock
    类中,提供了公平锁和非公平锁的使用。

    1. ReentrantLock
    源码中,构造器提供了一个参数入口,
    1. public ReentrantLock(boolean fair) {
    2. sync = fair ? new FairSync() : new NonfairSync();
    3. }

    当fair为true的时候,会创造一个

    1. FairSync
    对象给
    1. sync
    属性,
    1. FairSync
    是继承自
    1. Sync
    的类,其中有一个
    1. Lock
    方法,而在
    1. ReentrantLockLcok
    中使用的是
    1. sync
    属性的
    1. Lock
    方法,故能够保证“公平”。

    使用非公平锁就不需要在构造器中传参数。

    在使用的时候,需要手动上锁和解锁。

    使用公平锁,会将占优势的线程进行限制,恢复挂起的线程,但是这个过程在CPU层面来讲,是存在明显时间差异的,非公平锁的执行效率相对更高,所以一般来说不建议使用公平锁,除非现实业务上需要符合实际需求。

    重入锁

    1. ReentrantLock
    本身还支持重入的功能。

    重入锁(Reentrant Lock)是一种支持重入的独占锁,它允许线程多次获取同一个锁,在释放锁之前必须相应地多次释放锁。重入锁通常由两个操作组成:上锁(lock)和解锁(unlock)。当一个线程获取了重入锁后,可以再次获取该锁而不被阻塞,同时必须通过相同数量的解锁操作来释放锁。

    重入锁具有如下特点:

    • 重入性:重入锁允许同一个线程多次获取同一把锁,避免了死锁的发生。

    • 独占性:与公平锁和非公平锁一样,重入锁也是一种独占锁,同一时刻只能有一个线程持有该锁。

    • 可中断性:重入锁支持在等待锁的过程中中断该线程的执行。

    • 条件变量:在使用 java.util.concurrent.locks.Condition 类配合重入锁实现等待/通知机制时,等待状态总是与重入锁相关联的。

    重入锁相对于 synchronized 关键字的优势在于,重入锁具有更高的灵活性和扩展性,支持公平锁和非公平锁、可中断锁和可轮询锁等特性,能够更好地满足多线程环境下的并发控制需要。

    1. synchroized
    也有重入性。
    1. ReentrantLock lock = new ReentrantLock(true);
    2. public void get(){
    3. while(true){
    4. try{
    5. lock.lock();
    6. lock.lock();
    7. }catch(Exception exception){
    8. }finally{
    9. lock.unlock();
    10. lock.unlock();
    11. }
    12. }
    13. }

    可重入的前提

    1. lock
    是同一个对象,而关键字
    1. synchroized
    1. Monitor
    也是同一个对象充当,才能判定为重入。
    1. public void get(){
    2. while(true){
    3. synchronized(this){
    4. System.out.println("外层");
    5. synchronized(this){
    6. System.out.println("内层");
    7. }
    8. }
    9. }
    10. }

    那么Java是怎么检测锁的重入和获取锁的次数的呢?在之前说过的

    1. ObjectMobitor
    的C++源代码中有
    1. _recursions_count
    来记录锁的重入次数和线程获取锁的次数。这样在Java层面就表示一个锁对象都拥有一个锁计数器
    1. _count
    和一个指向持有这个锁的线程的指针
    1. _owner
    只有当前持有锁的线程才能使得计数器+1,其他线程只有等待锁被释放(计数器置0)才能持有并+1。

    在源码中,非公平锁的

    1. lock
    方法如下:
    1. //ReentrantLock类中:
    2. final void lock() {
    3. if (compareAndSetState(0, 1))
    4. setExclusiveOwnerThread(Thread.currentThread());
    5. else
    6. acquire(1);
    7. }
    8. //0的参数为是expect,是期望值,而1是update,是更新值

    在执行

    1. comparaAndSetState
    方法的时候,它会询问锁的计数器(在底层执行
    1. compareAndSwapInt
    的本地方法),并期望数值为0,如果为0返回
    1. true
    ,然后设置执行线程主是当前线程。如果非0,那么他就会执行
    1. acquire
    1. //AbstractQueuedSynchronizer类中:
    2. public final void acquire(int arg) {
    3. if (!tryAcquire(arg) &&
    4. acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
    5. selfInterrupt();
    6. }
    7. //这里的tryAcquire,需要在其继承的子类中进一步实现对应的功能
    8. //子类可以根据自己的需要重新定义tryAcquire(int arg)的实现方式,从而实现更优秀的锁控制方案:
    9. //而在其子类FairSync中便覆盖了这个方法
    10. protected boolean tryAcquire(int arg) {
    11. throw new UnsupportedOperationException();
    12. }

    将线程放入等待队列。

    同时计数器是通过

    1. unlock
    来-1,所以
    1. lockunlock
    次数不匹配就会产生死锁,也就是当两个线程调用同一个
    1. ReentrantLock
    如果一个线程中的上锁解锁次数不相等,那么计数器没有被清零,当另一个线程请求锁的时候,看到锁计数器不是0,就认为被的线程仍然持有它,所以一直等待它被释放。需要了解底层的可以去看AQS中的
    1. release
    方法。

    而在

    1. ReentrantLock
    中有一个抽象内部类
    1. Sync
    ,它继承自抽象类
    1. AbstractQueuedSynchronizer
    (简称AQS),这个类中有一个内部
    1. Node
    类,当有线程等待这把锁的时候,会创建一个等待队列,放置这些处于等待的线程。(AQS实现比较复杂,有兴趣可以看看“竹子爱熊猫”大佬的文章。)

    小结

    1. ReentrantLock
    类中,有内部类三个,
    1. Sync,FairSync,NonfairSync
    ,他们的关系是
    1. Sync
    是后两个的父类,后两个是兄弟类,同时
    1. Sync
    继承自AQS类,在AQS中有很多实现公平和非公平、可重入的机制,而具体实现效果的是
    1. Sync,FairSync,NonfairSync

    疑惑

    在下列代码中,为什么在第一个线程的最后加上

    1. .join()
    ,没有使得线程阻塞,而没有它就会阻塞?
    1. Lock lock = new ReentrantLock();
    2. new CompletableFuture().runAsync(() -> {
    3. lock.lock();
    4. try{
    5. System.out.println(1);
    6. TimeUnit.SECONDS.sleep(2);
    7. }catch(Exception e){
    8. }finally{
    9. }});
    10. //上面加上.join()
    11. new CompletableFuture().runAsync(() -> {
    12. lock.lock();
    13. try{
    14. System.out.println(2);
    15. }catch(Exception e){
    16. }finally{
    17. lock.unlock();
    18. }}).join();

    以上就是Java多线程并发之ReentrantLock怎么使用的详细内容,更多关于Java多线程并发之ReentrantLock怎么使用的资料请关注九品源码其它相关文章!