ReentrantLock锁是jdk1.5之后加的轻量级锁,相对以前的重量级锁,它有很多的优势。ReentrantLock只支持独占方式的获取操作,它将同步状态用于保存锁获取操作的次数,并且还维护一个owner变量来保存当前所有的线程标识符,只有当线程获取或者释放锁的时候才会修改这个变量。
1. 可重入锁的源码分析:
当我打开的ReentrantLock源码的时候发现它的代码却是非常简单的。总共有三个内部类:第一个抽象内部类Sync (abstract static class Sync extends AbstractQueuedSynchronize) 直接继承AQS 我们可以大概了解一下什么 AQS?AQS是Java并发类库的基础,其提供了一个基于FIFO队列,可以用于构建锁或者其他相关同步装置的基础框架。该同步器(以下简称同步器)利用了一个int来表示状态,期望它能够成为实现大部分同步需求的基础。使用的方法是继承,子类通过继承同步器并需要实现它的方法来管理其状态,管理的方式就是通过类似acquire和release的方式来操纵状态。然而多线程环境中对状态的操纵必须确保原子性,因此子类对于状态的把握,需要使用 这个同步器提供的常用方法对状态进行操作;
第二个内部类NonfairSync(非公平锁):非公平锁是直接获取锁,没有维护等待队列.第三个内部类FairSync(公平锁):当遇到阻塞的时候,会把请求锁的进程添加到维护等待队列,下次释放锁的时候会从队列的头节头进行处理。
锁的申请和释放都是成对出现的,我们先来看一下ReentrantLock对常规lock和unlock的处理.对于常规的独占锁,ReentrantLock用0和1 分别表示是否有线程持有锁。0代表没有线程持有锁 ,如果有线程申请锁就会把状态改为1,如果释放锁了,就会把状态改为0;
我们来看一下源码:
lock方法:
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}compareAndSetState比较当前的状态是否是0,如果是0的同时,会把当前状态设置成1。如果两个步骤都完成,证明获取锁成功,同时设置进程状态为当前进程。
unlock方法:
public void unlock() {
sync.release(1);
}简单的把当前状态减1.
可重入方法trylock:
public boolean tryLock() {
return sync.nonfairTryAcquire(1);
}再看nonfairTryAcquire方法:
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;
}如果当前锁状态为0,那么直接获取锁并且返回,如果锁的状态不是0,证明有线程持有锁,再比较当前线程与请求线程是否是同一条线程,则会把累加当前持有的进程数,否则获取锁失败。
目前为此,我们大概已经可以明白可重入锁的实现了,主要是借助AQS 框架来实现,后面会再分析AQS。
2. 读写锁的源码分析:
读写锁和可重入锁是都是基于AQS 来实现的,所以读写内部还是会有一个Sync类。除此之外还有两个类:ReadLock和WriteLock类,不过这两个类都是用同一个 Sync的,当初没有源码的时候,我以为会有两个Sync类,所以读写锁是用一个AQS子类 同时管理讯读取加锁和写入加锁。AQS在内部维护一个等待线程队列,其中记录了某个线程是独占访问(相当于写)还是共享访问(相当于读)。当锁可用时,如果位于队列头部的线程是执行写入操作,那么线程会得到这个锁,如果位于队列头部的线程是读取访问,那么队列中在第一个写入线程之前的所有线程都将获得这个锁。这是一种没有读或者写优先等待的策略。
下面简单的分析一下(因为和上面太多相同):
看一下ReadLock的lock方法:
public void lock() {
sync.acquireShared(1);
}
这里申请的是共享锁。
WriteLock的lock方法:
public void lock() {
sync.acquire(1);
}
这里申请的是独占锁。从这里可以看到AQS的强大,下面我们还是重点看一下AQS。
3. AQS分析
前面已经说过了,AQS用一个int来表示的状态,子类通过继承同步器并需要实现它的方法来管理其状态,管理的方式就是通过类似acquire和release的方式来操纵状态。然而多线程环境中对状态的操纵必须确保原子性,因此AQS提供getState,setState 和compareAndSetState 这三个原子方法.常用的获取锁和释放锁的流程:
获取操作过程如下:
if(尝试获取成功){
return;
}else{
加入等待队列;park自己
}
释放操作:
if(尝试释放成功){
unpark等待队列中第一个节点
}else{
return false
}
在多线程中也必须保证等待队列是线程安全,而且是非阻塞式的。我们来看一下队列的实现:
Node类里面有分别pre指向上一个节点,next指向下一个节点。同时有SHARED共享和EXCLUSIVE独占两种模式.我们先接着从前面的ReentrantLock的lock 方法分 析,如果没有获取锁就会调用 acquire(1) 方法。我们接着AQS里面的方法:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
} tryAcquire再次尝试获取锁,如果还是失败,就会添加到队列:
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}首先:判断尾节点是否为空,如果不为空,直接插入到尾部,如果为空则做特殊处理
我们再来看一下:
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
1. 获取当前节点的前驱节点; 需要获取当前节点的前驱节点,而头结点所对应的含义是当前站有锁且正在运行。 2. 当前驱节点是头结点并且能够获取状态,代表该当前节点占有锁; 如果满足上述条件,那么代表能够占有锁,根据节点对锁占有的含义,设置头结点为当前节点。 3. 否则进入等待状态。 如果没有轮到当前节点运行,那么将当前线程从线程调度器上摘下,也就是进入等待状态。
我们再来看一下AQS的release方法:
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
1. 尝试释放状态;
tryRelease能够保证原子化的将状态设置回去,当然需要使用compareAndSet来保证。如果释放状态成功过之后,将会进入后继节点的唤醒过程。一般由子类实现。
2. 唤醒当前节点的后继节点所包含的线程。
通过LockSupport的unpark方法将休眠中的线程唤醒,让其继续acquire状态。
我们再来看一下unparkSuccessor方法:
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
/*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);
}先取出当前节点的那个一个节点,如果为空,则从最后一个节点一直遍历到第一个节点,直到找到阻塞的节点,同时会唤醒该节点.
下面我们再看一下利用AQS自定义的锁:
package javaThread;
import java.util.concurrent.locks.AbstractQueuedLongSynchronizer;
public class MyAQSLock {
private final Sync sync = new Sync();
public void signal() {
sync.releaseShared(0);
}
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(0);
}
private class Sync extends AbstractQueuedLongSynchronizer {
@Override
protected long tryAcquireShared(long arg) {
return (getState() == 1) ? 1 : -1;
}
@Override
protected boolean tryReleaseShared(long arg) {
setState(1);
return true;
}
}
} 这里只是一个简单的锁,用0表示关闭,1表示打开. 当调用 await方法的时候,然后会调用 tryAcquireShared方法,如果已经打开了闭锁,那么就允许通过。