在学习Java语言的过程中,多线程是一个算是进阶的选择。我最近又学到了一个新技能ReentrantLock
类,这个应该目前最简单的线程安全使用方式了,当然暴力synchronized
除外。
首先回顾一下之前的线程安全常用同步类的文章:
- CountDownLatch类在性能测试中应用
- CyclicBarrier类在性能测试中应用
- Phaser类在性能测试中应用
- Java线程同步三剑客
下面介绍一下ReentrantLock
类,看名字就是一个可重入锁,这个概念跟synchronized
中有点类似。我理解为这是一个简单易上手的线程安全操作类,不需要理解synchronized
带来的语法,不需要其他线程同步类锁需要的对各类API功能记忆。ReentrantLock
真的是一个非常好用的多线程安全工具类。
ReentrantLock
核心(个人看法)有两个方法lock()
和unlock()
,顾名思义,一个是加锁一个是释放锁,所有线程安全的操作可以写在这两个方法之间。这一点跟之前的文章如何mock固定QPS的接口、moco固定QPS接口升级补偿机制中用到的线程安全之流量控制类java.util.concurrent.Semaphore
的使用基本一致,相比之下java.util.concurrent.locks.ReentrantLock
相当于new java.util.concurrent.Semaphore((1, true)
。java.util.concurrent
这个包简直就是一个宝藏,欢迎有兴趣学习的童鞋翻一翻源码,其乐无穷。
下面演示一下ReentrantLock
的基本使用,中间用到了- Java自定义异步功能实践、利用守护线程隐式关闭线程池中用到的异步关键字,有兴趣的可以翻一翻,就是关键字fun
后面的代码块会有单独线程执行,这里用Java
语言进行演示。
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
fun(() -> {
lock.lock();
output("FunTester获取锁了");
sleep(2.0);
lock.unlock();
output("FunTester释放锁了");
return null;
});
sleep(1.0);
lock.lock();
output("main线程获取锁");
lock.unlock();
output("main线程释放锁");
}
控制台输出如下:
代码语言:javascript复制INFO-> main 当前用户:oker,工作目录:/Users/oker/IdeaProjects/funtester/,系统编码格式:UTF-8,系统Mac OS X版本:10.16
INFO-> main 守护线程:FT-D开启!
INFO-> FT-1 FunTester获取锁了
INFO-> FT-1 FunTester释放锁了
INFO-> main main线程获取锁
INFO-> main main线程释放锁
WARN-> FT-D 异步线程池关闭!
Process finished with exit code 0
我觉得这两个API使用基本满足了常见的多线程编程需求了,唯一需要注意的就是unlock()
方法可能因为程序异常不会被执行,所以一般都放在finally
代码块中。
下面介绍几个非常用的API,有助于解决非常见的需求:
获取锁,但是有超时时间,会返回是否加锁成功。
代码语言:javascript复制 public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
获取等待队列长度,这个不准,因为队列可能会发生变化:
代码语言:javascript复制 public final int getQueueLength() {
return sync.getQueueLength();
}
判断是否上锁,这个和上面tryLocal
功能上有点重复:
public boolean isLocked() {
return sync.isLocked();
}
还有一个标准较不常用的,获取当前线程持有锁的次数,这个牵扯到ReentrantLock
重入锁机制,就是一个线程可以持有很多次,但是只要次数不为零,其他线程都无法加锁成功,平时使用不多,没有谁单线程持有N多次锁:
public int getHoldCount() {
return sync.getHoldCount();
}