ReentrantLock那些事儿(一)

ReentrantLock那些事儿(一)

薛定谔的汪

ReentrantLock 是 Lock 的一个子类,和 synchronize 一样称之为可重入锁,它有着和 synchronize 同样的内存语义,但功能比 synchronize 功能更多,使用起来更为灵活。

可重入锁:能够支持对资源的重复加锁,一个典型例子就是同一个类的 synchironized 方法内可以调用本类其他的 synchronize 方法。

使用 ReentrantLock 实现同步

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class ReentrantLockThread implements Runnable {

public static int num = 0;

private static final Lock LOCK = new ReentrantLock();
@Override
public void run() {

for (int i = 0; i < 10000; i++) {
//每次计算加锁
LOCK.lock();
try {
num++;
}finally {
//解锁
LOCK.unlock();
}
}
}
}

Run 类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Run {

public static void main(String[] args) throws InterruptedException {
ReentrantLockThread lockTest = new ReentrantLockThread();
Thread t1 = new Thread(lockTest);
Thread t2 = new Thread(lockTest);
t1.start();
t2.start();
//保证t1和t2线程执行完后,主线程才打印num
t1.join();
t2.join();
System.out.println(ReentrantLockThread.num);

}
}

结果:

多次执行都是20000。

ReentrantLock 可以完全替代 synchronize 来使用,而且更好用。

ReentrantLock 其他功能

尝试非阻塞地获取锁:当前线程尝试获取锁,如果这一时刻锁没有被其他线程获取到,则成功获取并持有锁。

能被中断地获取锁:获取到锁的线程能被中断,抛出中断异常并释放锁。

超时获取锁:在指定的截止时间之前获取锁,如果在指定的时间内仍未获取锁则返回,避免死锁。

尝试非阻塞地获取锁

对应 API: tryLock() 和 tryLock(long time, TimeUnit unit),这两个 API 具体的区别前面已经提到,这里不再重复描述。

代码:

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
public class TryLockTest implements Runnable{

private static final ReentrantLock LOCK = new ReentrantLock();

@Override
public void run() {
//线程尝试获取锁
if(LOCK.tryLock()){
System.out.println("线程"+Thread.currentThread().getName()+"获取锁成功!");
try {
//获取到锁后睡眠3秒,sleep是不释放锁的
Thread.sleep(3000);
}catch (Exception e) {

}finally {
LOCK.unlock();
}
}else {
System.err.println("线程"+Thread.currentThread().getName()+"获取锁失败!");
}
}


public static void main(String[] args) {
TryLockTest tryLockTest = new TryLockTest();
Thread t1 = new Thread(tryLockTest, "t1");
Thread t2 = new Thread(tryLockTest, "t2");
t1.start();
t2.start();
}
}

打印结果:

1
2
线程t1获取锁成功!
线程t2获取锁失败!

这是因为t1获取锁后,sleep 3秒,这期间仍然持有锁不释放,t2去尝试获取锁时自然获取不到。

可被中断地获取锁

API: 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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
public class LockInterruptiblyTest implements Runnable {

private String type;

private static final ReentrantLock LOCK_A = new ReentrantLock();
private static final ReentrantLock LOCK_B = new ReentrantLock();

public LockInterruptiblyTest(String type) {
this.type = type;
}

@Override
public void run() {
//lockInterruptibly本身抛出InterruptedException异常,抛出后释放锁
try {
if ("a".equals(type)) {
//A方式 LOCK_A先加锁,LOCK_B 再加锁
LOCK_A.lockInterruptibly();
Thread.sleep(1000);
LOCK_B.lockInterruptibly();
}else if ("b".equals(type)){
//B方式 LOCK_B 先加锁,LOCK_A 再加锁
LOCK_B.lockInterruptibly();
Thread.sleep(1000);
LOCK_A.lockInterruptibly();
}
} catch (Exception e) {
System.err.println(Thread.currentThread().getName()+"被中断");
} finally {
//如果当前线程持有锁才释放,多这一步判断是当前线程获取锁后被打断会自动释放锁
if(LOCK_A.isHeldByCurrentThread()){
LOCK_A.unlock();
}
if(LOCK_B.isHeldByCurrentThread()){
LOCK_B.unlock();
}
}
System.err.println(Thread.currentThread().getName()+"退出!");
}

public static void main(String[] args) {
LockInterruptiblyTest testA = new LockInterruptiblyTest("a");
LockInterruptiblyTest testB = new LockInterruptiblyTest("b");

Thread tA = new Thread(testA);
Thread tB = new Thread(testB);
tA.start();
tB.start();
//中断tA 或者 tB ,随意
//tA.interrupt();
}
}

tA.interrupt();先注释掉,运行程序,必然产生死锁,因为双方都持有对方想要的锁不释放,将tA.interrupt();代码放开,再次运行,打印:

1
2
3
A被中断
A退出!
B退出!

程序结束,验证 lockInterruptibly()方法,获取锁的线程可以被中断后并释放锁。

公平锁和非公平锁

ReentrantLock 还支持公平锁和非公平锁,可以先了解下什么是公平锁和非公平锁:

公平锁:保障了多线程下各线程获取锁的顺序,先到的线程优先获取锁。

非公平锁:各线程获取锁不是顺序而是随机的。

从网上找了一副图很形象地描述了什么是公平锁和非公平锁:

  • Title: ReentrantLock那些事儿(一)
  • Author: 薛定谔的汪
  • Created at : 2018-08-14 18:01:54
  • Updated at : 2023-11-17 19:37:37
  • Link: https://www.zhengyk.cn/2018/08/14/java/ReentrantLock-1/
  • License: This work is licensed under CC BY-NC-SA 4.0.