实现 Lock 接口并重写 lock() 和 unlock() 方法。
Java 提供了多种锁,对修改数据的操作加锁,确保多线程下的线程安全。
下面一步步自定义一个可重入锁。所谓可重入,就是同一个线程可以多次进入被加锁的代码块。因为出现线程不安全的一个条件是:在多线程下运行。所以一个被锁代码块,面对同一个线程时,应该是可以重复进入的。
就像下面这样:
1 | /** |
方法 A 和方法 B 中使用同一个锁,如果不具备可重入性,那么调用方法 B 时线程肯定会一直被阻塞。
不具备可重入性的锁
现在这个自定义锁只有一个标志:标志这个锁是否处于锁定状态。
Java 中自带的锁远没有这么简单,甚至它们都没有用到 synchronized 关键字。
这个锁的思路是:调用 lock() 方法的时候,先判断是否已经被锁,如果在其他线程中被锁了,那么当前线程就老老实实地进入阻塞。
当其他线程释放锁并调用 notify() 唤醒当前线程时,当前线程获得锁,并将 isLocked 标志置为 true。
这样的实现是不具备可重入性的,当运行测试代码时,会一直处于阻塞状态,所以还需要优化。
1 | import java.util.concurrent.TimeUnit; |
可重入的锁
可重入的一个关键是:同一个线程。所以自定义锁里面就有一个字段记录自己是被哪个线程锁定的。
另外,本来释放锁时就将标志 isLocked 置为 false,但是现在就不行了:方法 B 调用 unlock() 方法释放锁,但方法 A 中可能还需要对共享资源进行操作,此时仍然不能让其他线程进入,所以对于 unlock() 方法也需要修改,而 reenterCount 就是用来判断能不能释放锁。
跟上面不同之处是:
- 如果没有被任何线程锁定,那么记录当前线程,标志位置为 true,reenterCount++。
- 如果进来的是同一个线程,那么就不能将它阻塞,而是将 reenterCount++。
- 如果没有被锁,调用 unlock() 没有意义,什么也不用做。
- 正常情况下,reenterCount–,如果 reenterCount 为 0,才真正的释放锁。
1 | import java.util.concurrent.TimeUnit; |
总结
自定义这个锁没什么实际意义,不过可以体会一下“可重入性”是怎么一回事。