文章目录
一、锁
1.锁分类
在java中,锁按不同的条件及状态可以划分为不同种类的锁,对锁进行细分的主要原因是,给出不同场景下性能及安全性的实践方案,根据实际情况选择最适合的锁。
2.无锁技术
2.1 比较与交换(Compare And Swap)
CAS时是指对某一个变量进行修改的算法,该算法包含三个要素,值所在内存地址(V),预期原值(A),新值(B)。即修改的线程期望在内存地址V中的值是A,如果是A没有变化,更新为新值B,如果不为A有变化时,返回A变化后的值,不更新。
在使用CAS时,有如下问题需要注意:
- ABA问题
如果一个值修改为A,又修改为了B,最后又修改回了A,对于执行修改的线程,预期原值仍然是A,认为这个变量没有被修改过,但实际已经被修改过了。解决该问题应使用版本号,更改变量后版本号+1,在CAS的基础上,多比对一下版本号是否变化。 - 当循环时间长时,CPU的开销大
由于CAS使用自旋的方式一直尝试设置新的值,设置失败继续失败,如果一直不成功,将一直占用CPU的时间,造成很多无用的开销 - 只能保证一个共享变量的操作
CAS并不能保证原子性,所以如果涉及多个共享变量的操作需要进行加锁,或者直接将多个共享变量和为一个变量(锁实现源码中就是用的该方式),又或者将多个共享变量封装为一个对象进行设置(AtomicReference)
2.2 写入时复制 (Copy-on-write)
写入时复制是指,在写入时复制一份共享变量的副本,在副本中进行写入,待写入完成后使用乐观或悲观锁写回原共享变量中,这样做的目的是为了使读操作无锁。
在使用Copy-on-write时,有如下问题需要注意:
- 仅适用于写操作少的情况
若存在大量写操作的情况,会导致每一次写入都会创建一个变量的副本,造成大量的开销。 - 允许短暂的读写不一致
在存在写入时,读取的是原变量的未进行修改前的值,只能保证最终一致性,并不能保证强一致性。
2.3 线程本地存储 (Thread Local Storage, TLS)
线程本地存储的原理是,不同线程使用不同的变量,避免共享。可以使用ThreadLocal来达到每个线程都拥有自己独有的资源,使用方式如下:
public class ThreadValue {
/**
* 定义每个线程独有的资源
*/
final static AtomicLong THREAD_VALUE = new AtomicLong(0);
/**
* 定义并初始化ThreadLocal
*/
final static ThreadLocal<Long> THREAD_LOCAL = ThreadLocal.withInitial(new Supplier<Long>() {
@Override
public Long get() {
return THREAD_VALUE.getAndIncrement();
}
});
/**
* 获取独有资源的值
* @return
*/
public static Long get(){
return THREAD_VALUE.get();
}
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程1获得的变量:"+ ThreadValue.get());
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程2获得的变量:"+ ThreadValue.get());
}
}).start();
}
}
调用结果如下,由结果可知,不同线程使用的是不同的ThreadLocal对象
线程1获得的变量:0
线程2获得的变量:0
注:在使用ThreadLocal需要注意内存泄漏问题,手动清理ThreadLocal对象。
ExecutorService es;
ThreadLocal tl;
es.execute(()->{
//ThreadLocal增加变量
tl.set(obj);
try {
// 省略业务逻辑代码
}finally {
//手动清理ThreadLocal
tl.remove();
}
});
3.JUC中的锁
3.1 AbstractQueuedSynchronizer(AQS)
3.1.1 定义
AbstractQueuedSynchronizer抽象队列同步器(AQS),提供一个框架来实现阻塞锁和依赖先进先出(FIFO)等待队列的相关同步器(信号量、事件等)。对于大多数依赖于单个原子int值来表示状态的同步器,这个类被设计成一个有用的基础
3.1.2 核心思想
AQS核心思想是实现阻塞锁及同步器,需要具备如下的要点
3.1.2.1 记录同步状态(state变量)
定义state变量,并提供了cas设置state的方法
- state=0,,没有线程持有锁
- state=1,有一个线程持有锁
- state>1,该线程重入了锁
//共享变量,使用volatile修饰保证线程可见性
private volatile int state;
//返回同步状态的当前值
protected final int getState() {
return state;
}
// 设置同步状态的值
protected final void setState(int newState) {
state = newState;
}
//原子地(CAS操作)将同步状态值设置为给定值update如果当前同步状态的值等于expect(期望值)
protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
3.1.2.2 记录当前持有锁的线程
AQS父类AbstractOwnableSynchronizer 记录了持有锁的线程
public abstract class AbstractOwnableSynchronizer implements java.io.Serializable {
// 记录持有锁的线程
private transient Thread exclusiveOwnerThread;
}
3.1.2.3 支持阻塞/唤醒操作
AQS使用LockSupport.park()使线程阻塞,LockSupport.unpark()精准唤醒对应线程
public class LockSupport {
public static void park() { U.park(false, 0L); }
public static void unpark(Thread thread) { if (thread != null) U.unpark(thread); }
}
park/unpark与wait/notify的核心区别
park/unpark | wait/notify |
---|---|
park与unpark无论先执行哪句都会唤醒对应线程 | 先调用notify再调用wait会抛出IllegalMonitorStateException异常 |
park/unpark可以在线程的任意地方执行 | wait/notify只能在synchronized块中执行 |
park/unpark支持精准唤醒 | wait/notify只能唤醒随机的一个线程 |
3.1.2.4 阻塞队列(CLH)
AQS内部实现CLH(Craig,Landin,and Hagersten)队列,实际的数据结构为双向链表,当node初始化时为空,head=tail=null,从尾结点入从头结点出
3.1.3 资源共享方式
AQS定义如下两种资源共享方式
-
Exclusive(独占)
只允许一个线程访问,如ReentrantLock -
Share(共享)
运行多个线程访问,如ReadWriteLock、Semaphore、CountDownLatch、 CyclicBarrier等
3.1.4 模板方法
AQS采用模板方法定义了继承AQS需进行重写的方法即对state的获取和释放
/**
* 独占方式,尝试获取资源
* @param arg
* @return 获取资源是否成功
*/
protected boolean tryAcquire(int arg)
/**
* 独占方式,尝试释放资源
* @param arg
* @return 释放资源是否成功
*/
protected boolean tryRelease(int arg)
/**
* 共享方式,尝试获取资源
* @param arg
* @return 负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源
*/
protected int tryAcquireShared(int arg)
/**
* 共享方式,尝试释放资源
* @param arg
* @return 释放资源是否成功
*/
protected boolean tryReleaseShared(int arg)
/**
* 只有用到condition才需要去实现它。
* @return 该线程是否正在独占资源
*/
protected boolean isHeldExclusively()
3.1.5 核心方法
3.1.5.1 acquire
acquire以独占模式获取资源,忽略中断,在线程阻塞过程中,中断无效,源码如下:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
//头结点出队
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
//当获取(资源)失败后,检查并且更新结点状态
if (shouldParkAfterFailedAcquire(p, node) &&
//阻塞并检查是否中断
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
-
addWaiter
为当前线程生成node插入队列尾部 -
acquireQueued
头结点出队,执行资源申请,未申请成功的线程进入阻塞状态,不会响应中断 ,但会记录并返回中断状态 -
selfInterrupt
中断补偿,由于中断时线程处于阻塞状态,故此时补偿响应中断 -
tryAcquire
定义的模板方法由子类实现,计算当前线程获取锁后的state值
3.1.5.2 acquireShared
acquireShared以共享模式获取资源,忽略中断,在线程阻塞过程中,中断无效,核心逻辑与acquire类似,增加唤醒后继节点的步骤setHeadAndPropagate,源码如下:
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
private void doAcquireShared(int arg) {
//为当前线程生成node插入队列尾部
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) {
//用来设置新head,没有后继节点或者后继节点是共享类型,进行唤醒下调用doReleaseShared
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);
}
}
-
tryAcquireShared
定义的模板方法由子类实现,计算当前线程获取锁后的state值 -
doAcquireShared
头结点出队,执行资源申请,未申请成功的线程进入阻塞状态,不会响应中断 ,但会记录并返回中断状态
3.1.5.3 release
提供独占模式下,资源释放的方法
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);
}
-
tryRelease
父类定义的模板方法,计算当前线程释放锁后的state值(此时为单线程) -
unparkSuccessor
唤醒队列中的后继者
3.1.5.4 releaseShared
提供共享模式下,资源释放的方法
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}
-
tryReleaseShared
父类定义的模板方法,计算当前线程释放锁后的state值(此时为多线程,需要使用cas) -
doReleaseShared
唤醒后续的第一个获取写锁之前的所有获取读锁的节点,没有写锁将会唤醒整个队列(批量唤醒)
3.1.6 ConditionObject
3.1.6.1 源码结构
ConditionObject是Condition的实现,子类可以使用该new ConditionObject()构造条件变量,使用的数据结构为双向链表,与锁使用的阻塞队列是同一个类
public class ConditionObject implements Condition, java.io.Serializable
{
private transient Node firstWaiter;
private transient Node lastWaiter;
}
3.1.6.2 await
await流程为,先释放锁,判断不在AQS阻塞队列中,不在阻塞,待signal唤醒,唤醒后在AQS阻塞队列中,重新获取锁
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
// 加入Condition的等待队列
Node node = addConditionWaiter();
// 阻塞在Condition之前必须先释放锁,否则会死锁
int savedState = fullyRelease(node);
int interruptMode = 0;
//不在AQS阻塞队列中
while (!isOnSyncQueue(node)) {
//阻塞当前对象
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
// 重新获取锁
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
// 被中断唤醒,抛中断异常
reportInterruptAfterWait(interruptMode);
}
-
acquireQueued
AQS队列,头结点出队,执行资源申请,未申请成功的线程进入阻塞状态,不会响应中断 ,但会记录并返回中断状态 -
isOnSyncQueue
用于判断该Node是否在AQS的同步队列里面。初始的时候,Node只在Condition的队列里,而不在AQS的队列里。但执行signal操作的时候,会放进AQS的同步队列 -
checkInterruptWhileWaiting
检测在park期间是否收到过中断信号。若收到中断信号,会使interruptMode!=0,退出循环并抛中断异常。未收到中断信号interruptMode=0 被唤醒后 检查是否在AQS的同步队列里 存在则退出循环
3.1.6.2 signal
signal流程为头结点出Condition队列,放入AQS阻塞队列中,再唤醒该结点
public final void signal() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
// 唤醒队列中的第1个线程
private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
final boolean transferForSignal(Node node) {
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
// 先把Node放入互斥锁的同步队列中,再调用unpark方法
Node p = enq(node);
int ws = p.waitStatus;
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
3.2 Lock
Lock是对外提供的接口,提供加锁解锁方法,包含的方法如下:
public interface Lock {
/**
* 加锁
*/
void lock();
/**
* 可中断的加锁
* @throws InterruptedException 可中断异常
*/
void lockInterruptibly() throws InterruptedException;
/**
* 尝试获取锁,未获取到锁不进行阻塞
* @return 是否成功获取锁
*/
boolean tryLock();
/**
* 尝试获取锁,未获取到锁不进行阻塞,可设置超时时间
* @param time 超时时间
* @param unit 时间单位
* @return 是否成功获取锁
* @throws InterruptedException 可中断异常
*/
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
/**
* 解锁
*/
void unlock();
/**
* 初始化条件变量
* @return 条件变量
*/
Condition newCondition();
}
3.3 Condition
Condition是对外提供的条件相关的接口,提供阻塞唤醒方法(类似wait/notify),包含的方法如下:
public interface Condition {
/**
* 阻塞,可中断
* @throws InterruptedException 中断异常
*/
void await() throws InterruptedException;
/**
* 阻塞,不可中断
* @throws InterruptedException 中断异常
*/
void awaitUninterruptibly();
/**
* 等待多少纳秒后唤醒,可中断
* @param nanosTimeout 等待的纳秒的long型数值
* @return 剩余等待时间 <=0 代表超时
* @throws InterruptedException 中断异常
*/
long awaitNanos(long nanosTimeout) throws InterruptedException;
/**
* 等待多少时间后唤醒,可中断
* @param time 等待时间
* @param unit 时间单位
* @return false 等待超时 true 未到时间
* @throws InterruptedException 中断异常
*/
boolean await(long time, TimeUnit unit) throws InterruptedException;
/**
* 等待到截止日期后唤醒,可中断
* @param deadline 截止日期
* @return false 等待超时 true 未到时间
* @throws InterruptedException 中断异常
*/
boolean awaitUntil(Date deadline) throws InterruptedException;
/**
* 唤醒第一个
*/
void signal();
/**
* 唤醒全部
*/
void signalAll();
}
3.4 ReentrantLock
3.4.1 继承类图
如下图所示
3.4.2 源码结构
ReentrantLock意为可重入锁(同一线程可以多次获取同一把对象锁),内部存在Sync抽象类,NonfairSync(非公平锁实现)及FairSync(公平锁实现),实现了lock接口,lock接口中的方法实现由Sync及其子类来实现。
构造方法默认使用非公平锁,也支持传入是否公平参数来对应构造公平或非公平锁
public class ReentrantLock implements Lock, java.io.Serializable {
private final Sync sync;
abstract static class Sync extends AbstractQueuedSynchronizer{}
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();
}
public void lock() {
sync.lock();
}
public void unlock() {
sync.release(1);
}
}
3.4.3 NonfairSync
非公平锁的实现,即直接尝试设置state的值不管是否存在排队的线程
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
//state为0 说明没有线程占用锁
if (c == 0) {
//直接尝试设置state的值,不管是否存在排队的线程
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//如果state不为为0 且占有线程的是当前线程 state+1 表示重入
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;
}
3.4.4 FairSync
公平锁的实现,需要调用hasQueuedPredecessors()判断是否有等待的线程,并尝试设置state为1
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
//state为0 说明没有线程占用锁
if (c == 0) {
//判断是否有等待的线程,并尝试设置state为1
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//如果state不为为0 且占有线程的是当前线程 state+1 表示重入
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
3.4.5 使用示例
生产者消费者模式,实现如下
public class MyQueue<E> {
/**
* 锁
*/
private final Lock lock;
/**
* 消费者条件队列
*/
private final Condition notEmpty;
/**
* 生产者条件队列
*/
private final Condition notFull;
/**
* 阻塞队列
*/
private final Object[] items;
/**
* 消费索引
*/
private int takeIndex;
/**
* 生产索引
*/
private int putIndex;
/**
* 队列数量
*/
private int count;
public MyQueue(int capacity) {
items = new Object[capacity];
lock = new ReentrantLock();
notEmpty = lock.newCondition();
notFull = lock.newCondition();
}
public void enqueue(E x) {
lock.lock();
try {
//队列满时阻塞生产者
while (count == items.length) {
try {
notFull.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//入队
items[putIndex] = x;
//到边界生产索引置为0
if (++putIndex == items.length) {
putIndex = 0;
}
count++;
//不空时唤醒消费者
notEmpty.signal();
} finally {
lock.unlock();
}
}
public E dequeue() {
lock.lock();
try {
//队列空时阻塞消费者
while (count == 0) {
try {
notEmpty.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//出队并清空
E item = (E) items[takeIndex];
items[takeIndex] = null;
//到边界消费索引置为0
if (++takeIndex == items.length) {
takeIndex = 0;
}
count--;
//不满时唤醒生产者
notFull.signal();
return item;
} finally {
lock.unlock();
}
}
}
public class QueueTest {
public static void main(String[] args) {
MyQueue<String> queue = new MyQueue<>(10);
CustomerThread customerThread;
for (int i = 0; i < 10; i++) {
customerThread = new CustomerThread(queue);
customerThread.start();
}
ProducerThread producerThread;
for (int i = 0; i < 10; i++) {
producerThread = new ProducerThread(queue);
producerThread.start();
}
}
private static class ProducerThread extends Thread {
private final MyQueue<String> queue;
private final Random random = new Random();
public ProducerThread(MyQueue<String> queue) {
this.queue = queue;
}
@Override
public void run() {
String name = "物品" + random.nextInt(100);
System.out.println("线程"+Thread.currentThread().getName()+"开始生产物品"+name);
queue.enqueue(name);
}
}
private static class CustomerThread extends Thread {
private final MyQueue<String> queue;
public CustomerThread(MyQueue<String> queue) {
this.queue = queue;
}
@Override
public void run() {
String name = queue.dequeue();
System.out.println("线程"+Thread.currentThread().getName()+"开始消费物品"+name);
}
}
}
3.5 ReentrantReadWriteLock
3.5.1 继承类图
如下图所示
3.5.2 源码结构
ReentrantReadWriteLock意为可重入读写锁(读读不互斥、写读不互斥、读写互斥、写写互斥),实现了ReadWriteLock接口,内部存在Sync抽象类,由NonfairSync(非公平锁实现)及FairSync(公平锁实现),Lock接口由ReadLock(读锁)及WriteLock(写锁)实现
构造方法默认使用非公平锁,也支持传入是否公平参数来对应构造公平或非公平锁
public interface ReadWriteLock {
Lock readLock();
Lock writeLock();
}
public class ReentrantReadWriteLock
implements ReadWriteLock, java.io.Serializable {
private final ReentrantReadWriteLock.ReadLock readerLock;
private final ReentrantReadWriteLock.WriteLock writerLock;
final Sync sync;
public ReentrantReadWriteLock() {
this(false);
}
public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
readerLock = new ReadLock(this);
writerLock = new WriteLock(this);
}
public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }
public ReentrantReadWriteLock.ReadLock readLock() { return readerLock; }
abstract static class Sync extends AbstractQueuedSynchronizer{}
static final class NonfairSync extends Sync{}
static final class FairSync extends Sync{}
public static class ReadLock implements Lock, java.io.Serializable{}
public static class WriteLock implements Lock, java.io.Serializable{}
}
3.5.3 Sync
3.5.3.1 state
ReentrantReadWrite用一个state变量同时表示读锁和写锁,高16读锁,低16位为写锁,读锁写锁都可重入,最大次数为65535,这样就可以实现用一个CAS操作设置读锁和写锁的值。当state!=0时,要么持有读锁,要么持有写锁,通过sharedCount及exclusiveCount方法来判断持有的是哪个锁。
abstract static class Sync extends AbstractQueuedSynchronizer {
// 版本序列号
private static final long serialVersionUID = 6317671515068378041L;
// 高16位为读锁,低16位为写锁
static final int SHARED_SHIFT = 16;
// 读锁单位
static final int SHARED_UNIT = (1 << SHARED_SHIFT);
// 读锁最大数量
static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1;
// 写锁最大数量
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
// 本地线程计数器
private transient ThreadLocalHoldCounter readHolds;
// 缓存的计数器
private transient HoldCounter cachedHoldCounter;
// 第一个读线程
private transient Thread firstReader = null;
// 第一个读线程的计数
private transient int firstReaderHoldCount;
//获得读锁数量
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
//获得写锁数量
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
}
3.5.3.2 HoldCounter
HoldCounter用于保存持有持有共享锁的线程个数及线程id
// 计数器
static final class HoldCounter {
// 计数
int count = 0;
// 获取当前线程的TID属性的值
final long tid = getThreadId(Thread.currentThread());
}
3.5.3.3 ThreadLocalHoldCounter
通过ThreadLocal保存每个进入的线程重入锁的个数,以及对应ThreadId的值,记录每个线程的重入次数
// 本地线程计数器
static final class ThreadLocalHoldCounter
extends ThreadLocal<HoldCounter> {
// 重写初始化方法,初始化HoldCounter
public HoldCounter initialValue() {
return new HoldCounter();
}
}
3.5.3.4 tryAcquire(重写AQS)
独占式线程获取锁的方法,返回值true表示获取资源成功,false为失败
protected final boolean tryAcquire(int acquires) {
Thread current = Thread.currentThread();
//获取状态
int c = getState();
//获取写线程数量
int w = exclusiveCount(c);
//状态非0 要么是读 要么是写
if (c != 0) {
// 如果没有写 那么必定是读 有读时不能写 返回获取锁失败
// 如果有写 非重入(当前线程不为所有者)返回获取锁失败
if (w == 0 || current != getExclusiveOwnerThread())
return false;
//超过最大写线程数量
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// 线程重入
setState(c + acquires);
return true;
}
//状态为0 说明没有线程在写或读 可以获取锁
//如果写应该阻塞或者cas设置状态未成功 返回获取锁失败
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
//获取锁成功
setExclusiveOwnerThread(current);
return true;
}
3.5.3.5 tryAcquireShared(重写AQS)
共享式线程获取锁的方法,返回值小于0失败,大于0成功
protected final int tryAcquireShared(int unused) {
Thread current = Thread.currentThread();
//获取状态
int c = getState();
//有写锁或当前线程不为锁持有者 获取锁失败
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
//获取读线程数量
int r = sharedCount(c);
//读不阻塞 并且读线程数量小于最大数量 并且cas设置状态成功
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {
//为当前第一个读线程
if (r == 0) {
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
//读线程重入
firstReaderHoldCount++;
} else {
//读锁数量不为0并且不为当前线程 更新缓存
//缓存的线程ID及数量
HoldCounter rh = cachedHoldCounter;
//未缓存或缓存的不是当前线程 设置为当前线程 readHolds为ThreadLocal
if (rh == null || rh.tid != getThreadId(current))
cachedHoldCounter = rh = readHolds.get();
//有当前线程的缓存且HoldCounter未初始化
else if (rh.count == 0)
readHolds.set(rh);
//设置缓存及readHolds对象中的HoldCounter数量+1
rh.count++;
}
return 1;
}
//读阻塞或读线程大于最大数量或者cas设置失败 死循环用来保证相关操作可以成功
return fullTryAcquireShared(current);
}
3.5.3.6 tryRelease(重写AQS)
独占式线程释放锁的方法
protected final boolean tryRelease(int releases) {
//不为独占线程 抛异常
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
int nextc = getState() - releases;
boolean free = exclusiveCount(nextc) == 0;
if (free)
setExclusiveOwnerThread(null);
setState(nextc);
return free;
}
3.5.3.7 tryReleaseShared(重写AQS)
共享式线程释放锁的方法,更改state的值,清除缓存中ThreadLocal HoldCounter的值等操作。
protected final boolean tryReleaseShared(int unused) {
Thread current = Thread.currentThread();
if (firstReader == current) {
// assert firstReaderHoldCount > 0;
if (firstReaderHoldCount == 1)
firstReader = null;
else
firstReaderHoldCount--;
} else {
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
rh = readHolds.get();
int count = rh.count;
if (count <= 1) {
readHolds.remove();
if (count <= 0)
throw unmatchedUnlockException();
}
--rh.count;
}
for (;;) {
int c = getState();
int nextc = c - SHARED_UNIT;
if (compareAndSetState(c, nextc))
// Releasing the read lock has no effect on readers,
// but it may allow waiting writers to proceed if
// both read and write locks are now free.
return nextc == 0;
}
}
3.5.4 NonfairSync、FairSync
可以看到NonfairSync(非公平实现)、FairSync(公平实现)内部只实现了两个方法,核心逻辑在Sync中
static final class NonfairSync extends Sync {
// 写线程抢锁的时候是否应该阻塞
final boolean writerShouldBlock() {
// 写线程在抢锁之前永远不被阻塞,非公平锁
return false;
}
// 读线程抢锁的时候是否应该阻塞
final boolean readerShouldBlock() {
// 读线程抢锁的时候,当队列中第一个元素是写线程的时候要阻塞
return apparentlyFirstQueuedIsExclusive();
}
}
static final class FairSync extends Sync {
// 写线程抢锁的时候是否应该阻塞
final boolean writerShouldBlock() {
// 写线程在抢锁之前,如果队列中有其他线程在排队,则阻塞。公平锁
return hasQueuedPredecessors();
}
// 读线程抢锁的时候是否应该阻塞
final boolean readerShouldBlock() {
// 读线程在抢锁之前,如果队列中有其他线程在排队,阻塞。公平锁
return hasQueuedPredecessors();
}
}
3.5.5 ReadLock、WriteLock
ReadLock(读锁)、WriteLock(写锁)的核心逻辑在Sync中实现。写锁支持Condition,读锁不支持Condition
public static class ReadLock implements Lock, java.io.Serializable {
private final Sync sync;
protected ReadLock(ReentrantReadWriteLock lock) {
sync = lock.sync;
}
public void lock() {sync.acquireShared(1);}
public void unlock() {sync.releaseShared(1);}
public Condition newCondition() {throw new UnsupportedOperationException();}
//....lock接口中的其他方法
}
public static class WriteLock implements Lock, java.io.Serializable {
private final Sync sync;
protected WriteLock(ReentrantReadWriteLock lock) {
sync = lock.sync;
}
public void lock() {sync.acquire(1);}
public void unlock() {sync.release(1);}
public Condition newCondition() {return sync.newCondition();}
//....lock接口中的其他方法
}
3.5.6 锁降级与升级
3.4.3.1 锁升级
先持有读锁,不释放读锁的情况下,持有写锁。释放读锁后,再获取写锁不属于锁升级
//读锁
r.lock();
try {
//写锁
w.lock();
try {
//执行业务逻辑
} finally{
w.unlock();
}
} finally{
r.unlock();
}
注:ReentrantReadWriteLock不支持锁升级,因为读锁存在时,无法获取写锁(读写互斥),会导致写锁永久阻塞,读锁也不能正确释放
3.5.3.1 锁降级
现持有写锁,不释放写锁的情况下,持有读锁。释放写锁后,再获取读锁不属于锁降级
//写锁
w.lock();
try {
//执行业务逻辑
//读锁
r.lock();
} finally{
w.unlock();
}
try {
//此时获取的是最新的数据
} finally{
r.unlock();
}
注:ReentrantReadWriteLock支持锁降级,因为写读不互斥,且读锁可以直接获取到。这样做的目的是为了保证数据可见性,例如释放写锁后,另一个线程进行写入修改,当前线程获取读锁,读取到的就不是最新的数据(写读不互斥),但若在未释放写锁时,获取读锁,就会阻塞另一个线程的写入(读写互斥)
3.5.7 使用示例
实现缓存,需要包含put方法,及按需加载的get方法
public class Cache<K, V> {
private final Map<K, V> cache = new HashMap<>();
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private final Lock readLock = readWriteLock.readLock();
private final Lock writeLock = readWriteLock.writeLock();
public V get(K key) {
V value = null;
readLock.lock();
try {
value = cache.get(key);
} finally {
readLock.unlock();
}
if (value != null) {
return value;
}
//缓存不存在,写入缓存
writeLock.lock();
try {
value = cache.get(key);
//防止其他线程已查询过数据库
if(value==null){
//模拟查询数据库操作
value = (V)new Object();
cache.put(key,value);
}
}finally {
writeLock.unlock();
}
return value;
}
public void put(K key, V value) {
writeLock.lock();
try {
cache.put(key, value);
}finally {
writeLock.unlock();
}
}
}
3.6 StampedLock
3.6.1 定义
在JDK8中,新增了带版本戳的读写锁StampedLock,使用时需传入版本戳(同数据库乐观锁Version使用类似),并不是基于现有的AQS实现,而是重新实现了一个阻塞队列,内部实现了乐观读、悲观读、悲观写三种策略,乐观读的引入,便是为了在读写锁的基础上,提高并发度使读写不互斥,减少写线程被饿死的风险,同时由于乐观读使用CAS无锁方案,效率高于读写锁。
3.6.2 使用示例
3.6.2.1 悲观读、悲观写示例
final StampedLock sl = new StampedLock();
// 获取/释放悲观读锁示意代码
long stamp = sl.readLock();
try {
//省略业务相关代码
} finally {
sl.unlockRead(stamp);
}
// 获取/释放写锁示意代码
long stamp = sl.writeLock();
try {
//省略业务相关代码
} finally {
sl.unlockWrite(stamp);
}
3.6.2.2 乐观读示例
来自官网示例,实现逻辑为尝试乐观读tryOptimisticRead,判断在读的过程中是否被修改过validate,被修改过则升级为悲观读
class Point {
private int x, y;
final StampedLock sl = new StampedLock();
//计算到原点的距离
int distanceFromOrigin() {
// 乐观读
long stamp = sl.tryOptimisticRead();
// 保存局部变量,读的过程数据可能被修改
int curX = x, curY = y;
//判断执行读操作期间,是否存在写操作,如果存在,则sl.validate返回false
if (!sl.validate(stamp)){
// 升级为悲观读锁
stamp = sl.readLock();
try {
curX = x;
curY = y;
} finally {
//释放悲观读锁
sl.unlockRead(stamp);
}
}
return Math.sqrt(curX * curX + curY * curY);
}
}
3.6.3 注意事项
StampedLock在使用过程中需要注意如下几点:
- StampedLock写锁不支持重入
StampedLock用低8位表示读和写的状态,其中低7位表示读锁,第8位表示写锁的状态,因为写锁只有一个bit位,所以写锁是不可重入的。 - StampedLock 不支持条件变量
StampedLock 的悲观读锁、写锁都不支持条件变量 - StampedLock readLock或writeLock不能调用中断操作
如果线程阻塞在 StampedLock 的 readLock() 或者 writeLock() 上时,此时调用该阻塞线程的 interrupt() 方法,会导致 CPU 飙升。如果需要中断需要使用悲观读锁 readLockInterruptibly() 和写锁 writeLockInterruptibly()