一、Lock与Synchronized的区别
Lock是个接口,有如下方法:
- lock():获取锁
- lockInterruptibly():可中断的获取锁,与lock()不同的点是该方法会响应中断(锁获取中,可中断当前县城)
- tryLock(): 非阻塞获取锁,立即返回结果
- tryLock(long time,TimeUnit unit) :超时获取锁,(1)获得锁(2)超时被中断 (3)超时结束,返回false
- unlock() :释放锁
- newCondition(): 获取等待通知组件,线程获得锁,才能调用该组件的wait,调用完,释放锁
重量级锁。
Lock接口与synchronized 关键字都可实现同步,Lock接口具备Synchronized没有的主要特征如下:
(1) 尝试非阻塞获取锁: tryLock()
(2) 能被中断的获取锁: lockInterruptibly() ,获取锁的线程能够相应中断,当获取到锁的线程被中断时,中断议程会抛出,并释放锁。
(3) 超时获取锁:tryLock(long time,TimeUnit unit)在指定的截止时间之前获取锁,如果截止时间到了无法获取,则返回。
二、Java锁的实现基石---AbstractQueuedSynchronizer
Java中有各种不同的锁及同步组件(ReentrantLock,ReentrantReadWriteLock),而这些锁是面向使用者的,隐藏了实现细节。而其中实现最为关键的基石即是队列同步器 AbstractQueuedSynchronizer,其实现了同步状态管理、线程排队、等待与唤醒等底层实现。
对于AbstractQueuedSynchronizer(AQS)来说,
实现同步状态的管理有三个方法:
getState(),setState(int newState),compareAndSetState(int expect,int update)--使用CAS设置当前状态,用来保证状态设置的原子性。
AQS使用了模板方法模式,重写相关方法,在相关方法内部实现对同步状态的管理,可重写相关的方法如下:
- tryAcquire(int): 独占式获取同步状态,实现该方法需要使用CAS设置同步状态,compareAndSetState
- tryRelease(int):独占式释放同步状态
- tryAcquireShared(int): 共享式获取同步状态,返回大于等于0的值,则成功
- tryReleaseShared(int): 共享式释放同步状态
- isHeldExclusively(): 是否被当前线程独占
实现自定义同步组件时,一般通过自定义子类Sync 继承AQS,重写以上若干方法,会调用同步器提供的模板方法,可被调用的与同步相关的模板方法如下:
- acquire(int): 独占式获取同步状态, 如果当前线程获取同步状态成功,则由该方法返回,否则,进入同步队列等待, 会调用重写的tryAcquire(int arg)
- acquireInterruptibly(int):与acquire一样,不过会响应中断。若线程未获取到同步状态进入同步队列中,当前线程被中断,该方法会抛出InterruptedException并返回。
- tryAcquireNanos(int,long): 在acquireInterruptibly(int) 基础上增加了超时限制,当前线程在超时时间内没有获取到同步状态,则返回false。
- release(int): 独占式释放同步状态,并将同步队列里第一个节点包含的线程唤醒
- acquireShared(int): 共享式获取同步状态,与独占式区别是同一时刻有多个线程获取到同步状态。
- acquireSharedInterruptibly(int): 与acquireShared(int)相同,该方法相应中断
- tryAcquireSharedNanos(int,long):在acquireSharedInterruptibly(int) 基础上增加超时限制
- releaseShared(int) : 共享式释放同步状态
在看具体的代码实例之前,着重描述下AQS的原理:
既然有同步队列,一定会有相应的结点,Node结构如下:
waitStatus: 等待状态:
- (1)CANCELLED 值为1,由于同步队列中等待超时或被中断,需要从同步队列中取消等待,最终状态。
- (2) SIGNAL 值为-1 ,后继节点的线程处于等待状态,当前节点的线程若释放了同步状态或被取消,将会通知后继节点,使后继节点的线程得以运行。
- (3) CONDITION 值为-2 ,节点在等待队列中,线程等待在Condition上,当其他线程对Condition 调用了signal()后,节点将从等待队列中转移到同步队列中。
- (4) PROPAGATE,值为-3,表示下一次共享式同步状态获取将会无条件地被传播下去
- (5) INITIAL,值为0,初始状态。
prev: 前驱节点,当节点加入同步队列时被设置(尾部添加)
next: 后继节点
nextWaiter: 等待队列中的后继节点,如果当前节点是共享的,那么这个字段将是一个SHARED常量,也就是说节点类型(独占和共享) 和等待队列中的后继节点共用一个字段。
thread: 获取同步状态的线程。
一个同步队列的基本结构如下图:
2.1 独占式获取
在AQS实现类的注解里有如下代码,实现了一个简单的不可重入的互斥锁:
public class Mutex implements Lock {
// 静态内部类,自定义同步器
private static class Sync extends AbstractQueuedSynchronizer {
// 是否处于占用状态
protected boolean isHeldExclusively() {
return getState() == 1;
}
// 当状态为0的时候获取锁
public boolean tryAcquire(int aquires) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
// 释放锁,将状态设置为0
protected boolean tryRelease(int releases) {
if (getState() == 0)
throw new IllegalMonitorStateException();
setExclusiveOwnerThread(null);
setState(0);
return true;
}
// 返回一个Condition,每个condition都包含了一个condition队列
Condition newCondition() {
return new ConditionObject();
}
}
// 仅需要将操作代理到Sync上即可
private final Sync sync = new Sync();
@Override
public void lock() {
sync.acquire(1);
}
@Override
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
@Override
public boolean tryLock() {
return sync.tryAcquire(1);
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(time));
}
@Override
public void unlock() {
sync.release(1);
}
@Override
public Condition newCondition() {
return sync.newCondition();
}
}
Mutex 重写了AQS的 tryAcquire ,tryRelease 方法,当调用lock时,调用 AQS的acquire() 方法:
// 独占式获取同步状态,对中断不敏感
public final void acquire(int arg) {
// 首先调用tryAcquire获取同步状态,若失败,构造同步节点并调用addWaiter加入到同步队列中
// 调用acquireQueued 使该节点以死循环方式获取同步状态
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
tryAcquire里,重写的逻辑是若state为0,CAS为1,返回true,否则返回false。
addWaiter方法如下:
// 创建一个指定mode的Node节点,并入队。 mode 为EXCLUSIVE即是独占模式
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// 快速尝试在尾部添加node
Node pred = tail;
if (pred != null) {
node.prev = pred;
// 使用unsafe.compareAndSwapObject 保证是线程安全的
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
// 入队,初始化,使用死循环方式,保证节点能够正确添加,否则不断重试
// 并且通过CAS将并发添加节点的请求 串行化
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // 初始化队列
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
而节点进入同步队列后,就通过acquireQueued方法 进入了一个自旋的过程,每个节点都在自省的观察,条件满足,则获取到同步状态,否则就处于自旋过程中,阻塞节点的线程。
// 独占模式获取锁,且忽略 中断
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;
}
// 节点不是头节点,检查节点状态看是否SIGNAL,若是,则阻塞当前线程,LockSupport.park()
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
而unlock时会调用 sync.release 方法,释放同步状态,并唤醒后续节点,
// 释放独占同步状态,并唤醒后继节点
public final boolean release(int arg) {
// 本例setState 为0
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);
}
2.2 共享式获取
共享式获取与独占式获取不同在于,能否有多个线程同时获取到同步状态。文件读写,写是独占,读可以共享。
而AQS的acquireShared 可共享式的获取同步状态,此方法内部调用 doAcquireShared:
private void doAcquireShared(int arg) {
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);
}
}
2.3 独占超时获取
AQS得问doAcquireNanos(int arg,long nanosTimeout) 方法可以超时获取同步状态,指定时间未获取到,则返回false,并且此方法算是acquireInterruptibly的增强版,若线程被中断,会立刻返回,抛出InterruptedException。
private boolean doAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (nanosTimeout <= 0L)
return false;
final long deadline = System.nanoTime() + nanosTimeout;
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return true;
}
nanosTimeout = deadline - System.nanoTime();
// 超时返回
if (nanosTimeout <= 0L)
return false;
// spinForTimeoutThreshold为1000纳秒
if (shouldParkAfterFailedAcquire(p, node) &&
nanosTimeout > spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanosTimeout);
if (Thread.interrupted())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
三、ReetrantLock的实现细节
可重入锁支持重进入,一个线程可以对资源重复加锁,此外,还支持获取锁的公平和非公平性选择。
上面实现mutex 是典型的不可重入锁,线程获取mutex资源后,在调用lock方法,调用tryAcquire会返回false,导致线程被阻塞,synchronized关键字隐式支持重进入,比如synchronized修饰的递归方法。
公平性锁,简单来说就是先获取的请求优先被满足。公平性锁性能没有非公平的效率高,由于非公平锁连续获得同步状态的记录非常大,所以公平性锁会有更多的线程切换,导致性能损耗。
ReetrantLock 使用了上述提到的AQS实现 可重入和公平性 ,先看ReetrantLock如何重写AQS里的方法:
Sync是个抽象类,lock为抽象方法, 继承自AQS,FairSync 与 NonfairSync实现了不同的lock 和 tryAcquire,可看出公平锁与非公平锁主要不同在 tryAcquire上。
3.1 可重入的实现
非公平时,获取同步状态时 都会使用到 Sync里的nonfairTryAcquire(int),此方法实现了可重入,源码如下:
// nonfair try 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;
}
}
// 判断是否是同一线程,若是,则加acquires,同一时刻只有当前线程访问
// 实现可重入
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;
}
释放同步状态时,会使用到 tryRelease方法 ,如下:
// 释放同步资源
protected final boolean tryRelease(int releases) {
// 释放 releases个同步状态
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
// 只有同步状态为0 才真正释放此锁
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
3.2 公平性的实现
ReentrantLock其中有个构造方法:
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
调用lock 时,会调用sync.lock(), 若是非公平性锁,NonfairSync 的实现如下:
final void lock() {
// CAS设置成功 ,则获取锁
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
// 若不成功,看是否重进入
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
若是公平性锁, FairSync的实现如下:
final void lock() {
acquire(1);
}
// 公平性锁的实现
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// 第一次加锁
if (c == 0) {
// 与非公平唯一区别在此,判断是否有当前节点的前驱节点
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());
}
四、读写锁的实现细节
Mutex与ReentrantLock都是排它锁,同一时刻只允许一个线程访问。 这种对于文件读写这样的场景来说效率是低的,若能对文件的读可以多线程,写时,所有线程阻塞。读写锁ReetrantReadWriteLock实现了这点 ,其维护了一个读锁和一个写锁,通过分离读锁和写锁,使得性能有很大提升。
使用方式如下:
static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
static Lock r = rwl.readLock();
static Lock w = rwl.writeLock();
之后读场景对r.lock() r.unlock,而对于写锁 w.lock,w.unlock。
读写锁同样依赖AQS,通过设置同步状态实现锁。读写锁将一个同步整型变量 『按位切割』,将变量分为两个部分,高16位表示读,低16位表示写,划分如下:
public void lock() {
sync.acquire(1);
}
这样,假设同步状态值为S,写状态等于S&0x0000FFFF(高16位全部抹去),读状态等于S >>> 16 (无符号补0右移16位)。当写状态增加1时,等于S+1,当读状态增加1时,等于S+(1<<16) ,也就是S + 0x00010000。
当S不等于0时,当写状态(S & 0x0000FFFF)等于0时,则 读状态大于0,即读锁已被获取。
4.1 写锁的实现
ReentrantReadWriteLock的结构如下:
Sync实现了AQS,我们先看WriteLock的获取和释放:
writeLock.lock 调用如下方法:sync.acquire(1), 由上文可知,会调用我们再sync里重写的tryAcquire 方法:
//1.如果读锁非0 或 写锁非0且持有者是不同的线程,失败
//2.如果计数值溢出,失败
//3.非上述情况,则说明锁可被获取
protected final boolean tryAcquire(int acquires) {
Thread current = Thread.currentThread();
int c = getState();
// 得到写状态
int w = exclusiveCount(c);
if (c != 0) {
// (Note: if c != 0 and w == 0 then shared count != 0)
if (w == 0 || current != getExclusiveOwnerThread())
return false;
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// Reentrant acquire
setState(c + acquires);
return true;
}
// 公平锁则判断是否有前驱,非公平直接获取
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
setExclusiveOwnerThread(current);
return true;
}
writerShouldBlock():
static final class NonfairSync extends Sync {
final boolean writerShouldBlock() {
return false;
}
}
static final class FairSync extends Sync {
final boolean writerShouldBlock() {
return hasQueuedPredecessors();
}
}
写锁的释放,会调用sync的tryRelease(int) 方法:
protected final boolean tryRelease(int releases) {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
// 写锁-1
int nextc = getState() - releases;
// 看是否为0,0表示写锁释放
boolean free = exclusiveCount(nextc) == 0;
if (free)
setExclusiveOwnerThread(null);
setState(nextc);
return free;
}
4.2 读锁的实现
readLock 比 之前见到的逻辑略显复杂,是因为增加了一些关于计数的功能,如下:
在Sync类里:
// 每个线程读锁的计数器,是threadLocal的,cache在cachedHoldCounter
static final class HoldCounter {
int count = 0;
// 用id ,而不是引用,避免垃圾保留
final long tid = getThreadId(Thread.currentThread());
}
// ThreadLocal的子类,为了反序列化机制,易于明确定义
static final class ThreadLocalHoldCounter
extends ThreadLocal<HoldCounter> {
public HoldCounter initialValue() {
return new HoldCounter();
}
}
// 当前线程拿到的可重入读锁的数目,在构造函数和readObject里初始化。
// 当一个线程读锁次数降到0时,删除此对象
private transient ThreadLocalHoldCounter readHolds;
// 最后一个成功获取readLock的线程的读锁次数
private transient HoldCounter cachedHoldCounter;
// firstReader是第一个获得读锁的线程,firstReaderHoldCount是此线程获得读锁的次数
private transient Thread firstReader = null;
private transient int firstReaderHoldCount;
Sync() {
readHolds = new ThreadLocalHoldCounter();
setState(getState()); // ensures visibility of readHolds
}
readLock 的lock方法,调用了sync.acquireShared(1) ,其调用我们重写的tryAcquireShared方法:
// 1.如果写锁被其他线程获取,则失败
// 2.若写锁没被占,线程是可以拿状态的,这样取决于是否是公平锁。若不是,尝试CAS更新状态以及更新计数,
// 注意此步骤不检查可重入的获取,延期到full version,避免大多数是非重入锁,而导致的消耗
// 3.如果第二步失败,重试。
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);
// 公平锁则检查前驱节点,非公平锁则判断第一个节点是否是写节点,若是,则阻塞,来避免写 无限等待
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {
// 第一个锁节点
if (r == 0) {
firstReader = current;
firstReaderHoldCount = 1;
// 第一个锁节点重进入
} else if (firstReader == current) {
firstReaderHoldCount++;
} else {
// 锁计数++
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
}
return 1;
}
return fullTryAcquireShared(current);
}
static final class NonfairSync extends Sync {
final boolean readerShouldBlock() {
return apparentlyFirstQueuedIsExclusive();
}
}
final boolean apparentlyFirstQueuedIsExclusive() {
Node h, s;
return (h = head) != null &&
(s = h.next) != null &&
!s.isShared() &&
s.thread != null;
}
调用tryAcquireShared 完整版:
//full version的读获取锁,处理CAS失败与重复读
final int fullTryAcquireShared(Thread current) {
// 代码比tryAcquireShared要重,但是使得tryAcquiredShared方法简单化,避免重试,延后读hold counts
HoldCounter rh = null;
for (;;) {
int c = getState();
if (exclusiveCount(c) != 0) {
if (getExclusiveOwnerThread() != current)
return -1;
// else we hold the exclusive lock; blocking here
// would cause deadlock.
} else if (readerShouldBlock()) {
// 确认我们不是重进入获取锁
if (firstReader == current) {
// assert firstReaderHoldCount > 0;
// 不是重进入
} 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;
}
}
}
readLock的unlock方法,调用了sync.releaseShared(1),其调用了tryReleaseShared(1)方法:
protected final boolean tryReleaseShared(int unused) {
Thread current = Thread.currentThread();
// 若当前线程是firstReader,若计数为1 ,则firstReader至为空
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))
// 释放读锁对所有的readers没有影响,但是会允许等待的写线程优先处理
// 如果读锁与写锁都存在
return nextc == 0;
}
}
至此,JUC里中两种主要的锁的实现,和锁的原理已解读完毕。