【Java】重入锁 实现原理
来源:互联网 发布:淘宝密码对的登不上 编辑:程序博客网 时间:2024/06/03 12:53
ReentrantLock 是java继synchronized关键字之后新出的线程锁,今天看了看实现源码。主要是通过自旋来实现的。使用自旋的基本思路就是为所有的线程构建一个node,连成一个队列,然后每一个node都轮询前驱节点,如果前驱已经释放锁了,那么当前阶段就可以获取锁,否则就继续循环。
之前了解过使用ThreadLocal机制实现的自旋锁,但是reentrant lock应该是另一种。
/** * Performs lock. Try immediate barge, backing up to normal * acquire on failure. */ final void lock() { if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); }这是加锁方法,先进行一次快速加锁,失败了再进行常规加锁。快速加锁的情景指的是当前没有锁,所以直接CAS原子操作看看能不能获取,也就是if块里的操作,如果没有成功,常规获取,也就是acquire操作。
public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }首先执行一次tryAcquire()尝试获取,分为公平和非公平,这里就只看非公平的情况吧
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; }首先获取当前线程,然后得到锁此时的state,如果state是0,说明可以争取锁,CAS一下,否则说明锁被用了,但是如果用锁的就是当前线程,就把state加1,获取成功,否则就获取失败。
一旦tryAcquire()返回了false,说明获取锁失败了,就必须进入等待队列中,所以会执行后面的acquireQueued(addWaiter)方法。先看addWaiter
private Node addWaiter(Node mode) { Node node = new Node(Thread.currentThread(), mode); // Try the fast path of enq; backup to full enq on failure Node pred = tail; if (pred != null) { node.prev = pred; if (compareAndSetTail(pred, node)) { pred.next = node; return node; } } enq(node); return node; }addWaiter所做的是把当前线程加入到等待队列中,也就是把锁的tail变量设置为当前线程,也是先快设置一次,也就是一次CAS,如果成功就返回,否则就执行一次常规加入,即enq操作
private Node enq(final Node node) { for (;;) { Node t = tail; if (t == null) { // Must initialize if (compareAndSetHead(new Node())) tail = head; } else { node.prev = t; if (compareAndSetTail(t, node)) { t.next = node; return t; } } } }enq就是典型的自旋+CAS的实现,因为CAS控制并发是非阻塞的,所以如果一定要让操作执行,必须把CAS放入循环内。所以enq就是一个while循环,不断检测CAS是否成功
一旦加入队列了,剩下的就是执行acquireQueued方法,既然进入队列了,为什么还要执行这个方法?因为需要让这个新加入的节点自旋,也就是让其进入等待状态,或者说让它循环等待前驱阶段是否释放锁
final boolean acquireQueued(final Node node, int arg) { boolean failed = true; try { boolean interrupted = false; for (;;) { final Node p = node.predecessor(); if (p == head && tryAcquire(arg)) { setHead(node); p.next = null; // help GC failed = false; return interrupted; } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); } }可以看到,确实有一个循环,不断检测前驱节点,如果前驱是head(这是一个dump头节点),说明自己已经是真正的头节点了,可以互获锁了,就会持续执行tryAcquire去竞争。这个队列的消费是直接把消费的节点从队列删除,而之前博客的CLH是通过节点的一个状态字来检测的。
可以看到,整个重入锁就是通过自旋+CAS来实现的
获取锁的大致过程如下:
执行一次CAS获取,若不成功则
执行一次tryAcquire获取,若不成功则
把当前节点加入到队列,也是先CAS加入一次,不成功再自旋
自旋检测前驱是否释放锁,并尝试获取
与自旋锁相对应的概念是互斥锁,等待时是通过让线程睡眠实现的。自旋锁适用于占用锁时间比较短的场景,这样线程状态转换的开销相比较与cpu循环,代价会变大,但是随着锁获取时间的增长,cpu的代价会越来越大
阅读全文
0 0
- 【Java】重入锁 实现原理
- Java多线程--重入锁的实现原理
- Java HashMap实现原理
- java 监听器实现原理
- Java过滤器实现原理
- Java HashMap实现原理
- java 监听器实现原理
- java map实现原理
- java实现hashmap原理
- Java HashMap实现原理
- Java集合实现原理
- java forEach实现原理
- java注解实现原理
- java HASHMAP 实现原理
- java forEach实现原理
- java forEach实现原理
- java forEach实现原理
- JAVA HashMap 实现原理
- jquery-尺寸方法
- discuz如何设置上传附件大小及类型
- MySQL数据库索引的4大类型以及相关的索引创建
- BZOJ1227: [SDOI2009]虔诚的墓主人
- windows环境下redis集群的安装配置
- 【Java】重入锁 实现原理
- 剑指offer第三章
- JDBC
- UVA
- jquery过滤选择器
- jsp动态生成静态网页
- Excel2016超绚功能—从web端获取数据
- BZOJ2006: [NOI2010]超级钢琴
- SAP ABAP转换特殊字符乱码