ReentrantLock
ReentrantLock 是Lock的默认实现,了解之前需要先了解一些概念
1.可重入锁:可重入锁是指同一个线程可以多次获得同一把锁;ReentrantLock和Synchronized都是可重入锁
2.可终中断锁:可中断锁是子线程在获取锁的过程中,是否可以响应线程中断操作,synchronized是不可中断的,ReentrantLock是可终端的
3.公平锁和非公平锁:公平锁是指多个线程尝试获取同一把锁的顺序按照线程到达的先后顺序获取,synchronized非公平,而Renentrant默认非公平,但两种方式都可以实现
基本实现
public class Demo3 {
private static int num = 0;
private static ReentrantLock lock = new ReentrantLock();
private static void add() {
lock.lock();
try {
num++;
} finally {
lock.unlock();
}
}
public static class T extends Thread {
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
Demo3.add();
}
}
}
public static void main(String[] args) throws InterruptedException {
T t1 = new T();
T t2 = new T();
T t3 = new T();
t1.start();
t2.start();
t3.start();
t1.join();
t2.join();
t3.join();
System.out.println(Demo3.num);
}
}
ReentrantLock 的使用过程:
创建锁:ReentrantLock lock = new ReentrantLock();
获取锁:lock.lock()
释放锁:lock.unlock();
注意lock.unlock()一定要放在 finally 中,否则,若程序出现了异常,锁没有释放,那么其他线程就再也没有机会获取这个锁了。
ReentrantLock实现公平锁
synchronized 关键字默认是有 jvm 内部实现控制的,是非公平锁。而 ReentrantLock 运行开发者自己设置锁的公平性。
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
第 2 个构造方法,有个 fair 参数,当 fair 为 true 的时候创建的是公平锁,公平锁看起来很不错,不过要实现公平锁,系统内部肯定需要维护一个有序队列,因此公平锁的实现成本比较高,性能相对于非公平锁来说相对低一些。因此,在默认情况下,锁是非公平的,如果没有特别要求,则不建议使用公平锁。
ReentrantLock获取锁的过程是可中断的
对于 synchronized 关键字,如果一个线程在等待获取锁,最终只有 2 种结果:
要么获取到锁然后继续后面的操作
要么一直等待,直到其他线程释放锁为止
而 ReentrantLock 提供了另外一种可能,就是在等待获取锁的过程中(发起获取锁请求到还未获取到锁这段时间内)是可以被中断的,也就是说在等待锁的过程中,程序可以根据需要取消获取锁的请求。有些使用这个操作是非常有必要的。比如:你和好朋友越好一起去打球,如果你等了半小时朋友还没到,突然你接到一个电话,朋友由于突发状况,不能来了,那么你一定打道回府。中断操作正是提供了一套类似的机制,如果一个线程正在等待获取锁,那么它依然可以收到一个通知,被告知无需等待,可以停止工作了。
关于获取锁的过程中被中断,注意几点:
ReentrankLock 中必须使用实例方法lockInterruptibly()获取锁时,在线程调用 interrupt()方法之后,才会引发InterruptedException异常
线程调用 interrupt()之后,线程的中断标志会被置为 true
触发 InterruptedException 异常之后,线程的中断标志会被清空,即置为 false
所以当线程调用 interrupt()引发 InterruptedException 异常,中断标志的变化是:false->true->false
ReentrantLock锁申请等待限时
申请锁等待限时是什么意思?一般情况下,获取锁的时间我们是不知道的,synchronized 关键字获取锁的过程中,只能等待其他线程把锁释放之后才能够有机会获取到锁。所以获取锁的时间有长有短。如果获取锁的时间能够设置超时时间,那就非常好了。
ReentrantLock 刚好提供了这样功能,给我们提供了获取锁限时等待的方法tryLock(),可以选择传入时间参数,表示等待指定的时间,无参则表示立即返回锁申请的结果:true 表示获取锁成功,false 表示获取锁失败。
tryLock无参方法
public boolean tryLock()
返回 boolean 类型的值,此方法会立即返回,结果表示获取锁是否成功
tryLock有参方法
public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException
该方法在指定的时间内不管是否可以获取锁,都会返回结果,返回 true,表示获取锁成功,返回 false 表示获取失败。此方法有 2 个参数,第一个参数是时间类型,是一个枚举,可以表示时、分、秒、毫秒等待,使用比较方便,第 1 个参数表示在时间类型上的时间长短。此方法在执行的过程中,**如果调用了线程的中断 interrupt()方法,会触发 InterruptedException 异常。
**
关于 tryLock()方法和 tryLock(long timeout, TimeUnit unit)方法,说明一下:
都会返回 boolean 值,结果表示获取锁是否成功
tryLock()方法,不管是否获取成功,都会立即返回;而有参的 tryLock 方法会尝试在指定的时间内去获取锁,中间会阻塞的现象,在指定的时间之后会不管是否能够获取锁都会返回结果
tryLock()方法不会响应线程的中断方法;而有参的 tryLock 方法会响应线程的中断方法,而触发InterruptedException异常,这个从 2 个方法的声明上可以可以看出来
ReetrantLock其他常用方法
isHeldByCurrentThread:实例方法,判断当前线程是否持有 ReentrantLock 的锁,上面代码中有使用过。
总结
entrantLock 可以实现公平锁和非公平锁
ReentrantLock 默认实现的是非公平锁
ReentrantLock 的获取锁和释放锁必须成对出现,锁了几次,也要释放几次
释放锁的操作必须放在 finally 中执行
lockInterruptibly()实例方法可以响应线程的中断方法,调用线程的 interrupt()方法时,lockInterruptibly()方法会触发InterruptedException异常
关于InterruptedException异常说一下,看到方法声明上带有 throws InterruptedException,表示该方法可以响应线程中断,调用线程的 interrupt()方法时,这些方法会触发InterruptedException异常,触发 InterruptedException 时,线程的中断中断状态会被清除。所以如果程序由于调用interrupt()方法而触发InterruptedException异常,线程的标志由默认的 false 变为 ture,然后又变为 false
实例方法 tryLock()会尝试获取锁,会立即返回,返回值表示是否获取成功
实例方法 tryLock(long timeout, TimeUnit unit)会在指定的时间内尝试获取锁,指定的时间内是否能够获取锁,都会返回,返回值表示是否获取锁成功,该方法会响应线程的中断