先做总结:

1、为什么要用ReentrantLock?

(1)ReentrantLock与synchronized具有相同的功能和内存语义;

(2)synchronized是重量级锁,性能不好。ReentrantLock性能好;

(3)ReentrantLock可操作性强,如:实现条件Condition,读写锁,可轮询,使用更灵活。

2、ReentrantLock实现原理

(1)ReentrantLock的属性sync是一个Sync(继承了AQS)对象

(2)获取锁的标志:

    sync.state>0(0代表没有被占用,大于0代表有线程持有当前锁(锁可以重入,每次重入都+1))

    sync.exclusiveOwnerThread == Thread.currentThread()

(3)Sync重写了tryAcquire()方法(获取锁)和tryRelease()方法(释放锁),其实就是对sync.state和sync.exclusiveOwnerThread的操作。

(4)获取不到锁的线程加入sync的同步队列,上一篇说过 Java并发(八):AbstractQueuedSynchronizer

3、公平锁与非公平锁

ReentrantLock只能时公平锁和非公平锁中的一个

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

不同:

(1)非公平锁在调用 lock 后,首先就会调用 CAS 进行一次抢锁,如果这个时候恰巧锁没有被占用,那么直接就获取到锁。

(2)非公平锁在 CAS 失败后,和公平锁一样都会进入到 tryAcquire 方法。在 tryAcquire 方法中,如果发现锁这个时候被释放了(state == 0),非公平锁会直接 CAS 抢锁(即使同步队列中有线程等待),而公平锁会判断等待队列是否有线程处于等待状态,如果有则不去抢锁,乖乖排到后面。

公平锁和非公平锁就这两点区别,如果这两次 CAS 都不成功,那么后面非公平锁和公平锁是一样的,都要进入到阻塞队列等待唤醒。

因此,非公平锁会有更好的性能,因为它的吞吐量比较大。当然,非公平锁让获取锁的时间变得更加不确定,可能会导致在阻塞队列中的线程长期处于饥饿状态。

两个不同的源码:

    /**
* 不同一:
* 非公平锁在调用 lock 后,首先就会调用 CAS 进行一次抢锁,如果这个时候恰巧锁没有被占用,那么直接就获取到锁。
*/
// NonfairSync
final void lock() {
if (compareAndSetState(0, 1)) // 不同一:NonfairSync会CAS尝试获取锁
setExclusiveOwnerThread(Thread.currentThread()); // 如果拿到锁就设置当前线程
else
acquire(1);
} // FairSync(FairSync不会先尝试拿锁)
final void lock() {
acquire(1);
} /**
* 不同二:
* 如果发现锁这个时候被释放了(state == 0),
* 非公平锁会直接 CAS 抢锁(即使同步队列中有线程等待),而公平锁会判断等待队列是否有线程处于等待状态,如果有则不去抢锁,乖乖排到后面。
*/
// NonfairSync
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
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) { // 即使锁没有被占用,也要排在同步队列中等待的线程之后
if (!hasQueuedPredecessors() && // CLH队列为空或者队列头结点是当前线程节点 才能获得锁
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;
}

一、ReentrantLock类结构

public class ReentrantLock implements Lock, java.io.Serializable {
private final Sync sync; // 锁 大部分功能都是委托给Sync来实现的
abstract static class Sync extends AbstractQueuedSynchronizer {}
static final class FairSync extends Sync {}
static final class NonfairSync extends Sync {}
}

二、以NonfairSync为例解析重入锁

获取锁标志:

(NonfairSync extends Sync extends AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer)

1.AbstractQueuedSynchronizer.state>0(0代表没有被占用,大于0代表有线程持有当前锁(锁可以重入,每次重入都+1))

2.AbstractOwnableSynchronizer.exclusiveOwnerThread == Thread.currentThread()

获取锁:

public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();// 默认是非公平锁
lock.lock();
} // ReentrantLock
public void lock() {
sync.lock();
} // NonfairSync
final void lock() {
if (compareAndSetState(0, 1)) // 尝试获取锁
setExclusiveOwnerThread(Thread.currentThread()); // 如果拿到锁就设置当前线程
else
acquire(1);
} // AbstractQueuedSynchronizer
public final void acquire(int arg) {
if (!tryAcquire(arg) && // 尝试获取锁
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // 没有获取到锁,将线程加入同步队列(参考上一篇AbstractQueuedSynchronizer)
selfInterrupt();
} // NonfairSync
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
} /**
* 获取锁标志:
* (NonfairSync extends Sync extends AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer)
* 1.AbstractQueuedSynchronizer.state>0(0代表没有被占用,大于0代表有线程持有当前锁(锁可以重入,每次重入都+1))
* 2.AbstractOwnableSynchronizer.exclusiveOwnerThread == Thread.currentThread()
* Sync(NonfairSync没有重写nonfairTryAcquire)
*/
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;
}

释放锁:

    // ReentrantLock
public void unlock() {
sync.release(1);
} // AbstractQueuedSynchronizer
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h); // 唤醒队列下一个节点线程 参考上一篇:AbstractQueuedSynchronizer
return true;
}
return false;
} // Sync
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false; // 重入锁,直到state==0才算释放
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}

三、公平锁与非公平锁

    // FairSync(NonfairSync会先尝试拿锁,FairSync不会)
final void lock() {
acquire(1);
} // FairSync
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() && // CLH队列为空或者队列头结点是当前线程节点 才能获得锁
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;
} /**
* AbstractQueuedSynchronizer
* true - CLH队列为空或者队列头结点是当前线程节点
*/
public final boolean hasQueuedPredecessors() {
Node t = tail;
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}

以上代码可以看出,公平锁和非公平锁只有两处不同:

(1)非公平锁在调用 lock 后,首先就会调用 CAS 进行一次抢锁,如果这个时候恰巧锁没有被占用,那么直接就获取到锁。

(2)非公平锁在 CAS 失败后,和公平锁一样都会进入到 tryAcquire 方法。在 tryAcquire 方法中,如果发现锁这个时候被释放了(state == 0),非公平锁会直接 CAS 抢锁,而公平锁会判断等待队列是否有线程处于等待状态,如果有则不去抢锁,乖乖排到后面。

公平锁和非公平锁就这两点区别,如果这两次 CAS 都不成功,那么后面非公平锁和公平锁是一样的,都要进入到阻塞队列等待唤醒。

因此,非公平锁会有更好的性能,因为它的吞吐量比较大。当然,非公平锁让获取锁的时间变得更加不确定,可能会导致在阻塞队列中的线程长期处于饥饿状态。

四、ReentrantLock优势

ReentrantLock与synchronized具有相同的功能和内存语义。

1、与synchronized相比,ReentrantLock提供了更多,更加全面的功能,具备更强的扩展性。例如:时间锁等候,可中断锁等候,锁投票。

2、ReentrantLock还提供了条件Condition,对线程的等待、唤醒操作更加详细和灵活,所以在多个条件变量和高度竞争锁的地方,ReentrantLock更加适合。

3、ReentrantLock提供了可轮询的锁请求。它会尝试着去获取锁,如果成功则继续,否则可以等到下次运行时处理,而synchronized则一旦进入锁请求要么成功要么阻塞,所以相比synchronized而言,ReentrantLock会不容易产生死锁些。

4、ReentrantLock支持更加灵活的同步代码块,但是使用synchronized时,只能在同一个synchronized块结构中获取和释放。注:ReentrantLock的锁释放一定要在finally中处理,否则可能会产生严重的后果。

5、ReentrantLock支持中断处理,且性能较synchronized会好些。

参考资料 / 相关推荐

【死磕Java并发】—–J.U.C之重入锁:ReentrantLock

一行一行源码分析清楚AbstractQueuedSynchronizer

Java并发(八):AbstractQueuedSynchronizer

Java并发(九):重入锁 ReentrantLock的更多相关文章

  1. Java并发:重入锁 ReentrantLock(二)

    一.理解锁的实现原理 1. 用wait()去实现一个lock方法,wait()要和synchronized同步关键字一起去使用的,直接使用wait方法会直接报IllegalMonitorStateEx ...

  2. Java并发:重入锁 ReentrantLock(一)

    ReentrantLock 是一种可重入的互斥锁,它不像 synchronized关键字一样支持隐式的重进入,但能够使一个线程(不同的方法)重复对资源的重复加锁而不受阻塞. ReentrantLock ...

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

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

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

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

  5. 探索JAVA并发 - 可重入锁和不可重入锁

    本人免费整理了Java高级资料,涵盖了Java.Redis.MongoDB.MySQL.Zookeeper.Spring Cloud.Dubbo高并发分布式等教程,一共30G,需要自己领取.传送门:h ...

  6. java 可重入锁ReentrantLock的介绍

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

  7. Java 重入锁 ReentrantLock 原理分析

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

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

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

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

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

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

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

随机推荐

  1. MacOS Safari 中 button 不能使用 text-gradient

    @mixin text-gradient ($deg: 90deg, $from: $gradientFrom, $to: $gradientEnd) { background-image: line ...

  2. perl6 登录phpmyadmin

    use HTTP::UserAgent; my $ua = HTTP::UserAgent.new; my $url = 'http://localhost/phpMyAdmin/index.php' ...

  3. 大美西安writeup

    http://202.112.51.184:10080/ admin/admin 弱口令登入 发现注入 但是这个注入实在是不知道怎么利用.很蛋疼.后来get了一个姿势. 先-1让前面的不被下载然后后面 ...

  4. MYSQL的隐式类型转换

    官方文档中是这么说的 当操作者使用不同类型的操作数,操作数类型兼容的出现使 转换.一些 发生隐式转换.例如,MySQL会自动 将数字转换为字符串的必要,反之亦然. 也可以将数字转换为字符串明确 使用( ...

  5. virsh 命令最新整理。 每个“;”之后是正解

    1,migrate --domain --destURL --dname  --live(热迁移) migrate lf 192.168.16.3 dname 2,managedsave domain ...

  6. system()函数

    windows下system () 函数详解 windows操作系统下system () 函数详解(主要是在C语言中的应用) 函数名: system   功 能: 发出一个DOS命令   用 法: i ...

  7. plsql实例精讲部分笔记

    转换sql: create or replace view v_sale(year,month1,month2,month3,month4,month5,month6,month7,month8,mo ...

  8. csu 1803(余数分类)

    1803: 2016 Time Limit: 5 Sec  Memory Limit: 128 MBSubmit: 565  Solved: 364[Submit][Status][Web Board ...

  9. gtk+学习笔记(三)

    gtk感觉函数好多,需要记好多函数,还是多练习,多总结,今天写了一个登陆窗口,很简单,主要是为了加深对这些东西的记忆,直接贴代码 #include<gtk/gtk.h> static Gt ...

  10. [tensorflow]的安装

    1 pip install 最简单直接的方法,通过pip install安装,命令如下: pip install tensorflow-gpu //安装gpu版tensorflow pip insta ...