并发安全性之Lock锁及原理分析
J.U.C(java.util.concurrent)中与锁相关的API
定义了一个Lock接口,作用与synchronized相似,都是为了保证线程安全性提供的解决方案,Lock定义了与抢占锁和释放锁有关的操作
- lock(),抢占锁资源方法,如果当前线程没有抢占到锁,则阻塞
- tryLock(),尝试抢占锁资源,如果抢占成功则返回true,否则返回false
- unlock(),释放锁
- …
Lock是一个接口,提供了释放锁和抢占锁的抽象方法,在J.U.C中提供了一下的具体实现
- ReentrantLock,重入锁,属于排它锁类型,功能synchronized相似
- ReentrantReadWriteLock,可重入读写锁,该类中维护两个锁,一是ReadLock,二是WriteLock
- StampedLock,Java 8引入了新的锁机制,它是ReentrantReadWriteLock的改进版本
ReentrantLock原理分析
ReentrantLock的应用
public class ReentrantLockExample {
// 一定是同一把锁
static Lock lock = new ReentrantLock();
private int count = 0;
public void incr(){
lock.lock();
try{
count ++ ;
}finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
ReentrantLockExample reentrantLockExample = new ReentrantLockExample();
Thread[] threads = new Thread[2];
for (int i = 0; i < 2; i++) {
threads[i]=new Thread(()->{
for (int j = 0; j < 1_00_00; j++) {
reentrantLockExample.incr();
}
});
threads[i].start();
}
// 确保线程执行结束
threads[0].join();
threads[1].join();
System.out.println(reentrantLockExample.count);
}
}
上述代码使用ReentrantLock的lock()和unlock方法针对于count++的非原子性操作进行加锁,保证了count++在多线程访问下的线程安全性
// 优化后
public class ReentrantReadWriteLockExample {
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private final Lock readLock = readWriteLock.readLock();
private final Lock writeLock = readWriteLock.writeLock();
private List<String> dataList = new ArrayList<>();
public void add(String data){
writeLock.lock();
try{
dataList.add(data);
}finally {
writeLock.unlock();
}
}
private String get(int index){
readLock.lock();
try{
return dataList.get(index);
}finally {
readLock.unlock();
}
}
}
ReentrantReadWriteLock通过readLock和writeLock把读和写的操作做了分离(读写分离的事项),从而减少了读操作带来的锁的竞争
ReentrantLock加锁源码分析
public class ReentrantReadWriteLockExample {
// ---1---
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private final Lock readLock = readWriteLock.readLock();
private final Lock writeLock = readWriteLock.writeLock();
private List<String> dataList = new ArrayList<>();
public void add(String data){
// ---2---对加锁进行分析
writeLock.lock();
try{
dataList.add(data);
}finally {
writeLock.unlock();
}
}
private String get(int index){
readLock.lock();
try{
return dataList.get(index);
}finally {
readLock.unlock();
}
}
}
// ---1--- 默认使用的是非公平锁
public ReentrantReadWriteLock(boolean fair) {
// 1.1 通过new NonfairSync()获取到一个非公平锁实例
sync = fair ? new FairSync() : new NonfairSync();
// 设置读锁,共享锁
readerLock = new ReadLock(this);
// 设置写锁,独占锁
writerLock = new WriteLock(this);
}
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
final void lock() {
// 比较并替换将state改为1
if (compareAndSetState(0, 1))
// 设置当前线程为独占状态
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
// -------2.1.1尝试获取锁资源-------
protected final boolean tryAcquire(int acquires) {
// -------2.1.1.1尝试获取非公平锁资源-------
return nonfairTryAcquire(acquires);
}
}
// ---2---调用lock进行获得锁的操作
public void lock() {
//---2.1---
sync.acquire(1);
}
// ----2.1 获取锁的步骤--- arg = 1;
public final void acquire(int arg) {
// -------2.1.1尝试获取锁资源-------
if (!tryAcquire(arg) &&
// 2.1.2 添加同步队列 && 2.1.3 获取锁处理
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
// -------2.1.1.1尝试获取非公平锁资源-------
// 非公平锁的获取:
// 1.如果当前线程是第一次进来,且没有其他线程抢占到锁的情况,此时给独占线程的变量赋值为当前线程,并且更改status值为1
// 2.如果status不为1,说明已经有现成获取到了锁,则判断是否重入,如果重入,则记录重入次数
final boolean nonfairTryAcquire(int acquires) {
// 获取当前线程
final Thread current = Thread.currentThread();
// 获取当前锁的状态 此时获取的是变量1的内容赋值给局部变量
int c = getState();
// 如果第一个线程第一次进来此时给当前线程设置为独占状态并且给变量1设置为 1 的状态
// 第一个线程第一次进来的c就等于0
if (c == 0) {
// 如果CAS成功,此时变量1由0变成1,
if (compareAndSetState(0, acquires)) {
// 把当前线程设置为独占状态 把变量2设置为当前线程
setExclusiveOwnerThread(current);
return true;
}
}
// 获取变量2 如果变量2为当前线程 (重入)
else if (current == getExclusiveOwnerThread()) {
// 变量1 做累加操作
int nextc = c + acquires;
// 超过了重入的最大次数 最大为2的32次方-1
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
// 更新变量1的值
setState(nextc);
return true;
}
return false;
}
// 不安全的类,主要实现在JVM层
private static final Unsafe unsafe = Unsafe.getUnsafe();
// 重入锁的状态
private static final long stateOffset;
// 头节点
private static final long headOffset;
// 尾节点
private static final long tailOffset;
// 等待状态,多用于await和singal
private static final long waitStatusOffset;
// 下一个节点
private static final long nextOffset;
// 反射获取AbstractQueuedSynchronizer.class类中的属性
static {
try {
stateOffset = unsafe.objectFieldOffset
(AbstractQueuedSynchronizer.class.getDeclaredField("state"));
headOffset = unsafe.objectFieldOffset
(AbstractQueuedSynchronizer.class.getDeclaredField("head"));
tailOffset = unsafe.objectFieldOffset
(AbstractQueuedSynchronizer.class.getDeclaredField("tail"));
waitStatusOffset = unsafe.objectFieldOffset
(Node.class.getDeclaredField("waitStatus"));
nextOffset = unsafe.objectFieldOffset
(Node.class.getDeclaredField("next"));
} catch (Exception ex) { throw new Error(ex); }
}
// 变量1 此变量为int类型的属性 state初始化为0
private volatile int state;
// 获取变量1的值
protected final int getState() {
return state;
}
// 变量1赋值
protected final void setState(int newState) {
state = newState;
}
// 变量2 独占的线程
private transient Thread exclusiveOwnerThread;
// 设置独占线程
protected final void setExclusiveOwnerThread(Thread thread) {
exclusiveOwnerThread = thread;
}
// 获取变量2
protected final Thread getExclusiveOwnerThread() {
return exclusiveOwnerThread;
}
// 2.1.2 添加同步队列 mode = Node.EXCLUSIVE 独占状态
private Node addWaiter(Node mode) {
// 创建一个独占状态的节点
Node node = new Node(Thread.currentThread(), mode);
// 获取尾节点
Node pred = tail;
// 当前尾节点不为空,通过尾插法维护一个双向链表
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
// 2.1.2.1 这个方法主要是涉及到初始化的动作,通过尾插法维护一个双向链表
enq(node);
// 返回当前节点
return node;
}
// 2.1.2.1 通过尾插法维护一个双向链表
private Node enq(final Node node) {
// 自旋,进行链表的连接处理
for (;;) {
// 获取尾节点,并通过中间变量t承接,主要是为了维护双向链表
Node t = tail;
// 如果尾节点为空,则设置一个哨兵节点进行初始化,并将该哨兵节点设置为头部和尾部
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
// 当尾部节点已经经过了初始化之后
} else {
// 将当前节点的前一个节点为t结点,将尾结点的指针偏移量移到当前节点,如果CAS成功,则将t节点的下一个节点设置为当前节点并返 回
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
// 2.1.3 获取锁处理 根据此处可得出维护的双向链表遵循先进先出的原则
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
// 自旋进行抢占锁处理
for (;;) {
// 获取当前节点的前一个节点,主要是因为插入的过程是尾插法,获取数据则采取头插法,
final Node p = node.predecessor();
// 如果当前节点为头节点,则直接抢占锁操作,如果抢占成功,则将当前节点设置为头节点,清空当前节点的线程以及跟原来链表的头结 点的连接,帮助GC回收,并且返回中断标记为false
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
// 如果当前节点的前一个节点不是头结点,或者是获取锁失败,则进入 2.1.3.1 尝试获取锁失败的逻辑中
if (shouldParkAfterFailedAcquire(p, node) &&
// 尝试获取锁失败逻辑处理完成之后进入到 2.1.3.2挂起处理
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
采用先进先出,主要是考虑到两点
1、不至于让最先进来的线程等待太长的时间
2、防止多线程情况下维护链表导致的混乱性问题
/**
*
* pred:当前节点的前一个节点
* node:当前节点
*/
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
// 第一次进入是前一个节点的waitStatus为0,则将前一个节点的等待状态设置为-1
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
return true;
// waitStatus >0 的状态为CANCELLED状态,表示被取消。如果当前节点的前一个节点被取消,则接着从后往前查找,知道找到未取消的节点, 并与取消的节点断开连接
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
// 通过CAS操作将waitStatus更新为SIGNAL状态
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
waitStatus=0(INITAL):初始状态
waitStatus=-1(SIGNAL):后继节点的线程仍然会处于等待状态,如果当前节点的线程抢占到了锁或者被取消,则会通知后继节点,后继节点得以进行运行。
waitStatus=1(CANCELLED):可能是因为中断导致节点被取消,此时这样的节点需要被清理
// 2.1.3.2挂起处理
private final boolean parkAndCheckInterrupt() {
// 挂起当前线程,此时程序在此处让出CPU的时间片,如果有其他线程调用了共享变量的unpark方法,则唤醒此处的方法此时线程将在此处进行往下 处理。因为在挂起的过程中,可能会导致线程中断。
LockSupport.park(this);
// 此处是为了判断是否是由现成中断唤醒的,如果是由中断唤醒的则返回true,不是由中断唤醒的则返回false,并复位中断标识即置为false状 态。中断不清楚的可以去线程的第一堂课重新学习
return Thread.interrupted();
}
interrupt:调用线程的中断方法,并不能真正的中断,而是设置中断标识
interrupted:检查当前线程是否是中断状态,并清除中断标志
isInterrupted:检查当前线程的中断状态,但不具备清除功能
ReentrantLock释放锁源码分析
// 1 释放锁操作
public void unlock() {
// 1.1 释放锁 设置释放数为1,因为加锁时就给锁的状态根据重入次数依次累加1
sync.release(1);
}
// 1.1 释放锁
public final boolean release(int arg) {
// 1.1.1 释放锁成功
if (tryRelease(arg)) {
// 获取AQS中的head节点
Node h = head;
// 如果当前节点不为空,并且当前节点的状态不为0,则调用unparkSuccessor(h)方法唤醒后续节点
if (h != null && h.waitStatus != 0)
// 1.1.2 唤醒节点进行后续处理
unparkSuccessor(h);
return true;
}
return false;
}
// 1.1.1 释放锁成功
protected final boolean tryRelease(int releases) {
// 因为锁支持重入,则释放锁需要根据当前的state的状态依次减一进行释放一直减到0为止,才算完全释放
int c = getState() - releases;
// 如果当前线程非独占状态的线程,直接报错
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
// 当前状态为0,则将当前的独占状态线程设置为null即可,将c=0置入state变量中即释放锁结束
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
// 1.1.2 唤醒节点进行后续处理
/**
*
* node = head
*/
private void unparkSuccessor(Node node) {
// 理论上加完锁之后的等待状态如果没有被清理,则都是小于0的
int ws = node.waitStatus;
// 如果小于0,则通过CAS操作进行waitStatus状态归0操作
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
// 获取head节点的下一个节点
Node s = node.next;
// 当head节点的下一个节点为空,或者等待节点大于0(大于0的节点都是取消节点的状态)
if (s == null || s.waitStatus > 0) {
s = null;
// 通过从尾部节点开始扫描,找到距离head最近的一个waitStatus小于或等于0的节点
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
// 如果next节点不为空,则直接唤醒这个检点就可以
if (s != null)
LockSupport.unpark(s.thread);
}
为什么此处发现第二个节点为空时不是从头部开始遍历?
因为存在其他线程也调用unlock()方法从head开始往后遍历,有可能存在一种情况为链表关系没有建立完全,导致遍历t节点时被中断。但是如果从尾部开始遍历,一直往前找,则此时找到的节点都是节点已经建立完全的节点,就一定不会存在这个问题。
唤醒之后,则继续进入上方 2.1.3.2挂起处理
方法的park处进行往下处理,至此ReentrantLock已全部分析完毕
ReentrantReadWriteLock原理分析
ReentrantReadWriteLock的应用
// 待优化
public class ReadWriteLockExample {
private final Lock lock = new ReentrantLock();
private List<String> dataList = new ArrayList<>();
public void add(String data) {
lock.lock();
try {
dataList.add(data);
} finally {
lock.unlock();
}
}
private String get(int index) {
lock.lock();
try {
return dataList.get(index);
} finally {
lock.unlock();
}
}
}
RentrantReadWriteLock特性如下:
- 读/读不互斥,如果多个线程访问读方法,那么这些线程不会阻塞
- 读/写互斥,如果有一个线程在访问读方法,另一个线程要访问写方法,那么为了保证数据的一致性,调用写方法的线程要阻塞,反之也一样
- 写/写互斥,如果多线程访问该方法,则必须要按照互斥规则进行同步
// 优化后
public class ReentrantReadWriteLockExample {
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private final Lock readLock = readWriteLock.readLock();
private final Lock writeLock = readWriteLock.writeLock();
private List<String> dataList = new ArrayList<>();
public void add(String data) {
writeLock.lock();
try {
dataList.add(data);
} finally {
ReentrantReadWriteLock通过readLock和writeLock把读和写的操作做了分离(读写分离的事项),从而
减少了读操作带来的锁的竞争
ReentrantReadWriteLock源码分析
writeLock.unlock();
}
}
private String get(int index) {
readLock.lock();
try {
return dataList.get(index);
} finally {
readLock.unlock();
}
}
}
ReentrantReadWriteLock通过readLock和writeLock把读和写的操作做了分离(读写分离的事项),从而减少了读操作带来的锁的竞争
ReentrantReadWriteLock源码分析
package com.example.mybatisdemo;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
* @author : Doug Liang
* @date : 2024/8/7 9:01
* @Version: 1.0
*/
// 读锁的内部类
private final ReentrantReadWriteLock.ReadLock readerLock;
// 写锁的内部类
private final ReentrantReadWriteLock.WriteLock writerLock;
// 全局的执行所有同步机制的Sync(读锁和写锁内部还各有一个内部的,所以有下面的那种初始化写法)
final Sync sync;
--AQS--
// 重入次数
private volatile int state;
static final int SHARED_SHIFT = 16;
// 十进制为:65536 二进制为:00000000 00000001 00000000 00000000
static final int SHARED_UNIT = (1 << SHARED_SHIFT);
// 锁重入次数的最大值:65535
static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1;
// 十进制为:65535 二进制为:11111111 11111111
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
/**
* Returns the number of shared holds represented in count
*/
static int sharedCount(int c) {
return c >>> SHARED_SHIFT;
}
// 返回表示的独占保存的个数
static int exclusiveCount(int c) {
return c & EXCLUSIVE_MASK;
}
// firstReader是第一个获得读锁的线程。
private transient Thread firstReader = null;
// firstReaderHoldCount是firstReader的保持计数
private transient int firstReaderHoldCount;
// 最后一个成功获得readLock的线程的持有数。
private transient HoldCounter cachedHoldCounter;
// 当前线程持有的可重入读锁的数量。只在构造函数和readObject中初始化。当线程的读保持计数下降到0时删除。
private transient ThreadLocalHoldCounter readHolds;
// 初始化读写锁
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
// 获取读锁
private final Lock readLock = readWriteLock.readLock();
// 获取写锁
private final Lock writeLock = readWriteLock.writeLock();
// new出来的对象调用构造方法
public ReentrantReadWriteLock() {
this(false);
}
// 默认还是非公平锁
public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
readerLock = new ReadLock(this);
writerLock = new WriteLock(this);
}
protected ReadLock(ReentrantReadWriteLock lock) {
// 将ReentrantReadWriteLock的全局同步机制的Sync赋值给ReadLock内部的同步机制的Sync
sync = lock.sync;
}
protected WriteLock(ReentrantReadWriteLock lock) {
// 将ReentrantReadWriteLock的全局同步机制的Sync赋值给WriteLock内部的同步机制的Sync
sync = lock.sync;
}
ReentrantReadWriteLock读锁加锁分析
public void lock() {
// 获取共享锁
sync.acquireShared(1);
}
// 获取共享锁
public final void acquireShared(int arg) {
// 如果抢占锁失败,返回 -1,进入CLH队列进行锁的抢占
if (tryAcquireShared(arg) < 0)
// 维护CLH队列进行锁的抢占
doAcquireShared(arg);
}
// 尝试获取共享锁: 小于0 表示获取共享锁失败,需要尝试抢占共享锁 其他的表示抢占共享锁成功
/*
* 1.如果是写锁被其他线程抢占直接失败
* 2.否则,在线程竞争不激烈时,直接进入到线程的读写锁的状态处理。
* 3.线程竞争激烈的情况下,直接进入完成版本的获取锁fullTryAcquireShared(current)
*/
protected final int tryAcquireShared(int unused) {
// 获取当前线程
Thread current = Thread.currentThread();
// 获取共享变量state的值,初始值为0
int c = getState();
// 如果count不为0且不是独占状态则直接返回: 代码块1
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
// 通过位运算计算读锁的count: 代码块2
int r = sharedCount(c);
// 代码块3:判断是否需要阻塞
if (!readerShouldBlock() &&
// 判断当前的共享统计是否大于65535
r < MAX_COUNT &&
// 高位存储一个1即 第一次为:00000000 00000001 00000000 00000000并
以此类推
compareAndSetState(c, c + SHARED_UNIT)){
// 当读锁是第一个进来时,保存firstReader和firstReaderHoldCount
if (r == 0) {
firstReader = current;
// 初始化firstReaderHoldCount = 1;
firstReaderHoldCount = 1;
// 如果是firstReader重入状态时,直接记录第一个进来的次数
} else if (firstReader == current) {
firstReaderHoldCount++;
// 如果不是第一个读锁
} else {
// 每个线程读取保持计数的计数器,线程的tid 代码块4
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
// 此处使用了我们之前学到的知识点ThreadLocal 代码块5
cachedHoldCounter = rh = readHolds.get();
// 如果count为0,将当前的rh设置到readHolds中
else if (rh.count == 0)
readHolds.set(rh);
// 进行count++的动作。
rh.count++;
}
return 1;
}
return fullTryAcquireShared(current);
}
// 读取获取的完整版本,用于处理CAS错误和重入读未在tryacquirered中处理。此处与上面代码最大的区别在于多了一块关于最大可重入次数判断的逻辑。主要是为了处理更复杂的不可重入锁的场景,而将重入次数的检查之后的处理。
final int fullTryAcquireShared(Thread current) {
HoldCounter rh = null;
for (; ; ) {
int c = getState();
if (exclusiveCount(c) != 0) {
if (getExclusiveOwnerThread() != current)
return -1;
} else if (readerShouldBlock()) {
if (firstReader == current) {
} else {
if (rh == null) {
rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current)) {
rh = readHolds.get();
if (rh.count == 0)
readHolds.remove();
}
}
if (rh.count == 0)
return -1;
}
}
if (sharedCount(c) == MAX_COUNT)
throw new Error("Maximum lock count exceeded");
if (compareAndSetState(c, c + SHARED_UNIT)) {
if (sharedCount(c) == 0) {
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
firstReaderHoldCount++;
} else {
if (rh == null)
rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
cachedHoldCounter = rh; // cache for release
}
return 1;
}
}
}
// 代码块1: 通过与运算获取重入次数
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
// 代码块2: 通过位运算计算读锁的count,此处是个很巧妙的设计,通过高16位存储读锁
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
// 代码块3:读与读不阻塞 读与写阻塞
final boolean readerShouldBlock() {
return apparentlyFirstQueuedIsExclusive();
}
final boolean apparentlyFirstQueuedIsExclusive() {
Node h, s;
return (h = head) != null &&
(s = h.next) != null &&
!s.isShared() &&
s.thread != null;
}
// 代码块4:每个线程读取保持计数的计数器,count表示记数值,tid表示的是线程ID。
static final class HoldCounter {
int count = 0;
final long tid = getThreadId(Thread.currentThread());
}
// 通过反射获取到线程的tid,tid是Thread.class内部定义的一个属性
static final long getThreadId(Thread thread) {
return UNSAFE.getLongVolatile(thread, TID_OFFSET);
}
private static final long TID_OFFSET;
static {
try {
UNSAFE = sun.misc.Unsafe.getUnsafe();
Class<?> tk = Thread.class;
TID_OFFSET = UNSAFE.objectFieldOffset
(tk.getDeclaredField("tid"));
} catch (Exception e) {
throw new Error(e);
}
}
// 代码块五:调用readHolds.get()方法时会进行初始化,重写了ThreadLocal中的initialValue的方
法
static final class ThreadLocalHoldCounter
extends ThreadLocal<HoldCounter> {
public HoldCounter initialValue() {
return new HoldCounter();
}
}
// 维护CLH队列进行锁的抢占 与之前讲的内容一致。
private void doAcquireShared(int arg) {
// 维护一个CLH队列添加一个Node.SHARED类型的节点,此处在之前讲的AQS里
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
for (; ; ) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
ReentrantReadWriteLock读锁释放锁分析
// 释放读锁
public void unlock() {
// 释放共享锁
sync.releaseShared(1);
}
// 释放共享锁
public final boolean releaseShared(int arg) {
// 尝试释放共享锁
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
protected final boolean tryReleaseShared(int unused) {
// 获取当前线程
Thread current = Thread.currentThread();
// 如果当前线程是firstReader,
if (firstReader == current) {
// firstReaderHoldCount为1,表示释放完成(在获取到读锁时,直接将firstReaderHoldCount为1赋值成1了,所以此时直接置空firstReader就可以完成锁的释放),直接将firstReader置为空即可
if (firstReaderHoldCount == 1)
firstReader = null;
// 否则重入次数进行累减即可
else
firstReaderHoldCount--;
} else {
// 如果firstReader不是当前线程,则进行ThreadLocal的处理。(ThreadLocal使用remove方法能够防止内存溢出)
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
rh = readHolds.get();
int count = rh.count;
// 只有在小于等于1时,才能保证当前锁已释放完毕,才可以调用remove方法。有些同学有疑
问,为什么会是 1 啊?因为累减的操作是在下面的。
if (count <= 1) {
readHolds.remove();
if (count <= 0)
throw unmatchedUnlockException();
}
--rh.count;
}
// 自旋进行锁释放的操作。
for (; ; ) {
int c = getState();
int nextc = c - SHARED_UNIT;
// 通过CAS操作进行锁的释放
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
关于此处的一个思考:为什么要把firstReader单独拎出来处理?
这个问题源于jdk1.6时的一个bug,当时使用ThreadLocal进行计数,没有调用remove,所以导致
在使用过程中有内存溢出的现象。大量的使用ThreadLocal,导致内存占用过大,所以做了优化。
再次思考第一个线程不使用 ThreadLocal 有哪些作用:
- 在一定程度上确实可以减少 ThreadLocal 的使用,降低一些内存吧,假设有50000个锁,就可以最多减少50000个 ThreadLocal 的内存。
- 对于无竞争时,只有一个线程获取读锁的计数省去了去 ThreadLocal 中查找而性能高效。但是一般情况都是有竞争的,所以实际收效甚微。
- 获取当前线程的读锁重入次数,若是第一个线程则可以免去 ThreadLocal 中查找,也能提高一点性能。
ReentrantReadWriteLock写锁源码分析
ReentrantReadWriteLock写锁加锁分析
public void lock() {
// 获取锁
sync.acquire(1);
}
public final void acquire(int arg) {
// 尝试获取锁
if (!tryAcquire(arg) &&
// 添加同步队列(同AQS章节一致)
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
protected final boolean tryAcquire(int acquires) {
// 获取当前线程
Thread current = Thread.currentThread();
// ReentrantReadWriteLock写锁释放锁分析
// 获取当前的锁状态
int c = getState();
// 获取独占锁重入次数
int w = exclusiveCount(c);
// 解释一下为什么下面判断是用w来判断的,如果c不等于0就说明已经有锁在占用了。但是不确定当前被占用的是 读锁还是写锁,所以通过位运算获取写锁(低位是写锁,高位是读锁)
if (c != 0) {
// 如果当前写锁状态为0,并且当前线程不是独占状态则直接返回
if (w == 0 || current != getExclusiveOwnerThread())
return false;
// 如果当前线程是独占状态,则记录重入次数,重入次数不得大于65535
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// 记录重入次数
setState(c + acquires);
return true;
}
// 这个方法是为公平锁和非公平锁做的钩子方法,非公平锁直接返回false
if (writerShouldBlock() ||
// 更state的值(此时需要考虑多线程),如果更新失败返回false
!compareAndSetState(c, c + acquires))
return false;
// 设置独占锁
setExclusiveOwnerThread(current);
return true;
}
ReentrantReadWriteLock写锁释放锁分析
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
protected final boolean tryRelease(int releases) {
// 如果当前线程不是独占状态则直接报错
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
// 减state即可
int nextc = getState() - releases;
boolean free = exclusiveCount(nextc) == 0;
// 根据条件判断是否要清空独占线程
if (free)
setExclusiveOwnerThread(null);
setState(nextc);
return free;
}
// 获取当前线程是否为独占线程
protected final boolean isHeldExclusively() {
return getExclusiveOwnerThread() == Thread.currentThread();
}
StampedLock原理分析
StampedLock的应用
ReentrantReadWriteLock锁有一个问题,如果当前有现成在调用get()方法,那么所有调用add()方法的线程必须等待调用get()方法的线程释放锁之后才能写,也就是说在读的过程中不允许写。而如果get的方法的线程比较多时,会导致写线程一直类阻塞。
为解决ReentrantReadWriteLock中的这个潜在的问题,在java8中引入了StampedLock机制,即优化了读写锁的访问,简单的说就是提供了一种乐观锁策略,当有现成调用get方法读取数据时,不会阻塞准备执行写操作的线程。
public class Point {
private double x, y;
private final StampedLock sl = new StampedLock();
void move(double deltaX, double deltaY) { // an exclusively locked method
long stamp = sl.writeLock();
try {
x += deltaX;
y += deltaY;
} finally {
sl.unlockWrite(stamp);
}
}
double distanceFromOrigin() { // A read-only method
long stamp = sl.tryOptimisticRead();
double currentX = x, currentY = y;
if (!sl.validate(stamp)) {
stamp = sl.readLock();
try {
currentX = x;
currentY = y;
} finally {
sl.unlockRead(stamp);
}
}
return Math.sqrt(currentX * currentX + currentY * currentY);
}
void moveIfAtOrigin(double newX, double newY) { // upgrade
// Could instead start with optimistic, not read mode
long stamp = sl.readLock();
try {
while (x == 0.0 && y == 0.0) {
long ws = sl.tryConvertToWriteLock(stamp);
if (ws != 0L) {
stamp = ws;
x = newX;
y = newY;
break;
} else {
sl.unlockRead(stamp);
stamp = sl.writeLock();
}
}
} finally {
sl.unlock(stamp);
}
}
}