下面是个人的理解与查看源码的备注,如果有不正确的地方,请评论指出。万分感谢
lock是可重入、 可公平与非公平的 、可共享与独占的
如何证明?请看下列测试代码
public class ReentrantLockTest {
private static int sum = 0;
private static Lock lock = new ReentrantLock();
public static void main(String[] args) {
for (int i = 0; i < 3; i++) {
Thread thread = new Thread(()->{
//加锁 这里打断点并使用右键查看每个线程
lock.lock();
try {
for (int j = 0; j < 10000; j++) {
sum++;
}
} finally {
// 释放锁
lock.unlock();
}
});
thread.start();
}
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(sum);
}
}
- 第一步查看new ReentrantLock()
// 默认非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
/**
* Creates an instance of {@code ReentrantLock} with the
* given fairness policy.
*
* @param fair {@code true} if this lock should use a fair ordering policy
*/
// 通过传参可以设置是非公平锁还是公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
为什么说new FairSync()和new NonfairSync()就是代表公平锁和非公平锁?
什么是公平锁和非公平锁?
如果一个线程进来先插队直接一上来就获取锁,那么就是非公平锁。反之就是公平锁
在java\util\concurrent\locks\ReentrantLock.java
// 非公平
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
// cas修改state锁状态,
if (compareAndSetState(0, 1))
// 修改成功,那么直接设置独占锁为当前线程
setExclusiveOwnerThread(Thread.currentThread());
else
// 失败,进入队列
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
// 公平
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
// 直接进入队列
acquire(1);
}
- 调用lock方法后,查看如何入列的,查看acquire(1)方法?
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
!tryAcquire(arg)方法内部 -------这里可以说明是可重入锁
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
// 当前线程
final Thread current = Thread.currentThread();
// 当前线程状态,如果是第一次进来这里c就等于1,1的状态是代表已经有锁了
int c = getState();
if (c == 0) {
// 这里是尝试获取锁,CAS修改state成功,设置锁
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// getExclusiveOwnerThread()这里是获取锁的对象
// 如果当前对象和getExclusiveOwnerThread()相等,说明是同一个对象。那么state就会加1,说明这是可重入的
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;
}
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))重点方法重点方法重点方法
拆分成两个方法
addWaiter(Node.EXCLUSIVE) 添加任务到队列
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
addWaiter(Node.EXCLUSIVE)
一个重要属性Node,这里的队列也是使用双向链表,所以AQS内部维持了一个Node节点类
static final Node EXCLUSIVE = null;
Node(Thread thread, Node mode) { // Used by addWaiter
this.nextWaiter = mode;
this.thread = thread;
}
// 主要看这,其他的是调用的方法
private Node addWaiter(Node mode) {
// mode是等于null的,可以自己去看下。这里new Node节点
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
// tail是定义为队列的尾部,第一次进来肯定等于null
Node pred = tail;
if (pred != null) {
// 设置当前节点的父节点
node.prev = pred;
// 把之前的父节点改成当前节点,但是我debug看他的thread值并没有被改变!!! 有点奇怪
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
// tail等于null走enq方法 重要方法重要方法 实现队列初始化
// 说实话,可能java淘汰了我都写不出来这么经典的代码
enq(node);
return node;
}
// 初始化队列 node等于当前线程的node
private Node enq(final Node node) {
// node等于当前线程的节点,除了thread等于当前线程,其他属性都是默认值
for (;;) {
// 第一次等于null,把尾节点复制给t
Node t = tail;
if (t == null) { // Must initialize
// 新建队列头和队列尾,然后再次循环
if (compareAndSetHead(new Node()))
// 引用相等
tail = head;
} else {
// 再次进来,设置当前节点的父节点等于t,可能有些人疑问,t不是尾节点吗,这里又是赋值父节点。
// 这是因为这个t就是一个空节点,也等于head
// 总结:设置新的尾节点的父节点等于上次的尾节点
node.prev = t;
// CAS修改尾节点tail等于当前节点,并不是t=node
if (compareAndSetTail(t, node)) {
// 这时候的t和head是引用相等,t.next等于hread的next等于node
// 设置上个节点的next等于当前节点
t.next = node;
// 返回尾节点,这里有点容易混淆!!!
// 这里如果是第一次进来的,t可以说是父节点,也是尾节点。但第二次进来那么就是尾节点!!! **确定是尾节点**
return t;
}
}
}
}
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
阻塞队列,因为addWaiter只是形成链表
// node等于尾节点,arg等于1
final boolean acquireQueued(final Node node, int arg) {
// 未知
boolean failed = true;
try {
// 设置中断标志
boolean interrupted = false;
// 又是一个自循环
for (;;) {
// 获取node节点的父节点
final Node p = node.predecessor();
// 父节点是否等于队列头 tryAcquire这里再次尝试获取锁
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);
}
}
// 我删掉了方法的英文注释,因为看不懂
// 参数 尾节点的父节点和尾节点 node也可以说当前节点,因为当前节点就是尾节点
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
// 获取父节点的锁状态,非常之重要这个状态,第一次进来是0
int ws = pred.waitStatus;
// Node.SIGNAL表示这个线程是需要被阻塞的
if (ws == Node.SIGNAL)
return true;
if (ws > 0) {
do {
// 这里没太明白,是说这里这个节点不需要了,这里删除节点
// 大概是循环重组队列,删除
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
// 设置当前线程为Node.SIGNAL
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
// 退出,再次进来因为是自循环
return false;
}
private final boolean parkAndCheckInterrupt() {
// 设置线程阻塞
LockSupport.park(this);
return Thread.interrupted();
}
- ReentrantLock出队逻辑
lock.unLock()
// arg=1
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 状态等于0
int c = getState() - releases;
// 当前线程不等于独占锁里的线程报错
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
// 独占锁赋值为空
setExclusiveOwnerThread(null);
}
// state赋值为0,识为可加锁
setState(c);
return free;
}
private void unparkSuccessor(Node node) {
// ws=-1
int ws = node.waitStatus;
if (ws < 0)
// 把waitStatus 改成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);
}
释放完锁之后又会进入到之前加锁那里
// node是线程1的node
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
// p=head
final Node p = node.predecessor();
// tryAcquire(arg)再这里获取了锁,必定成功,因为thread0已经释放了锁
if (p == head && tryAcquire(arg)) {
// 设置head为当前node------------>其实队列头就是当前被锁住的线程
setHead(node);
// 清除之前的head节点与子节点的引用
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
// 从这里出来 <----------------
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
private void setHead(Node node) {
head = node;
node.thread = null; // 这一步可以看到,其实head就是当前获取锁对象,只不过thread被清空了
node.prev = null;
}
总结一下: release方法是释放锁,释放完锁后还需要在acquireQueued方法内重组队列,lock是遵循先进先出原则
入队是可公平/可不公平的,但出队是公平的,先进先出