并发包 - ReentrantLock

Author Avatar
xuanzh.cc 11月 14, 2017
  • 在其它设备中阅读本文章

谈到锁,就应该马上想起 synchronized 关键字,他可以用到方法上,也可以用于代码块上,作用都是获取锁。它的加锁和释放锁都是由jvm自动完成的,虽然使用方便,但是所以没有那么灵活,下面我们来说说重入锁。

实现:public class ReentrantLock implements Lock, java.io.Serializable

与 synchronized 的区别
序号 synchronized ReentrantLock
1 锁的获取的释放由JVM控制 加锁和释放完全有用户控制
2 无法响应中断 通过 lockInterruptibly() 方法可以响应中断
3 获取不到锁的时候会一直等待 通过 tryLock(long timeout, TimeUnit unit)方法可以在等待指定时间后超时通过 tryLock() 方法尝试获取锁,在获取不到的时候直接返回 false
4 无对应的配套操作 Condition 作为搭档
标准用法
1
2
3
4
5
6
7
8
ReentrantLock lock = new ReentrantLock();
try{
lock.lock();
}
//一定要在finally里面unlock
finally{
lock.unlock();
}

之所以叫做重入,是因为这种在一个线程内是可以反复进入的,也就是说可以 lock 多次,当然也要在 finally 块里面unlock 相同的次数

中断响应

对于synchronized 一直等待锁来说,可重入锁提供了所中断的机制,当一个线程由于某种原因(死锁)迟迟获取不到锁的时候,可以通过中断线程(interrupt 方法)取消对锁的请求,这对于解决死锁很有帮助。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
import java.util.concurrent.locks.ReentrantLock;
public class TestLockInterruptibly {
private static ReentrantLock lock1 = new ReentrantLock();
private static ReentrantLock lock2 = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new TestThread(true), "线程1");
Thread t2 = new Thread(new TestThread(false), "线程2");
t1.start();
//为演示效果而休眠
Thread.sleep(50);
t2.start();
//主线程休眠3秒
Thread.sleep(3000);
//t2发送中断,让等待lock1的t2响应该中断,取消对lock1的请求
//t2.interrupt();
}
static class TestThread implements Runnable{
//构造
public TestThread(boolean flag) {
this.flag = flag;
}
//true 先获取 lock1,再获取lock2
//false 先获取 lock2,再获取lock1
private boolean flag;
@Override
public void run() {
try{
if(flag) {
System.out.println(Thread.currentThread().getName() + " 开始获取lock1 ");
lock1.lockInterruptibly();
System.out.println(Thread.currentThread().getName() + " 获取到了 lock1");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 开始获取lock2 ");
lock2.lockInterruptibly();
System.out.println(Thread.currentThread().getName() + " 获取到了 lock2");
} else {
System.out.println(Thread.currentThread().getName() + " 开始获取lock2 ");
lock2.lockInterruptibly();
System.out.println(Thread.currentThread().getName() + " 获取到了 lock2");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 开始获取lock1 ");
lock1.lockInterruptibly();
System.out.println(Thread.currentThread().getName() + " 获取到了 lock1");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if(lock1.isHeldByCurrentThread()) {
lock1.unlock();
}
if(lock2.isHeldByCurrentThread()){
lock2.unlock();
}
System.out.println(Thread.currentThread().getName() + " " + "end...");
}
}
}
}

当t2不中断的时候,就回产生死锁,t1 等待 t2 持有的lock2, t2 等待 t1持有的lock1,结果如下图:

当 t2 中断的时候,发现t1 和 t2 都可以的退出,死锁被解决了

tryLock

上述的响应中断需要我们主动调用 中断方法 interrupt,使用tryLock 可以让线程限时等待,在指定时间内获取到锁后返回true,否则返回false,总之不会造成死锁。

Condition

还记得 与 synchronized 配套使用的 wait 和 notify,notifyAll 方法吗? 类似的重入提供了Condition ,和 lock 配套实现类似的操作。

通过 Lock 的 newCondition() 方法可以生意一个与当前锁绑定的 Condition 对象,再利用如下的方法,可以实现类似 wait 和 notify的操作。

下面是一个生产者消费者的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class TestCondition {
private static ReentrantLock lock = new ReentrantLock();
private static Condition produceGood = lock.newCondition();
private static Condition consumeGood = lock.newCondition();
/* 是否有物品 */
private static boolean hasGood = false;
public static void main(String[] args) {
Thread producer = new Thread(new Producer(),"生产者");
Thread consumer = new Thread(new Consumer(),"消费者");
producer.start();
consumer.start();
}
/**生产者*/
static class Producer implements Runnable {
@Override
public void run() {
while(true) {
try{
lock.lock();
//如果有生产
if (hasGood) {
System.out.println("线程 " + Thread.currentThread().getName() + " 物品等待被消费..");
try {
//等待物品被消费后,被消费者唤醒
consumeGood.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//没有物品,生产者则生产物品
else {
System.out.println("线程 " + Thread.currentThread().getName() + " 生产了一个物品...");
hasGood = true;
//唤醒正在等待的消费者开始消费
produceGood.signal();
}
} finally {
lock.unlock();
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
/*消费者*/
static class Consumer implements Runnable{
@Override
public void run() {
while (true) {
try {
lock.lock();
//如果有物品
if(hasGood) {
//消费物品
System.out.println("线程 " + Thread.currentThread().getName() + " 消费了物品...");
hasGood = false;
//唤醒等待生产者开始生产
consumeGood.signal();
}
//没有物品
else {
System.out.println("线程 " + Thread.currentThread().getName() + "等待物品被生产..");
try {
//等待被生产者唤醒
produceGood.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} finally {
lock.unlock();
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}

当然,也可以用 wait 和 notify实现