ReentrantLock
类实现了 Lock
,它拥有与 synchronized
相同的并发性和内存语义,但是添加了类似锁投票、定时锁等候和可中断锁等候的一些特性。
此外,它还提供了在激烈争用情况下更佳的性能。
ReentrantLock与synchronized的不同点
(1)ReentrantLock功能性方面更全面,比如时间锁等候,可中断锁等候,锁投票等,因此更有扩展性。在多个条件变量和高度竞争锁的地方,
用ReentrantLock更合适,ReentrantLock还提供了Condition,对线程的等待和唤醒等操作更加灵活,一个ReentrantLock可以有多个Condition
实例,所以更有扩展性。
(2)ReentrantLock 的性能比synchronized会好点。
(3)ReentrantLock提供了可轮询的锁请求,他可以尝试的去取得锁,如果取得成功则继续处理,取得不成功,可以等下次运行的时候处理,所以不
容易产生死锁,而synchronized则一旦进入锁请求要么成功,要么一直阻塞,所以更容易产生死锁。
二。作用
1.实现可轮询的锁请求
在内部锁中,死锁是致命的,唯一的恢复方法是重新启动程序,唯一的预防方法是在构建程序时不要出错。而可轮询的锁获取模式具有更完善的错误
恢复机制,可以规避死锁的发生。
所有需要的锁,那么使用可轮询的获取方式使你能够重新拿到控制权,它会释放你已经获得的这些锁,然后再重新尝试。可轮询的锁
获取模式,由tryLock()方法实现。此方法仅在调用时锁为空闲状态才获取该锁。如果锁可用,则获取锁,并立即返回值true。如果锁不可用,则此方法将
立即返回值false。此方法的典型使用语句如下
Lock lock = ...;
if (lock.tryLock()) {
try {
// manipulate protected state
} finally {
lock.unlock();
}
} else {
// perform alternative actions
}
2. 实现可定时的锁请求
synchronized进入锁请求要么成功,要么一直阻塞,所以更容易产生死锁。tryLock(long, TimeUnit)如果在期待的时间内没能获得锁,程序返回false。
1 package concurrent;
2
3 import java.util.concurrent.TimeUnit;
4 import java.util.concurrent.locks.ReentrantLock;
5
6
7 public class AttemptLocking {
8
9 private ReentrantLock lock = new ReentrantLock();
10
11 public void untimed(){
12 boolean captured = lock.tryLock();
13 try{
14 System.out.println("tryLock(): " + captured);
15 }finally{
16 if(captured){
17 lock.unlock();
18 }
19 }
20 }
21
22
23 public void timed(){
24 boolean captured = false;
25
26 try {
27 captured = lock.tryLock(2, TimeUnit.SECONDS);
28 } catch (InterruptedException e) {
29 e.printStackTrace();
30 }
31
32 try{
33 System.out.println("tryLock(2, TimeUnit.SECONDS): " + captured);
34 }finally{
35 if(captured){
36 lock.unlock();
37 }
38 }
39 }
40
41 public static void main(String[] args) throws InterruptedException {
42 final AttemptLocking attemp = new AttemptLocking();
43 attemp.untimed();
44 attemp.timed();
45
46 new Thread(){
47 {setDaemon(true);}
48 public void run(){
49 attemp.lock.lock(); //该线程一直没释放锁
50 System.out.println("lock");
51 }
52 }.start();
53 Thread.sleep(3600);
54 Thread.yield();
55
56 attemp.untimed();
57 attemp.timed();
58
59 }
60
61 }
结果:
tryLock(): true
tryLock(2, TimeUnit.SECONDS): true
lock
tryLock(): false
tryLock(2, TimeUnit.SECONDS): false
3.实现可中断的锁获取请求
可中断的锁获取操作允许在可取消的活动中使用。lockInterruptibly()方法能够使你获得锁的时候响应中断。
public class TestLockInterruptibly {
public static void testLock() throws Exception {
final Lock lock = new ReentrantLock();
//锁已被获取
lock.lock();
Thread t1 = new Thread(()->{
//不能获取,不会响应中断
lock.lock();
//下面的语句没有执行机会
System.out.println(Thread.currentThread().getName() + " lock不会响应中断, 一直等到锁");
});
t1.start();
t1.interrupt();
}
public static void testLockInterruptibly() throws Exception{
final Lock lock = new ReentrantLock();
//锁已被获取
lock.lock();
Thread t1 = new Thread(()->{
try {
//不能获取,可以响应中断
lock.lockInterruptibly();
} catch (InterruptedException e) {
System.out.println("InterruptedException : " + Thread.currentThread().getName() + " interrupted.");
}
});
t1.start();
t1.interrupt();
}
public static void main(String[] args) throws Exception {
testLock();
// testLockInterruptibly();
}
}
三、公平锁、非公平锁
默认是非公平锁
/**
* Creates an instance of {@code ReentrantLock}.
* This is equivalent to using {@code ReentrantLock(false)}.
*/
public ReentrantLock() {
sync = new NonfairSync();
}
有一个state变量,初始值为0,假设当前线程为A,每当A获取一次锁,status++. 释放一次,status--.锁会记录当前持有的线程。
当A线程拥有锁的时候,status>0. B线程尝试获取锁的时候会对这个status有一个CAS(0,1)的操作,尝试几次失败后就挂起线程,进入一个等待队列。
如果A线程恰好释放,--status==0, A线程会去唤醒等待队列中第一个线程,即刚刚进入等待队列的B线程,B线程被唤醒之后回去检查这个status的值,尝试CAS(0,1),而如果这时恰好C线程也尝试去争抢这把锁
非公平锁实现:
C直接尝试对这个status CAS(0,1)操作,并成功改变了status的值,B线程获取锁失败,再次挂起,这就是非公平锁,B在C之前尝试获取锁,而最终是C抢到了锁。
公平锁:
C发现有线程在等待队列,直接将自己进入等待队列并挂起,B获取锁
非公平锁性能高于公平锁性能的原因:在恢复一个被挂起的线程与该线程真正运行之间存在着严重的延迟。
当持有锁的时间相对较长或者请求锁的平均时间间隔较长,应该使用公平锁。在这些情况下,插队带来的吞吐量提升(当锁处于可用状态时,线程却还处于被唤醒的过程中)可能不会出现。
四、选择
一般来说,除非您对 Lock
的某个高级特性有明确的需要,或者有明确的证据(而不是仅仅是怀疑)表明在特定情况下,同步已经成为
可伸缩性的瓶颈,否则还是应当继续使用 synchronized。
- 在使用 synchronized 的时候,不能忘记释放锁;在退出
synchronized
块时,JVM 会为您做这件事。 - 用 synchronized 管理锁定请求和释放时,JVM 在生成线程转储时能够包括锁定信息。这些对调试非常有价值,因为它们能标识死锁或
者其他异常行为的来源。 Lock
类只是普通的类,JVM 不知道具体哪个线程拥有 Lock
对象。