3.1 互斥锁
目录
3.1 互斥锁
3.2 读写锁
3.3 Condition
3.4 StampedLock
3.1.1 锁的可重入性:在加锁的内部可以继续加锁,lock与synchronized都支持。
3.1.2 类的继承层次:
3.1.3 锁的公平性与非公平性
Sync是一个抽象类,它的两个子类FairSync与NofairSync分别是公平锁与非公平锁。
3.1.4 非公平锁的请求时序图
3.1.5 锁实现的基本原理
需要以下几个核心要素:
①.需要一个state变量标记该锁的状态,对state变量的操作,要确保线程安全,也就是会用到CAS操作。
②.需要记录当前哪个线程持有锁。
③.需要底层支持对一个线程进行阻塞或者唤醒操作。
④.需要一个队列维护所有阻塞线程,这个队列必须是无锁安全的,也需要用到CAS。
针对①②④在上面两个类中有所体现,如下源码:
public abstract class AbstractOwnableSynchronizer
implements java.io.Serializable {
/** Use serial ID even though all fields transient. */
private static final long serialVersionUID = 3737899427754241961L;
/**
* Empty constructor for use by subclasses.
*/
protected AbstractOwnableSynchronizer() { }
/**
* The current owner of exclusive mode synchronization. ①.记录锁被哪个线程持有
*/
private transient Thread exclusiveOwnerThread;
...
}
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
static final class Node {//④.双向链表阻队列内容
}
private transient volatile Node head;//双向链表阻塞队列头部
private transient volatile Node tail;//双向链表阻塞队列尾部
/**
* The synchronization state. ②.记录锁的状态
*/
private volatile int state;
}
针对要素③,在Unsafe类中,提供了阻塞或唤醒线程的一对操作原语,也就是park/unpark
public final class Unsafe {
//唤醒
public native void unpark(Object var1);
//阻塞
public native void park(boolean var1, long var2);
}
在LockSupport工具类中进行了简单的封装
public class LockSupport {
public static void unpark(Thread var0) {
if (var0 != null) {
UNSAFE.unpark(var0);
}
}
public static void park(Object var0) {
Thread var1 = Thread.currentThread();
setBlocker(var1, var0);
UNSAFE.park(false, 0L);
setBlocker(var1, (Object)null);
}
}
3.1.6 公平与非公平的lock()的实现差异
static final class NonfairSync extends ReentrantLock.Sync {
final void lock() {
if (this.compareAndSetState(0, 1)) {//一上来就抢锁
this.setExclusiveOwnerThread(Thread.currentThread());//抢锁成功直接吧excluseThread设置到当前线程
} else {
this.acquire(1);//没抢成功调用AQS模块方法
}
}
}
static final class FairSync extends ReentrantLock.Sync {
final void lock() {
this.acquire(1);//调用AQS模块方法
}
}
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
//tryAcquire尝试拿锁,被NonfairSync和FairSync分别实现 acquireQueued是将线程加入阻塞队列,然后阻塞该线程
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
}
tryAcquire代码不贴了,主要区别就是FairSync比NonFairSync多一个hasQueuedPredecessors方法判断,此方法的作用是排在队列的第一个时才去抢锁。
3.1.7 阻塞队列与唤醒机制
接上面在acquireQueued(addWaiter(Node.EXCLUSIVE), arg)中阻塞队列,其中addWaiter(Node.EXCLUSIVE)是把线程加入队列中,acquireQueued则是将线程无限期阻塞,即使其他线程调用interrupt()函数也不会将其唤醒,除非其他线程释放锁并且当前线程拿到锁,才会在acquireQueued(...)中返回。
acquireQueued(...)的返回值表示被阻塞期间是否有其他线程发出中断信号,如果有则true,否则false。
然后基于这个值会有下面的是否调用selfInterrupt()方法
static void selfInterrupt() {
//自己给自己设置中断标志位
Thread.currentThread().interrupt();
}
阻塞发生在下面的函数
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
线程调用park()函数,自己把自己阻塞起来,直到被其他线程唤醒,该函数返回。park()函数返回有两种情况。
①.其他线程调用unpark(Thread t)。
②.其他线程调用t.interrupt()。这里注意lock()不能响应中断,但LockSupport.park()会响应中断。
被唤醒后通过Thread.interrupted()来判断是否是中断唤醒。
3.1.8 unlock()实现分析
unlock中调用release方法,release方法主要做两件事:
①.调用tryRelease(...)函数释放锁;②.调用unparkSuccessor(...)唤醒队列中的后继者。
在tryRelease(...)中对state操作直接减一就行,不需要CAS操作。但是对于读写锁中的读锁,调用的releaseShared(...)中就是不一样了,后面分析。
3.1.9 lockInterruptibly()实现分析
lock不能被中断,lockInterruptibly可以被中断。
源码区别,在原先lock返回其他线程调用中断设置标志位的地方lockInterruptibly直接是抛出InterruptedException异常。
非公平锁和公平锁的区别:
1.非公平锁一上来就抢锁(compareAndSetState),抢锁失败了才调用尝试抢锁(tryAcquire),公平锁最开始调用的就是尝试抢锁(tryAcquire)。
2.在tryAcquire中公平锁多了一个hasQueuedPredecessors方法判断,此方法作用是排在队列第一个时才去抢锁。
3.2 读写锁
和互斥锁相比,读写锁(ReentrantReadWriteLock)就是读线程与读线程之间可以不用互斥
3.2.1 类继承层次
3.2.2 读写锁实现的基本原理
public class ReentrantReadWriteLock
implements ReadWriteLock, java.io.Serializable {
...
public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
readerLock = new ReadLock(this);
writerLock = new WriteLock(this);
}
...
public static class ReadLock implements Lock, java.io.Serializable {
...
private final Sync sync;
protected ReadLock(ReentrantReadWriteLock lock) {
sync = lock.sync;//读锁对象
}
...
}
public static class WriteLock implements Lock, java.io.Serializable {
...
private final Sync sync;
protected WriteLock(ReentrantReadWriteLock lock) {
sync = lock.sync;//写锁对象
}
...
}
}
上面代码证明了读锁和写锁是同一个sync对象,并且都继承自AQS,同一个对象的两把锁(读锁和写锁)
读写锁也是用state变量来表示锁的状态,只是读写锁中的state表示内容更多。
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 6317671515068378041L;
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;
/** Returns the number of shared holds represented in count. 持有读锁线程的重入次数*/
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
/** Returns the number of exclusive holds represented in count.持有写锁线程的重入次数 */
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
}
如上state变量被分成两半,低16位用来记录写锁,高16位用来记录读锁。这个设计巧妙之处在于一次CAS无法同时操作两个int变量,所以用此方式一个变量来表示读锁和写锁的状态。
public static class ReadLock implements Lock, java.io.Serializable {
...
public void lock() {
sync.acquireShared(1);//具体实现在ASQ模块方法中
}
public void unlock() {
sync.releaseShared(1);//具体实现在ASQ模块方法中
}
...
}
public static class WriteLock implements Lock, java.io.Serializable {
...
public void lock() {
sync.acquire(1);//具体实现在ASQ模块方法中
}
public void unlock() {
sync.release(1);//具体实现在ASQ模块方法中
}
...
}
ASQ类中
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0) //tryAcquireShared被各种Sync子类实现,并且方法中有readerShouldBlock是否应该阻塞被公平锁和非公平锁分别实现
doAcquireShared(arg);
}
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) { //tryReleaseShared被各种Sync子类实现
doReleaseShared();
return true;
}
return false;
}
public final void acquire(int arg) {
if (!tryAcquire(arg) && //tryAcquire被各种Sync子类实现,并且方法中有writerShouldBlock是否应该阻塞被公平锁和非公平锁分别实现
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
public final boolean release(int arg) {
if (tryRelease(arg)) { //tryRelease被各种Sync子类实现
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
将读写锁与公平非公平锁组合有四种实现方式,但是注意前面说的读写锁对象是一个,所以一个锁要么是公平锁,要么是非公平锁。
下面看读写锁是否应该阻塞的四种情况
static final class NonfairSync extends ReentrantReadWriteLock.Sync {
...
//任何情况下写锁在抢锁的时候不能阻塞
final boolean writerShouldBlock() {
return false;
}
//读锁在抢锁的时候当队列中第一个元素是写锁的时候阻塞,防止写线程一直拿不到锁
final boolean readerShouldBlock() {
return this.apparentlyFirstQueuedIsExclusive();
}
}
static final class FairSync extends ReentrantReadWriteLock.Sync {
...
//排队抢锁
final boolean writerShouldBlock() {
return this.hasQueuedPredecessors();
}
//排队抢锁
final boolean readerShouldBlock() {
return this.hasQueuedPredecessors();
}
}
上面的唯一区别是非公平读锁在抢锁的时候要看阻塞队列里面第一个是不是写线程,如果是则不能抢锁。
3.2.4 WriteLock 公平与非公平实现
写锁是排它锁,实现类似互斥锁。
Sync类中
protected final boolean tryAcquire(int var1) {
Thread var2 = Thread.currentThread();
int var3 = this.getState();
int var4 = exclusiveCount(var3);//写锁只有一个线程,但可以多次重入
if (var3 != 0) {//state!=0说明有线程持有锁
//w != 0说明读线程不持有锁并且写线程是自己的,写入,对state进行操作
if (var4 != 0 && var2 == this.getExclusiveOwnerThread()) {
if (var4 + exclusiveCount(var1) > 65535) {//state不能超过最大值
throw new Error("Maximum lock count exceeded");
} else {
this.setState(var3 + var1);
return true;
}
} else {
return false;
}
//如果state==0分支,不阻塞并且CAS可以正常完成情况下抢锁成功调用setExclusiveOwnerThread把ownerThread设置为自己
} else if (!this.writerShouldBlock() && this.compareAndSetState(var3, var3 + var1)) {
this.setExclusiveOwnerThread(var2);
return true;
} else {
return false;
}
}
protected final boolean tryRelease(int var1) {
if (!this.isHeldExclusively()) {
throw new IllegalMonitorStateException();
} else {
int var2 = this.getState() - var1;
boolean var3 = exclusiveCount(var2) == 0;
if (var3) {
this.setExclusiveOwnerThread((Thread)null);
}
this.setState(var2);//state值直接减一即可
return var3;
}
}
3.2.5 ReadLock 公平与非公平实现
读锁是共享锁,与之前的互斥锁相差还是蛮大的。
protected final int tryAcquireShared(int var1) {
Thread var2 = Thread.currentThread();
int var3 = this.getState();
//写线程持有锁并且当前线程(不是排在最前面)不是自己的,肯定拿不到锁
if (exclusiveCount(var3) != 0 && this.getExclusiveOwnerThread() != var2) {
return -1;
} else {
int var4 = sharedCount(var3);
if (!this.readerShouldBlock() && var4 < 65535 && this.compareAndSetState(var3, var3 + 65536)) {//不阻塞并且最大值不超过最大数,并且CAS拿到锁高16位加一
if (var4 == 0) {//==0说明是第一个读锁拿到的线程
this.firstReader = var2;
this.firstReaderHoldCount = 1;
} else if (this.firstReader == var2) {
++this.firstReaderHoldCount;
} else {
ReentrantReadWriteLock.Sync.HoldCounter var5 = this.cachedHoldCounter;
if (var5 != null && var5.tid == ReentrantReadWriteLock.getThreadId(var2)) {
if (var5.count == 0) {
this.readHolds.set(var5);
}
} else {
this.cachedHoldCounter = var5 = (ReentrantReadWriteLock.Sync.HoldCounter)this.readHolds.get();
}
++var5.count;
}
return 1;
} else {
return this.fullTryAcquireShared(var2);//读锁失败进入不断自旋
}
}
}
protected final boolean tryReleaseShared(int var1) {
Thread var2 = Thread.currentThread();
int var4;
if (this.firstReader == var2) {
if (this.firstReaderHoldCount == 1) {
this.firstReader = null;
} else {
--this.firstReaderHoldCount;
}
} else {
ReentrantReadWriteLock.Sync.HoldCounter var3 = this.cachedHoldCounter;
if (var3 == null || var3.tid != ReentrantReadWriteLock.getThreadId(var2)) {
var3 = (ReentrantReadWriteLock.Sync.HoldCounter)this.readHolds.get();
}
var4 = var3.count;
if (var4 <= 1) {
this.readHolds.remove();
if (var4 <= 0) {
throw this.unmatchedUnlockException();
}
}
--var3.count;
}
int var5;
//多个线程持有锁,需要do while +CAS不断重试,这是与tryRelease的根本差异
do {
var5 = this.getState();
var4 = var5 - 65536;
} while(!this.compareAndSetState(var5, var4));
return var4 == 0;
}
读锁多个线程持有锁,需要do while +CAS不断重试,这是与tryRelease的根本差异
3.2.6 读写锁与互斥锁的区别
1.读写锁是同一个对象(Sync)的两把锁(readLock,writeLock),互斥锁是同一个对象一把锁(Sync)
2.互斥锁state只标记锁的状态,读写锁state变量被分成两半,低16位用来记录写锁,高16位用来记录读锁
3.非公平读锁在抢锁的时候要看阻塞队列里面第一个是不是写线程,如果是则不能抢锁。互斥锁抢锁时不需要考虑
4.读锁释放锁多个线程持有锁,需要do while +CAS不断重试,互斥锁只需要state减一即可
3.3 Condition
3.3.1 Condition与Lock的关系
①.Condition本身是一个接口,其功能和wait/notify类似
public interface Condition {
void await() throws InterruptedException;
void awaitUninterruptibly();
long awaitNanos(long var1) throws InterruptedException;
boolean await(long var1, TimeUnit var3) throws InterruptedException;
boolean awaitUntil(Date var1) throws InterruptedException;
void signal();
void signalAll();
}
②.wait()/notify()必须与synchronized一起使用,Condition也是必须与Lock一起使用
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();//所有Condition都是从Lock中构造出来的
}
3.3.2 Condition的使用场景
如下是Condition使用场景之一,数组实现的阻塞队列:ArrayBlockingQueue
public class ArrayBlockingQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable {
...
/** Main lock guarding all access */
final ReentrantLock lock;
/** Condition for waiting takes */
private final Condition notEmpty;
/** Condition for waiting puts */
private final Condition notFull;
public ArrayBlockingQueue(int capacity, boolean fair) {
if (capacity <= 0)
throw new IllegalArgumentException();
this.items = new Object[capacity];
lock = new ReentrantLock(fair);//一把锁两个条件
notEmpty = lock.newCondition();
notFull = lock.newCondition();
}
public void put(E e) throws InterruptedException {
Objects.requireNonNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == items.length)
notFull.await();//如果队列填满则等待
enqueue(e);
} finally {
lock.unlock();
}
}
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();//如果队列为空则等待
return dequeue();
} finally {
lock.unlock();
}
}
private void enqueue(E x) {
// assert lock.getHoldCount() == 1;
// assert items[putIndex] == null;
final Object[] items = this.items;
items[putIndex] = x;
if (++putIndex == items.length) putIndex = 0;
count++;
notEmpty.signal();//通知队列中已经添加数据
}
private E dequeue() {
// assert lock.getHoldCount() == 1;
// assert items[takeIndex] != null;
final Object[] items = this.items;
@SuppressWarnings("unchecked")
E x = (E) items[takeIndex];
items[takeIndex] = null;
if (++takeIndex == items.length) takeIndex = 0;
count--;
if (itrs != null)
itrs.elementDequeued();
notFull.signal();//通知队列中已经拿出数据
return x;
}
...
}
3.3.3 Condition 实现原理
可以发现Condition的使用很简洁,并且避免了生产者通知生产者,消费者通知消费者的问题。
原理是Condition与Lock一起使用,所以Condition的实现也是Lock的一部分。源码分析如下:
public class ReentrantLock implements Lock, Serializable {
public Condition newCondition() {
return this.sync.newCondition();//调用内部类Sync的newCondition
}
abstract static class Sync extends AbstractQueuedSynchronizer {
final ConditionObject newCondition() {
return new ConditionObject();//new 一个ConditionObject
}
}
}
public class ReentrantReadWriteLock implements ReadWriteLock, Serializable {
public static class WriteLock implements Lock, Serializable {
public Condition newCondition() {
return this.sync.newCondition();//调用内部类Sync的newCondition
}
}
public static class ReadLock implements Lock, Serializable {
public Condition newCondition() {
throw new UnsupportedOperationException();//读锁不支持
}
}
abstract static class Sync extends AbstractQueuedSynchronizer {
final ConditionObject newCondition() {
return new ConditionObject();//new 一个ConditionObject
}
}
}
//AQS类
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
//ConditionObject 内部是一个双向链表
public class ConditionObject implements Condition, Serializable {
private transient AbstractQueuedSynchronizer.Node firstWaiter;
private transient AbstractQueuedSynchronizer.Node lastWaiter;
public ConditionObject() {
}
}
}
下面分析一下await()/signal()函数是如何使用这个队列的
3.3.4 await()实现分析
public final void await() throws InterruptedException {
if (Thread.interrupted()) {//正常执行,收到中断信号抛出异常
throw new InterruptedException();
} else {
AbstractQueuedSynchronizer.Node var1 = this.addConditionWaiter();//加入阻塞队列
int var2 = AbstractQueuedSynchronizer.this.fullyRelease(var1);//阻塞之前先释放锁
int var3 = 0;
while(!AbstractQueuedSynchronizer.this.isOnSyncQueue(var1)) {
LockSupport.park(this);//自己阻塞自己
if ((var3 = this.checkInterruptWhileWaiting(var1)) != 0) {
break;
}
}
if (AbstractQueuedSynchronizer.this.acquireQueued(var1, var2) && var3 != -1) {//重新拿锁
var3 = 1;
}
if (var1.nextWaiter != null) {
this.unlinkCancelledWaiters();
}
if (var3 != 0) {
this.reportInterruptAfterWait(var3);//被中断唤醒,向外抛出中断异常
}
}
}
//原先锁在自己这,不需要CAS操作
private AbstractQueuedSynchronizer.Node addConditionWaiter() {
AbstractQueuedSynchronizer.Node var1 = this.lastWaiter;
if (var1 != null && var1.waitStatus != -2) {
this.unlinkCancelledWaiters();
var1 = this.lastWaiter;
}
AbstractQueuedSynchronizer.Node var2 = new AbstractQueuedSynchronizer.Node(Thread.currentThread(), -2);
if (var1 == null) {
this.firstWaiter = var2;
} else {
var1.nextWaiter = var2;
}
this.lastWaiter = var2;
return var2;
}
3.3.5 awaitUninterruptibly()实现分析
与await()不同,awaitUninterruptibly()不会响应中断,不会抛出中断异常。
public final void awaitUninterruptibly() {
AbstractQueuedSynchronizer.Node var1 = this.addConditionWaiter();
int var2 = AbstractQueuedSynchronizer.this.fullyRelease(var1);
boolean var3 = false;
while(!AbstractQueuedSynchronizer.this.isOnSyncQueue(var1)) {
LockSupport.park(this);
if (Thread.interrupted()) {//不会抛出异常,设置标志位
var3 = true;
}
}
if (AbstractQueuedSynchronizer.this.acquireQueued(var1, var2) || var3) {
AbstractQueuedSynchronizer.selfInterrupt();
}
}
3.3.6 signal()实现分析
public final void signal() {
if (!AbstractQueuedSynchronizer.this.isHeldExclusively()) {//只有持有锁才有资格调用doSignal(...)
throw new IllegalMonitorStateException();
} else {
AbstractQueuedSynchronizer.Node var1 = this.firstWaiter;//第一个阻塞线程
if (var1 != null) {
this.doSignal(var1);
}
}
}
private void doSignal(AbstractQueuedSynchronizer.Node var1) {
do {
if ((this.firstWaiter = var1.nextWaiter) == null) {
this.lastWaiter = null;
}
var1.nextWaiter = null;
//唤醒第一个阻塞线程
} while(!AbstractQueuedSynchronizer.this.transferForSignal(var1) && (var1 = this.firstWaiter) != null);
}
//AQS方法
final boolean transferForSignal(AbstractQueuedSynchronizer.Node var1) {
if (!compareAndSetWaitStatus(var1, -2, 0)) {
return false;
} else {
AbstractQueuedSynchronizer.Node var2 = this.enq(var1);//此处保证是被唤醒不是被中断唤醒
int var3 = var2.waitStatus;
if (var3 > 0 || !compareAndSetWaitStatus(var2, var3, -1)) {
LockSupport.unpark(var1.thread);
}
return true;
}
}
3.3.7 Condition总结
1.用数组实现的阻塞队列(ArrayBlockingQueue)核心原理是一把锁(Reentrantlock)+两个条件(Condition notEmpty和notFull)
2.读写锁中的读锁不支持Condition,读写锁中的写锁和互斥锁都支持Condition
3.await必须先释放锁(fullRelease)然后加入到阻塞队列,否则发生死锁;线程从等待中唤醒需要重新拿锁(acquireQueued),在signal中必须是先拿到锁的,否则抛出异常。
3.4 StampedLock
3.4.1 为什么引入StampedLock
JDK 8新增StampedLock
锁 | 并发度 |
ReentrantLock | 全都互斥 |
ReentrantReadWriteLock | 读与读不互斥,读与写互斥,写与写互斥 |
StampedLock | 读与读不互斥,读与写不互斥,写与写互斥 |
3.4.2 使用场景
官方案例看一下StampedLock如何使用。
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);
}
3.4.3 “乐观锁”的实现原理
这里我就不写了,有兴趣看源码,state变量还是分成三部分,第一至七位表示读锁的状态,第八位表示写锁的状态,其他位数表示版本号,防止脏数据产生
StampedLock读锁加锁过程是先自旋,达到一定次数阻塞,阻塞线程通过cowait指针串联,1被唤醒后,2,3也被唤醒。因为读和读之间是不互斥的
StampedLock释放锁过程,一是将state置回原位,二是唤醒阻塞队列的第一个节点。