锁实现基础
Lock接口
Lock接口是JDK1.5新加入的,它可以实现比synchronized更丰富灵活的功能,同时也更易用。
在Lock接口基础上建立了重入锁,读写锁等一系列并发控制工具,因此Lock接口以及和它紧密相关的同步器是需要理解的概念。
Lock接口定义如下方法:
void lock() 获取锁
void unlock() 释放锁
void lockInterruptibly() 可中断地获取锁
boolean trylock() 尝试非阻塞的获取锁
boolean trylock(long,TimeUnit) 超时地获取锁
Condition newCondition() 获取等待通知组件
同步器AbstractQueuedSynchronizer
AbstractQueuedSynchronizer是一个抽象类:
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable {
AbstractQueuedSynchronizer继承AbstractOwnableSynchronizer。AbstractOwnableSynchronizer是一个辅助接口,定义了一个域private transient Thread exclusiveOwnerThread;
和它的两个Get,Set方法,exclusiveOwnerThread记录目前持有这个锁的线程,在排他锁的实现有应用。
AbstractQueuedSynchronizer提供三个方法:
getState() setState() compareAndSetState(int expect,int update)
state是一个记录锁状态的变量
private volatile int state;
AbstractQueuedSynchronizer提供以下方法:
protected boolean tryAcquire(int arg)
protected boolean tryRelease(int arg)
protected int tryAcquireShared(int arg)
protected boolean tryReleaseShared(int arg)
protected boolean isHeldExclusively()
protected boolean tryAcquire(int arg)
和protected boolean tryRelease(int arg)
是排他锁的获取和释放方法,protected int tryAcquireShared(int arg)
和protected boolean tryReleaseShared(int arg)
是共享锁的获取和释放方法。重入读写锁就具有共享锁方法,因为读写锁的读锁是可以共享的。
具体锁的实现
设计一个具体的锁工具类需要有一个继承了AbstractQueuedSynchronizer类的同步器内部类,具体化AbstractQueuedSynchronizer提供的方法;同时主类需要实现Lock接口,实例化Lock要求的方法,具体实现是调用同步器内部类的方法。
这样就将锁的使用和实现分离开来。接下来以重入锁ReentrantLock作为例子来分析这种膜版化的设计方式。
ReentrantLock
重入特性
重入锁,如其名,可重入。可重入意味着:
任意线程多次获取锁不会造成阻塞
通过计数来记录重入和释放
释放锁需要和重入次数同样次数的释放操作,使计数归零才能最终释放
重入锁还考虑了获取锁的公平性:
公平性即获取锁的顺序是否符合时间的先后顺序。这一实现依赖于AbstractQueuedSynchronizer中维护的获取等待队列,是一个双向链表。有相关的方法来操作这个队列。
重入实现
以下是“非公平锁获取”,“公平锁获取”和“锁释放”的源码:
非公平锁获取
final boolean nonfairTryAcquire(int acquires){
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if(compareAndSetState(0,acquires)){
setExclusiveOwnerThread()
}
} else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
公平锁获取
final boolean nonfairTryAcquire(int acquires){
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if(!hasQueuedPredecessors() && compareAndSetState(0,acquires)){
setExclusiveOwnerThread()
}
} 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()判断,判断队列中是否有前驱,如果有就不能获取锁,必须等前驱获得锁退出队列后,后续才能获取锁。
释放锁
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;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
公平性带来的问题
公平性可以避免“饥饿”现象发生,即有线程长时间不能获取锁,但是使用公平锁会造成大量的获取锁失败,线程切换开销。两者性能差距巨大,因此非公平锁反而是默认的方式,通过极少的线程切换,保证其更大的吞吐量,获得宏观上的高速。