1、什么是ReentrantLock?
ReentrantLock 是Java 中的一个锁机制的实现,用来保证并发场景下的线程安全问题,类似于synchronized,但是比synchronized性能要好。
2、ReentrantLock的基本使用
使用方式1:lock() \ unlock()方法
/**
* Java ReentrantLock 基本使用 ---》使用1000条线程让一个数加从0加增到1000,每条线程增加1。
*/
public class ReentrantLocakDemo {
public static Integer count = 0;
//构建一把可重入锁
static ReentrantLock lock = new ReentrantLock();
public static void incr(){
lock.lock(); //获取锁
count++;
lock.unlock(); //释放锁
}
public static void main(String[] args) throws InterruptedException {
for (int i =0; i<1000; i++){
new Thread(()->{
incr();
}).start();
}
Thread.sleep(5000L);
System.out.println(count);
}
}
lock() 当前线程会去获取锁,如果获取成功就会执行业务代码,如果获取失败就会进入WAITING状态阻塞,直到获取到锁,如下代码可以验证---->未获取到锁会出现阻塞:
/**
* Java ReentrantLock 操作 ,演示当T1线程获取到ReentrantLock后,T2线程再去获取锁,T2就会进入到waiting状态,等待获取锁。
*/
public class ReentrantLocakDemo1 {
public static Integer count = 0;
//构建一把可重入锁
static ReentrantLock lock = new ReentrantLock();
public static void incr() throws InterruptedException {
lock.lock();
System.out.println(Thread.currentThread().getName()+"获取到锁");
count++;
Thread.sleep(5000L);
lock.unlock();
System.out.println(Thread.currentThread().getName()+"释放锁");
}
public static void main(String[] args) throws InterruptedException {
System.out.println("=============================================");
new Thread(()->{
try {
incr();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"T1").start();
Thread.sleep(1000L);
new Thread(()->{
try {
System.out.println("T2 start..");
incr();
System.out.println("T2 end..");
} catch (InterruptedException e) {
e.printStackTrace();
}
},"T2").start();
Thread.sleep(10000L);
System.out.println(count);
}
}
使用方式2:tryLock() \ tryLock(long timeout, TimeUnit unit) 方法
/**
* Java ReentrantLock 操作
* 演示 lock.tryLock(): 当前线程T1 tryLock() 成功后就获取到了锁。
* 此时T2也tryLock() 返回false,那么T2就会执
* 行到案例中的else,如果没有else 就会直接结束incr方法。
*
* 同理tryLock(long time, TimeUnit unit) 差不多,只是获
* 取不到锁就会进入到TIMED_WAITING状态,持续时间为指定时间。
*/
public class ReentrantLocakDemo2 {
public static Integer count = 0;
//构建一把可重入锁
static ReentrantLock lock = new ReentrantLock();
public static void incr() throws InterruptedException {
if (lock.tryLock(100,TimeUnit.SECONDS)){
System.out.println(Thread.currentThread().getName()+"获取到锁");
count++;
Thread.sleep(50000L);
System.out.println(Thread.currentThread().getName()+"释放锁");
}else {
System.out.println(Thread.currentThread().getName()+"没有获取到锁,没有执行+1 操作就结束了结束incr方法");
}
}
public static void main(String[] args) throws InterruptedException {
System.out.println("=============================================");
new Thread(()->{
try {
incr();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"T1").start();
Thread.sleep(1000L);
new Thread(()->{
try {
System.out.println("T2 start..");
incr();
System.out.println("T2 end..");
} catch (InterruptedException e) {
e.printStackTrace();
}
},"T2").start();
Thread.sleep(10000L);
System.out.println(count);
}
}
tryLock()方法会立马返回是否获取到锁,tryLock(long timeout, TimeUnit unit)方法如果获取到锁立马返回true,如果不能立马获取到锁,阻塞指定时间后再去获取锁,如果此时白眉获取到就会快速返回false。
3、ReentrantLock实现原理
3.1 我们先看看ReentrantLock类的结构
3.2、lock()方法源码分析:
lock.lock()为入口:
lock()方法源码:
public void lock() {
//使用同步者来加锁
sync.lock();
}
sync.lock()方法源码:由于我们使用的无参数构造器创建的ReentrantLock因此默认是非公平锁NonfairSync
NonfairSync.lock() 源码:我们假设有三条线程去获取锁,我们以线程A先去获取锁。
final void lock() {
/*先用CAS操作去修改一个叫state的标记,这个state的标记在其父类
AbstractQueuedSynchronizer(AQS)中。初始值是0 表示无锁,
compareAndSetState(0, 1)这行代码的意思就是:
如果 state=0,那就将其改为1,并返回true。
如果state不等于0,那就直接返回false
*/
if (compareAndSetState(0, 1))
/*如果成功,说明获取到锁,那就将AbstractQueuedSynchronizer(AQS)父类
AbstractOwnableSynchronizer中的exclusiveOwnerThread 的熟悉设置为当前线程,表
示当前线程占领锁。
*/
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
threadA获取到锁后,我们假设threadB去获取锁,那么此时threadA没有释放锁,那么threadB执行 compareAndSetState(0, 1)就会失败,那么就会执行到acquire(1)方法:
acquire(1)方法源码:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
tryAcquire(int acquires)源码:
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
nonfairTryAcquire(int acquires)源码:
final boolean nonfairTryAcquire(int acquires) {
//获取当前线程
final Thread current = Thread.currentThread();
//获取AQS中的state标记
int c = getState();
if (c == 0) {
//如果标记为0那就再次获取锁
if (compareAndSetState(0, acquires)) {
//再次获取锁成功后就设置独占的线程为当前线程
setExclusiveOwnerThread(current);
//返回true
return true;
}
}
//判断是否是重入,如果是那就将设置state = state+1, 并发挥false
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
//如果再次尝试获取锁失败,并且也不是重入,那就返回false。
return false;
}
addWaiter(Node.EXCLUSIVE)源码:
private Node addWaiter(Node mode) {
//使用当前的线程创建一个Node实例
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
//判断AQS的尾节点tail是否为空。
Node pred = tail;
if (pred != null) {
/*如果AQS的尾节点tail不为空,那就将当期的node添加到队列里面,就是将当前节点设置为尾节
点,将上一个尾节点设置为当前节点的前,将上一个尾节点的next设置为当前节点,就是构建一
个双向的列表关系
*/
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
//如果为空,那就初始化由链表实现的queue,并将第一个等待的线程node添加到队列
enq(node);
return node;
}
enq(final Node node)源码:
private Node enq(final Node node) {
//自旋
for (;;) {
Node t = tail;
//如果父类AQS的queue的尾节点为null,那就初始化
if (t == null) { // Must initialize
//初始化的方式就是创建一个空node实例并使用CAS设置到父类AQS的头节点,如果设置头节
点为这个空节点成功,那就将尾节点也设置为刚设置好的头节点。
if (compareAndSetHead(new Node()))
tail = head;
} else {
//当第二次循环的时候,就会进入到此处,此处会将当前的第一个需要等待的有当前线程的节
点,添加到队列,并与之前的空node建立双向链表的关系。
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
//返回当前线程节点。
return t;
}
}
}
}
我们假设的threadB是第一个需要等待的线程,threadB的node入队列后,lock中的情况如下:
addWaiter(Node.EXCLUSIVE)执行结束后,lock的情况如上图,接下来就需要去阻塞threadB了,那即是 acquireQueued(final Node node, int arg)方法:
final boolean acquireQueued(final Node node, int arg) {
//入参的node为当前需要阻塞的线程node , arg=1
boolean failed = true;
try {
boolean interrupted = false;
//自旋
for (;;) {
//获取当前线程node的前一个node,如果获取到的是AQS的head节点,那就在尝试去获取
锁,由于此处是自旋,当当前线程使用parkAndCheckInterrupt阻塞后,占有锁的线程
释放锁后,会唤醒头节点的下一个节点,如果此时头节点的下一个节点的线程后去到锁,
就会将头节点的下一个节点设置为头节点,这样就可以让已经不用的节点移除队列了
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
//当前线程node的前一个node 是AQS的head节点,且当前线程尝试获取锁成功,那就
设置AQS的head节点为当前线程node。
setHead(node);
//将之前的头节点移除队列,此时会被GC回收
p.next = null; // help GC
failed = false;
//获取到锁了,说明没有被打断,那就返回false,不让线程自己打断。
return interrupted;
}
//当前线程node的前一个node不是AQS的head节点,或者当前线程尝试获取锁失败,
那就阻塞当前线程,会将当前线程节点的前一个节点的waitStatus修改为-1,在释放锁
后,唤醒头节点的下一个节点的到时候会将头节点的waitStatus改为0
parkAndCheckInterrupt方法就会阻塞当前线程。
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
修改head节点的waitStatus=-1
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
if (ws > 0) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
阻塞线程:
private final boolean parkAndCheckInterrupt() {
//使用LockSupport阻塞当前线程
LockSupport.park(this);
return Thread.interrupted();
}
阻塞当前线程后lock中情况如下,我们假设的threadB被阻塞了:
threadB被阻塞后,threadC调用lock.lock()方法且也被阻塞了,threadA还没有释放锁,就会变成如下情况:
此时threadA释放锁了,且threadB获取到锁了,唤醒head节点的下一个嘛,那就是threadB,由于是非公平锁,如果 刚好在threadA释放锁后,有一条线程threadD也去获取锁了,且获取到了,这里体现了非公平性
接下来我们来分析lock.unlock()原理:
lock.unlock()源码:
public void unlock() {
//使用sync实例来释放锁
sync.release(1);
}
sync.release()源码:
public final boolean release(int arg) {
//尝试释资源 就是修改AQS中的state标记,以及AbstractOwnableSynchronizer类中的独占线程
if (tryRelease(arg)) {
//获取到head节点
Node h = head;
if (h != null && h.waitStatus != 0)
// h.waitStatus != 0 表示在AQS的queue中存在排队等候的线程node。
//唤醒head节点的下一个节点的线程,让其去争取锁。
unparkSuccessor(h);
return true;
}
return false;
}
tryRelease()源码:
protected final boolean tryRelease(int releases) {
//获取到AQS中的state标记 并减1,releases=1
int c = getState() - releases;
//如果当前释放资源的线程不是获取到锁的线程就抛出异常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
//处理重入次数,当c=0 就表示要释放锁了,那就把独占的线程设置为null
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
//重新设置state 标记,可能是重入次数-1,也可能是真正释放锁。
setState(c);
return free;
}
unparkSuccessor(Node head)源码:
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
//通过上面的跟踪,我们知道此处的node为head节点
int ws = node.waitStatus;
//我们在线程节点入队列的时候会将前一个节点的waitState设置为SINGAL(-1),那么在此处先将头节
点的waitState设置为0
if (ws < 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.
*/
//获取到head节点的下一个接点
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)
//如果不是脏节点,那就唤醒head节点的next节点中的线程。
LockSupport.unpark(s.thread);
}
我们知道等待队列里的节点的线程都阻塞在acquireQueued(final Node node, int arg)方法的parkAndCheckInterrupt处