作为关键字synchronized的替代品(或者说是增强版),重入锁是synchronized的功能扩展。在JDK 1.5的早期版本中,重入锁的性能远远好于synchronized,但从JDK 1.6开始,JDK优化了synchronized,使两者性能差距不大。重入锁使用java.util.concurrent.locks.ReentrantLock类来实现。

  使用重入锁可以指定何时加锁和何时释放锁,对逻辑控制的灵活性远远好于synchronized,退出临界区时必须释放锁。之所以称为重入锁,是因为一个线程可以连续获得这种锁。而synchronized隐式地支持重进入。具体而言,重进入指线程在获取锁之后能再次获取该锁而不会被锁阻塞,该特性的实现需要解决两个问题:

  1 线程再次获取锁

  锁需要识别获取锁的线程是否为当前占有锁的线程,如果是,则再次成功获取。

  2 锁的最终释放

  线程重复n次获取了锁,随后在第n次释放该锁后,其他线程能够获取到该锁。锁的最终释放要求获取锁需要计数自增,计数表示锁被重复获取的次数,而锁被释放时计数自减,当计数为0时表示锁已经最终成功释放。

  ReentrantLock通过组合自定义同步器实现锁的获取与释放,以非公平性(默认)实现为例,获取同步状态代码如下:

 /**
* Performs non-fair tryLock. tryAcquire is implemented in
* subclasses, but both need nonfair try for trylock method.
*/
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;
}

  再次获取同步方法的处理逻辑:通过判断当前线程是否为获取锁的线程来决定获取操作是否成功,如果是获取锁的线程再次请求,则增加同步状态值并返回true,表示获取同步状态成功。也就是说,成功获取锁的线程再次获取锁,只是增加了同步状态值,这也要求ReentrantLock在释放同步状态时减少同步状态值,释放同步状态代码如下:

 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;
}

  如果锁被获取了n次,那么前n-1次tryRelease(int releases)方法必须返回false。也就是说,只有同步状态完全释放了,该方法才能返回true。该方法将同步状态是否为0作为最终释放的条件,当同步状态为0时,将占有线程设置为null,并返回true,表示释放成功。

  简单的重入锁使用例子如下:

  

  修改上述代码第7-12行:

  

  在这种情况下,一个线程连续两次获得同一把锁。如果不允许该操作,那么同一个线程在第2次请求获得锁时,会和自己产生死锁,程序卡死在第2次申请锁的过程中。如果同一个线程多次获得锁,那么释放锁的次数与其相同。如果释放锁的次数多,那么会有java.lang.IllegalMonitorStateException异常;反之,如果释放锁的次数少了,那么该线程依然持有这个锁。

  除了使用上的灵活性之外,重入锁还提供了一些高级功能:

  1 中断响应

  对于synchronized,如果一个线程在等待锁,那么结果只有两种可能:要么获得这把锁继续执行,要么保持等待。而使用重入锁可以提供另外一种可能:线程可以被中断。也就是说,线程在等待锁的过程中,程序可以根据需要取消对锁的请求。例如,小王与小明一起去打球,如果小王等了10分钟,小明还没到,接到小明的电话了解到由于突发情况不能如约了,那么小王就扫兴地打道回府了。对于线程而言,如果一个线程正在等待锁,那么它可以收到一个通知,被告知无需再等待,可以停止工作了。这种情况对于处理死锁是有一定帮助的。

  2 锁申请等待限时

  除了等待外部通知之外,避免死锁还有另外一种方法:限时等待。继续上面的例子,如果小王等了半个小时,小明还不来,那么小王就扫兴离去了。对于线程而言,如果给定一个等待时间,让线程自动放弃,那么可以避免线程持续等待。

  3 公平锁

  在大多数情况下,锁的申请都是非公平的。只要CAS设置同步状态成功,则表示当前线程获得了锁。也就是说,线程1首先申请了锁A,接着线程2也申请了锁A,那么当锁A可用时,哪个线程获得锁是不一定的,系统会从锁A的等待队列中随机选择一个,不能保证公平性。如同买票不排队,大家乱哄哄地围在售票窗口前,售票员忙得焦头烂额,顾不得谁先谁后,随便找个人出票就完事了。而公平锁保证锁的获取遵守FIFO原则即先到先得,不会产生饥饿现象,代价是大量的线程切换。synchronized产生的锁是非公平的,而重入锁可以设置是否公平。公平的重入锁要求系统维护一个有序队列,实现成本比较高,性能较低。所以,默认情况下的锁是非公平的。

  ReentrantLock类常见方法如下:

  1 public ReentrantLock(boolean fair)

  当参数fair是true时,表示该重入锁是公平的。

  2 lock()

  获取锁,如果锁已被占用,则等待。

  3 lockInterruptibly()

  获取锁,但优先响应中断。

  4 tryLock()

  尝试获取锁,如果成功,返回true;否则,返回false。该方法不等待,立即返回。

  5 tryLock(long time, TimeUnit unit)

  在给定时间内尝试获取锁。

  6 unlock()

  释放锁。

  重入锁的实现包含3个要素:

  1 原子状态

  原子状态使用CAS操作来存储当前锁的状态,判断锁是否已被其他线程持有。

  2 等待队列

  所有没有获取到锁的线程会进入等待队列进行等待。当有线程释放锁后,系统就从等待队列中唤醒一个线程,继续工作。

  3 阻塞原语park()和unpark()

  挂起和恢复线程,没有获取到锁的线程会被挂起。

  

  参考资料

  《实战Java高并发程序设计》 P71-80

  《Java并发编程的艺术》 5.3 重入锁

Java 多线程 重入锁的更多相关文章

  1. 轻松学习java可重入锁(ReentrantLock)的实现原理

    转载自https://blog.csdn.net/yanyan19880509/article/details/52345422,(做了一些补充) 前言 相信学过java的人都知道 synchroni ...

  2. java 可重入锁ReentrantLock的介绍

    一个小例子帮助理解(我们常用的synchronized也是可重入锁) 话说从前有一个村子,在这个村子中有一口水井,家家户户都需要到这口井里打水喝.由于井水有限,大家只能依次打水.为了实现家家有水喝,户 ...

  3. 轻松学习java可重入锁(ReentrantLock)的实现原理(转 图解)

    前言 相信学过java的人都知道 synchronized 这个关键词,也知道它用于控制多线程对并发资源的安全访问,兴许,你还用过Lock相关的功能,但你可能从来没有想过java中的锁底层的机制是怎么 ...

  4. Java不可重入锁和可重入锁的简单理解

    基础知识 Java多线程的wait()方法和notify()方法 这两个方法是成对出现和使用的,要执行这两个方法,有一个前提就是,当前线程必须获其对象的monitor(俗称“锁”),否则会抛出Ille ...

  5. Java可重入锁如何避免死锁

    本文由https://bbs.csdn.net/topics/390939500和https://zhidao.baidu.com/question/1946051090515119908.html启 ...

  6. java可重入锁reentrantlock

    public class ReentrantDemo { //重入锁 保护临界区资源count,确保多线程对count操作的安全性 /*public static ReentrantLock rtlo ...

  7. Java 可重入锁

    一般意义上的可重入锁就是ReentrantLock http://www.cnblogs.com/hongdada/p/6057370.html 广义上的可重入锁是指: 可重入锁,也叫做递归锁,指的是 ...

  8. Java 可重入锁的那些事(一)

    本文主要包含的内容:可重入锁(ReedtrantLock).公平锁.非公平锁.可重入性.同步队列.CAS等概念的理解 显式锁 上一篇文章提到的synchronized关键字为隐式锁,会自动获取和自动释 ...

  9. Java可重入锁与不可重入锁

    可重入锁,指的是以线程为单位,当一个线程获取对象锁之后,这个线程可以再次获取本对象上的锁,而其他的线程是不可以的. synchronized 和   ReentrantLock 都是可重入锁. 可重入 ...

随机推荐

  1. MySql中 delimiter 详解

    转载于:http://blog.csdn.net/yuxin6866/article/details/52722913 其实就是告诉MySQL解释器,该段命令是否已经结束了,mysql是否可以执行了. ...

  2. javascript 列表定时滚动效果

    HTML结构: <div style="width:200px;height:100px;overflow:hidden;border:1px solid #ddd;margin:20 ...

  3. java 环境安装

    因为现在的java安装需要点击同意许可才能安装,造成了在linux中使用wget命令下载无法正常完整下载,即便下载下来也是不完整的 安装会提示 no such file等一堆提示 我们使用参数如下的命 ...

  4. 【Android】Android 广播大全

    [Android]Android 广播大全 String ADD_SHORTCUT_ACTION 动作:在系统中添加一个快捷方式. String ALL_APPS_ACTION 动作:列举所有可用的应 ...

  5. nginx配置2

    第四节 nginx 配置文件 1 keepalive_timeout   65;  设定保存长久连接时长 0代表禁止, 若不设置默认是75s 2keepalive_requests   nu;  在一 ...

  6. ES6新特性:使用export和import实现模块化(转载)

    在ES6前, 前端就使用RequireJS或者seaJS实现模块化, requireJS是基于AMD规范的模块化库,  而像seaJS是基于CMD规范的模块化库,  两者都是为了为了推广前端模块化的工 ...

  7. 【转】科大校长给数学系学弟学妹的忠告&本科数学参考书

    1.老老实实把课本上的题目做完.其实说科大的课本难,我以为这话不完整.科大的教材,就数学系而言还是讲得挺清楚的,难的是后面的习题.事实上做1道难题的收获是做10道简单题所不能比的. 2.每门数学必修课 ...

  8. linux中通过lsof恢复删除的文件,前题是fd被占用。

    http://www.serverwatch.com/tutorials/article.php/3822816/Recovering-Deleted-Files-With-lsof.htm One ...

  9. 045 RDD与DataFrame互相转换

    一:RDD与DataFrame互相转换 1.总纲 二:DataFrame转换为RDD 1.rdd 使用schema可以获取DataFrame的schema 使用rdd可以获取DataFrame的数据 ...

  10. Python 2 和 3 的区别记录

    Python 2 和 3 的区别记录 print 2:关键字,可以 print a,也可以 print(a) 3:内置函数,必须带(),print(a) reload() 2:内置函数,可以直接使用 ...