什么是可重入锁 重试次数锁

1024人阅读
java(115)
可重入锁,也叫做递归锁,指的是同一线程外层函数获得锁之后,内层递归函数仍然有获取该锁的代码,但不受影响。
本文里面讲的是广义上的可重入锁,而不是单指JAVA下的ReentrantLock。
可重入锁,也叫做递归锁,指的是同一线程 外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但不受影响。
在JAVA环境下 ReentrantLock 和synchronized 都是 可重入锁。
下面是使用实例:
public class Test implements Runnable{
&public synchronized void get(){
&&System.out.println(Thread.currentThread().getId());
&public synchronized void set(){
&&System.out.println(Thread.currentThread().getId());
&@Override
&public void run() {
&public static void main(String[] args) {
&&Test ss=new Test();
&&new Thread(ss).start();
&&new Thread(ss).start();
&&new Thread(ss).start();
两个例子最后的结果都是正确的,即 同一个线程id被连续输出两次。
结果如下:
Threadid: 8
Threadid: 8
Threadid: 10
Threadid: 10
Threadid: 9
Threadid: 9
可重入锁最大的作用是避免死锁。
我们以自旋锁作为例子。
public class SpinLock {
&private AtomicReference&Thread& owner =new AtomicReference&&();
&public void lock(){
&&Thread current = Thread.currentThread();
&&while(!pareAndSet(null, current)){
&public void unlock (){
&&Thread current = Thread.currentThread();
&&pareAndSet(current, null);
对于自旋锁来说:
1、若有同一线程两调用lock() ,会导致第二次调用lock位置进行自旋,产生了死锁
说明这个锁并不是可重入的。(在lock函数内,应验证线程是否为已经获得锁的线程)
2、若1问题已经解决,当unlock()第一次调用时,就已经将锁释放了。实际上不应释放锁。
(采用计数次进行统计)
修改之后,如下:
public class SpinLock1 {
&private AtomicReference&Thread& owner =new AtomicReference&&();
&private int count =0;
&public void lock(){
&&Thread current = Thread.currentThread();
&&if(current==owner.get()) {
&&&count++;
&&while(!pareAndSet(null, current)){
&public void unlock (){
&&Thread current = Thread.currentThread();
&&if(current==owner.get()){
&&&if(count!=0){
&&&&count--;
&&&&pareAndSet(current, null);
该自旋锁即为可重入锁。
&&相关文章推荐
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:598181次
积分:5972
积分:5972
排名:第4150名
原创:144篇
转载:219篇
评论:72条
(3)(6)(3)(2)(8)(7)(1)(2)(20)(5)(17)(1)(18)(21)(12)(7)(2)(21)(16)(12)(7)(8)(6)(10)(13)(13)(1)(9)(25)(26)(19)(5)(21)(1)(2)(1)(3)(2)(4)(1)(3)java的可重入锁用在哪些场合? - 知乎36被浏览6243分享邀请回答public class SpinLock {
private AtomicReference&Thread& owner =new AtomicReference&&();
public void lock(){
Thread current = Thread.currentThread();
while(!pareAndSet(null, current)){
public void unlock (){
Thread current = Thread.currentThread();
pareAndSet(current, null);
对于自旋锁来说,1、若有同一线程两调用lock() ,会导致第二次调用lock位置进行自旋,产生了死锁说明这个锁并不是可重入的。(在lock函数内,应验证线程是否为已经获得锁的线程)2、若1问题已经解决,当unlock()第一次调用时,就已经将锁释放了。实际上不应释放锁。(采用计数次进行统计)public class SpinLock1 {
private AtomicReference&Thread& owner =new AtomicReference&&();
private int count =0;
public void lock(){
Thread current = Thread.currentThread();
if(current==owner.get()) {
while(!pareAndSet(null, current)){
public void unlock (){
Thread current = Thread.currentThread();
if(current==owner.get()){
if(count!=0){
pareAndSet(current, null);
修改之后,就可以重复进入代码区域了。自然上面的while死循环的线程CPU空转是非常不友好的。10添加评论分享收藏感谢收起0添加评论分享收藏感谢收起查看更多回答Java可重入锁原理 - 文章 - 伯乐在线
& Java可重入锁原理
本文首先介绍Lock接口、ReentrantLock的类层次结构以及锁功能模板类AbstractQueuedSynchronizer的简单原理,然后通过分析ReentrantLock的lock方法和unlock方法,来解释ReentrantLock的内部原理,最后做一个总结。本文不涉及ReentrantLock中的条件变量。
1.1、Lock接口
Lock接口,是对控制并发的工具的抽象。它比使用synchronized关键词更灵活,并且能够支持条件变量。它是一种控制并发的工具,一般来说,它控制对某种共享资源的独占。也就是说,同一时间内只有一个线程可以获取这个锁并占用资源。其他线程想要获取锁,必须等待这个线程释放锁。在Java实现中的ReentrantLock就是这样的锁。另外一种锁,它可以允许多个线程读取资源,但是只能允许一个线程写入资源,ReadWriteLock就是这样一种特殊的锁,简称读写锁。下面是对Lock接口的几个方法的总体描述:
获取锁,如果锁无法获取,那么当前的线程就变为不可被调度,直到锁被获取到
lockInterruptibly
获取锁,除非当前线程被中断。如果获取到了锁,那么立即返回,如果获取不到,那么当前线程变得不可被调度,一直休眠直到下面两件事情发生:1、当前线程获取到了锁
2、其他的线程中断了当前的线程
如果调用的时候能够获取锁,那么就获取锁并且返回true,如果当前的锁无法获取到,那么这个方法会立刻返回false
tryLcok(long time,TimeUnit unit)
在指定时间内尝试获取锁如果可以获取锁,那么获取锁并且返回true,如果当前的锁无法获取,那么当前的线程变得不可被调度,直到下面三件事之一发生:1、当前线程获取到了锁
2、当前线程被其他线程中断
3、指定的等待时间到了
释放当前线程占用的锁
newCondition
返回一个与当前的锁关联的条件变量。在使用这个条件变量之前,当前线程必须占用锁。调用Condition的await方法,会在等待之前原子地释放锁,并在等待被唤醒后原子的获取锁
接下来,我们将围绕lock和unlock这两个方法,来介绍整个ReentrantLock是怎么工作的。在介绍ReentrantLock之前,我们首先来看一下ReentrantLock的类层次结构以及和它密切相关的AbstractQueuedSynchronizer
1.2、ReentrantLock类层次结构
ReentrantLock实现了Lock接口,内部有三个内部类,Sync、NonfairSync、FairSync,Sync是一个抽象类型,它继承AbstractQueuedSynchronizer,这个AbstractQueuedSynchronizer是一个模板类,它实现了许多和锁相关的功能,并提供了钩子方法供用户实现,比如tryAcquire,tryRelease等。Sync实现了AbstractQueuedSynchronizer的tryRelease方法。NonfairSync和FairSync两个类继承自Sync,实现了lock方法,然后分别公平抢占和非公平抢占针对tryAcquire有不同的实现。
1.3、AbstractQueuedSynchronizer
首先,AbstractQueuedSynchronizer继承自AbstractOwnableSynchronizer,AbstractOwnableSynchronizer的实现很简单,它表示独占的同步器,内部使用变量exclusiveOwnerThread表示独占的线程。
其次,AbstractQueuedSynchronizer内部使用CLH锁队列来将并发执行变成串行执行。整个队列是一个双向链表。每个CLH锁队列的节点,会保存前一个节点和后一个节点的引用,当前节点对应的线程,以及一个状态。这个状态用来表明该线程是否应该block。当节点的前一个节点被释放的时候,当前节点就被唤醒,成为头部。新加入的节点会放在队列尾部。
二、 非公平锁的lock方法
2.1、lock方法流程图
2.2、lock方法详细描述
1、在初始化ReentrantLock的时候,如果我们不传参数是否公平,那么默认使用非公平锁,也就是NonfairSync。
2、当我们调用ReentrantLock的lock方法的时候,实际上是调用了NonfairSync的lock方法,这个方法先用CAS操作,去尝试抢占该锁。如果成功,就把当前线程设置在这个锁上,表示抢占成功。如果失败,则调用acquire模板方法,等待抢占。代码如下:
static final class NonfairSync extends Sync {
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
acquire(1);
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
123456789101112
static final class NonfairSync extends Sync {&&&&&&&&final void lock() {&&&&&&&&&&&&if (compareAndSetState(0, 1))&&&&&&&&&&&&&&&&setExclusiveOwnerThread(Thread.currentThread());&&&&&&&&&&&&else&&&&&&&&&&&&&&&&acquire(1);&&&&&&&&}&&&&&&&&&protected final boolean tryAcquire(int acquires) {&&&&&&&&&&&&return nonfairTryAcquire(acquires);&&&&&&&&}}
3、调用acquire(1)实际上使用的是AbstractQueuedSynchronizer的acquire方法,它是一套锁抢占的模板,总体原理是先去尝试获取锁,如果没有获取成功,就在CLH队列中增加一个当前线程的节点,表示等待抢占。然后进入CLH队列的抢占模式,进入的时候也会去执行一次获取锁的操作,如果还是获取不到,就调用LockSupport.park将当前线程挂起。那么当前线程什么时候会被唤醒呢?当持有锁的那个线程调用unlock的时候,会将CLH队列的头节点的下一个节点上的线程唤醒,调用的是LockSupport.unpark方法。acquire代码比较简单,具体如下:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
public final void acquire(int arg) {&&&&&&&&if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))&&&&&&&&&&&&selfInterrupt();}
3.1、acquire方法内部先使用tryAcquire这个钩子方法去尝试再次获取锁,这个方法在NonfairSync这个类中其实就是使用了nonfairTryAcquire,具体实现原理是先比较当前锁的状态是否是0,如果是0,则尝试去原子抢占这个锁(设置状态为1,然后把当前线程设置成独占线程),如果当前锁的状态不是0,就去比较当前线程和占用锁的线程是不是一个线程,如果是,会去增加状态变量的值,从这里看出可重入锁之所以可重入,就是同一个线程可以反复使用它占用的锁。如果以上两种情况都不通过,则返回失败false。代码如下:
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
else if (current == getExclusiveOwnerThread()) {
int nextc = c +
if (nextc & 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
123456789101112131415161718
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;&&&&&&&&}
3.2、tryAcquire一旦返回false,就会则进入acquireQueued流程,也就是基于CLH队列的抢占模式:
3.2.1、首先,在CLH锁队列尾部增加一个等待节点,这个节点保存了当前线程,通过调用addWaiter实现,这里需要考虑初始化的情况,在第一个等待节点进入的时候,需要初始化一个头节点然后把当前节点加入到尾部,后续则直接在尾部加入节点就行了。
代码如下:
private Node addWaiter(Node mode) {
// 初始化一个节点,这个节点保存当前线程
Node node = new Node(Thread.currentThread(), mode);
// 当CLH队列不为空的视乎,直接在队列尾部插入一个节点
Node pred =
if (pred != null) {
node.prev =
if (compareAndSetTail(pred, node)) {
pred.next =
// 当CLH队列为空的时候,调用enq方法初始化队列
enq(node);
private Node enq(final Node node) {
for (;;) {
if (t == null) { // 初始化节点,头尾都指向一个空节点
if (compareAndSetHead(new Node()))
} else {// 考虑并发初始化
node.prev =
if (compareAndSetTail(t, node)) {
1234567891011121314151617181920212223242526272829303132
private Node addWaiter(Node mode) {
// 初始化一个节点,这个节点保存当前线程&&&&&&&&Node node = new Node(Thread.currentThread(), mode);&&&&&&&&// 当CLH队列不为空的视乎,直接在队列尾部插入一个节点&&&&&&&&Node pred = tail;&&&&&&&&if (pred != null) {&&&&&&&&&&&&node.prev = pred;&&&&&&&&&&&&if (compareAndSetTail(pred, node)) {&&&&&&&&&&&&&&&&pred.next = node;&&&&&&&&&&&&&&&&return node;&&&&&&&&&&&&}&&&&&&&&}
// 当CLH队列为空的时候,调用enq方法初始化队列&&&&&&&&enq(node);&&&&&&&&return node;}&private Node enq(final Node node) {&&&&&&&&for (;;) {&&&&&&&&&&&&Node t = tail;&&&&&&&&&&&&if (t == null) { // 初始化节点,头尾都指向一个空节点&&&&&&&&&&&&&&&&if (compareAndSetHead(new Node()))&&&&&&&&&&&&&&&&&&&&tail = head;&&&&&&&&&&&&} else {// 考虑并发初始化&&&&&&&&&&&&&&&&node.prev = t;&&&&&&&&&&&&&&&&if (compareAndSetTail(t, node)) {&&&&&&&&&&&&&&&&&&&&t.next = node;&&&&&&&&&&&&&&&&&&&&return t;&&&&&&&&&&&&&&&&}&&&&&&&&&&&&}&&&&&&&&}}
3.2.2、将节点增加到CLH队列后,进入acquireQueued方法。
首先,外层是一个无限for循环,如果当前节点是头节点的下个节点,并且通过tryAcquire获取到了锁,说明头节点已经释放了锁,当前线程是被头节点那个线程唤醒的,这时候就可以将当前节点设置成头节点,并且将failed标记设置成false,然后返回。至于上一个节点,它的next变量被设置为null,在下次GC的时候会清理掉。
如果本次循环没有获取到锁,就进入线程挂起阶段,也就是shouldParkAfterFailedAcquire这个方法。
代码如下:
final boolean acquireQueued(final Node node, int arg) {
boolean failed =
boolean interrupted =
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = // help GC
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted =
} finally {
if (failed)
cancelAcquire(node);
123456789101112131415161718192021
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);&&&&&&&&}}
3.2.3、如果尝试获取锁失败,就会进入shouldParkAfterFailedAcquire方法,会判断当前线程是否挂起,如果前一个节点已经是SIGNAL状态,则当前线程需要挂起。如果前一个节点是取消状态,则需要将取消节点从队列移除。如果前一个节点状态是其他状态,则尝试设置成SIGNAL状态,并返回不需要挂起,从而进行第二次抢占。完成上面的事后进入挂起阶段。
代码如下:
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitS
if (ws == Node.SIGNAL)
if (ws & 0) {
node.prev = pred = pred.
} while (pred.waitStatus & 0);
pred.next =
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
1234567891011121314151617
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {&&&&&&&&int ws = pred.waitStatus;&&&&&&&&if (ws == Node.SIGNAL)&&&&&&&&&&&&//&&&&&&&&&&&&return true;&&&&&&&&if (ws & 0) {&&&&&&&&&&&&//&&&&&&&&&&&&do {&&&&&&&&&&&&&&&&node.prev = pred = pred.prev;&&&&&&&&&&&&} while (pred.waitStatus & 0);&&&&&&&&&&&&pred.next = node;&&&&&&&&} else {&&&&&&&&&&&&//&&&&&&&&&&&&compareAndSetWaitStatus(pred, ws, Node.SIGNAL);&&&&&&&&}&&&&&&&&return false;&&&&}
3.2.4、当进入挂起阶段,会进入parkAndCheckInterrupt方法,则会调用LockSupport.park(this)将当前线程挂起。代码:
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
private final boolean parkAndCheckInterrupt() {&&&&&&&&LockSupport.park(this);&&&&&&&&return Thread.interrupted();}
三、 非公平锁的unlock方法
3.1、unlock方法的活动图
3.2、unlock方法详细描述
1、调用unlock方法,其实是直接调用AbstractQueuedSynchronizer的release操作。
2、进入release方法,内部先尝试tryRelease操作,主要是去除锁的独占线程,然后将状态减一,这里减一主要是考虑到可重入锁可能自身会多次占用锁,只有当状态变成0,才表示完全释放了锁。
3、一旦tryRelease成功,则将CHL队列的头节点的状态设置为0,然后唤醒下一个非取消的节点线程。
4、一旦下一个节点的线程被唤醒,被唤醒的线程就会进入acquireQueued代码流程中,去获取锁。
具体代码如下:
unlock代码:
public void unlock() {
sync.release(1);
public void unlock() {&&&&&&&&sync.release(1);}
release方法代码:
public final boolean release(int arg) {
if (tryRelease(arg)) {
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
public final boolean release(int arg) {&&&&&&&&if (tryRelease(arg)) {&&&&&&&&&&&&Node h = head;&&&&&&&&&&&&if (h != null && h.waitStatus != 0)&&&&&&&&&&&&&&&&unparkSuccessor(h);&&&&&&&&&&&&return true;&&&&&&&&}&&&&&&&&return false;}
Sync中通用的tryRelease方法代码:
protected final boolean tryRelease(int releases) {
int c = getState() -
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free =
if (c == 0) {
setExclusiveOwnerThread(null);
setState(c);
123456789101112
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; }
unparkSuccessor代码:
private void unparkSuccessor(Node node) {
int ws = node.waitS
if (ws & 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.
if (s == null || s.waitStatus & 0) {
for (Node t = t != null && t != t = t.prev)
if (t.waitStatus &= 0)
if (s != null)
LockSupport.unpark(s.thread);
1234567891011121314
private void unparkSuccessor(Node node) {&&&&&&&&int ws = node.waitStatus;&&&&&&&&if (ws & 0)&&&&&&&&&&&&compareAndSetWaitStatus(node, ws, 0);&&&&&&&&Node s = node.next;&&&&&&&&if (s == null || s.waitStatus & 0) {&&&&&&&&&&&&s = null;&&&&&&&&&&&&for (Node t = tail; t != null && t != node; t = t.prev)&&&&&&&&&&&&&&&&if (t.waitStatus &= 0)&&&&&&&&&&&&&&&&&&&&s = t;&&&&&&&&}&&&&&&&&if (s != null) &&&&&&&&&&&&LockSupport.unpark(s.thread);}
四、 公平锁和非公平锁的区别
公平锁和非公平锁,在CHL队列抢占模式上都是一致的,也就是在进入acquireQueued这个方法之后都一样,它们的区别在初次抢占上有区别,也就是tryAcquire上的区别,下面是两者内部调用关系的简图:
NonfairSync
lock —& compareAndSetState
| —& setExclusiveOwnerThread
—& accquire
| —& tryAcquire
|—&nonfairTryAcquire
|—& acquireQueued
lock —& acquire
| —& tryAcquire
|—&!hasQueuePredecessors
|—&compareAndSetState
|—&setExclusiveOwnerThread
|—& acquireQueued
123456789101112131415
NonfairSynclock —& compareAndSetState&&&&&&&&&&&&&&&&| —& setExclusiveOwnerThread&&&&&&—& accquire
&&&& | —& tryAcquire&&&&&&&&&&&&&&&&&&&&&&&&&& |—&nonfairTryAcquire&&&&&&&&&&&&&&&&|—& acquireQueued&FairSynclock —& acquire&&&&&&&&&&&&&& | —& tryAcquire&&&&&&&&&&&&&&&&&&&&&&&&&& |—&!hasQueuePredecessors&&&&&&&&&&&&&&&&&&&&&&&&&& |—&compareAndSetState&&&&&&&&&&&&&&&&&&&&&&&&&& |—&setExclusiveOwnerThread&&&&&&&&&&&&&& |—& acquireQueued
真正的区别就是公平锁多了hasQueuePredecessors这个方法,这个方法用于判断CHL队列中是否有节点,对于公平锁,如果CHL队列有节点,则新进入竞争的线程一定要在CHL上排队,而非公平锁则是无视CHL队列中的节点,直接进行竞争抢占,这就有可能导致CHL队列上的节点永远获取不到锁,这就是非公平锁之所以不公平的原因。
线程使用ReentrantLock获取锁分为两个阶段,第一个阶段是初次竞争,第二个阶段是基于CHL队列的竞争。在初次竞争的时候是否考虑队列节点直接区分出了公平锁和非公平锁。在基于CHL队列的锁竞争中,依靠CAS操作保证原子操作,依靠LockSupport来做线程的挂起和唤醒,使用队列来保证并发执行变成了串行执行,从而消除了并发所带来的问题。总体来说,ReentrantLock是一个比较轻量级的锁,而且使用面向对象的思想去实现了锁的功能,比原来的synchronized关键字更加好理解。
关于作者:
可能感兴趣的话题
关于伯乐在线博客
在这个信息爆炸的时代,人们已然被大量、快速并且简短的信息所包围。然而,我们相信:过多“快餐”式的阅读只会令人“虚胖”,缺乏实质的内涵。伯乐在线内容团队正试图以我们微薄的力量,把优秀的原创文章和译文分享给读者,为“快餐”添加一些“营养”元素。
新浪微博:
推荐微信号
(加好友请注明来意)
– 好的话题、有启发的回复、值得信赖的圈子
– 分享和发现有价值的内容与观点
– 为IT单身男女服务的征婚传播平台
– 优秀的工具资源导航
– 翻译传播优秀的外文文章
– 国内外的精选文章
– UI,网页,交互和用户体验
– 专注iOS技术分享
– 专注Android技术分享
– JavaScript, HTML5, CSS
– 专注Java技术分享
– 专注Python技术分享
& 2017 伯乐在线匿名用户不能发表回复!|
每天回帖即可获得10分可用分!小技巧:
你还可以输入10000个字符
(Ctrl+Enter)
请遵守CSDN,不得违反国家法律法规。
转载文章请注明出自“CSDN(www.csdn.net)”。如是商业用途请联系原作者。4034人阅读
java并发(8)
相信学过java的人都知道 synchronized 这个关键词,也知道它用于控制多线程对并发资源的安全访问,兴许,你还用过Lock相关的功能,但你可能从来没有想过java中的锁底层的机制是怎么实现的。如果真是这样,而且你有兴趣了解,今天我将带领你轻松的学习下java中非常重要,也非常基础的可重入锁-ReentrantLock的实现机制。
听故事把知识掌握了
在一个村子里面,有一口井水,水质非常的好,村民们都想打井里的水。这井只有一口,村里的人那么多,所以得出个打水的规则才行。村长绞尽脑汁,最终想出了一个比较合理的方案,咱们来仔细的看看聪明的村长大人的智慧。
井边安排一个看井人,维护打水的秩序。
打水时,以家庭为单位,哪个家庭任何人先到井边,就可以先打水,而且如果一个家庭占到了打水权,其家人这时候过来打水不用排队。而那些没有抢占到打水权的人,一个一个挨着在井边排成一队,先到的排在前面。打水示意图如下 :
是不是感觉很和谐,如果打水的人打完了,他会跟看井人报告,看井人会让第二个人接着打水。这样大家总都能够打到水。是不是看起来挺公平的,先到的人先打水,当然不是绝对公平的,自己看看下面这个场景 :
看着,一个有娃的父亲正在打水,他的娃也到井边了,所以女凭父贵直接排到最前面打水,羡煞旁人了。
以上这个故事模型就是所谓的公平锁模型,当一个人想到井边打水,而现在打水的人又不是自家人,这时候就得乖乖在队列后面排队。
事情总不是那么一帆风顺的,总会有些人想走捷径,话说看井人年纪大了,有时候,眼力不是很好,这时候,人们开始打起了新主意。新来打水的人,他们看到有人排队打水的时候,他们不会那么乖巧的就排到最后面去排队,反之,他们会看看现在有没有人正在打水,如果有人在打水,没辄了,只好排到队列最后面,但如果这时候前面打水的人刚刚打完水,正在交接中,排在队头的人还没有完成交接工作,这时候,新来的人可以尝试抢打水权,如果抢到了,呵呵,其他人也只能睁一只眼闭一只眼,因为大家都默认这个规则了。这就是所谓的非公平锁模型。新来的人不一定总得乖乖排队,这也就造成了原来队列中排队的人可能要等很久很久。
java可重入锁-ReentrantLock实现细节
ReentrantLock支持两种获取锁的方式,一种是公平模型,一种是非公平模型。在继续之前,咱们先把故事元素转换为程序元素。
咱们先来说说公平锁模型:
初始化时, state=0,表示无人抢占了打水权。这时候,村民A来打水(A线程请求锁),占了打水权,把state+1,如下所示:
线程A取得了锁,把 state原子性+1,这时候state被改为1,A线程继续执行其他任务,然后来了村民B也想打水(线程B请求锁),线程B无法获取锁,生成节点进行排队,如下图所示:
初始化的时候,会生成一个空的头节点,然后才是B线程节点,这时候,如果线程A又请求锁,是否需要排队?答案当然是否定的,否则就直接死锁了。当A再次请求锁,就相当于是打水期间,同一家人也来打水了,是有特权的,这时候的状态如下图所示:
到了这里,相信大家应该明白了什么是可重入锁了吧。就是一个线程在获取了锁之后,再次去获取了同一个锁,这时候仅仅是把状态值进行累加。如果线程A释放了一次锁,就成这样了:
仅仅是把状态值减了,只有线程A把此锁全部释放了,状态值减到0了,其他线程才有机会获取锁。当A把锁完全释放后,state恢复为0,然后会通知队列唤醒B线程节点,使B可以再次竞争锁。当然,如果B线程后面还有C线程,C线程继续休眠,除非B执行完了,通知了C线程。注意,当一个线程节点被唤醒然后取得了锁,对应节点会从队列中删除。
非公平锁模型
如果你已经明白了前面讲的公平锁模型,那么非公平锁模型也就非常容易理解了。当线程A执行完之后,要唤醒线程B是需要时间的,而且线程B醒来后还要再次竞争锁,所以如果在切换过程当中,来了一个线程C,那么线程C是有可能获取到锁的,如果C获取到了锁,B就只能继续乖乖休眠了。这里就不再画图说明了。
其它知识点
java5中添加了一个并发包, java.util.concurrent,里面提供了各种并发的工具类,通过此工具包,可以在java当中实现功能非常强大的多线程并发操作。对于每个java攻城狮,我觉得非常有必要了解这个包的功能。虽然做不到一步到位,但慢慢虚心学习,沉下心来,总能慢慢领悟到java多线程编程的精华。
可重入锁的实现会涉及到CAS,AQS,java内存可见性(volatile)等知识,为了避免大家直接被代码搞晕,故而想以最简单的方式把可重入锁进行抽象,讲明白其中的实现原理,这样看起源码也有个借鉴的思路,希望本篇能够帮助到你们。
&&相关文章推荐
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:28300次
排名:千里之外
原创:34篇
评论:20条
(3)(1)(7)(1)(3)(4)(3)(1)(2)(1)(4)(2)(1)(1)}

我要回帖

更多关于 JAVA 重入锁 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信