## 什么是可重入锁?

可重入锁是一种特殊的互斥锁,它可以被同一个线程多次获取,而不会产生死锁。

1. 首先它是互斥锁:任意时刻,只有一个线程锁。即假设A线程已经获取了锁,在A线程释放这个锁之前,B线程是无法获取到这个锁的,B要获取这个锁就会进入阻塞状态。

2. 其次,它可以被同一个线程多次持有。即,假设A线程已经获取了这个锁,如果A线程在释放锁之前又一次请求获取这个锁,那么是能够获取成功的。

举例说明:

“` java

public class LockTest {

public synchronized void lockA() {

lockB(); //lockA方法中调用lockB方法

}

public synchronized void lockB() {

System.out.println(“lock B”);

}

}

“`

如上面的的代码所示,假如我们要调用lockA方法,而lockA方法又要调用lockB方法,并且这两个方法上都有synchronized关键字,即两个方法都会以类对象作为锁,所以两个方法是统一把锁,如果没有可重入锁的机制,那么这个方法就无法被正确执行。另外可以想象一下,假如我们有一个递归方法,而这个方法是需要加锁的,如果没有可重入锁的机制,加锁的递归方法也是不能实现的。这就是为什么要有可重入锁。

## Java中的可重入锁

### 1. synchronized

synchronized是Java提供的内置锁,是互斥锁,又是可重入锁。

synchronized关键字有三种用法:

* 加在对象方法上:锁住的是当前对象,不同的对象可以被同时访问

* 加在类方法上:锁住的是当前类对应在JVM中的Class对象

* 加在代码块上:锁住的是synchronized(lockobj)中的lockobj

synchronized可重入举例:

我们知道Hashtable是线程安全的,因为它的所有对元素的读写操作都有synchronized关键字修饰,其中有个putAll方法:

“` java

/**

* Copies all of the mappings from the specified map to this hashtable.

* These mappings will replace any mappings that this hashtable had for any

* of the keys currently in the specified map.

*

* @param t mappings to be stored in this map

* @throws NullPointerException if the specified map is null

* @since 1.2

*/

public synchronized void putAll(Map extends K, ? extends V> t) {

for (Map.Entry extends K, ? extends V> e : t.entrySet())

put(e.getKey(), e.getValue());

}

“`

可以看到这个方法是被synchronized关键字修饰的,方法里面是通过for循环调用put(K,V)方法,我们再来看一下put方法:

“` java

public synchronized V put(K key, V value) {

// Make sure the value is not null

if (value == null) {

throw new NullPointerException();

}

// Makes sure the key is not already in the hashtable.

Entry,?> tab[] = table;

int hash = key.hashCode();

int index = (hash & 0x7FFFFFFF) % tab.length;

@SuppressWarnings(“unchecked”)

Entry entry = (Entry)tab[index];

for(; entry != null ; entry = entry.next) {

if ((entry.hash == hash) && entry.key.equals(key)) {

V old = entry.value;

entry.value = value;

return old;

}

}

addEntry(hash, key, value, index);

return null;

}

“`

可以看到,put方法也是被synchronized关键字修饰的。

所以,当我们调用一个Hashtable对象的putAll方法的时候,如果synchronized关键字不支持重入,那么程序将不能正常运行。

### 2. ReentrantLock

ReentrantLock是Java提供的显示锁,它也是互斥锁,也是可重入锁(通过名字就知道,哈哈)。

例如:

“` java

public class ReentrantLockTest {

private ReentrantLock lock = new ReentrantLock();

public void doFirstLock() {

lock.lock();

try {

System.out.println(“doFirstLock—” + Thread.currentThread().getId());

doSecondLock();

} finally {

lock.unlock();

}

}

public void doSecondLock() {

lock.lock();

try {

System.out.println(“doSecondLock—” + Thread.currentThread().getId());

} finally {

lock.unlock();

}

}

public static void main(String[] args) throws InterruptedException {

for (int i = 0; i < 10; i++) {

new Thread() {

@Override

public void run() {

new ReentrantLockTest().doFirstLock();

}

}.start();

}

TimeUnit.SECONDS.sleep(100 * 50);

}

}

“`

运行结果:

“` java

doFirstLock—9

doFirstLock—10

doSecondLock—9

doSecondLock—10

doFirstLock—11

doSecondLock—11

doFirstLock—12

doSecondLock—12

doFirstLock—13

doSecondLock—13

doFirstLock—16

doSecondLock—16

doFirstLock—14

doSecondLock—14

doFirstLock—15

doSecondLock—15

doFirstLock—17

doSecondLock—17

doFirstLock—18

doSecondLock—18

“`

## Java中的不可重入锁

下面是我在[自旋锁](//自旋锁)中写的一个自旋锁的例子,它就是不可重入的。

“` java

public class SpinLock {

private AtomicReference cas = new AtomicReference();

public void lock() {

Thread current = Thread.currentThread();

// 利用CAS

while (!cas.compareAndSet(null, current)) {

// DO nothing

}

}

public void unlock() {

Thread current = Thread.currentThread();

cas.compareAndSet(current, null);

}

}

“`

上面的例子,如果线程A两次调用lock方法获取锁,那么只有第一次可以成功,第二次将进入循环等待。

测试代码:

“` java

public static void main(String[] args) {

final SpinLock spinLock = new SpinLock();

System.out.println(“invoke lock”);

spinLock.lock();

System.out.println(” invoke lock success”);

System.out.println(“try lock again”);

spinLock.lock();

System.out.println(“try lock success”);

}

“`

输出结果:

“` java

invoke lock

invoke lock success

try lock again

“`

可以看到,并没有输出”try lock success”,是因为在第二次获取锁的时候,由于不可重入,而进入循环等待。

## 总结

* 可重入锁:即某个线程获得了锁之后,在锁释放前,它可以多次重新获取该锁。

* 可重入锁解决了重入锁死的问题。

* java的内置锁synchronized和ReentrantLock都是可重入锁


版权声明:本文为weixin_39984952原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/weixin_39984952/article/details/114778281