一、简介
在上一篇文章中,我们介绍了ReentrantLock
类的一些基本用法,今天我们重点来介绍一下ReentrantLock
其它的常用方法,以便对ReentrantLock
类的使用有更深入的理解。
二、常用方法介绍
2.1、构造方法
ReentrantLock
类有两个构造方法,核心源码内容如下:
/**
* 默认创建非公平锁
*/
public ReentrantLock() {
sync = new NonfairSync();
}
/**
* fair为true表示是公平锁,fair为false表示是非公平锁
*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
相比于synchronized
同步锁,ReentrantLock
有一个很大的特点,就是开发人员可以手动指定采用公平锁机制还是非公平锁机制。
公平锁:顾名思义,就是每个线程获取锁的顺序是按照线程排队的顺序来分配的,最前面的线程总是最先获取到锁。
- 优点:所有的线程都有机会得到资源
- 缺点:公平锁机制实现比较复杂,程序流程比较多,执行速度比较慢
非公平锁:每个线程获取锁的顺序是随机的,并不会遵循先来先得的规则,任何线程在某时刻都有可能直接获取并拥有锁,之前介绍的synchronized
其实就是一种非公平锁
- 优点:公平锁机制实现相对比较简单,程序流程比较少,执行速度比较快
- 缺点:有可能某些线程一直拿不到锁,导致饿死
ReentrantLock
默认的构造方法是非公平锁,如果想要构造公平锁机制,只需要传入true
就可以了。
示例代码如下:
public static void main(String[] args) {
// 创建公平锁实现机制
Lock lock = new ReentrantLock(true);
// 创建5个线程
for (int i = 0; i < 5; i++) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("ThreadName:" + Thread.currentThread().getName() + ", 启动了!");
// 尝试获取锁
lock.lock();
try {
System.out.println("ThreadName:" + Thread.currentThread().getName() + ", 获得锁!");
} finally {
lock.unlock();
}
}
}).start();
}
}
运行一下程序,结果如下:
ThreadName:Thread-0, 启动了!
ThreadName:Thread-1, 启动了!
ThreadName:Thread-0, 获得锁!
ThreadName:Thread-1, 获得锁!
ThreadName:Thread-2, 启动了!
ThreadName:Thread-2, 获得锁!
ThreadName:Thread-3, 启动了!
ThreadName:Thread-3, 获得锁!
ThreadName:Thread-4, 启动了!
ThreadName:Thread-4, 获得锁!
从日志上可以看到,启动顺序为0,1,2,3,4
,获取锁的顺序为0,1,2,3,4
,启动与获取锁的排队机制一致。
假如我们构造方法里面的把true
改成false
,也就是非公平锁机制,在看看运行效果,结果如下:
ThreadName:Thread-1, 启动了!
ThreadName:Thread-2, 启动了!
ThreadName:Thread-1, 获得锁!
ThreadName:Thread-0, 启动了!
ThreadName:Thread-2, 获得锁!
ThreadName:Thread-3, 启动了!
ThreadName:Thread-3, 获得锁!
ThreadName:Thread-0, 获得锁!
ThreadName:Thread-4, 启动了!
ThreadName:Thread-4, 获得锁!
从日志上可以看到,启动顺序为1,2,0,3,4
,获取锁的顺序为1,2,3,0,4
,线程启用与获取锁的顺序不一致。
从实际的运行结果看,非公平锁要比公平锁执行速度要快一些,当线程数越多的时候,效果越明显。
2.2、核心方法
ReentrantLock
类的核心方法就比较多了,如下表!
方法 | 描述 |
---|---|
public void lock() | 阻塞等待获取锁;不允许Thread.interrupt 中断,即使检测到Thread.isInterrupted 一样会继续尝试 |
public void lockInterruptibly() | 当前线程未被中断,则获取锁;允许在等待时由其它线程调用等待线程的Thread.interrupt 方法来中断等待线程的等待而直接返回 |
public boolean tryLock() | 尝试申请一个锁,在成功获得锁后返回true ,否则,立即返回false |
public boolean tryLock(long timeout, TimeUnit unit) | 在一段时间内尝试申请一个锁,在成功获得锁后返回true ,否则,立即返回false |
public void unlock() | 释放锁 |
public Condition newCondition() | 条件实例,用于线程等待/通知模式 |
public int getHoldCount() | 获取当前线程持有此锁的次数 |
public boolean isHeldByCurrentThread() | 检测是否被当前线程持有 |
public boolean isLocked() | 查询此锁是否由任意线程持有 |
public final boolean isFair() | 如果是公平锁返回true ,否则返回false |
public final boolean hasQueuedThreads() | 查询是否有线程正在等待 |
public final boolean hasQueuedThread(Thread thread) | 查询给定线程是否正在等待获取此锁 |
public final int getQueueLength() | 获取正等待获取此锁的线程数 |
public boolean hasWaiters(Condition condition) | 是否存在正在等待并符合相关给定条件的线程 |
public int getWaitQueueLength(Condition condition) | 正在等待并符合相关给定条件的线程数量 |
虽然方法很多,但是实际上常用方法就那么几个,下面我们主要抽一些常用的方法进行介绍。
2.2.1、tryLock 方法
lock()
、lockInterruptibly()
、tryLock()
和tryLock(long timeout, TimeUnit unit)
这几个方法,目的其实是一样的,都是为了获取锁,只是针对不同的场景做了单独的处理。
lock()
:阻塞等待获取锁,如果没有获取到会一直阻塞,即使检测到Thread.isInterrupted
一样会继续尝试;
lockInterruptibly()
:同样也是阻塞等待获取锁,稍有不同的是,允许在等待时由其它线程调用等待线程的Thread.interrupt
方法来中断等待线程的等待而直接返回tryLock()
:表示尝试申请一个锁,在成功获得锁后返回true
,否则,立即返回false
,不会阻塞等待获取锁tryLock(long timeout, TimeUnit unit)
:表示在一段时间内尝试申请一个锁,在成功获得锁后返回true
,否则,立即返回false
其中tryLock(long timeout, TimeUnit unit)
方法的应用最广泛,因为它能防止程序发生死锁,即使在一段时间内没有获取锁,也会自动退出,不会一直阻塞。
我们可以看一个简单的例子,如下!
public static void main(String[] args) {
// 创建公平锁实现机制
Lock lock = new ReentrantLock();
// 创建5个线程
for (int i = 0; i < 5; i++) {
new Thread(new Runnable() {
@Override
public void run() {
boolean flag = false;
try {
// 尝试3秒内获取锁
flag = lock.tryLock(