锁是java同步的重要机制,在之前的博客中写了一些关于锁的内容,但比较浅显,这里从源码的角度来分析一下java中方的锁。java中的锁主要有两种:Synchronized和ReenTrarantLock,前者是从jvm的层面实现的,后者是从代码的层面实现的,因此这篇文章的主要对象是针对后者。谈到ReenTrarantLock的实现,就不得不提到AQS。
AbstractQueuedSynchronizer抽象同步队列简称AQS,他是实现同步器的基础组件,并发包中的许多组件CountDownLatch、CycleBarrier、Semaphore都是基于AQS实现的,虽然在实际的项目中很少用到,但是其架构设计还是值得学习的,下面是一张AQS的主要类图结构,只包含了主要内容:
AQS的主要思想就是维护一个单一的状态信息state,通过getState(),setState(),compareAndSetState()方法来修改其值。对于AQS来说,线程同步的关键就是对状态值state进行操作,根据操作state的方式,又分为独享锁和共享锁;在独占方式下的获取和释放资源的方法为:acquire()和release(),在共享方式下的获取和释放资源的方法为:acquireShared()和releaseShared()。
使用独占方式获取的资源是与具体线程绑定的,就是如果一个线程获取了资源,就会标记是这个线程获取到了,其他线程再尝试操作state时会发现当前资源不是自己持有的,就会在获取失败后阻塞,然后该线程会被包装为一个Node节点,加入到AQS阻塞队列当中,直到持有state的线程释放资源,该线程会被唤醒继续争取资源。比如独占锁ReentrantLock的实现,当一个线程获取了ReentrantLock后,在AQS内部会使用CAS操作把state状态值从0改为1,然后设置当前锁的持有者为当前线程,当该线程再次获取锁的时候发现它就是锁的持有者,则会把状态值从1改为2,也就是设置了重入次数为2,当另一个线程获取锁时发现自己并不是锁的持有者就会被放入AQS阻塞队列后挂起。
在独占方式下,获取与释放资源的流程如下:
(1)当一个线程调用acquire(int arg)方法获取资源时,会先尝试使用tryAcquire方法获取资源,如成果则直接返回,失败则将当前线程封装为Node.EXCLUSIVE的Node节点插入到AQS阻塞队列的尾部。
public final void acquire(int arg) {
//尝试使用tryAcquire方法获取资源,具体就是设置state状态的值
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
(2)当一个线程调用release(int arg)方法时会尝试使用tryRelease操作释放资源,设置状态变量state的值,然后激活AQS阻塞队列中一个被阻塞的线程,被激活的线程调用tryAcquire方法进行尝试,看当前状态变量state的值是否为0,若为0,则该线程被激活,继续向下运行,否则会被放入AQS队列被挂起。
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
如果头节点不为null且状态值不为0,则唤醒一个阻塞线程
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
}
在这里强调一下,AQS没有提供可用的tryAcquire和tryRelease方法,因为它是一个基础框架,这两个方法需要由具体的子类来实现;比如实现AQS的ReentrentLock,定义status为0时表示锁空闲,为1时表示锁被占用,则在实现tryAcquire方法时,需要使用CAS算法查看当前state是否为0,如果为0则修改为1,并将当前锁的持有线程设置为当前线程,关于ReentrantLock我会在以后的博文中进行详细讲解。
在共享方式下,获取与释放锁的流程如下:
(1)当线程调用acquireShared(int arg)获取共享资源时,会先使用tryAcquiredShared方法尝试获取资源,具体是设置state变量的值,若成功则直接返回,失败则将当前线程封装为Node.SHARED的Node节点插入到AQS阻塞队列末尾。
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
protected int tryAcquireShared(int arg) {
throw new UnsupportedOperationException();
}
(2)当一个线程调用releaseShared(int arg)方法时会尝试使用tryReleaseShared操作释放资源,设置状态变量state的值,然后激活AQS阻塞队列中一个被阻塞的线程,被激活的线程调用tryAcquireShared方法进行尝试,看当前状态变量state的值是否为0,若为0,则该线程被激活,继续向下运行,否则会被放入AQS队列被挂起。
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
protected boolean tryReleaseShared(int arg) {
throw new UnsupportedOperationException();
}
AQS的阻塞队列
在之前的操作中,我们反复提及节点和阻塞队列,下面我们来看看这些数据的结构,Node是AQS的一个内部类,封装竞争资源失败被阻塞的线程,当线程竞争失败时,会调用enq()方法将该线程放入阻塞队列末尾,AQS提供一个双向队列存储Node节点。
static final class Node {
//表示线程已被取消
static final int CANCELLED = 1;
//表示线程需要被唤醒
static final int SIGNAL = -1;
//线程在条件队列中等待
static final int CONDITION = -2;
//线程释放资源时需要通知其它节点
static final int PROPAGATE = -3;
//封装线程的状态,有如上几种
volatile int waitStatus;
//线程对应节点的前节点
volatile Node prev;
//线程对应节点的后节点
volatile Node next;
//下一个在条件队列中等待的节点
Node nextWaiter;
//AQS提供的入队操作
private Node enq(final Node node) {
//经典的无限循环
for (;;) {
Node t = tail;
//若头尾节点都为null,设置一个标记节点(不含内容),并让首尾节点都指向该节点
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
CAS设置当前节点为最后一个节点,并返回
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
}
AQS的条件队列
大家都知道,notify和wait是配和Synchronized关键字实现线程同步的基础设施,同样的,条件变量的signal和await方法也是配额和AQS实现的锁来实现线程同步的基础设施,先通过一个例子来看看条件变量是什么:
ReentrantLock lock=new ReentrantLock();
//创建了一个ConditionObject对象,即所谓的条件变量
Condition condition=lock.newCondition();
lock.lock();
try{
System.out.println("begin");
//调用条件变量awai()方法阻塞当前线程,直到其它线程调用signal()方法唤醒
condition.await();
System.out.println("end");
}catch(InterruptedException e){
e.printStackTrace();
}finally{
lock.unlock();
}
lock.lock();
try{
System.out.println("begin");
//调用条件变量的signa()方法唤醒一个线程
condition.signal();
System.out.println("end");
}catch(InterruptedException e){
e.printStackTrace();
}finally{
lock.unlock();
}
通过这个例子,大家应该已经明白了什么是条件变量,我们来看看条件变量的数据结构,注释已经解释的比较清楚。ConditionObject是AQS的内部类,能够访问AQS的变量state和方法,在每个条件变量内部对维护了一个条件队列,用来存放调用条件变量的await()方法而被阻塞的线程,注意条件队列是针对条件变量的,AQS阻塞队列是针对整个AQS的。
public class ConditionObject implements Condition, java.io.Serializable {
private static final long serialVersionUID = 1173984872572414699L;
//条件等待队列的首节点
private transient Node firstWaiter;
//条件等待队列的尾节点
private transient Node lastWaiter;
//调用该方法的线程会被放入条件变量的阻塞队列
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
//创建节点,并插入到条件队列末尾
Node node = addConditionWaiter();
//释放当前线程获取的锁
int savedState = fullyRelease(node);
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
//调用park方法阻塞该线程
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
//唤醒条件队列中被阻塞的线程
public final void signal() {
//如果不是独占,抛出异常
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
//将条件队列首个节点移动到AQS阻塞队列,准备争取锁资源
doSignal(first);
}
//将一个被封装的线程加入条件等待队列末尾
private Node addConditionWaiter() {
Node t = lastWaiter;
// 如果最后一个节点被取消,则删除
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
//将当前线程封装为一个节点加入到条件队列末尾,并返回
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
}
总结一下工作流程:当多个线程调用lock()方法获取锁时,只有一个线程获得了锁,其它节点会被封装为Node节点插入到AQS阻塞队列末尾,并做CAS尝试获取锁;这时如果获得了锁的线程调用了await()方法,则会释放获得的锁,并被转换为Node节点插入到该条件变量的条件队列里面,这时因为调用lock()方法被阻塞到AQS阻塞队列中的一个线程会得到锁,如果该线程调用了调用变量的signal()方法,则会将条件队列里面的一个Node节点移入到AQS阻塞队列,继续争夺锁资源。
最后我们基于AQS实现一个自定义锁,完成生产者-消费者模型:
//基于AQS实现一个自定义锁
public class NonReentrantLock implements Lock,java.io.Serializable {
private static class Sync extends AbstractQueuedSynchronizer{
//判断锁是否被其它线程持有
protected boolean isHeldExclusively(){
return getState()==1;
}
public boolean tryAcquire(int acquires){
assert acquires==1;
//尝试使用CAS修改state,若成功则获得锁,并设置该线程为锁的持有者
if(compareAndSetState(0,1)){
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
protected boolean tryRelease(int releases){
assert releases==1;
if(getState()==0){
throw new IllegalMonitorStateException();
}
setExclusiveOwnerThread(null);
setState(0);
return true;
}
Condition newCondition(){
return new ConditionObject();
}
}
private final Sync sync=new Sync();
@Override
public void lock() {
sync.acquire(1);
}
public boolean isLocked(){
return sync.isHeldExclusively();
}
@Override
public void lockInterruptibly() throws InterruptedException {
}
@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();
}
}
//基于自定义锁实现生产者-消费者模型
public class ReetrantLockList {
private ArrayList<String> arrayList=new ArrayList<>();
private volatile ReentrantLock reentrantLock=new ReentrantLock();
public void add(String e){
reentrantLock.lock();
try {
arrayList.add(e);
}finally {
reentrantLock.unlock();
}
}
public void remove(String e){
reentrantLock.lock();
try {
arrayList.remove(e);
}finally {
reentrantLock.unlock();
}
}
public String get(int index){
reentrantLock.lock();
try {
return arrayList.get(index);
}finally {
reentrantLock.unlock();
}
}
}