ReetrantLock

ReentrantLock的概念和结构

概念

  • ReentrantLock基于AQS,在并发编程中可以实现公平锁和非公平锁来对共享资源进行同步,同时,和synchronized一样,ReentrantLock支持可重入,除此之外,ReetrantLock在调度上更加灵活,支持更多丰富的功能。

结构

  • ReetrantLock实现Lock接口,同时里面包含三个内部类,Sync、FairSync、NonfairSync
  • Sync继承AQS,除了lock()、readObject()方法其他的都用final修饰,防止子类修改,是对AQS的封装和拓展,本身实现已完全可靠、不希望被外部破坏;
  • FairSync、NonfairSync继承Sync,都只重写Sync的lock()、AQS的tryAcquire()方法


公平锁与非公平锁

构造函数

  • 默认采用非公平的方式;
  • true是公平、false是非公平
public ReentrantLock() {
    sync = new NonfairSync();
}

public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

说明

  • FairSync:只要资源被其他线程占用,该线程就会添加到sync queue中的尾部,而不是先尝试获取资源;
  • NonFairSync:每一次都尝试获取资源,如果此时资源恰好被释放,则会被当前线程获取,造成不公平现象,当获取不成功,再加入队列尾部。


Lock类的常见方法

lock.lock()

  • 用于获取锁,如果当前锁被其他线程占用,将会等待直到获取为止。
流程
  1. 根据传入具体的实体类,判断调用FairSync.lock() 或者 NonfairSync.lock();
  2. 通过CAS尝试获取锁,失败调用AQS的acquire();
  3. AQS的acquire()会首先调用tryAcquire()、对应有不同的实体类重写FairSync.tryAcquire() 或者 NonfairSync.tryAcquire();
  4. FairSync.tryAcquire() 会直接返回Sync的nonfairTryAcquire(),里面获取state,如果为0,进行CAS尝试锁,不为0,如果当前线程就是占有锁的线程,进行重入,使用nextc记录重入次数
  5. 如果tryAcquire()失败,调用 addWaiter() 将当前线程封装成Node,加入队列中;acquireQueued() 将队列的head之后的一个节点在自旋CAS获取锁,其他线程都被挂起。挂起的线程等待执行release()释放锁资源

以NonfairSync为例的详解
  • sync通过构造函数传入初始化,判断是new FairSync 还是 new NonfairSync
public void lock() {
        sync.lock();
    }

NonfairSync的lock方法

  • 首先尝试一次对锁的获取,如果CAS成功,那么当前线程成功获得锁,同时将当前线程置为独占线程,并返回true;
  • 否则,调用AQS的acquire方法;
final void lock() {
            //  state为0,表示锁状态为空闲
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

AQS的acquire方法

  • 首先会调用tryAcquire()、子类NonfairSync重写了此方法;
  • 如果失败调用addWaiter(),将调用此方法的线程封装成为一个结点并放入Sync queue;
  • 调用acquireQueued(),将在sync queue中的结点不断尝试获取资源,如果成功返回true,失败返回false。
public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

NonfairSync.tryAcquire() 直接返回 Sync的nonfairTryAcquire()

  • 获取state,该值由AQS维护;
  • 当state为0,表示锁状态空闲,便可以通过一次CAS来原子地更改state,如果state更改成功,则代表获取了锁,将当前线程置为独占线程,并返回true;
  • 当state不为0,说明锁被占用,判断当前线程是否为独占线程(占用锁的线程);如果是,则代表重入,使用nextc记录重入次数;

补充:

判断state是否小于0的意义?

  • state使用int类型存储,最大的有符号数为2147483647,一旦超出便会溢出变为负数;也可以理解为,ReetrantLock允许重入的最大次数其实就是2147483647

可重入性?

  • 一个线程可以不用释放而重复获取一个锁n次,只是在释放的时候也需要相应的释放n次。
// NonfairSync
protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }

// Sync
final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
补充:fairSync.lock()、tryAcquire()的区别
  • 判断锁是否空闲,如果空闲,并不是直接尝试通过CAS获取锁,而是需要判断是否存在前置等待结点。如果不存在,说明队列中确实已经轮到当前线程尝试获取锁,体现了公平性。
        final void lock() {
                    acquire(1);
                }



        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

lock.tryLock()

  • 尝试获取锁、tryLock()的操作都是非公平的,不管传入的是公平还是非公平
  • nonfairTryAcquire()具体详解上面讲lock()的时候已经涉及到了。
// ReentrantLock
public boolean tryLock() {
        return sync.nonfairTryAcquire(1);
    }

// Sync
final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

lock.unlock()

  • 释放锁,tryRelease()需要完全释放;如果遇到重入的情况,可能会返回释放多次。
// ReentrantLock
public void unlock() {
        sync.release(1);
    }

// AQS
 public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

// Syn
protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }

lock.lockInterruptibly()

用于获取锁,如果当前线程在等待锁的过程中被中断,会退出等待并抛出异常。

lock.newCondition()

创建condition对象,相当于Object类的wait、notify方法


参考
hshuo的面试之路 文章被收录于专栏

作者目标是找到一份Java后端方向的工作 此专栏用来记录从Bilibili、书本、其他优质博客上面学习的内容 用于巩固、总结内容 主要包含Docker、Dubbo、Java基础、JUC、Maven、MySQL、Redis、SpringBoot、SpringCloud、数据结构、杂文、算法、计算机网络、操作系统、设计模式等相关内容

全部评论

相关推荐

xdm&nbsp;早上喝奶茶差点喷出来。事情是这样的,我们班有个哥们儿,简称&nbsp;L,去年秋招拿了字节sp,专业方向是后端。我们当时都震惊:这哥们儿平时课上从来不发言,期末小组作业基本是划水的那种,刷题平台&nbsp;commit记录我点进去看过,绿格子稀稀拉拉。但他面试一路绿灯。一面二面三面&nbsp;hr&nbsp;面,全过,给的还是sp。当时班级群里恭喜他的、问他经验的、约饭的,热闹了一周。他说自己"运气好,准备充分"。我们都信了,直到三月初他入职。入职第二周开始,班里另一个进字节的同学W(在隔壁组的)开始跟我他的不对劲。一开始是写代码慢,后来写不出来,再后来是组里&nbsp;mentor&nbsp;让他fix&nbsp;一个简单&nbsp;bug&nbsp;都搞了一下午没动静。最离谱的是上周。W&nbsp;说他们大部门搞了个新人分享会,让新人讲一下自己负责模块的设计思路。L&nbsp;上去讲了&nbsp;20分钟,全程念稿子,问答环节别人随便问一个"那你这里为什么用&nbsp;Redis&nbsp;不用&nbsp;Memcached",他直接卡&nbsp;30秒说"这个我回去再确认一下"。会后他&nbsp;mentor&nbsp;直接找&nbsp;leader&nbsp;谈,leader&nbsp;找&nbsp;hr&nbsp;谈,hr调出了他面试录像,全程对比口型和回答节奏,发现他二三面有大量时长在偷偷看屏幕外(推测开了双机位&nbsp;AI&nbsp;答题)。(这段是&nbsp;W后来转述给我的,他自己也是听他组里同事八卦来的)昨天下班前,W&nbsp;告诉我L&nbsp;被辞退了,让他自己走,不走就走仲裁但会发函到学校。L&nbsp;现在已经回学校了,朋友圈仅三天可见。我说真的,我不是个心眼小的人,但是我看到这个消息的时候真的有种"嗯,挺好"的感觉。去年秋招我投字节后端,简历挂。我准备了八个月,背&nbsp;八股&nbsp;+&nbsp;刷&nbsp;500&nbsp;题&nbsp;+项目改了三版,连面试机会都没拿到。班里这哥们儿凭着一个外挂上岸,最后还是被甩出来了。不是说作弊就一定会被发现,但是当面试拿到的&nbsp;offer远远超出真实能力的时候,迟早会有这一天。试用期三个月不是给你过家家的,是真的要写代码、要在会议上回答问题、要扛需求的。我现在反而有点同情他。同情他相信"上岸就是终点"。发出来不是为了嘲笑谁,就是想说给那些正在被身边作弊上岸的同学搞得很&nbsp;emo&nbsp;的&nbsp;uu&nbsp;们听——别急,回旋镖很长,但它一定会回来。你继续刷你的题,写你的项目,背你的八股。该是你的迟早是你的,不是你的早晚还得还回去。xdm&nbsp;共勉。
牛客12588360...:我不想评论面试方式,作弊是绝对不对的,但是你八股加刷题也不过是个做题小子,他穿帮纯粹是他菜,你也没有高明到哪里去
点赞 评论 收藏
分享
评论
1
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务