前言
Java语言直接提供了synchronized
关键字用于加锁,但这种锁一是很重,二是获取时必须一直等待,没有额外的尝试机制。
java.util.concurrent.locks
包提供的ReentrantLock
用于替代synchronized
加锁,我们来看一下传统的synchronized
代码:
public class Counter {
private int count;
public void add(int n) {
synchronized(this) {
count += n;
}
}
}
ReentrantLock使用样例。
public class Counter {
private final Lock lock = new ReentrantLock();
private int count;
public void add(int n) {
lock.lock();
try {
count += n;
} finally {
lock.unlock();
}
}
}
构造器
ReentrantLock默认是非公平锁。
如果new ReentrantLock(true),则为公平锁,否则是非公平锁。
/**
* 默认是非公平锁
*/
public ReentrantLock() {
sync = new NonfairSync();
}
/**
* 看boolean的值,true为公平锁,false为非公平锁。
*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
Sync
此处使用的Sync是提供所有实现机制的同步器,是一个抽象类,继承AbstractQueuedSynchronizer(即AQS)。
private final Sync sync;
/**
* 此锁的同步控制基础。下面分为公平和非公平版本。使用 AQS 状态来表示持有锁的次数
*/
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
/**
* 抽象lock
*/
abstract void lock();
/**
* 执行非公平tryLock。tryAcquire在子类中实现,但两者都需要非空的trylock方法
*/
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
//获取锁状态,使用的是AQS volatile state状态
int c = getState();
if (c == 0) {
//如果状态为0,表示没有线程上锁。则用CAS获取锁
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;
}
//尝试释放锁
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
//当前线程没有持有锁,抛错
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
//是否完全释放标志
boolean free = false;
//c为0,则表示状态为完全释放
if (c == 0) {
//将标识改为true,并将持有锁线程改为空
free = true;
setExclusiveOwnerThread(null);
}
//重新设置锁的状态,并返回释放锁的状态
setState(c);
return free;
}
//判断持有锁的是否为当前线程
protected final boolean isHeldExclusively() {
// While we must in general read state before owner,
// we don't need to do so to check if current thread is owner
return getExclusiveOwnerThread() == Thread.currentThread();
}
//获取一个新的Condition对象
final ConditionObject newCondition() {
return new ConditionObject();
}
//获取持有当前锁的线程,当为0则代表为没有线程,否则获取持有锁的当前线程
final Thread getOwner() {
return getState() == 0 ? null : getExclusiveOwnerThread();
}
//获取当前锁被持有的次数
final int getHoldCount() {
return isHeldExclusively() ? getState() : 0;
}
//判断是否有线程持有当前锁
final boolean isLocked() {
return getState() != 0;
}
/**
* Reconstitutes the instance from a stream (that is, deserializes it).
*/
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
setState(0); // reset to unlocked state
}
}
FairSync:公平锁
继承关系
源码分析
/**
* Sync object for fair locks
*/
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
//公平锁版本的tryAcquire
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
//获取锁状态
int c = getState();
//当前锁状态为0,即没有线程持有
if (c == 0) {
//hasQueuedPredecessors:判断有没有别的线程排在了当前线程的前面。
//如果当前线程没有,并且CAS成功,则直接设置持有锁的线程为当前线程
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;
}
}
其中hasQueuedPredecessors()函数,方法来自于AQS。用来判断有没有别的线程排在了当前线程的前面。
public final boolean hasQueuedPredecessors() {
// The correctness of this depends on head being initialized
// before tail and on head.next being accurate if the current
// thread is first in queue.
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
return h != t &&((s = h.next) == null || s.thread != Thread.currentThread());
}
NonfairSync:非公平锁
继承关系
源码解析
/**
* Sync object for non-fair locks
*/
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
//调用lock方法时,直接做处理
final void lock() {
//直接尝试去获取锁,获取成功,则直接设置持有锁的线程为当前线程
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
acquire()方法来自于父类AQS中,首先是调用tryAcquire方法尝试获取锁,失败则调用addWaiter方法将线程包装为Node。然后自旋CAS入队,接着在acquireQueued方法中完成线程的阻塞。
public final void acquire(int arg) {
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
公平锁与非公平锁的区别
通过源码分析可以看出,有几点不同:
锁类型 | 效率 | 线程执行 | 执行情况 |
---|---|---|---|
公平锁 | 效率相对低,因为每次要询问 | 阳光普照,每个线程有执行的机会 | 线程进入后,要先看队列是否为空,若为空则直接上锁,若不为空则自动排队 |
非公平锁 | 效率高,锁状态可以用,可以直接上锁 | 线程饿死,有些线程可能执行不到 | 线程进入后,不按照顺序,会优先尝试争抢锁,若抢不到则按照公平锁的方式处理 |
重入锁
重入锁是如果线程已获取到锁后,则会再次获取到锁而不会被锁阻塞。
锁的获取和释放过程如下:
线程再次获取锁。锁需要去识别获取锁的线程是否为当前占据锁的线程,如果是则再次获取成功。
锁的最终释放。线程重复n次获取了锁,随后在第n次释放该锁后,其他线程能够获取到该锁。锁的最终释放要求锁对获取进行计数自增,计数表示当前锁被重复获取的次数,而锁被释放时,计数自减,当计数器等于0时表示锁已经成功释放了。