## 什么是可重入锁?
可重入锁是一种特殊的互斥锁,它可以被同一个线程多次获取,而不会产生死锁。
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都是可重入锁