0%

java | ReentrantLock

ReentrantLock,相对于 Synchronized 有以下特点

  • 可中断
  • 可以设置超时时间
  • 可以设置为公平锁
  • 支持多个变量条件

Synchronized 一样都支持可重入。

基本语法

1
2
3
4
5
6
7
8
reentrantLock.lock();

try{
//临界区
}finally{
//释放锁
reentrantLock.unlock();
}

可重入 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
33
34
35
36
37
38
39
40
41
package com.redisc;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.ReentrantLock;

@Slf4j(topic = "c.Run")
public class Run {

private static ReentrantLock reentrantLock = new ReentrantLock();

public static void main(String[] args) throws Exception {
reentrantLock.lock();
try {
log.debug("主方法");
m1();
} finally {
reentrantLock.unlock();
}
}

public static void m1() {
reentrantLock.lock();
try {
log.debug("m1");
m2();
} finally {
reentrantLock.unlock();
}
}

public static void m2() {
reentrantLock.lock();
try {
log.debug("m2");
} finally {
reentrantLock.unlock();
}
}

}

输出

1
2
3
10:49:53.026 [main] DEBUG c.Run - 主方法
10:49:53.028 [main] DEBUG c.Run - m1
10:49:53.028 [main] DEBUG c.Run - m2

重复加锁了 3 次。

可打断 lockInterruptibly()

可打断主要是防止死锁出现。

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
package com.redisc;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.ReentrantLock;

@Slf4j(topic = "c.Run")
public class Run {

private static ReentrantLock reentrantLock = new ReentrantLock();

public static void main(String[] args){
Thread t1 = new Thread(() -> {
try{
// 如果没有竞争那么此方法就会获取 lock 对象锁
// 如果有竞争进入阻塞队列,那么可以被其他线程用 interrupt 方法打断
reentrantLock.lockInterruptibly();
}catch (InterruptedException e){
e.printStackTrace();
return;
}

try{
log.debug("获取到锁");
}finally {
reentrantLock.unlock();
}
},"t1");

t1.start();
}


}

但是,我们让主线程先把锁加上。

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
package com.redisc;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.ReentrantLock;

@Slf4j(topic = "c.Run")
public class Run {

private static ReentrantLock reentrantLock = new ReentrantLock();

public static void main(String[] args){
Thread t1 = new Thread(() -> {
try{
// 如果没有竞争那么此方法就会获取 lock 对象锁
// 如果有竞争进入阻塞队列,那么可以被其他线程用 interrupt 方法打断
reentrantLock.lockInterruptibly();
}catch (InterruptedException e){
e.printStackTrace();
return;
}

try{
log.debug("获取到锁");
}finally {
reentrantLock.unlock();
}
},"t1");

reentrantLock.lock();
t1.start();
}


}

没有输出。进行打断

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
package com.redisc;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.ReentrantLock;

@Slf4j(topic = "c.Run")
public class Run {

private static ReentrantLock reentrantLock = new ReentrantLock();

public static void main(String[] args) {
Thread t1 = new Thread(() -> {
try {
// 如果没有竞争那么此方法就会获取 lock 对象锁
// 如果有竞争进入阻塞队列,那么可以被其他线程用 interrupt 方法打断
reentrantLock.lockInterruptibly();
} catch (InterruptedException e) {
e.printStackTrace();
log.debug("没有获得锁");
return;
}

try {
log.debug("获取到锁");
} finally {
reentrantLock.unlock();
}
}, "t1");

reentrantLock.lock();
t1.start();

t1.interrupt();
}


}

输出

1
2
3
4
5
6
java.lang.InterruptedException
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1220)
at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
at com.redisc.Run.lambda$main$0(Run.java:17)
at java.lang.Thread.run(Thread.java:748)
11:35:30.715 [t1] DEBUG c.Run - 没有获得锁

锁超时 tryLock()

无参数,是立刻尝试获得锁。

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
package com.redisc;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.ReentrantLock;

@Slf4j(topic = "c.Run")
public class Run {

private static ReentrantLock reentrantLock = new ReentrantLock();

public static void main(String[] args) {
Thread t1 = new Thread(() -> {
// 如果成功,则会获得
// 如果失败,则下次继续等待获得锁
if (!reentrantLock.tryLock()) {
log.debug("获取不到锁");
return;
}

try {
log.debug("获取到锁");
} finally {
reentrantLock.unlock();
}
}, "t1");

t1.start();
}


}

进行主线程获得锁

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
package com.redisc;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.ReentrantLock;

@Slf4j(topic = "c.Run")
public class Run {

private static ReentrantLock reentrantLock = new ReentrantLock();

public static void main(String[] args) {
Thread t1 = new Thread(() -> {
// 如果成功,则会获得
// 如果失败,则下次继续等待获得锁
if (!reentrantLock.tryLock()) {
log.debug("获取不到锁");
return;
}

try {
log.debug("获取到锁");
} finally {
reentrantLock.unlock();
}
}, "t1");

reentrantLock.lock();
t1.start();
}


}

输出

1
12:04:18.223 [t1] DEBUG c.Run - 获取不到锁

参数超时机制,有参数,是在参数时间内尝试获得锁

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
package com.redisc;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

@Slf4j(topic = "c.Run")
public class Run {

private static ReentrantLock reentrantLock = new ReentrantLock();

public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
// 如果成功,则会获得
// 如果失败,则下次继续等待获得锁
try {
if (!reentrantLock.tryLock(2, TimeUnit.SECONDS)) {
log.debug("获取不到锁");
return;
}
} catch (InterruptedException e) {
e.printStackTrace();
}

try {
log.debug("获取到锁");
} finally {
reentrantLock.unlock();
}
}, "t1");

log.debug("获取锁");
reentrantLock.lock();
t1.start();
Thread.sleep(1000);
reentrantLock.unlock();
}


}

输出

1
2
12:06:26.150 [main] DEBUG c.Run - 获取锁
12:06:27.154 [t1] DEBUG c.Run - 获取到锁

公平锁

ReentrantLock 默认情况和 Synchronized都是不公平锁。

不公平指的是,当锁释放后,所有的线程又处于同一地位来争抢锁,导致有的线程可能一直抢不到。

ReentrantLock 通过构造参数传入 True 来设计成公平锁。

公平锁会降低并发,一般用 trylock 就好了。

条件变量

  • Synchronized 也有条件变量,就是 waitset 中的休息室。当条件不满足的时候,进入 waitset 等待
  • ReentrantLock 的条件变量比 Synchronized 强大之处,在于它是支持多个条件变量的
    • Synchronized 是那些不满足条件的线程都在一间休息室等消息
    • ReentrantLock 支持多间休息室

使用流程

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

java | wait && notify 正确姿势 | 把 if 换成 while 解决虚假唤醒 | 进行优化

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
package com.redisc;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

@Slf4j(topic = "c.Run")
public class Run {

static final Object room = new Object();
static boolean hasCigarette = false;
static boolean hasTakeout = false;

static ReentrantLock ROOM = new ReentrantLock();
static Condition waitCigaretteSet = ROOM.newCondition();
static Condition waitTakeoutSet = ROOM.newCondition();

public static void main(String[] args) throws Exception {
new Thread(() -> {
ROOM.lock();
try {
log.debug("有烟没1?[{}]", hasCigarette);
while (!hasCigarette) {
log.debug("没烟,先歇会");
try {
waitCigaretteSet.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("有烟没2?[{}]", hasCigarette);
if (hasCigarette) {
log.debug("可以干活了");
}
} finally {
ROOM.unlock();
}
}, "小南").start();

new Thread(() -> {
ROOM.lock();
try {
log.debug("有外卖没?[{}]", hasTakeout);
while (!hasTakeout) {
log.debug("没外卖,先歇会");
try {
waitTakeoutSet.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("有外卖没?[{}]", hasTakeout);
if (hasTakeout) {
log.debug("可以干活了");
}
} finally {
ROOM.unlock();
}
}, "小女").start();

for (int i = 0; i < 5; i++) {
new Thread(() -> {
synchronized (room) {
log.debug("可以干活了");
}
}, "其他人").start();
}

Thread.sleep(1000);

new Thread(() -> {
ROOM.lock();
try {
hasCigarette = true;
log.debug("烟到了");
waitCigaretteSet.signal();
} finally {
ROOM.unlock();
}

}, "送烟的").start();
}

}

输出

1
2
3
4
5
6
7
8
9
10
11
12
12:39:59.230 [其他人] DEBUG c.Run - 可以干活了
12:39:59.230 [小南] DEBUG c.Run - 有烟没1?[false]
12:39:59.233 [小南] DEBUG c.Run - 没烟,先歇会
12:39:59.233 [其他人] DEBUG c.Run - 可以干活了
12:39:59.233 [其他人] DEBUG c.Run - 可以干活了
12:39:59.233 [小女] DEBUG c.Run - 有外卖没?[false]
12:39:59.233 [其他人] DEBUG c.Run - 可以干活了
12:39:59.233 [小女] DEBUG c.Run - 没外卖,先歇会
12:39:59.233 [其他人] DEBUG c.Run - 可以干活了
12:40:00.230 [送烟的] DEBUG c.Run - 烟到了
12:40:00.231 [小南] DEBUG c.Run - 有烟没2?[true]
12:40:00.231 [小南] DEBUG c.Run - 可以干活了
请我喝杯咖啡吧~