ReentrantLock是依赖于AQS的一个锁的实现,它是一把独占锁,并且是一把可重入锁。本人看源码的功底比较差,还是只能从经常用到的东西入手。那么就开始咯。
1.构造器
//非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
//可以指定公平锁与否(其实两种锁的很多地方应该是相似的,因此我们先考虑非公平锁)
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
2.生成了对象,那么我们肯定是直接开始让他干活。lock()方法。
public void lock() {
sync.lock();
}
//sync其实就是一个内部类,继承aqs,也是一个抽象类,其实现有公平锁和非公平锁
//那么我们直接去看非公平锁了
final void lock() {
//state是一个volatile修饰的代表锁的状态,0代表没被占用,1代表被占用
//这里是通过cas进行占有锁
if (compareAndSetState(0, 1))
//如果占有成功,那么将独占线程设置为当前线程
setExclusiveOwnerThread(Thread.currentThread());
else
//锁被占用,尝试获取锁
acquire(1);
}
(1)当锁没有被占用的时候,那么就将锁给当前线程就完事了。
(2)锁被别的线程占用,那么就会去尝试获取锁
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
//acquires为1
//如果当前线程获取到了锁,那么就返回true 否则返回false
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
//这个时候,锁被释放了,那么就通过cas设置锁被占有
//这里也就是不公平的原因了,因为这个时候,在队列当中的线程可能还在排队,而当前线程就已
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;
}
//放入等待链表当中
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;
//通过cas确保tail还是之前的tail
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
//这里之前说错了:下面跟着修改
//代表了
enq(node);
return node;
}
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // 初始化尾巴
//这里是指头节点是一个空节点
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();//前驱节点-可以看成是获得了锁的节点
//如果前驱是head,即该结点已成老二,那么便有资格去尝试获取资源
//这个地方应该是比较重要的,也就是说,一旦放入队列当中,没有获取到锁的话
//那么就会一直for自旋
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);
}
}
那么总结一下该方法,lock()是一把独占锁,会将占有锁的线程的状态设置为1,如果重入的话,那么就会+1,这时有其他线程的时候,会有两种手段,一是直接尝试获取锁,不用管其他排队的线程(这里就是不公平的地方);二是指将该线程放入到由链表形成的队列当中,其中的线程一直for循环自旋下去,知道获取到锁为止。
3.中国一句古话,解铃还须系铃人。那么既然锁住了,你肯定得解锁吧。那么就看看unlock()
public void unlock() {
//读读名字,猜一下,是释放的意思
sync.release(1);
}
public final boolean release(int arg) {
//尝试释放锁
//释放成功与否
if (tryRelease(arg)) {
//释放成功
Node h = head;//等待队列的头
//(1)先判断有没有等待线程
//(2)该线程是不是处于等待状态
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
//看看tryRelease(1)
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
//只能是占锁的才能解锁
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
//其实就===这里是指重入的思想,要是没有重入,那么就将锁还原
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
------------------------------------------------------------接下来一起看一下公平锁----------------------------------------------------------------------------
公平锁也就是公平的,那么意味着线程都是要排队的,那么大致猜想一下,我认为区别比较大的地方就在于tryAcquire(1)这个地方。那么我们拿点代码出来说话。
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
//果不其然,就是这个地方的玄机
//通过方法名猜测,又要公平,那么就应该是判断有没有队列存在
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
public final boolean hasQueuedPredecessors() {
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
/**有点复杂 一个个讲
* h!=t是指没有队列存在就返回false
* ( s=h.next )==null 有队列存在,但是只有一个线程在排队
* 当前排队的线程不是该线程
*/
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
2.接着看一下上面看漏的东西。lock的强大之处,tryLock()系列。尝试获取锁。有两个方法,一个是定时了,一个是没定时。那么先来看一下没定时的。
public boolean tryLock() {
//可以看出就是瞬间去尝试获得锁,能够获得就返回true
//获得失败就返回false,也就是放弃执行的意思?
//那么就可以看成是一种降级处理了,例如多人修改文件
//我没保存成功就算了,那么我就将该文件放到一个备份文件夹下,人工补偿
return sync.nonfairTryAcquire(1);
}
哇,这么简单??那就再看看定时的。tryLock(long timeout, TimeUnit unit):时长,单位
public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
public final boolean tryAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
//是看被打断没
if (Thread.interrupted())
throw new InterruptedException();
//tryAcquire(1)尝试去获取锁
//继续看后面这个doAcquireNanos(),这里猜测一下,应该是用一种计时循环的方式一直去
//尝试获取锁
return tryAcquire(arg) ||
doAcquireNanos(arg, nanosTimeout);
}
private boolean doAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (nanosTimeout <= 0L)
return false;
//死亡倒计时
final long deadline = System.nanoTime() + nanosTimeout;
//将自己放入队列当中
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
for (;;) {
//获得前面的节点
final Node p = node.predecessor();
//自己是老二了就能够开始尝试获取锁了
//获取成功就完了
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return true;
}
//判断是否超时
nanosTimeout = deadline - System.nanoTime();
if (nanosTimeout <= 0L)
//超时,取消获取锁,并且返回失败
return false;
// 超时时间未到,且需要挂起(这个地方不是很懂)
if (shouldParkAfterFailedAcquire(p, node) &&
nanosTimeout > spinForTimeoutThreshold)
// 阻塞当前线程直到超时时间到期
LockSupport.parkNanos(this, nanosTimeout);
//判断线程状态是否为:中断
if (Thread.interrupted())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}