定义
可重入锁,也叫做递归锁,指的是在同一线程内,外层函数获得锁之后,内层递归函数仍然可以获取到该锁。换一种说法:同一个线程再次进入同步代码时,可以使用自己已获取到的锁。
作用
防止在同一线程中多次获取锁而导致死锁发生。
如下,我们通过自旋锁来判断是否会发生死锁:
public class SpinLock {
private AtomicReference<Thread> sign = new AtomicReference<>();
public void lock(){
Thread current = Thread.currentThread();
while (!sign.compareAndSet(null,current)){
}
}
public void unlock(){
Thread cur = Thread.currentThread();
sign.compareAndSet(cur,null);
}
}
public class SpinLockDemo {
private SpinLock lock = new SpinLock();
class Widget{
public void doSomething(){
lock.lock();
System.out.println("Widget calling doSomething");
lock.unlock();
}
}
class LoggingWidget extends Widget {
@Override
public void doSomething() {
lock.lock();
System.out.println("LoggingWidget calling doSomething");
super.doSomething();
lock.unlock();
}
}
public static void main(String[] args){
SpinLockDemo spinLockDemo = new SpinLockDemo();
SpinLockDemo.Widget widget = spinLockDemo.new LoggingWidget();
widget.doSomething();
}
}
输出结果:
LoggingWidget calling doSomething
我们可以看到在LoggingWidget类中doSomething方法时,通过锁进入临界区,并在临界区中调用了父类的该方法,而父类的方法要获取到同一个锁,被阻塞,导致死锁发生。
常见的可重入锁
- Synchronized关键字:
public class SynchronizedDemo {
class Widget{
public synchronized void doSomething(){
System.out.println("Widget calling doSomething...");
}
}
class LoggingWidget extends Widget{
@Override
public synchronized void doSomething() {
System.out.println("LoggingWidget calling doSomething");
super.doSomething();
}
}
public static void main(String[] args){
SynchronizedDemo synchronizedDemo = new SynchronizedDemo();
SynchronizedDemo.Widget widget = synchronizedDemo.new LoggingWidget();
widget.doSomething();
}
}
输出结果:
LoggingWidget calling doSomething
Widget calling doSomething...
根据结果,我们可以看到Synchronized关键字是可重入锁。
- ReetrantLock:
public class ReentrantLockDemo {
private Lock lock = new ReentrantLock();
class Widget{
public void doSomething(){
lock.lock();
System.out.println("Widget calling doSomething");
lock.unlock();
}
}
class LoggingWidget extends Widget {
@Override
public void doSomething() {
lock.lock();
System.out.println("LoggingWidget calling doSomething");
super.doSomething();
lock.unlock();
}
}
public static void main(String[] args){
ReentrantLockDemo reentrantLockDemo = new ReentrantLockDemo();
Widget widget = reentrantLockDemo.new LoggingWidget();
widget.doSomething();
}
}
输出结果:
LoggingWidget calling doSomething
Widget calling doSomething
根据结果,我们可以看出ReetrantLock锁时可重入的。
实现可重入锁
为每个锁关联一个获取计数器和一个所有者线程,当计数值为0时,这个锁就被认为是没有被任何线程所占有的。当线程请求一个未被持有的锁时,计数值将会递增。而当线程退出同步代码时,计数器会相应地递减。当计数值为0时,则释放该锁。
如下:我们通过修改自旋锁来实现一个可重入的自旋锁
public class SpinLockDemo {
private MySpinLock lock = new MySpinLock();
class Widget{
public void doSomething(){
lock.lock();
System.out.println("Widget calling doSomething");
lock.unlock();
}
}
class MySpinLock{
private AtomicReference<Thread> owner = new AtomicReference<>();
private int count = 0;
public void lock(){
Thread cur = Thread.currentThread();
if (cur == owner.get()){
count ++;
return;
}
while (! owner.compareAndSet(null,cur)){
}
}
public void unlock(){
Thread cur = Thread.currentThread();
if (cur == owner.get()){
if (count != 0){
count --;
} else {
owner.compareAndSet(cur,null);
}
}
}
}
class LoggingWidget extends Widget {
@Override
public void doSomething() {
lock.lock();
System.out.println("LoggingWidget calling doSomething");
super.doSomething();
lock.unlock();
}
}
public static void main(String[] args){
SpinLockDemo spinLockDemo = new SpinLockDemo();
SpinLockDemo.Widget widget = spinLockDemo.new LoggingWidget();
widget.doSomething();
}
}
输出结果:
LoggingWidget calling doSomething
Widget calling doSomething