属性

重入锁是基于AQS实现的,它提供了公平锁和非公平锁两个版本的实现。

public class ReentrantLock implements Lock, java.io.Serializable {

    /***************公平锁和非公平锁*****************/

    //锁(该类核心)
private final Sync sync;
//非公平锁版本
static final class NonfairSync extends Sync{……}
//公平锁版本
static final class FairSync extends Sync{……} /**
* 默认使用非公平锁
*/
public ReentrantLock() {
sync = new NonfairSync();
} /**
* 手动指定公平策略
*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
} }

非公平锁和公平锁两个类都是内部类,而ReentrantLock中只引入Sync类型的sync,面向接口编程。实际使用哪个版本则由构造器决定,默认使用非公平锁,也可以在构造时手动指定。

构造器

    /**
* 构造器:默认非公平,等价于ReentrantLock(false)
*/
public ReentrantLock() {
//默认使用非公平锁
sync = new NonfairSync();
} /**
* 构造器:使用指定的公平策略
*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}

加锁(非公平版)-lock

因为默认使用非公平版本,我们先跟踪一下非公平版的lock()源码,看看是如何实现的

public void lock() {
sync.lock();
} //内部类NonfairSync中的lock方法
final void lock() {
//先使用CAS方式【抢占一次】锁。若成功则独占该锁(将AQS的state由0改为1,并将当前线程设置锁拥有者)
if (compareAndSetState(0, 1))
//将当前线程设置为锁拥有者(exclusiveOwnerThread表示“持有锁的线程”)
setExclusiveOwnerThread(Thread.currentThread());
else//失败,则加入等待队列(入队前会先进行一次获取锁操作)
acquire(1);
} //父类AQS类中的acquire方法
public final void acquire(int arg) {
//入队前,此时锁可能已被释放,先尝试一次获取锁的操作。
//获取失败,则将线程包装成节点,加入等待队列尾部,完成后中断线程。
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
} protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
} final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
//锁没被线程持有,则竞争该锁,成功则将state由0改为1,并将当前线程标记为锁拥有者
if (c == 0) {
        if (compareAndSetState(0, acquires)) {//CAS方式获取锁
//将当前线程标记为锁拥有者
setExclusiveOwnerThread(current);
return true;
}
}//锁已经被持有
//锁已被线程持有,则统计重入次数。
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;//state值+1(传进来的是1)
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
//修改state【不需要使用CAS来修改state,相当于偏向锁】
setState(nextc);
return true;
}
return false;
}

加锁(公平版)-lock

再来跟踪一下公平版本的lock()方法源码

/**
* 没有抢占操作,直接进入等待队列排队(公平)
*/
final void lock() {
acquire(1);
} //父类AQS类中的acquire方法
public final void acquire(int arg) {
//入队前,此时锁可能已被释放,先尝试一次获取锁的操作。
//获取失败,则将线程包装成节点,加入等待队列尾部,完成后中断线程。
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
} /**
* tryAcquire公平版本。
*/
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
//锁没被线程持有
if (c == 0) {
//【快捷方式】先判断是否有前驱节点(因为队列是FIFO,前驱等待时间更长,既然公平,就要保证先来先获取)
//若无前驱,则通过CAS操作获取,成功则将state由0改为1,并设置当前线程拥有该锁。
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;
}

释放锁-unlock

unlock方法实现都是相同的。

public void unlock() {
sync.release(1);
} protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
//锁状态已为0,则可以释放锁了。(state累加了多少次,就要对应的减多少次,才能把锁解开)
if (c == 0) {
free = true;//释放锁成功
setExclusiveOwnerThread(null);//设置锁拥有者为null
}
//更新state
setState(c);
return free;
}

总结

1.锁的状态变化?

锁由state表示,初始状态为0。线程初次获取到锁,则将state由0改为1,锁拥有者为当前线程。线程重入获取到锁,依旧将state状态加1,每次重入都加1。

退出临界区state就减1,最终直至0,锁释放,锁拥有者为null。

2.非公平锁获取锁和公平获取锁过程的区别?

非公平lock:

①先进行一次CAS抢占获取锁,成功则返回,失败则进入等待队列。

②入队列前,可能此时锁已被释放,先进行一次CAS获取锁,成功则返回。失败将线程封装成节点加入队列尾部,并中断线程。

公平lock:

①直接进入等待队列。

②入队列前,可能此时锁已被释放,先进行一次获取锁操作。过程是:判断是否有前驱节点,如果有前驱,根据FIFO必然前驱更应优先获取锁,因此获取锁失败。若无前驱,则再通过CAS获取锁,成功则返回,失败将线程封装成节点加入队列尾部,并中断线程。

参考:

五月的仓颉 ReentrantLock实现原理深入探究

活在梦里 AQS源码解读

ReentrantLock源码分析的更多相关文章

  1. Java并发编程-ReentrantLock源码分析

    一.前言 在分析了 AbstractQueuedSynchronier 源码后,接着分析ReentrantLock源码,其实在 AbstractQueuedSynchronizer 的分析中,已经提到 ...

  2. java多线程---ReentrantLock源码分析

    ReentrantLock源码分析 基础知识复习 synchronized和lock的区别 synchronized是非公平锁,无法保证线程按照申请锁的顺序获得锁,而Lock锁提供了可选参数,可以配置 ...

  3. ReentrantLock源码分析--jdk1.8

    JDK1.8 ArrayList源码分析--jdk1.8LinkedList源码分析--jdk1.8HashMap源码分析--jdk1.8AQS源码分析--jdk1.8ReentrantLock源码分 ...

  4. JUC AQS ReentrantLock源码分析

    警告⚠️:本文耗时很长,先做好心理准备,建议PC端浏览器浏览效果更佳. Java的内置锁一直都是备受争议的,在JDK1.6之前,synchronized这个重量级锁其性能一直都是较为低下,虽然在1.6 ...

  5. ReentrantLock 源码分析以及 AQS (一)

    前言 JDK1.5 之后发布了JUC(java.util.concurrent),用于解决多线程并发问题.AQS 是一个特别重要的同步框架,很多同步类都借助于 AQS 实现了对线程同步状态的管理. A ...

  6. JUC之ReentrantLock源码分析

    ReentrantLock:实现了Lock接口,是一个可重入锁,并且支持线程公平竞争和非公平竞争两种模式,默认情况下是非公平模式.ReentrantLock算是synchronized的补充和替代方案 ...

  7. Java并发编程之ReentrantLock源码分析

    ReentrantLock介绍 从JDK1.5之前,我们都是使用synchronized关键字来对代码块加锁,在JDK1.5引入了ReentrantLock锁.synchronized关键字性能比Re ...

  8. Java并发系列[5]----ReentrantLock源码分析

    在Java5.0之前,协调对共享对象的访问可以使用的机制只有synchronized和volatile.我们知道synchronized关键字实现了内置锁,而volatile关键字保证了多线程的内存可 ...

  9. Java并发编程 ReentrantLock 源码分析

    ReentrantLock 一个可重入的互斥锁 Lock,它具有与使用 synchronized 方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大. 这个类主要基于AQS(Abst ...

  10. concurrent(三)互斥锁ReentrantLock & 源码分析

    参考文档:Java多线程系列--“JUC锁”02之 互斥锁ReentrantLock:http://www.cnblogs.com/skywang12345/p/3496101.html Reentr ...

随机推荐

  1. win10同时安装 office2016和visio2016

    一.下载镜像文件 因为office 2016和 visio2016 镜像文件是一样的,只是名称不一样,所以只需要下载一个即可. 二.下载Office 2016 Deployment Tool工具 到微 ...

  2. UnicodeEncodeError: 'ascii' codec can't encode characters in position

    UnicodeEncodeError: 'ascii' codec can't encode characters in position python运行时出现这个错误,解决方法如下: 加入如下语句 ...

  3. Docker 从入门到实践(一)Docker 简介

    读前须知:本教程大部分都是[Docker 从入门到实践 ]一书的知识,有兴趣可以直接观看书籍.同时,借鉴书籍的知识,如有侵权,请告知我,我会删除处理.谢谢. 一.什么是 Docker? Docker ...

  4. webdriver设置浏览器全屏及设置浏览器窗口为特定大小的方法

    from selenium import webdriver driver = webdriver.Chrome() #全屏 driver.maximize_window() #具体大小 driver ...

  5. 14.UA池和代理池

    今日概要 scrapy下载中间件 UA池 代理池 今日详情 一.下载中间件 先祭出框架图: 下载中间件(Downloader Middlewares) 位于scrapy引擎和下载器之间的一层组件. - ...

  6. 复制数据库的Shell命令

    mysqldump -h$host db_old -uroot -p$pass | mysql -h$host db_new -uroot -p$pass 管道符号,是unix一个很强大的功能,符号为 ...

  7. 第5章 Java中的锁

    5.1 Lock接口 并发编程安全性需要使用到锁,synchronized是一种隐式的获得与释放锁的关键字,除此之外还有Lock接口及其实现类,该接口及实现类提供了显示获取和释放锁的方式. 除了上述编 ...

  8. 【angularjs】使用angular搭建项目,实现隔行换色

    描叙:使用ng-class实现每隔3行换色 代码: <!DOCTYPE html> <html ng-app="myApp"> <head> & ...

  9. 微信接入arduino

    https://blog.csdn.net/liudongdong19/article/details/81072857 一.准备工作.      1.微信公众号,个人的就可以了,不用企业号什么的.  ...

  10. .NET中使用Redis 转发

    .NET中使用Redis   Redis是一个用的比较广泛的Key/Value的内存数据库,新浪微博.Github.StackOverflow 等大型应用中都用其作为缓存,Redis的官网为http: ...