一、ReetrantLock的使用示例
static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
new Thread(ClassLayOutTest::reentrantLockDemo, "threadA").start();
Thread.sleep(1000);
new Thread(ClassLayOutTest::reentrantLockDemo, "threadB").start();
Thread.sleep(1000);
new Thread(ClassLayOutTest::reentrantLockDemo, "threadC").start();
}public static void reentrantLockDemo() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + ": 获取到锁! -->" + System.currentTimeMillis() / 1000);
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
二、流程分析
上面的有三个线程都会去执行reentrantLockDemo()方法,在抢占到锁后会睡眠5S。然后在线程A启动后先睡眠1秒再启动B线程,线程B启动后睡眠1秒再启动线程C。这样就能保证了线程ABC按照固定顺序去抢占,那么程序执行的顺序是:
1、线程A抢占锁资源
线程A直接过来后,由于没有锁还没有被线程抢占。可以直接抢占到锁,线程A直接通过CAS将state的值由0修改为1,head及tail为null
2、线程B抢占失败进入AQS队列
线程B执行加锁操作的时候,由于锁已经被线程A占有了,所以没抢占到,执行进入AQS队列操作。具体的操作就是把自己封装成一个Node对象
放入到双向队列的时候,前面需要有一个伪头节点(节点中thread为null,waitStatus = -1),B放入进入后对应的node节点属性(thread = 线程B,waitStatus = 0)
3、线程B抢占失败,继续进入到AQS队列
线程C过来后,由于线程A已经抢占到锁资源,并且其业务代码需要执行5s中,那么此时线程c也无法抢到锁,需要进行进入AQS队列操作。在线程C进入AQS队列后,AQS目前现在应该有3个节点,伪节点 + Node(B) + Node(C)三个节点,状态如下所示:
最终我们看到示意图如下:
三、代码分析
1、lock方法分析
1.1、执行lock方法后,公平锁和非公平锁的执行方法不一样
// 非公平锁的实现 final void lock() { // 上来先尝试使用CAS的方法将0设置为1 if (compareAndSetState(0, 1)) //获取锁资源成功后,将当前线设置给属性字段exclusiveOwnerThread setExclusiveOwnerThread(Thread.currentThread()); else // 尝试获取到1个资源 acquire(1); }// 公平锁
final void lock() { acquire(1); }
1.2、acquire方法分析,这里的公平锁和非公平锁的逻辑是相同的
public final void acquire(int arg) {
// tryAcquire:再次查看,当前线程是否可以尝试获取锁资源
if (!tryAcquire(arg) &&
// 没有拿到锁资源
// addWaiter(Node.EXCLUSIVE):将当前线程封装为Node节点,插入到AQS的双向链表的结尾
// acquireQueued:查看我是否是第一个排队的节点,如果是可以再次尝试获取锁资源,如果长时间拿不到,挂起线程
// 如果不是第一个排队的额节点,就尝试挂起线程即可
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
// 中断线程的操作
selfInterrupt();
}
1.3 、tryAcquire方法,,分为公平锁和非公平锁
// 非公平锁竞争锁资源逻辑 final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); // 获取当前state属性值 int c = getState(); // state等于0的时候,说明之前持有锁的线程已经释放了锁资源 if (c == 0) { // 基于CAS尝试将state由0设置为1 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"); // 设置statu加1的值 setState(nextc); return true; } return false; }-------------------------------------------------------------------------------------------------------------------------
// 公平锁实现逻辑
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; }
1.4、addWaiter方法分析
在没有拿到锁资源的时候,把线程放入到AQS队列中去
private Node addWaiter(Node mode) { // 将当前的线程封装成node对象,这里mode是互斥模式 Node node = new Node(Thread.currentThread(), mode); Node pred = tail; if (pred != null) { node.prev = pred; if (compareAndSetTail(pred, node)) { pred.next = node; return node; } } enq(node); return node; }