java中多线程的处理除了使用synchronize关键字还有就是使用J.U.C,也就是java.util.concurrent工具包,而J.U.C中的锁主要是通过Lock来控制和完成锁的基本操作,其中包括ReentrantLock(重入锁)、ReentrantReadWriteLock(读写锁:读写互斥、写写互斥、读读重入)
如图所示ReentrantLock重入锁中包含了公平锁(FairSync),非公平锁(NonfairSync),和Sync,其中Sync继承了AbstractQueuedSynchronizer也就是线程的同步队列。他是实现线程同步的核心组件。
AbstractQueuedSynchronizer维护了Node的一个双向列表用来存储争抢锁的对象
下面就开始分析ReentrantLock的源码
private Lock lock = new ReentrantLock();
public void test1(){
try{
lock.lock();// 加锁
}finally {
lock.unlock();//释放锁
}
}
通过lock.lock 调用ReentrantLock 的加锁的操作。
public void lock() {
sync.lock();
}
调用非公平锁的枷锁操作,公平锁和非公平锁的差异是公平锁在AQS队列中存在着节点的时候就不回去抢占锁,而非公平锁不会
final void lock() {
if (compareAndSetState(0, 1))
//通过CAS操作去尝试是否能够将state状态更改为1 如果能则线程抢占锁,因此如果state>1则表示当前锁被占用
setExclusiveOwnerThread(Thread.currentThread());//设置当前获取锁的线程
else
//没有抢占到锁 只能进行后续操作
acquire(1);
}
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
// 改代码被tryAcquire调用
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
//获取当前的状态如果是0 则进行抢占锁
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
//如果当前抢占锁的线程是当前线程则给当state+1,这就是重入
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
//把当前node增加近AQS队列中去
private Node addWaiter(Node mode) {
//通过当前线程创建一个node节点
Node node = new Node(Thread.currentThread(), mode);
// 查看尾部是否有节点如果没有则证明第一次进入则走enq
Node pred = tail;
if (pred != null) {
//如果pred不为空则证明tail存在,则证明这不是第一次 进入
node.prev = pred;
//通过aqs设置当前节点为为节点,当前的预期值是tail因此,当前节点为为节点
if (compareAndSetTail(pred, node)) {
//把前面tail节点的next节点设置为当前node,也就是说当前节点是tail节点
pred.next = node;
return node;
}
}
enq(node);
return node;
}
//通过自旋来创建一个列表
private Node enq(final Node node) {
for (;;) {
//通过自旋来创建一个链表,并把当前 的node节点 增加到链表中去
Node t = tail;
if (t == null) {
// 当前节点为空的时候创建一个空的节点设置到head和tail 因此有一个空的node是指向连表的投河尾
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
//把当前节点的前节点设置为head,把当前节点设置为tail节点
if (compareAndSetTail(t, node)) {//由于此处非原子性因此可能出现问题
//设置成功把当前head节点的后节点设置为当前节点
t.next = node;
return t;
}
}
}
}
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
// 判断当前节点的前一个节点是不是头结点,如果是头结点则尝试去获取锁并且把当前节点的前一个节点删除,并把当前的节点设置为head节点
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())//调用parkAndCheckInterrupt方法以后线程会在此处阻塞
//阻塞当前节点,阻塞以后并对当前节点进行复位,以防止有其他线程调用该线程的终止方法,如果当前线程的终止状态是true 则把当前线程的终止状态设置为true,parkAndCheckInterrupt 方法
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
//把 当前节点的前一个节点属性设置为SIGNAL
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;//第一次的时候默认为int 及初始化 状态为0
if (ws == Node.SIGNAL)//如果当前节点的前一个节点为-1 则返回成功
return true;
if (ws > 0) { //ws 只有在 CANCELLED状态才>0,也就是终止状态如果是终止状态则进行轮训把终止状态的线程从的队列中移除
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
//设置当前线程的前一个节点为SIGNAL状态
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
在此处lock.lock 就算调用完成了,接下来看lock.unlock的方法调用
public final boolean release(int arg) {
if (tryRelease(arg)) {//在此处进行解锁操作
Node h = head;
if (h != null && h.waitStatus != 0)
//获取当前链表的头结点并对当前节点 的头结点进行唤醒
unparkSuccessor(h);
return true;
}
return false;
}
//退出锁操作
protected final boolean tryRelease(int releases) {
//因为重入锁 可能state>1 因此减去unlock的一次判断
int c = getState() - releases;
//如果当前线程不等于正在执行的线程则跑出异常
if (Thread.currentThread() != getExclusiveOwnerThread())
new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
//如果当前state变成0则把当前执行的线程置空然后把state设置为0 并释放锁
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
private void unparkSuccessor(Node node) {
//查看当前节点的状态如果是小于1 则设置为1
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
//判断当前节点的下一个节点是否是终止的线程如果只则丢弃
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);
}
//唤醒之前挂起的方法
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;
}
//从这理唤醒,然后继续for循环
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
以上就是ReentrantLock的获取锁和释放锁的全过程
公平锁的原理和非公平锁的原理基本一致 只是在获取所得时候增加了一个判断:代码如下
protected final boolean tryAcquire(int acquires) {
...
if (c == 0) {
if (!hasQueuedPredecessors() &&// 在此处判断当前AQS中是否存在队列,如果存在则不进行获取,其与逻辑不变
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
...
return false;
}