java多线程---ReentrantLock源码分析
ReentrantLock源码分析
基础知识复习
synchronized和lock的区别
synchronized是非公平锁,无法保证线程按照申请锁的顺序获得锁,而Lock锁提供了可选参数,可以配置成公平锁,也可以配置成非公平锁。通常来说,非公平锁的效率比公平锁要高。
一个线程使用syn获取锁,除非该线程成功获取到锁,否则将一直阻塞住。而Lock锁提供了lockInterruptibly()接口,提供了可中断的操作
带超时时间的锁。Lock锁提供了tryLock(long time, TimeUnit unit)带超时时间的获取锁的接口,在等待指定时间后,如果获取不到锁,则放弃获取锁
自动释放锁。如果用syn加锁,当发生异常时(比方运行时异常),那么jvm会自动释放掉线程持有的锁,而lock锁则不会主动释放,除非调用了unlock接口,因此使用lock锁时有可能导致死锁
在ReentrantLock上可以绑定多个Condition条件,也就是可以拥有多个等待队列,比如在实现生产者消费者的时候,使用一个队列(锁的队列)存放等待 队列(生产者消费者的队列)有元素的消费者,使用另一个队列(锁的队列)存放等待 队列not full的生产者,相比较synchronized和wait notify而言,避免了错误的唤醒生产者或者消费者的开销
ReentrantLock 基础分析
ReentrantLock是lock的一个实现类,独占锁。首先看一下ReentrantLock的内部属性
private final Sync sync;
1
发现在只有一个Sync类型的属性,这个Sync是AQS的一个抽象类如下:
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
abstract void lock();
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;
}
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;
}
....
同时在ReentrantLock 内部,实现了Sync2个不同的类,一个是NonfairSync(非公平锁),一个是FairSync(公平锁)。也就是在一开头,synchronized和lock的区别的第一点,Lock可以创建2种不同的锁,根据传入的参数。
如下源码
//默认的ReentrantLock无参构造函数,是非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
//当传入fasle时,就创建了公平锁。
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
AbstractQueuedSynchronizer
那么Sync的父类是AQS,并发包中的锁底层就是使用了AQS:AbstractQueuedSynchronizer。所以来看一下AQS,那么首先来看下属性:
//这个是父类AbstractOwnableSynchronizer中的属性,标识了独占模式下获取锁的是哪一个线程
private transient Thread exclusiveOwnerThread;
//AQS的数据结构是FIFO的双向队列
//头部节点
private transient volatile Node head;
//尾部节点
private transient volatile Node tail;
//同步状态值
private volatile int state;
//设置这里使用CAS来更新 主要用于对state的更新
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long stateOffset;
private static final long headOffset;
private static final long tailOffset;
private static final long waitStatusOffset;
private static final long nextOffset;
可以看到有头部节点和尾部节点外,还有一个同步状态值state,该属性在不同的锁中,代表了不同的含义。
在ReentrantLock中,state可以用来表示当前线程获取锁的可重入次数
对与ReentrantReadWriteLock来说,高16位代表了读锁,也就是读锁的次数,低16位代表了写锁的可重入次数
在Semaphore中,state代表了可用信号的个数
在countdownlatch中,state用来表示计数器当前的值
所以在ReentrantLock 中 state用来表示当前线程获取锁的可冲入次数。当没有线程持有锁的时候,state为0,当有一个线程获取锁时,state为1。然后当前线程需要再次获取锁时,发现自己已经是锁的持有者,state+1
看下AQS的Node:
//是在获取共享资源时放入队列
static final Node SHARED = new Node();
//是在获取独占资源时放入对列的
static final Node EXCLUSIVE = null;
//线程被取消
static final int CANCELLED = 1;
//线程需要被唤醒
static final int SIGNAL = -1;
//线程在条件队列里面等待
static final int CONDITION = -2;
//释放共享资源时通知其他节点
static final int PROPAGATE = -3;
//表示等待的状态,可以为以上几个值
volatile int waitStatus;
volatile Node prev;
volatile Node next;
//在队列中的线程
volatile Thread thread;
Node nextWaiter;
在已知队列中的节点是这样的结构情况下,来看下何如进行获取资源
//根据源码中的英文来翻译,就是获取独占资源时调用该方法,
//也就是使用tryAcquire来改变state的值,如果失败就会把线程封装成Node.EXCLUSIVE放入队列尾部并挂起
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
//其中 tryAcquire是在实现类中具体实现的
对应的释放资源
//tryRelease是在实现类中具体实现的方法,如果成功释放资源,则激活队列中的一个线程,也就是头部节点的线程。
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
以上2个release和acquire,的最常见的实现例子就是Lock中的unlock和lock。但是在AQS源码中还存在着一个类似的方法,acquireInterruptibly ,这个方法和acquire功能一样,但是带Interruptibly 的方法,就是可以对中断进行响应。如果该线程在等待过程中被中断了,那么带Interruptibly 的方法就会抛出异常,也会终端。否则不会。对应了一开始的第二条,lockInterruptibly()可以对中断进行响应。
再来看一下在AQS中,是怎么进行加入队列操作的。
//有2种方式,如果尾部节点不为空,就直接加入封装好的node,为空则调用enq()加入队列
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
//加入一个空的node为哨兵节点
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
也就是说,当队列为空时,在加入第一个节点的时候,会先加入一个哨兵节点。
ReentrantLock 中的lock
这里分为2中情况分析lock()方法,之前说过ReentrantLock中的Sync有非公平和公平2中模式。又结合AQS的源码分析,所以我们知道区别在于FairSync 和NonfairSync 2个类中的 tryAcquire 实现不同。
FairSync 公平情况如下:
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
//当state为0时,就标识空闲,可以被获取
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;
}
可以看到对state的修改都是运行了CAS来更新,同时,在修改state的基础上,使用了公平策略hasQueuedPredecessors是在AQS中的方法
//判断当前节点是否是队列的第一个节点,如果当前节点有前驱结点返回true,当前队列为空或者当前节点是第一个节点则返回false
public final boolean hasQueuedPredecessors() {
Node t = tail;
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
这里最后的判断可以分开来看,h==s则是当前队列为空,直接返回false,如果h!=s且s==null则是,说明有一个元素将要作为AQS的第一个节点入队列,那么返回true,如果,s.thread != Thread.currentThread()) 就代表,第一个元素就不是当前的线程,返回true。
NonfairSync 非公平情况如下郑州不孕不育医院:http://www.zzfkyy120.com/
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
//非公平获取
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
//缺少了校验公平的策略,直接对state进行修改
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;
}
所以按照以上Lock的逻辑,lockInterruptibly便是调用了acquireInterruptibly的方法来获取资源
public final void acquireInterruptibly(int arg)
throws InterruptedException {
//判断线程的状态
if (Thread.interrupted())
throw new InterruptedException();
if (!tryAcquire(arg))
doAcquireInterruptibly(arg);
}
ReentrantLock 中的unlock
ReentrantLock 中的unlock,是没有策略之分,在Snyc中就实现了方法
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;
}
unlock的代码就比较好读了,先判断lock的持有线程与当前线程是否一致,然后是的话,就把state和持有线程清空。最后在AQS中,会移除该节点
当然在AQS中,还存在一个条件队列,在后续文章再谈。
下面是ReentrantLock 的一个例子,模拟领取优惠卷的情况
public class ReenTrantLockDemo extends Thread{
//模拟优惠卷
private static List array = new ArrayList<>();
private static Lock lock = new ReentrantLock();
public Integer get(){
lock.lock();
try {
Integer o= array.get(0);
array.remove(o);
return o;
}catch (Exception e){
System.out.println("获取出错");
}finally {
lock.unlock();
}
return -1;
}
@Override
public void run() {
Integer a = get();
System.out.println("获取到的优惠卷编号为"+a);
}
public static void main(String[] args) {
for(int i=0;i<10;i++){
array.add(i);
}
ReenTrantLockDemo reenTrantLockDemo = new ReenTrantLockDemo();
ExecutorService service = Executors.newCachedThreadPool();
ReenTrantLockDemo demo = new ReenTrantLockDemo();
for (int i = 0; i < 10; i++) {
service.submit(demo);
}
service.shutdown();
}
}
结果为
获取到的优惠卷编号为3
获取到的优惠卷编号为2
获取到的优惠卷编号为6
获取到的优惠卷编号为5
获取到的优惠卷编号为1
获取到的优惠卷编号为0
获取到的优惠卷编号为4
获取到的优惠卷编号为8
获取到的优惠卷编号为7
获取到的优惠卷编号为9
10
去掉lock的结果为:
获取到的优惠卷编号为0
获取到的优惠卷编号为5
获取到的优惠卷编号为0
获取到的优惠卷编号为0
获取到的优惠卷编号为0
获取到的优惠卷编号为7
获取到的优惠卷编号为4
获取到的优惠卷编号为8
获取到的优惠卷编号为0
获取到的优惠卷编号为6
名词解释
公平锁与非公平锁 :是否按照线程进入阻塞队列的顺序来执行
java多线程---ReentrantLock源码分析的更多相关文章
- Java多线程——ReentrantLock源码阅读
上一章<AQS源码阅读>讲了AQS框架,这次讲讲它的应用类(注意不是子类实现,待会细讲). ReentrantLock,顾名思义重入锁,但什么是重入,这个锁到底是怎样的,我们来看看类的注解 ...
- Java并发编程-ReentrantLock源码分析
一.前言 在分析了 AbstractQueuedSynchronier 源码后,接着分析ReentrantLock源码,其实在 AbstractQueuedSynchronizer 的分析中,已经提到 ...
- Java并发编程之ReentrantLock源码分析
ReentrantLock介绍 从JDK1.5之前,我们都是使用synchronized关键字来对代码块加锁,在JDK1.5引入了ReentrantLock锁.synchronized关键字性能比Re ...
- 细说并发5:Java 阻塞队列源码分析(下)
上一篇 细说并发4:Java 阻塞队列源码分析(上) 我们了解了 ArrayBlockingQueue, LinkedBlockingQueue 和 PriorityBlockingQueue,这篇文 ...
- JUC AQS ReentrantLock源码分析
警告⚠️:本文耗时很长,先做好心理准备,建议PC端浏览器浏览效果更佳. Java的内置锁一直都是备受争议的,在JDK1.6之前,synchronized这个重量级锁其性能一直都是较为低下,虽然在1.6 ...
- ReentrantLock 源码分析以及 AQS (一)
前言 JDK1.5 之后发布了JUC(java.util.concurrent),用于解决多线程并发问题.AQS 是一个特别重要的同步框架,很多同步类都借助于 AQS 实现了对线程同步状态的管理. A ...
- JUC之ReentrantLock源码分析
ReentrantLock:实现了Lock接口,是一个可重入锁,并且支持线程公平竞争和非公平竞争两种模式,默认情况下是非公平模式.ReentrantLock算是synchronized的补充和替代方案 ...
- ReentrantLock源码分析--jdk1.8
JDK1.8 ArrayList源码分析--jdk1.8LinkedList源码分析--jdk1.8HashMap源码分析--jdk1.8AQS源码分析--jdk1.8ReentrantLock源码分 ...
- Java split方法源码分析
Java split方法源码分析 public String[] split(CharSequence input [, int limit]) { int index = 0; // 指针 bool ...
随机推荐
- linux内核中ip,tcp等头的定义(转)
一.MAC帧头定义 /*数据帧定义,头14个字节,尾4个字节*/typedef struct _MAC_FRAME_HEADER{ char m_cDstMacAddress[6]; //目的m ...
- L2-023 图着色问题 (25 分)vector
图着色问题是一个著名的NP完全问题.给定无向图,,问可否用K种颜色为V中的每一个顶点分配一种颜色,使得不会有两个相邻顶点具有同一种颜色? 但本题并不是要你解决这个着色问题,而是对给定的一种颜色分配,请 ...
- 关于`babel-loader`和`babel-core`版本兼容性问题
1. 安装babel-loader和babel-core出现问题 1.1 安装babel的转换工具包: npm i babel-core babel-loader babel-plugin-trans ...
- C++11 并发编程基础(一):并发、并行与C++多线程
正文 C++11标准在标准库中为多线程提供了组件,这意味着使用C++编写与平台无关的多线程程序成为可能,而C++程序的可移植性也得到了有力的保证.另外,并发编程可提高应用的性能,这对对性能锱铢必较的C ...
- vm安装mac
需要 vm虚拟机:vm10 mac系统: mac10.9 vm安装mac补丁 : unlokc-all-v120 vm tools for mac10.9: darwin6.0.3.iso ...
- HTTPS为什么更安全,请看这里
本文转载于https://foofish.net/https-story-1.html HTTPS 是建立在密码学基础之上的一种安全通信协议,严格来说是基于 HTTP 协议和 SSL/TLS 的组合. ...
- 调用Web API将文件上传到服务器的方法(.Net Core)
最近遇到一个将Excel通过Web API存到服务器的问题,其中涉及到Excel的读取.调用API.Web API怎么进行接收. 一. Excel的读取.调用API Excel读取以及调用API的代 ...
- NSString 是否存在空格
NSString *_string = [NSString stringWithFormat:@"123 456"]; NSRange _range = [_string rang ...
- cell内存优化
UITableView的常用属性: 分割线颜色设置: 1> 设置separatorStyle: 分割线的颜色 方法:tableView.separatorStyle = UITableViewC ...
- js对象—类型和属性特性
前言 权威指南中摘要的,工作中用不到的,重要的js基础. 三类对象两类属性 内置对象(native object) 是由ECMScript规范定义的对象或者类.例如:函数,数组,日期,正则... 宿主 ...