常用方法
ReentrantLock 常用的方法就是 Lock 接口定义的几个方法,如下:
// 获取锁(阻塞式)
public void lock() {
sync.lock();
}
// 获取锁(响应中断)
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
// 尝试获取锁
public boolean tryLock() {
return sync.nonfairTryAcquire(1);
}
// 尝试获取锁(有超时等待)
public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
// 释放锁
public void unlock() {
sync.release(1);
}
这几个方法内部都是通过调用 Sync 类(或其子类)的方法来实现。具体如下:
// 抽象类,继承了 AQS
abstract static class Sync extends AbstractQueuedSynchronizer {
// 获取锁的方法,由子类实现
abstract void lock();
// 非公平锁的 tryLock 方法实现
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
// 获取 AQS 的 state 变量
int c = getState();
// 若为 0,表示当前没有被其他线程占用
if (c == 0) {
// CAS 修改 state,若修改成功,表示成功获取资源
if (compareAndSetState(0, acquires)) {
// 将当前线程设置为 owner,到这里表示当前线程成功获取资源
setExclusiveOwnerThread(current);
return true;
}
}
// state 不为 0,且 owner 为当前线程
// 表示当前线程已经获取到了资源,这里表示“重入”
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
// 修改 state 值(因为当前线程已经获取资源,不存在竞争,因此无需 CAS 操作)
setState(nextc);
return true;
}
return false;
}
// 释放锁操作(对 state 做减法)
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;
// 成功释放后将 owner 设为空
setExclusiveOwnerThread(null);
}
// 修改 state 的值
// PS: 因为可能存在“重入”,因此一次释放操作后当前线程仍有可能占用资源,
// 所以不会直接把 state 设为 0
setState(c);
return free;
}
// 其他方法...
final boolean isLocked() {
return getState() != 0;
}
}
锁的获取和释放是通过修改 AQS 的 state 变量来实现的。lock 方法可以看做对 state 执行“加法”操作,而 unlock 可以看做对 state 执行“减法”操作,当 state 为 0 时,表示当前没有线程占用资源。
公平锁&非公平锁
公平锁:
static final class FairSync extends Sync {
final void lock() {
acquire(1);
}
// 公平锁的 tryAcquire 实现
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// state 为 0,表示资源未被占用
if (c == 0) {
// 若队列中有其他线程在排队等待,则返回 false,表示获取失败;
// 否则,再尝试去修改 state 的值
// PS: 这里是公平锁与非公平锁的区别所在
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;
}
}
非公平锁:
static final class NonfairSync extends Sync {
final void lock() {
// CAS 尝试将 state 值修改为 1
if (compareAndSetState(0, 1))
// 若修改成功,则将当前线程设为 owner,表示成功获取锁
setExclusiveOwnerThread(Thread.currentThread());
// 若获取失败,则执行 AQS 的 acquire 方法(独占模式获取资源)
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
区别:
非公平锁再获取锁的时候不按排队顺序而是随机拿锁
tryAcquire方法中!hasQueuedPredecessors() 来判定队列中是否有前驱节点在等待锁
Java中的ReentrantLock是一个可重入锁的实现。它允许一个线程多次获取同一个锁,在获得锁的时候锁的计数器会加1,每次释放锁的时候计数器会减1,只有当锁的计数器变为0时,其他线程才能获取到该锁。
ReentrantLock的主要实现是通过继承AQS(AbstractQueuedSynchronizer)类来实现的。AQS类提供了同步器的框架,是Java并发包中重要的基础设施。AQS类中有两个主要的变量同步队列和状态量,线程通过AQS的acquire和release方法实现对同步队列和状态量的操作。
当一个线程获取锁时,如果锁没有被其他线程占用,那么该线程直接将状态量设为1,表示占用了锁。如果锁已经被占用了,那么当前线程就会被加入到同步队列中,等待其他释放锁的线程通知它。当一个线程释放锁时,如果同步队列中有等待线程,则会唤醒其中的一个线程,令其获取到锁。如果同步队列中有多个等待线程,则会唤醒其中最先入队列的线程。
在多线程环境下,可重入锁ReentrantLock的实现如下:
public class ReentrantLock implements Lock, java.io.Serializable {
private static final long serialVersionUID = 7373984872572414699L;
private final Sync sync;
abstract static class Sync extends AbstractQueuedSynchronizer {
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;
}
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;
}
final ConditionObject newCondition() {
return new ConditionObject();
}
}
final static class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
final static class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
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 ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
public void lock() {
sync.lock();
}
public boolean tryLock() {
return sync.nonfairTryAcquire(1);
}
public void unlock() {
sync.release(1);
}
public Condition newCondition() {
return sync.newCondition();
}
public int getHoldCount() {
return sync.getHoldCount();
}
public boolean isLocked() {
return sync.isLocked();
}
public boolean isFair() {
return sync instanceof FairSync;
}
protected Thread getOwner() {
return sync.getOwner();
}
}
其中Sync是一个抽象类,它继承了AQS类,并提供了可重入锁的主要实现。NonfairSync和FairSync分别表示不公平的锁和公平的锁,以控制线程抢占锁的顺序。
在lock方法中,当当前状态量为0时,线程会通过compareAndSetState方法将状态量设置为1,同时获取独占线程的所有权,表示 lock被当前线程占用,如果当前状态量不为0且当前线程是之前占用锁的线程,将状态量加1,表示占用的锁的数量加1。
在unlock方法中,如果线程并没有占用锁直接释放,那么就会抛出异常,如果线程是占用锁的线程,那么就将锁的数量减1。如果当前锁的数量为0,表明线程已经完全释放了该锁,那么独占线程将被置为null,同时同步队列中的锁将被唤醒。
ReentrantLock的主要思想就是通过利用同步队列和状态量来实现线程之间的协作,让线程在共享资源时能够互相协作,让占用资源的线程都按顺序地释放资源,避免竞争条件的发生。