本部分主要参考《java并发编程艺术》一书相关内容,同时参考https://blog.csdn.net/zhilinboke/article/details/83104597,说的非常形象。
  重入锁就是支持重入的锁,它表示该锁支持一个线程对资源的重复加锁。比如之前的在读AQS时的Mutex,在lock之后如果再次调用lock(),就会造成线程阻塞,这就不是重入锁。除此以外,ReentrantLock还可以支持公平锁跟非公平锁。对于这两种锁的支持,ReentrantLock实际上是内建了两个锁来分别实现的这两种锁,一个叫FairSync(公平锁),一个叫Sync(非公平锁)。
1、源码解读
  对于非公平锁,获取同步状态的方法如下:
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); // 是当前线程占用,把同步状态数值累加(实际上多数是+1)
return true;
}
return false;
}
  释放同步状态的代码,逻辑一样简单:

protected final boolean tryRelease(int releases) {
int c = getState() - releases; // 每退出一层,就减去相应的数字
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) { //同步状态变为0,说明全部释放了
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}

  公平锁的获取,跟非公平类似,仅有一个地方不同:

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;
}
  锁的释放跟非公平锁一样。
  通过以上逻辑,我们不难看出:公平锁跟非公平锁的区别就是获取同步资源的时候,公平锁要看一下是否有人排队,有则自己也排队;非公平锁的处理逻辑是不管有没人排队,先抢位置,抢到了就不管排队了,抢不到则跟公平锁一样进入排队;而对于排队队列里边的线程来说,公平锁跟非公平锁并没有什么区别,接下来都是按照排队顺序进行获取同步状态的。

2、测试与思考
  对书中的测试程序作了轻微改动,让两个线程尽量无序的各自启动多个线程,然后观察执行顺序:

public class FairAndUnfairTest {
private static Lock fairLock = new ReentrantLock2(true);
private static Lock unfairLock = new ReentrantLock2(false);
@Test
public void fair() throws Exception {
System.out.println("公平锁");
testLock(fairLock);
}
@Test
public void unfair() throws Exception {
System.out.println("非公平锁");
testLock(unfairLock);
}
public void testLock(Lock lock) throws Exception {
new Thread(new Runnable() {
@Override
public void run() {
for(int i=1; i<6; i++){
Thread thread = new Job(lock);
thread.setName(""+i+"");
thread.start();
mySleep(500);
}
}
}).start();
Thread.sleep(500);
new Thread(new Runnable() {
@Override
public void run() {
for(int i=6; i<11; i++){
Thread thread = new Job(lock);
thread.setName(""+i+"");
thread.start();
mySleep(500);
}
}
}).start(); Thread.sleep(11000);
} private static class Job extends Thread{
private Lock lock;
public Job(Lock lock){
this.lock = lock;
} public void run(){
for(int i=0; i<2; i++){
lock.lock();
try {
Thread.sleep(500);
System.out.println("lock by [" + Thread.currentThread().getName() + "], waiting by " + ((ReentrantLock2)lock).getQueuedThread());
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.unlock();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
} public static class ReentrantLock2 extends ReentrantLock{
public ReentrantLock2(boolean fair){
super(fair);
} public Collection<String> getQueuedThread(){
List<Thread> arrayList = new ArrayList<Thread>(super.getQueuedThreads());
Collections.reverse(arrayList);
List<String> list = new ArrayList<>();
for(Thread thread:arrayList){
list.add(thread.getName());
}
return list;
}
} private void mySleep(int time){
try {
Thread.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
  公平锁执行结果:

  可以看到每一个都是执行队列中的head节点对应的线程,对于非公平锁执行结果之一:

  可以看到排进队列的还是按照顺序执行的,但有偶尔被9之类的线程“插队”的情况。
  去掉第58-61几行的扰乱代码,非公平锁结果如下:

  可以看到非常整齐,每个线程连续执行了两次,书上的结论是:“(非公平锁)当一个线程请求锁时,只要获取了同步状态即成功获取锁。在这个前提下,刚释放锁的线程再次获取同步状态的几率会非常大,使得其他线程只能在同步队列中等待。”
  这个,,,小的愚钝,读了几遍代码,没觉得能得出这个结论。个人觉得非公平锁无非就是多了一次判断有没有前置节点,这一点上比公平锁要费劲一点,其它的,感觉两者并无差别。至于一些地方描述的两者性能相差百倍的描述,,,,没太懂。书中所给的例子,我认为是因为程序的逻辑导致执行过程中恰好本线程释放后立即获取,因而一直是同一线程连续获得同步资源,避免了线程切换而导致看上去性能高很多而已,网上的多数文章得出两者性能相差百倍的例子,多数是时间差异导致,在每个线程消耗时间不确定的情况下,两者都需要切换线程,性能应该差别不会太过离谱,个人认为性能差距很大这个结论是不严谨的,有了解的请留言指正
  关于性能的讨论,顺便附个连接,这个讨论的也挺深入的:https://www.cnblogs.com/yulinfeng/p/6899316.html

 

重入锁--ReentrantLock的更多相关文章

  1. synchronized关键字,Lock接口以及可重入锁ReentrantLock

    多线程环境下,必须考虑线程同步的问题,这是因为多个线程同时访问变量或者资源时会有线程争用,比如A线程读取了一个变量,B线程也读取了这个变量,然后他们同时对这个变量做了修改,写回到内存中,由于是同时做修 ...

  2. Java 重入锁 ReentrantLock 原理分析

    1.简介 可重入锁ReentrantLock自 JDK 1.5 被引入,功能上与synchronized关键字类似.所谓的可重入是指,线程可对同一把锁进行重复加锁,而不会被阻塞住,这样可避免死锁的产生 ...

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

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

  4. java 可重入锁ReentrantLock的介绍

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

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

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

  6. 17_重入锁ReentrantLock

    [概述] 重入锁可以完全代替synchronized关键字. 与synchronized相比,重入锁ReentrantLock有着显示的操作过程,即开发人员必须手动指定何时加锁,何时释放锁,所以重入锁 ...

  7. Java 显示锁 之 重入锁 ReentrantLock(七)

    ReentrantLock 重入锁简介 重入锁 ReentrantLock,顾名思义,就是支持同一个线程对资源的重复加锁.另外,该锁还支持获取锁时的公平与非公平性的选择. 重入锁 ReentrantL ...

  8. Java中可重入锁ReentrantLock原理剖析

    本文由码农网 – 吴极心原创,转载请看清文末的转载要求,欢迎参与我们的付费投稿计划! 一. 概述 本文首先介绍Lock接口.ReentrantLock的类层次结构以及锁功能模板类AbstractQue ...

  9. Java多线程——深入重入锁ReentrantLock

    简述 ReentrantLock 是一个可重入的互斥(/独占)锁,又称为“独占锁”. ReentrantLock通过自定义队列同步器(AQS-AbstractQueuedSychronized,是实现 ...

  10. java线程的同步控制--重入锁ReentrantLock

    我们常用的synchronized关键字是一种最简单的线程同步控制方法,它决定了一个线程是否可以访问临界区资源.同时Object.wait() 和Object.notify()方法起到了线程等待和通知 ...

随机推荐

  1. MongoDB整理笔记の新增Shard Server

    1.启动一个新Shard Server 进程 [root@localhost ~]# mkdir /data/shard/s2 [root@localhost ~]# /Apps/mongo/bin/ ...

  2. web集群时session同步的3种方法

    在做了web集群后,你肯定会首先考虑session同步问题,因为通过负载均衡后,同一个IP访问同一个页面会被分配到不同的服务器上,如果session不同步的话,一个登录用户,一会是登录状态,一会又不是 ...

  3. MongoDB插入时间不正确的问题

    关于mongodb插入时间不正确的问题 今天在给mongodb插入日期格式的数据时发现,日期时间相差8个小时,原来存储在mongodb中的时间是标准时间UTC +0:00,而中国的时区是+8.00 . ...

  4. [转] Draw Call未被批处理?告诉你在Unity 5.6中如何查找原因 [复制链接]

    Unity在5.6之前的版本中并未提供很直接的方式来查找Draw Call未被批处理的原因,但Unity 5.6在Frame Debugger中新增了一项功能,帮助开发者查找相关信息.今天这篇文章就为 ...

  5. 洛谷P4009 汽车加油行驶问题(分层最短路)

    传送门 说好的网络流24题呢……上次是状压dp,这次怎么又最短路了…… 不过倒是用这题好好学了一下分层图最短路 把每一个位置$(x,y)$,油量剩余$k$表示为一个状态,然后转化成一个$n$进制数,这 ...

  6. Java面向对象之关键字super 入门实例

    一.基础概念 (一)super关键字 super关键字的用法和this相似.this代表的是当前对象.super代表的是父类中内存空间. 子父类中是不会出现同名属性的情况. (二)继承中.成员变量问题 ...

  7. Sample-Code:Bing Search API

    Demo link: http://code.msdn.microsoft.com/windowsazure/How-to-use-bing-search-API-4c8b287e Aspx Code ...

  8. 通用动态树(Link-Cut Tree)模板

    一个没有维护任何东西的动态树模板 忘了怎么写可以直接来粘 int ch[300010][2], fa[300010], st[300010]; bool lazy[300010]; bool nroo ...

  9. mysql自动备份并上传至git仓库

      自动备份 备份需求 数据库备份的重要性再怎么强调也不为过.当你的操作出现差错,但又因为没有作备份导致数据无法还原时,你就能体会到“万念俱灰”的心情了. 数据库备份有多种形式,本文以我的个人网站数据 ...

  10. js 原生JS实现轮播图

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...