ReentrantLock和synchronized一样加锁方式,独占的获取同步的对象锁。
1. 简介
ReentrantLock内部由Sync类实例实现其中ReentrantLock.FairSync,ReentrantLock.NonfairSync两个实现,也就是常说的公平锁和不公平锁。
而Sync继承于AbstractQueuedSynchronizer。AbstractQueuedSynchronizer这个类真的很难也很复杂,是构建锁以及实现其他相关同步类的基础框架。本篇文章只能说是对自己看这个类的一点点理解和记录,若有错,请批评指正。
2. ReentrantLock类的lock()方法
由于锁Lock的实现都是委托给AbstractQueuedSynchronizer来实现的。因此,就将分析ReenterantLock类中如何获取锁和如何释放锁来理解。
public voidJava.util.concurrent.locks.ReentrantLock.lock()
如果该锁没有被另一个线程保持,则获取该锁并立即返回,并将锁的保持计数器设置为1.
如果当前线程已经保持该锁,则将保持计数加1,并且该方法立即返回。
如果该锁被另一个线程保持,则出于线程调度的目的,禁用该线程,并且在获得锁之前,该线程一直处于休眠状态,此时锁保持计数被设置为1.
保持计数就是AQS类的state变量。
默认ReentrantLock构造器
public ReentrantLock() {
sync =new NonfairSync();
}
默认是非公平锁,看看NonfairSync类下的lock方法:
final void lock() {
if (compareAndSetState(0,1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
compareAndSetState(0, 1) 这个是尝试获取锁,把state的状态从0改为1表示取得锁。这个方法原理是CAS,CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做!
setExclusiveOwnerThread()设置获取锁的线程就是当前线程.
具体调用的是:
protected final boolean compareAndSetState (int expect, int update) {
return unsafe.compareAndSwapInt (this, stateOffset, expect, update);
}
unsafe的compareAndSwapInt方法是native的.
但是我们更关注的是,当前线程申请锁不成功的时候是怎么做的.可以看到是AQS中的acquire(1);
public final void acquire(int arg) {
//先尝试获取锁,如果获取锁失败了,acquireQueued则执行
if( !tryAcquire(arg) && acquireQueued( addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
后面的方法。注意&&,前面为true后面才执行。获取锁失败后,会将该线程加入等待队列
这个看起来比较复杂,我们分解以下4个步骤。
1、如果tryAcquire(arg)成功,那就没有问题,已经拿到锁,整个lock()过程就结束了。如果失败进行操作2。
2、调用addWaiter方法:将当前线程创建一个独占节点(Node)并且此节点加入CHL队列末尾。进行操作3。
3、自旋尝试获取锁,失败根据前一个节点来决定是否挂起(park()),直到成功获取到锁。进行操作4。
4、如果当前线程已经中断过,那么就中断当前线程(清除中断位)。
下面我们对acquire方法中调用的其它方法一一进行分析。
tryAcquire(acquires)
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire (int acquires) {
final Thread current = Thread.currentThread();
int c = getState(); //对于AQS存在一个state来描述当前有多少线程持有锁
/*
如果c等于零,则没有线程持有锁,则将锁给当前线程即可
如果c不等于,说明当前线程已经获取了锁,这里是当前线程再次要获得锁,所以state 要继续+1
*/
if(c ==0) {
if(compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) { //判断当前线程是否为AQS的独占线程
int nextc = c + acquires;
if( nextc < 0 ) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
//获取锁失败,返回false。
return false;
tryAcquire的逻辑是这样的, c = getState() 就是当前没有锁竞争的时候,会再尝试去获得锁.
current == getExclusiveOwnerThread()):当前线程已经获取锁了,那么锁的记数加1.
addWaiter(mode)
如果tryAcquire没有成功, 就执行acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
addWaiter是把线程和线程的状态信息封装到一个node对象,加入CHL阻塞链表,Node封装了各种线程状态:
static final int CANCELLED = 1; //这个状态说明该节点已经被取消。
static final int SIGNAL = -1; //这个状态说明该节点后续有阻塞的节点 。
static final int CONDITION = -2; //表明该线程被处于条件队列,就是因为调用了Condition.await而被阻塞
static final int PROPAGATE(-3); //传播共享锁
0:0代表无状态
其实就是把当前线程放到一个链表的末尾去.具体怎么放有点讲究,而且用到了无限循环,也就是说,一定要把线程放进链表的!
static final Node EXCLUSIVE = null; //独占节点模式
static final Node SHARED = new Node(); //共享节点模式
addWaiter(mode)中的mode就是节点模式,也就是共享锁还是独占锁模式。
privateNode addWaiter(Node mode) {
//当前线程节点,线程的状态信息封装到一个node对象。
Node node =new Node(Thread.currentThread(), mode);
Node pred = tail;
//判断有没有尾节点(也就是前面是否有等待线程)。如果有尾节点,则将当前线程的节点插入到队列的尾部,也就是将当前线程变成尾节点。
if(pred != null) {
node.prev = pred;
//CAS的操作。
if(compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
//如果没有尾节点,说明前面还未有等待线程。调用下面的方法,创建等待队列
enq(node);
return node;
}
总而言之,addWaiter的目的就是通过CAS把当前现在追加到队尾,并返回包装后的Node实例。
acquireQueued(node,arg)
//这个方法是不断地获取锁,直到成功的获取锁,或者阻塞当前线程
acquireQueued也是个无限循环。就是说要么获取到锁,要么中断当前线程。
1. acquireQueued方法在无限循环内获取前继节点,判断前继节点是否为head,是就再尝试 获取锁,之后前继节点dequeue出队,node成为head。
2. 前继节点p != head 或者 前继节点p == head但是tryAcquire失败了,那么应该阻塞当前线 程等待前继唤醒。阻塞之前会再重试一次,还需要设置前继的waitStaus为SIGNAL。
3. 调用shouldParkAfterFailedAcquire方法,后面的方法是对当前线程进行阻塞并且判断是 否中断。这里注意的是,如果一个线程在等待锁期间这个线程被中断了,这里会将 interrupted赋为true,但是并不return。这个还一直进行for循环,知道这个线程获得了锁, 所以lock()方法不能立即响应中断,必须等线程获得了锁才可以响应中断。对应的可以立 即响应中断的方法为lockInterruptibly()方法。
//阻塞当前线程
private final boolean parkAndCheckInterrupt() {
//阻塞当前线程
LockSupport.park(this);
return Thread.interrupted();
}
unLock()方法
public void unlock() {
sync.release(1);
}