目录
4.3.2 原子包 java.util.concurrent.atomic(锁自旋)
一、介绍
ReentrantLock是从jdk1.5定义的可重入的独享锁,其拥有比较灵活的使用方式,提供了公平锁和非公平锁功能,内部是基于乐观锁思想实现的争抢策略;主要依托AQS提供的模板方法,实现其具有自身特色的锁功能;深入学习ReentrantLock内部实现,有助于提升编码能力,提升编程思想,可谓说是益处多多。
二、对比synchronized
功能描述 | ReentrantLock | Synchronized |
可重入锁 | 支持 | 支持 |
公平锁 | 支持 | 不支持 |
非公平锁 | 支持 | 支持 |
获取、释放锁顺序 | 无序 | 有序 |
获取锁方式 | 获取锁 尝试获取非阻塞 获取可被中断的锁 尝试可超时的锁的 |
获取锁 |
同一个线程,获取、释放锁顺序不同:
- synchronized获取、释放锁是有顺序的,获取节点A的锁,然后获取节点B的锁,先释放节点B的锁,再释放节点A的锁。
- ReentrantLock获取、释放锁是无序的,获取节点A的锁,然后获取节点B的锁,可不分先后分别释放节点A、B的锁。
获取锁方式:
ReentrantLock相比于synchronized增加了获取锁的方式,优化线程资源占用,通俗讲,在实际引用场景中,被锁的程序段需要一定的执行时间,而每个线程都需要排队执行此段代码,造成线程占用。举个极端例子,“A段代码”的执行时间为1s,在1s内有500次请求,即开启了500个线程,比如我们的容器Tomcat最大只开启了500个线程,那么“其他方法”将没有线程可执行,需要等待“A段代码”的线程释放,此场景我们需要平衡“A段代码”的线程使用,因此增加了其他获取锁的方式:
lockInterruptibly():获取锁,除非当前线程被中断。
tryLock():当此段代码未被锁时,才会获取锁。不会等待获取锁,不会有队列保持公平,如果一定要划分公平与非公平,那么属于非公平锁。
tryLock(long time, TimeUnit unit):如果锁在给定的等待时间内空闲且当前线程未中断,则获取锁。
三、示例代码
ReentrantLock应用场景
1 同步执行
和synchronized差不多,如果已经有线程占用,则进行排队等待
适用于资源的争抢,比如文件操作,同步消息的发送
@Slf4j
public class Reentrant01 {
//公平锁 默认非公平锁
private ReentrantLock lock = new ReentrantLock(true);
public void run() {
try {
// 阻塞获取锁
lock.lock();
// 模拟操作
log.info(Thread.currentThread().getName() + " get lock");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
Reentrant02 lock = new Reentrant02();
for (int i = 0; i < 10; i++) {
new Thread(() -> {
lock.run();
}, "t" + i).start();
}
}
}
当前线程释放后 其他线程才能获取到锁
2 尝试执行
如果已经被lock,则立即返回false不会等待,达到忽略操作的效果
@Slf4j
public class Reentrant02 {
private ReentrantLock lock = new ReentrantLock();
public void run() {
if (lock.tryLock()) {
try {
// 模拟操作
log.info(Thread.currentThread().getName() + " get lock");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
} finally {
lock.unlock();
}
} else {
log.info(Thread.currentThread().getName() + " abort");
}
}
public static void main(String[] args) {
Reentrant02 reentrant02 = new Reentrant02();
for (int i = 0; i < 10; i++) {
new Thread(() -> {
reentrant02.run();
}, "t" + i).start();
}
}
}
执行结果:
17:22:21.134 [t2] INFO test.Reentrant02 - t2 abort
17:22:21.134 [t4] INFO test.Reentrant02 - t4 abort
17:22:21.134 [t7] INFO test.Reentrant02 - t7 abort
17:22:21.134 [t9] INFO test.Reentrant02 - t9 abort
17:22:21.134 [t1] INFO test.Reentrant02 - t1 abort
17:22:21.134 [t8] INFO test.Reentrant02 - t8 abort
17:22:21.134 [t3] INFO test.Reentrant02 - t3 abort
17:22:21.134 [t6] INFO test.Reentrant02 - t6 abort
17:22:21.134 [t0] INFO test.Reentrant02 - t0 get lock
17:22:21.134 [t5] INFO test.Reentrant02 - t5 abort
具体场景:
• 用在定时任务时,如果任务执行时间可能超过下次计划执行时间,确保该有状态任务只有一个正在执行,忽略重复触发。
• 用在界面交互时点击执行较长时间请求操作时,防止多次点击导致后台重复执行(忽略重复触发)。
3 尝试等待执行
获取锁如果被占用等待一段时间
@Slf4j
public class Reentrant03 {
private ReentrantLock lock = new ReentrantLock();
public void run() {
try {
if (lock.tryLock(3, TimeUnit.SECONDS)) {
// 模拟操作
try {
log.info(Thread.currentThread().getName() + " do work");
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
} else {
log.info(Thread.currentThread().getName() + " abort");
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Reentrant03 lock = new Reentrant03();
for (int i = 0; i < 10; i++) {
new Thread(() -> {
lock.run();
}, "t" + i).start();
}
}
}
执行结果:
17:30:07.992 [t1] INFO test.Reentrant03 - t1 do work
17:30:10.988 [t5] INFO test.Reentrant03 - t5 abort
17:30:10.988 [t0] INFO test.Reentrant03 - t0 abort
17:30:10.990 [t6] INFO test.Reentrant03 - t6 abort
17:30:10.992 [t2] INFO test.Reentrant03 - t2 abort
17:30:10.992 [t3] INFO test.Reentrant03 - t3 abort
17:30:10.992 [t4] INFO test.Reentrant03 - t4 abort
17:30:10.992 [t7] INFO test.Reentrant03 - t7 abort
17:30:10.992 [t9] INFO test.Reentrant03 - t9 abort
17:30:10.992 [t8] INFO test.Reentrant03 - t8 abort
4 可中断执行
当正在进行的操作发生中断时,释放锁,不影响其他线程锁的获取
@Slf4j
public class Reentrant04 {
private ReentrantLock lock = new ReentrantLock();
public void run() {
try {
lock.lockInterruptibly();
// 模拟操作
try {
// t3线程报中断
if ("t3".equals(Thread.currentThread().getName())) {
throw new InterruptedException();
}
log.info(Thread.currentThread().getName() + " do work");
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
Reentrant04 reentrant01 = new Reentrant04();
for (int i = 0; i < 10; i++) {
new Thread(() -> {
reentrant01.run();
}, "t" + i).start();
}
}
}
运行结果: