持续创造,加快成长!这是我参加「日新方案 10 月更文应战」的第13天,点击检查活动详情
一、ReentrantLock是什么
ReentrantLock是一种根据AQS结构的运用完结,是JDK中的一种线程并发访问的同步手法,它的功能相似于synchronized是一种互斥锁,能够确保线程安全。
相对于 synchronized, ReentrantLock具备如下特色:
- 可中止
- 能够设置超时时刻
- 能够设置为公正锁
- 支撑多个条件变量
- 与 synchronized 一样,都支撑可重入
进入源码能够看到,其完结了公正锁和非公正锁
内部完结了加锁的操作,并且支撑重入锁。不必我们再重写
解锁操作
1-1、ReentrantLock和synchronized差异
synchronized和ReentrantLock的差异:
- synchronized是JVM层次的锁完结,ReentrantLock是JDK层次的锁完结;
- synchronized的锁状况是无法在代码中直接判断的,可是ReentrantLock能够通过ReentrantLock#isLocked判断;
- synchronized是非公正锁,ReentrantLock是能够是公正也能够是非公正的;
- synchronized是不能够被中止的,而ReentrantLock#lockInterruptibly办法是能够被中止的;
- 在发生反常时synchronized会主动开释锁,而ReentrantLock需求开发者在finally块中显示开释锁;
- ReentrantLock获取锁的形式有多种:如立即回来是否成功的tryLock(),以及等候指定时长的获取,愈加灵活;
- synchronized在特定的情况下对于已经在等候的线程是后来的线程先取得锁(回顾一下sychronized的唤醒策略),而ReentrantLock对于已经在等候的线程是先来的线程先取得锁;
1-2、ReentrantLock的运用
1-2-1、ReentrantLock同步履行,相似synchronized
运用ReentrantLock需求注意的是:必定要在finally中进行解锁,办法事务抛出反常,无法解锁
public class ReentrantLockDemo {
private static int sum = 0;
private static ReentrantLock lock=new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 3; i++) {
Thread thread = new Thread(()->{
//加锁
lock.lock();
try {
// 临界区代码
// TODO 事务逻辑:读写操作不能确保线程安全
for (int j = 0; j < 10000; j++) {
sum++;
}
} finally {
// 解锁--必定要在finally中解锁,避免事务代码反常,无法开释锁
lock.unlock();
}
});
thread.start();
}
Thread.sleep(2000);
System.out.println(sum);
}
}
测验成果:
1-2-2、可重入锁
可重入锁便是 A(加锁)–>调用—>B(加锁)–>调用–>C(加锁),从A到C即便B/C都有加锁,也能够进入
@Slf4j
public class ReentrantLockDemo2 {
public static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
method1();
}
public static void method1() {
lock.lock();
try {
log.debug("execute method1");
method2();
} finally {
lock.unlock();
}
}
public static void method2() {
lock.lock();
try {
log.debug("execute method2");
method3();
} finally {
lock.unlock();
}
}
public static void method3() {
lock.lock();
try {
log.debug("execute method3");
} finally {
lock.unlock();
}
}
}
履行成果:
1-2-3、锁中止
能够运用lockInterruptibly
来进行锁中止
lockInterruptibly()办法能够中止等候获取锁的线程。当两个线程一起通过lock.lockInterruptibly()
获取某个锁时,假若此时线程A获取到了锁,而线程B只要等候,那么对线程B调用threadB.interrupt()办法能够中止线程B的等候进程。
public class ReentrantLockDemo3 {
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
log.debug("t1发动...");
try {
lock.lockInterruptibly();
try {
log.debug("t1取得了锁");
} finally {
lock.unlock();
}
} catch (InterruptedException e) {
e.printStackTrace();
log.debug("t1等锁的进程中被中止");
}
}, "t1");
lock.lock();
try {
log.debug("main线程取得了锁");
t1.start();
//先让线程t1履行
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t1.interrupt();
log.debug("线程t1履行中止");
} finally {
lock.unlock();
}
}
}
履行成果:
1-2-4、取得锁超时失败
能够让线程等候指定的时刻,假如还未获取锁则进行失败处理。
如下代码,首要让主线程取得锁,然后让子线程发动测验获取锁,可是由于主线程获取锁之后,让线程等候了2秒,而子线程取得锁的超时时刻只要1秒,假如未取得锁,则进行return失败处理
public class ReentrantLockDemo4 {
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
log.debug("t1发动...");
//超时
try {
if (!lock.tryLock(1, TimeUnit.SECONDS)) {
log.debug("等候 1s 后获取锁失败,回来");
return;
}
} catch (InterruptedException e) {
e.printStackTrace();
return;
}
try {
log.debug("t1取得了锁");
} finally {
lock.unlock();
}
}, "t1");
lock.lock();
try {
log.debug("main线程取得了锁");
t1.start();
//先让线程t1履行
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} finally {
lock.unlock();
}
}
}
履行成果:
1-2-5、公正锁
ReentrantLock 默认是不公正的
首要发动500次for循环创立500个线程,然后进行加锁操作,并一起发动了。这样这500个线程就顺次排队等候加锁的处理
下面500个线程也是等候加锁操作
假如运用公正锁,下面500的线程只要等上面500个线程运转完结之后才干取得锁。
@Slf4j
public class ReentrantLockDemo5 {
public static void main(String[] args) throws InterruptedException {
ReentrantLock lock = new ReentrantLock(true); //公正锁
for (int i = 0; i < 500; i++) {
new Thread(() -> {
lock.lock();
try {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug(Thread.currentThread().getName() + " running...");
} finally {
lock.unlock();
}
}, "t" + i).start();
}
// 1s 之后去争抢锁
Thread.sleep(1000);
for (int i = 0; i < 500; i++) {
new Thread(() -> {
lock.lock();
try {
log.debug(Thread.currentThread().getName() + " running...");
} finally {
lock.unlock();
}
}, "强行刺进" + i).start();
}
}
}
测验成果(后进入的线程都在等候排队)
运用非公正锁的情况下,就能够看到下面500线程有些线程就能够抢占锁了
那ReentrantLock为什么默认运用非公正锁呢?实际上便是为了提高功能,假如运用公正锁,当时锁对象开释之后,还需求去队列中获取第一个排队的线程,然后进行加锁处理。而非公正锁,可能再当时对象开释锁之后,正好有新的线程在获取锁,这样就能够直接进行加锁操作,不必再去队列中读取。