可重入锁
锁作为并发共享数据,保证一致性的工具,在JAVA平台有多种实现(如 synchronized 和 ReentrantLock等等 ) 。这些已经写好提供的锁为我们开发提供了便利,但是锁的具体性质以及类型却很少被提及。
四、可重入锁:
本文里面讲的是广义上的可重入锁,而不是单指JAVA下的ReentrantLock。
可重入锁,也叫做递归锁,指的是同一线程外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但不受影响。
在JAVA环境下ReentrantLock 和synchronized 都是可重入锁。
下面是synchronized的可重入锁的实例:
package com.dxz.sync3; public class Test implements Runnable { public synchronized void get() { System.out.println(Thread.currentThread().getId()); set(); } public synchronized void set() { System.out.println(Thread.currentThread().getId()); } @Override public void run() { get(); } public static void main(String[] args) { Test ss = new Test(); new Thread(ss).start(); new Thread(ss).start(); new Thread(ss).start(); } }
下面是ReentrantLock的可重入锁的实例:
package com.dxz.sync4; import java.util.concurrent.locks.ReentrantLock; public class Test implements Runnable { ReentrantLock lock = new ReentrantLock(); public void get() { lock.lock(); System.out.println(Thread.currentThread().getId()); set(); lock.unlock(); } public void set() { lock.lock(); System.out.println(Thread.currentThread().getId()); lock.unlock(); } @Override public void run() { get(); } public static void main(String[] args) { Test ss = new Test(); new Thread(ss).start(); new Thread(ss).start(); new Thread(ss).start(); } }
两个例子最后的结果都是正确的,即 同一个线程id被连续输出两次。
结果如下:
Threadid: 8
Threadid: 8
Threadid: 10
Threadid: 10
Threadid: 9
Threadid: 9
可重入锁最大的作用是避免死锁。
我们以自旋锁作为例子,
package com.dxz.sync4; import java.util.concurrent.atomic.AtomicReference; public class SpinLock { private AtomicReference<Thread> owner = new AtomicReference<>(); public void lock() { Thread current = Thread.currentThread(); while (!owner.compareAndSet(null, current)) { } } public void unlock() { Thread current = Thread.currentThread(); owner.compareAndSet(current, null); } }
对于自旋锁来说,
1、若有同一线程两调用lock() ,会导致第二次调用lock位置进行自旋,产生了死锁。说明这个锁并不是可重入的。(在lock函数内,应验证线程是否为已经获得锁的线程)
2、若1问题已经解决,当unlock()第一次调用时,就已经将锁释放了。实际上不应释放锁。
(采用计数次进行统计)
修改之后,如下:
package com.dxz.sync4; import java.util.concurrent.atomic.AtomicReference; public class SpinLock1 { private AtomicReference<Thread> owner = new AtomicReference<>(); private int count = 0; public void lock() { Thread current = Thread.currentThread(); if (current == owner.get()) { count++; return; } while (!owner.compareAndSet(null, current)) { } } public void unlock() { Thread current = Thread.currentThread(); if (current == owner.get()) { if (count != 0) { count--; } else { owner.compareAndSet(current, null); } } } }
该自旋锁即为可重入锁。
可重入锁也支持在父子类继承的环境中,示例:
package com.dxz.sync; public class Main { public int i = 10; public synchronized void operateMainMethod() { try { i--; System.out.println("main print i = " + i); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }
子类:
package com.dxz.sync; public class Sub extends Main { public synchronized void operateISubMethod() { try { while (i > 0) { i--; System.out.println("sub print i=" + i); Thread.sleep(100); this.operateMainMethod(); } } catch (InterruptedException e) { e.printStackTrace(); } } }
线程:
package com.dxz.sync; public class MyThread extends Thread { @Override public void run() { Sub sub = new Sub(); sub.operateISubMethod(); } }
入口类:
package com.dxz.sync; public class Test { public static void main(String[] args) { MyThread thread = new MyThread(); thread.start(); } }
结果:
sub print i=9 main print i = 8 sub print i=7 main print i = 6 sub print i=5 main print i = 4 sub print i=3 main print i = 2 sub print i=1 main print i = 0
可重入锁也支持在父子类继承的环境中。
下面这个锁的实现是不可重入的:
package com.dxz.sync3; public class Lock { private boolean isLocked = false; public synchronized void lock() throws InterruptedException { while (isLocked) { wait(); } isLocked = true; } public synchronized void unlock() { isLocked = false; notify(); } }
如果一个线程在两次调用lock()间没有调用unlock()方法,那么第二次调用lock()就会被阻塞,这就出现了重入锁死。
避免重入锁死有两个选择:
- 编写代码时避免再次获取已经持有的锁
- 使用可重入锁
至于哪个选择最适合你的项目,得视具体情况而定。可重入锁通常没有不可重入锁那么好的表现,而且实现起来复杂,但这些情况在你的项目中也许算不上什么问题。无论你的项目用锁来实现方便还是不用锁方便,可重入特性都需要根据具体问题具体分析。