Fork me on GitHub

并发编程学习(一):编写一个简易的可重入锁

封面

什么是可重入锁

在Java多线程编程中,锁是用来控制代码操作的原子性的重要机制,即对某线程共享的数据进行操作的时候,保证该操作同一时间段只有一个线程操作,整个过程是原子操作,其他线程再操作的时候只能等待锁的释放(排他锁)。一般我们的锁都是声明在方法上或者代码块中,那么在实际编程中我们经常会出现一个类的实例方法调用另一个实例方法的情况,我们不希望这个时候同一个线程进入另一实例方法时还要再去等待锁的释放,可重入锁就是为了解决这个问题,即 线程可以进入任何一个它已经拥有的锁所同步着的代码块

如何实现一个简单的可重入锁

  • 首先加锁的时候需要记录当前是哪一个线程加锁,加锁之后加锁标志位标记锁已被占用
  • 维护一个加锁计数器,线程每次加锁计数器都要加1,每释放一次计数器减1
  • 当非占用锁的线程进来之后自旋等待锁的释放,如果锁还在被占用,就wait();
  • 所释放的时候只有当前获取锁的线程调用才有用,锁释放加锁计数器减1,当减到0的时候,加锁标志位置为false,然后唤醒其他等待的线程
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
public class MyLock implements Lock {

private boolean locked = false;// 当前锁是否已经被线程使用

private int lockCount;// 保存当前线程加锁的次数

private Thread thread;// 保存当前线程是哪一个

public synchronized void lock() {
try {
// 自旋等待
// 可重入:如果获取锁的不是当前线程并且当前已经有线程加锁,则等待
while (locked && Thread.currentThread() != this.thread) {
System.out.println("我等会儿。。。");
wait();
}
// 如果没有线程使用锁或者获取锁的是当前线程 加锁计数器+1 然后thread指向获取锁的线程
this.thread = Thread.currentThread();
lockCount++;
locked = true;
} catch (Exception e) {
e.printStackTrace();
} finally {

}
}

/**
* 释放锁的操作
*/
public synchronized void unlock() {
try {
// 如果不是当前线程 不需要做任何操作
if(this.thread == Thread.currentThread()){
// 锁计数器减1 如果当前线程获取锁个数释放完成
lockCount--;
if(lockCount == 0 ){
// 释放完成 加锁标志置为false 再唤醒等待锁的线程
locked = false;
notifyAll();
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {

}
}
}

在Java中常用的可重入锁

  • synchronized 关键字加锁
  • java.util.concurrent.locks.ReentrantLock
  • java.util.concurrent.locks.ReentrantReadWriteLock

    上述ReentrantLock内部实际上还是使用AQS,关于AQS,后面还要进行学习与整理

陈年风楼 wechat
Add my WeChat, share tech-skills to each other 🙆‍