【并发编程】简单易懂的了解ReentrantLock锁的各种机制
ReentrantLock是什么?
- ReentrantLock是一种基于AQS框架的应用实现。
- 是JDK中的一种线程并发访问的同步手段,它的功能类似于synchronized是一种互斥锁,可以保证线程安全。
ReentrantLock与synchronized的区别
- synchronized是JVM层次的锁实现,ReentrantLock是JDK层次的锁实现;
- synchronized的锁状态是无法在代码中直接判断的,但是ReentrantLock可以通过ReentrantLock#isLocked判断;
- synchronized是非公平锁,ReentrantLock是可以是公平也可以是非公平的;
- synchronized是不可以被中断的,而ReentrantLock#lockInterruptibly方法是可以被中断的;
- 在发生异常时synchronized会自动释放锁,而ReentrantLock需要开发者在finally块中显示释放锁;
- ReentrantLock获取锁的形式有多种:如立即返回是否成功的tryLock(),以及等待指定时长的获取,更加灵活;
- synchronized在特定的情况下对于已经在等待的线程是后来的线程先获得锁,而ReentrantLock对于已经在等待的线程是先来的线程先获得锁;
ReentrantLock的使用方式
- 使用的方式
//参数默认false,不公平锁
ReentrantLock lock = new ReentrantLock();
//公平锁
ReentrantLock lock = new ReentrantLock(true);
// 加锁
lock.lock();
try {
//临界区
} finally {
// 解锁
lock.unlock();
}
- 使用实例
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Tset1 {
// 定义数据
private static int sum = 0;
// 定义自己的锁
private static Lock lock = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
// 循环3吃,开启三个线程
for (int i = 0; i < 3; i++) {
Thread thread = new Thread(() -> {
// 加锁
lock.lock();
try {
// 不加锁这里执行完小于30000
for (int j = 0; j < 10000; j++) {
sum++;
}
} finally {
// 解锁
lock.unlock();
}
});
thread.start();
}
// 等待线程执行完
Thread.sleep(2000);
// 打印最后的结果:30000
System.out.println(sum);
}
}
ReentrantLock的可重入机制
- 减少加锁解锁的过程,然后去提高性能
import java.util.concurrent.locks.ReentrantLock;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class Tset2 {
public static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
// 第一个线程中调用第二个线程、第二个线程中调用第三个线程
method1();
}
public static void method1() {
lock.lock();
try {
log.debug("执行 method1");
method2();
} finally {
lock.unlock();
}
}
public static void method2() {
lock.lock();
try {
log.debug("执行 method2");
method3();
} finally {
lock.unlock();
}
}
public static void method3() {
lock.lock();
try {
log.debug("执行 method3");
} finally {
lock.unlock();
}
}
}
- 执行结果
ReentrantLock的可中断机制(立即中断)
- 应用场景:一个类只需要一个线程去初始化,其他线程初始化完成后当前线程不需要继续处理。
import java.util.concurrent.locks.ReentrantLock;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class Tset3 {
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
log.debug("t1启动...");
// 注意: 即使是设置的公平锁,此方法也会立即返回获取锁成功或失败,公平策略不生效
if (!lock.tryLock()) {
log.debug("t1获取锁失败,立即返回false");
return;
}
try {
log.debug("t1获得了锁");
} finally {
lock.unlock();
}
}, "t1");
lock.lock();
try {
log.debug("main线程获得了锁");
t1.start();
// 先让线程t1执行
try {
// 模拟业务
while (true) {
}
} catch (Exception e) {
e.printStackTrace();
}
} finally {
lock.unlock();
}
}
}
- 执行结果
ReentrantLock的锁超时超时失败机制
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class Tset4 {
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 {
// 模拟业务
while (true) {
}
} catch (Exception e) {
e.printStackTrace();
}
} finally {
lock.unlock();
}
}
}
- 执行结果
ReentrantLock的公平锁方式机制
- new ReentrantLock(true);的时候穿一个true就是公平锁,不传或者传一个false就是非公平锁!
import java.util.concurrent.locks.ReentrantLock;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class Tset5 {
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();
}
}
}
-
公平锁运行结果:按照进入的顺序执行,正常顺序日志,无突出的重点,不截图了。
-
非公平锁运行(定义ReentrantLock的时候不传值)结果:出现插队的线程!
ReentrantLock的阻塞唤醒机制
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class Tset6 {
private static ReentrantLock lock = new ReentrantLock();
private static Condition cigCon = lock.newCondition();
private static boolean hashcig = false;
public static void main(String[] args) {
// 定义线程需要被唤醒的线程
new Thread(() -> {
lock.lock();
log.debug("需要被唤醒的线程加锁");
try {
while (!hashcig) {
try {
log.debug("需要被唤醒的线程等待!");
cigCon.await();
} catch (Exception e) {
e.printStackTrace();
}
}
log.debug("需要被唤醒的线程跳出循环!");
} finally {
lock.unlock();
}
}).start();
// 准备要唤醒的线程
new Thread(() -> {
lock.lock();
try {
// 设置条件变了
hashcig = true;
// 唤醒等待线程
cigCon.signal();
} finally {
lock.unlock();
}
}, "t1").start();
}
}
- 运行结果
结束语
- 获取更多有价值的文章,让我们一起成为架构师!
- 关注公众号,可以让你对MySQL有非常深入的了解
- 关注公众号,每天持续高效的了解并发编程!
- 关注公众号,后续持续高效的了解spring源码!
- 这个公众号,无广告!!!每日更新!!!