-
什么是锁
锁是一段代码,当线程执行到这段代码时线程阻塞(挂起)或者进入轮询
-
ReentrantLock的原理
使用方式:
ReentrantLock lock = new ReentrantLock();//默认创建非公平锁
lock.lock();
xxx
锁住的代码逻辑
xxx
lock.unLock();
执行到 lock()方法时做的事情(创建的非公平锁):
(1)判断当前线程能否获取锁
(2)如果不能获取锁把当前线程加入到等待的线程队列中
(3)当前线程挂起
(4)等待当前线程的上一个线程释放锁以后唤醒当前线程
按着步骤做具体代码分析:
(1)判断当前线程能否获取锁
//ReentrantLock 的无参构造函数默认生成非公平锁
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
//上边代码中执行 lock.lock()函数就会执行该函数
final void lock() {
//尝试获取锁,这里使用 CAS 方式尝试把state【AQS类中的变量】 变量的状态改变,如果修改成功,则获得锁
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
//如果获取锁失败,执行 acquire 方法把线程加入到队列里
acquire(1);
}
}
(2)把线程加到队列中
//把线程加入到队列的方法入口
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
tryAcquire方法执行一下操作:
{
//尝试获取锁
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
//非公平方式获取锁
final boolean nonfairTryAcquire(int acquires) {
//当前线程
final Thread current = Thread.currentThread();
//获取当前锁的状态值
int c = getState();
//状态为0,说明没有线程占用锁
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;
}
}
addWaiter执行一下操作:
{
private Node addWaiter(Node mode) {
//new 一个节点对象,当前线程对其初始化,mode 为 null,用来作为当前节点的下一个节点
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;
}
}
//如果没有尾节点,也就是说队列为空,需要先初始化一个空的【节点的线程变量为null】节点当头,然后把当前节点往头节点后边加
enq(node);
return node;
}
}
(3) 当前线程挂起:
上一步中还执行了acquireQueued方法, 具体的操作如下:
{
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)) {
//设置当前节点为头结点(需要把Thread变量置为null)
setHead(node);
//头结点释放,方便 gc
p.next = null; // help GC
failed = false;
//返回
return interrupted;
}
//判断当前线程能否挂起
/**
*假如第一次循环当前线程不是头节点,判断是否能够挂起,经过下边的操作假如说不能挂起
*会进行二次循环,二次循环到这儿的时候当前线程的前一个节点已经是挂起状态,反正也轮
*不到当前线程啥事,也就直接挂起了当前线程
**/
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
//获取当前线程的节点的前一个线程节点的等待状态
int ws = pred.waitStatus;
//如果前一个线程节点的状态是等待触发状态,还没当前节点的什么事儿,所以当前节点可以被挂起
if (ws == Node.SIGNAL)
return true;
if (ws > 0) {
//前一个节点被打断了(等待的不耐烦了,放弃等待锁了),继续往前找,直到找到还在等待锁的节点
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
//把前一个节点的状态置为等待触发状态
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
//挂起当前线程
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
}
(4)等待当前线程的上一个线程释放锁以后唤醒当前线程
//当前线程执行完后执行释放锁方法,lock.unLock()方法
public void unlock() {
sync.release(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) {
//锁的状态值变更(因为可以重入,所以可以多次释放)
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;
}
private void unparkSuccessor(Node node) {
/*
* 获取当前线程节点的状态
*/
int ws = node.waitStatus;
//如果状态小于0,把node状态置为0,
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);
}
已上为ReentrantLock锁的原理
本文参照了两篇博客,加上个人理解整理得到,更详细可以看看下边大神的博客:
https://www.cnblogs.com/aspirant/p/8657681.html
https://www.jianshu.com/p/b8865fdf8445