Java 可定义可重入锁

实现 Lock 接口并重写 lock() 和 unlock() 方法。

Java 提供了多种锁,对修改数据的操作加锁,确保多线程下的线程安全。

下面一步步自定义一个可重入锁。所谓可重入,就是同一个线程可以多次进入被加锁的代码块。因为出现线程不安全的一个条件是:在多线程下运行。所以一个被锁代码块,面对同一个线程时,应该是可以重复进入的。

就像下面这样:

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
/**
* @program: studythread2
* @description: 自定义可重用锁测试
* @author: diaolizhi
* @create: 2018-11-14 21:35
**/
public class MyLockTest {

class TwiceLock {
MyLock myLock = new MyLock();

public void methodA() {
myLock.lock();
System.out.println("进入方法 A.");
methodB();
myLock.unlock();
}

public void methodB() {
myLock.lock();
System.out.println("进入方法 B.");
myLock.unlock();
}
}

@Test
void test1() {
new TwiceLock().methodA();
}
}

方法 A 和方法 B 中使用同一个锁,如果不具备可重入性,那么调用方法 B 时线程肯定会一直被阻塞。

不具备可重入性的锁

现在这个自定义锁只有一个标志:标志这个锁是否处于锁定状态。

Java 中自带的锁远没有这么简单,甚至它们都没有用到 synchronized 关键字。

这个锁的思路是:调用 lock() 方法的时候,先判断是否已经被锁,如果在其他线程中被锁了,那么当前线程就老老实实地进入阻塞。

当其他线程释放锁并调用 notify() 唤醒当前线程时,当前线程获得锁,并将 isLocked 标志置为 true。

这样的实现是不具备可重入性的,当运行测试代码时,会一直处于阻塞状态,所以还需要优化。

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
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

/**
* @program: studythread2
* @description: 自定义可重用锁
* @author: diaolizhi
* @create: 2018-11-14 21:34
**/
public class MyLock implements Lock {

private boolean isLocked = false;

@Override
public synchronized void lock() {
if(isLocked) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
isLocked = true;
}

@Override
public synchronized void unlock() {
notify();
isLocked = false;
}

//省略四个覆盖的方法
}

可重入的锁

可重入的一个关键是:同一个线程。所以自定义锁里面就有一个字段记录自己是被哪个线程锁定的。

另外,本来释放锁时就将标志 isLocked 置为 false,但是现在就不行了:方法 B 调用 unlock() 方法释放锁,但方法 A 中可能还需要对共享资源进行操作,此时仍然不能让其他线程进入,所以对于 unlock() 方法也需要修改,而 reenterCount 就是用来判断能不能释放锁。

跟上面不同之处是:

  • 如果没有被任何线程锁定,那么记录当前线程,标志位置为 true,reenterCount++。
  • 如果进来的是同一个线程,那么就不能将它阻塞,而是将 reenterCount++。
  • 如果没有被锁,调用 unlock() 没有意义,什么也不用做。
  • 正常情况下,reenterCount–,如果 reenterCount 为 0,才真正的释放锁。
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
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

/**
* @program: studythread2
* @description: 自定义可重用锁
* @author: diaolizhi
* @create: 2018-11-14 21:34
**/
public class MyLock implements Lock {

private boolean isLocked = false;
private Thread lockThread = null;
private int reenterCount = 0;

@Override
public synchronized void lock() {
if(isLocked && lockThread != Thread.currentThread()) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
lockThread = Thread.currentThread();
isLocked = true;
reenterCount++;
}

@Override
public synchronized void unlock() {
if (isLocked == true && Thread.currentThread() == lockThread) {
reenterCount--;
if (reenterCount == 0) {
isLocked = false;
lockThread = null;
notify();
}
}
}

//省略四个类
}

总结

自定义这个锁没什么实际意义,不过可以体会一下“可重入性”是怎么一回事。