ReentrantLock的使用

ReentrantLock

相对于synchronized,它具备的特点是:

  1. 可中断
  2. 可以设置超时时间
  3. 可以设置为公平锁
  4. 支持多个条件变量

与synchronized一样,都支持可重入。

基本语法:

1
2
3
4
5
6
7
8
//获取锁
reentrantLock.lock();
try{
//临界区
}finally{
//释放锁
reentrantLock.unlock();
}

1、ReentrantLock的特点

1、可重入

可重入是指:同一个线程如果首次获得了这把锁,那么因为它是这把锁的拥有者,因此由权力再次获取这把锁。

下面这个例子:主线程调用方法method1(),在method1中获取lock锁。

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
public static ReentrantLock lock = new ReentrantLock();

public static void method1(){
try{
lock.lock();
System.out.println("方法1 执行");
method2();
}finally {
lock.unlock();
}
}
public static void method2(){
try{
lock.lock();
System.out.println("方法2 执行");
method3();
}finally {
lock.unlock();
}
}
public static void method3(){
try {
lock.lock();
System.out.println("方法3 执行");
}finally {
lock.unlock();
}
}

public static void main(String[] args) {
method1();
}

image-20210602222121768

2、可打断

lock.lockInterruptibly()方法。

如果没有竞争那么此方法就会获取lock对象锁。

如果有竞争就进入阻塞队列,可以被其他线程用 interruput 方法打断。

下面这个例子:主线程先获取锁,t1线程进入阻塞队列。主线程执行中断。

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
public static void main(String[] args) throws InterruptedException {
ReentrantLock lock = new ReentrantLock();

Thread t1 = new Thread(() -> {
System.out.println("t1线程启动");

try{
lock.lockInterruptibly();
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("等待的过程中被打断");
return;
}

try {
System.out.println("获得了锁");
}finally {
lock.unlock();
}


},"t1");

lock.lock();
System.out.println("主线程获得了锁");
t1.start();

try {
sleep(10);
t1.interrupt();
System.out.println("执行中断");
}finally {
lock.unlock();
}
}

image-20210602223017282

3、 锁超时

当线程在尝试获取锁时,可能锁已经被其他线程占了。

这是我们可以设置一段时间,在这段时间内,线程可以获得锁。

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
public static void main(String[] args) throws InterruptedException {
ReentrantLock lock = new ReentrantLock();

Thread t1 = new Thread(() -> {
System.out.println("t1线程启动");

try {
if(!lock.tryLock(1, TimeUnit.SECONDS)){
System.out.println("获取锁等待1s后失败,返回");
return;
}
} catch (InterruptedException e) {
e.printStackTrace();
}

try{
System.out.println("t1线程获取锁");
}finally {
lock.unlock();
}
},"t1");

lock.lock();
System.out.println("主线程获取了锁");
t1.start();

try{
sleep(3000);
}finally {
System.out.println("主线程释放锁");
lock.unlock();
}
}

image-20210602225851021

4、公平锁

ReentrantLock 默认是不公平

1
2
3
4
5
6
7
public ReentrantLock() {
sync = new NonfairSync();
}

public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}

不公平锁

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
public static void main(String[] args) throws InterruptedException {
//设置为非公平锁
ReentrantLock lock = new ReentrantLock(false);

lock.lock();

//创建500个线程
for (int i = 0; i < 500; i++) {
new Thread(() -> {
lock.lock();
try{
System.out.println(Thread.currentThread().getName() + "Running");
}finally {
lock.unlock();
}
},"t" + i).start();
}

//1s之后争抢锁
Thread.sleep(1000);
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " start");
lock.lock();
try{
System.out.println(Thread.currentThread().getName() + " Running");
}finally {
lock.unlock();
}
},"强行插入").start();

lock.unlock();
}

非公平锁

公平锁

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
public static void main(String[] args) throws InterruptedException {

//公平锁
ReentrantLock lock = new ReentrantLock(true);

lock.lock();

//创建500个线程
for (int i = 0; i < 500; i++) {
new Thread(() -> {
lock.lock();
try{
System.out.println(Thread.currentThread().getName() + "Running");
}finally {
lock.unlock();
}
},"t" + i).start();
}

//1s之后争抢锁
Thread.sleep(1000);
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " start");
lock.lock();
try{
System.out.println(Thread.currentThread().getName() + " Running");
}finally {
lock.unlock();
}
},"强行插入").start();

lock.unlock();

}

公平锁

条件变量

在synchronized中也有条件变量。

某一个线程获取锁之后,调用wait()方法。就会进入waitiing队列中。(这就是为什么调用wait()方法必须先所得锁)

但是synchronized中只有一个wait队列。而ReentrantLock支持多个wait队列。

如何使用:

  • await前需要获得锁
  • await执行后,会释放锁,进入conditionObject等待
  • await的线程被唤醒(或打断、或超时)去重新竞争lock锁
  • 竞争lock锁成功后,从await后继续执行

例子:有两个员工需要等到烟和早餐才能工作。

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
public class test1 {

static ReentrantLock lock = new ReentrantLock();
//两个条件变量
static Condition waitCigaretteQueue = lock.newCondition();
static Condition waitbreakfastQueue = lock.newCondition();
//初始化 没有烟 也没有早餐
static volatile boolean hasCigrette = false;
static volatile boolean hasBreakfast = false;


public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
try {
lock.lock();
//没有烟时
while(!hasCigrette){
try{
waitCigaretteQueue.await();
System.out.println("t1线程正在等烟");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//有烟了
System.out.println("t1线程等到了他的烟");
}finally {
lock.unlock();
}
},"t1").start();

new Thread(() -> {
try {
lock.lock();
//没有早餐
while (!hasBreakfast) {
try {
System.out.println("t2线程正在等早餐");
waitbreakfastQueue.await();

} catch (InterruptedException e) {
e.printStackTrace();
}
}
//有早餐了
System.out.println("t2等到了他的早餐");
} finally {
lock.unlock();
}
},"t2").start();

sleep(1);
sendBreakfast();
sleep(1);
sendCigarette();

}


private static void sendCigarette() {
lock.lock();
try {
System.out.println("t1线程烟到了");
hasCigrette = true;
waitCigaretteQueue.signal();
} finally {
lock.unlock();
}
}
private static void sendBreakfast() {
lock.lock();
try {
System.out.println("t2线程早餐到了");
hasBreakfast = true;
waitbreakfastQueue.signal();
} finally {
lock.unlock();
}
}

}

ReentrantLock的使用
https://johnjoyjzw.github.io/2021/06/15/ReentrantLock/
Author
John Joy
Posted on
June 15, 2021
Licensed under