java 并发——ReentrantLock
java 并发——ReentrantLock
简介
public class ReentrantLock implements Lock, java.io.Serializable {
// 继承了 AbstractQueuedSynchronizer 具体操作的执行者
private final Sync sync;
abstract static class Sync extends AbstractQueuedSynchronizer {
// ...
}
}
重入锁: 一种可重入互斥锁具有与使用 synchronized 方法和语句访问的隐式监视锁相同的基本行为和语义,但是具有扩展功能。
ReentrantLock 类的构造方法可以接收一个布尔值,当设置为 true 的情况下就是公平锁模式,在竞争的情况下有利于授予等待最长时间的线程。否则 false 是非公平锁该锁不保证任何特定的访问顺序。使用多线程访问的情况下非公平锁比公平锁具有更快的吞吐量。但是请注意,锁的公平性不能保证线程调度的公平性。 因此,使用公平锁的许多线程之一可以连续获得多次,而其他活动线程不进行而不是当前持有锁。 另请注意, 未定义的 tryLock() 方法不符合公平性设置。 如果锁可用,即使其他线程正在等待,它也会成功。
建议使用方法是 lock 始终与 try 块成对出现。
方法分析
首先我们先来看看构造器
public ReentrantLock() {
// 默认使用非公平锁
sync = new NonfairSync();
}
// 传递 boolean 值来选择锁的类型
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
lock 非公平加锁操作
public void lock() {
sync.lock();
}
final void lock() {
// 首先进行 cas 操作修改 state 状态
if (compareAndSetState(0, 1))
// 如果状态修改成功则设置为当前线程所有
setExclusiveOwnerThread(Thread.currentThread());
else
// 没有修改成功则调用 AQS 的 acquire(int arg) 方法
acquire(1);
}
上面代码主要做了:
- 将 state 状态值设置为 1.
- 如果设置成功则将锁设置为当前线程所有.
- 如果 state 状态已经被其他线程设置了则会失败则调用 AQS 的 acquire(int arg) 方法.
public final void acquire(int arg) {
// 调用子类重写的 tryAcquire 方法
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
上面我们可以看到在 AQS 抽象类中我们发现了提供给子类重写 tryAcquire 的方法,那么我们就去 NonfairSync 类中看下其中实现代码
protected final boolean tryAcquire(int acquires) {
// 调用父类 Sync.nonfairTryAcquire
return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
// 获取当前线程
final Thread current = Thread.currentThread();
// 获取状态
int c = getState();
// 如果发现状态等于 0 说明没有锁处理空闲状态
if (c == 0) {
// 再次尝试修改 state 如果获取成功则设置当前线程所有
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 如果 state 不是 0 并且锁持有者的线程就是当前线程判定为重入
else if (current == getExclusiveOwnerThread()) {
// 将 state 的值 + 1
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
// 为 state 赋予新值
setState(nextc);
return true;
}
return false;
}
上面代码主要做了:
- 首先判断同步状态 state == 0
- 如果是表示该锁还没有被线程持有,直接通过 cas 获取同步状态,如果成功返回 true
- 如果 state != 0,则判断当前线程是否为获取锁的线程,如果是则获取锁,成功返回 true
unlock 释放锁操作
public void unlock() {
// 调用 AQS release
sync.release(1);
}
public final boolean release(int arg) {
// 调用子类重写的 Sync.tryRelease
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
protected final boolean tryRelease(int releases) {
// 获取状态减去 releases 如果没有重入则 c = 0
int c = getState() - releases;
// 如果锁持有者线程不是该线程则抛出异常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
// 如果 c=state==0 表示已经释放
if (c == 0) {
free = true;
// 设置锁持有者线程为 null
setExclusiveOwnerThread(null);
}
// 重新设置 state
setState(c);
return free;
}
上面代码可以看到只有同步状态 state == 0 时才算时真正的彻底释放锁,会将锁持有者线程设置为 null 表示释放成功。
lock 公平锁加锁操作
公平锁与非公平锁的区别在于获取锁的时候是否按照FIFO的顺序来。释放锁不存在公平性和非公平性。我们来看加锁操作。
final void lock() {
// 调用 AQS acquire
acquire(1);
}
public final void acquire(int arg) {
// 调用子类公平锁的实现 tryAcquire
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 我们发现多了一行 !hasQueuedPredecessors()
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;
}
public final boolean hasQueuedPredecessors() {
// 尾部节点
Node t = tail;
// 头部节点
Node h = head;
Node s;
// 头部节点不等于尾部节点并且(头部节点没有后继节点或者头部节点线程不等于当前线程)
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
我们发现公平锁和非公平锁的代码很大一部分都是一模一样的,只是多了一行 !hasQueuedPredecessors() 判断.
hasQueuedPredecessors 主要是判断同步队列中是否还有等待的节点线程,如果有则返回 true 没有返回 false.
总结
ReentrantLock 与 synchronized 比较
- ReentrantLock 提供了更多,更加全面的功能,具备更强的扩展性。
- ReentrantLock 还提供了条件 Condition,对线程的等待、唤醒操作更加详细和灵活。
- ReentrantLock 提供了可轮询的锁请求。它会尝试着去获取锁,如果成功则继续,否则可以等到下次运行时处理,而synchronized 则一旦进入锁请求要么成功要么阻塞,所以相比 synchronized 而言,ReentrantLock 会不容易产生死锁些。
- ReentrantLock 支持更加灵活的同步代码块,但是使用 synchronized 时,只能在同一个 synchronized 块结构中获取和释放。
- ReentrantLock 支持中断处理,且性能较 synchronized 会好些。
java 并发——ReentrantLock的更多相关文章
- Java并发--ReentrantLock原理详解
ReentrantLock是什么? ReentrantLock重入锁,递归无阻塞的同步机制,实现了Lock接口: 能够对共享资源重复加锁,即当前线程获取该锁,再次获取不会被阻塞: 支持公平锁和非公平锁 ...
- Java并发——ReentrantLock类源码阅读
ReentrantLock内部由Sync类实例实现. Sync类定义于ReentrantLock内部. Sync继承于AbstractQueuedSynchronizer. AbstractQueue ...
- java并发-ReentrantLock的lock和lockInterruptibly的区别
ReentrantLock的加锁方法Lock()提供了无条件地轮询获取锁的方式,lockInterruptibly()提供了可中断的锁获取方式.这两个方法的区别在哪里呢?通过分析源码可以知道lock方 ...
- Java并发ReentrantLock
ReentrantLock简介 可重入锁,作用是使线程安全.对比于sychronized,它能具有以下特点 减小资源锁的力度 更可控,减少发生死锁的概率 加锁.释放锁都是显示控制的 添加锁的作用时间来 ...
- Java并发基础框架AbstractQueuedSynchronizer初探(ReentrantLock的实现分析)
AbstractQueuedSynchronizer是实现Java并发类库的一个基础框架,Java中的各种锁(RenentrantLock, ReentrantReadWriteLock)以及同步工具 ...
- 【Java并发编程实战】-----“J.U.C”:ReentrantLock之三unlock方法分析
前篇博客LZ已经分析了ReentrantLock的lock()实现过程,我们了解到lock实现机制有公平锁和非公平锁,两者的主要区别在于公平锁要按照CLH队列等待获取锁,而非公平锁无视CLH队列直接获 ...
- 【Java并发编程实战】-----“J.U.C”:ReentrantLock之一简介
注:由于要介绍ReentrantLock的东西太多了,免得各位客官看累,所以分三篇博客来阐述.本篇博客介绍ReentrantLock基本内容,后两篇博客从源码级别分别阐述ReentrantLock的l ...
- Java并发编程总结3——AQS、ReentrantLock、ReentrantReadWriteLock(转)
本文内容主要总结自<Java并发编程的艺术>第5章——Java中的锁. 一.AQS AbstractQueuedSynchronizer(简称AQS),队列同步器,是用来构建锁或者其他同步 ...
- Java并发系列[5]----ReentrantLock源码分析
在Java5.0之前,协调对共享对象的访问可以使用的机制只有synchronized和volatile.我们知道synchronized关键字实现了内置锁,而volatile关键字保证了多线程的内存可 ...
随机推荐
- 爬虫(二)—— 请求库(二)selenium请求库
目录 selenium请求库 一.什么是selenium 二.环境搭建 三.使用selenium模块 1.使用chrome并设置为无GUI模式 2.使用chrome有GUI模式 3.显示等待与隐式等待 ...
- Linux系统ubuntu17安装jdk8并配置环境变量
上班多年,一直没有真正在Linux下开发过,没有捣鼓到Linux服务器,成为憾事.最近由于想学习Python,于是开始看书,学习Linux,学习shell编程. 选择Linux,先从最简单的ubunt ...
- 【UR #5】怎样跑得更快
题目 给定\(n,c,d\)和序列\(\{b_i\}\),求一个序列\(\{x_i\}\)满足 \[\sum_{j=1}^n\gcd(i,j)^c\times \rm{lcm(i,j)^d}\time ...
- HDU - 2181 C - 哈密顿绕行世界问题(DFS
题目传送门 C - 哈密顿绕行世界问题 一个规则的实心十二面体,它的 20个顶点标出世界著名的20个城市,你从一个城市出发经过每个城市刚好一次后回到出发的城市. Input 前20行的第i行有3个数, ...
- 第四节 RabbitMQ在C#端的应用-客户端连接
原文:第四节 RabbitMQ在C#端的应用-客户端连接 版权声明:未经本人同意,不得转载该文章,谢谢 https://blog.csdn.net/phocus1/article/details/87 ...
- vue中关于checkbox数据绑定v-model
vue.js为开发者提供了很多便利的指令,其中v-model用于表单的数据绑定很常见, 下面是最常见的例子: <div id='myApp'> <input type=&qu ...
- 面试题。线程pingpong的输出问题
第一种情况:public class Main { public static void main(String args[]) { Thread t = new Thread() { public ...
- find 文件查找
目录 find文件查找 1.为什么要使用文件查找 2.根据文件名称查找-name 3.根据文件大小查找-size 4.根据文件类型查找-type f 5.根据文件时间查找-mtime 6.根据文件用户 ...
- 低版本vsphere部署高版本导出的OVF 报“硬件系列vmx-13不受支持“解决办法
用文本编辑器之类的工具 打开ovf模板 然后下拉20多行左右 找到vmx-13这条报错内容 将13更改为数字较低的值 例如11,12
- hbase配置详解(转)
转自:http://www.cnblogs.com/viviman/archive/2013/03/21/2973539.html 1 准备工作 因为我只有一台机器,所以,一切都成为了伪分布,但是,其 ...