定義
可重入鎖,也叫做遞歸鎖,指的是在同一線程內(nèi),外層函數(shù)獲得鎖之后,內(nèi)層遞歸函數(shù)仍然可以獲取到該鎖。換一種說(shuō)法:同一個(gè)線程再次進(jìn)入同步代碼時(shí),可以使用自己已獲取到的鎖。
作用
防止在同一線程中多次獲取鎖而導(dǎo)致死鎖發(fā)生。
如下,我們通過(guò)自旋鎖來(lái)判斷是否會(huì)發(fā)生死鎖:
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();
}
}
輸出結(jié)果:
LoggingWidget calling doSomething
我們可以看到在LoggingWidget類中doSomething方法時(shí),通過(guò)鎖進(jìn)入臨界區(qū),并在臨界區(qū)中調(diào)用了父類的該方法,而父類的方法要獲取到同一個(gè)鎖,被阻塞,導(dǎo)致死鎖發(fā)生。
常見的可重入鎖
- Synchronized關(guān)鍵字:
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();
}
}
輸出結(jié)果:
LoggingWidget calling doSomething
Widget calling doSomething...
根據(jù)結(jié)果,我們可以看到Synchronized關(guān)鍵字是可重入鎖。
- 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();
}
}
輸出結(jié)果:
LoggingWidget calling doSomething
Widget calling doSomething
根據(jù)結(jié)果,我們可以看出ReetrantLock鎖時(shí)可重入的。
實(shí)現(xiàn)可重入鎖
為每個(gè)鎖關(guān)聯(lián)一個(gè)獲取計(jì)數(shù)器和一個(gè)所有者線程,當(dāng)計(jì)數(shù)值為0時(shí),這個(gè)鎖就被認(rèn)為是沒(méi)有被任何線程所占有的。當(dāng)線程請(qǐng)求一個(gè)未被持有的鎖時(shí),計(jì)數(shù)值將會(huì)遞增。而當(dāng)線程退出同步代碼時(shí),計(jì)數(shù)器會(huì)相應(yīng)地遞減。當(dāng)計(jì)數(shù)值為0時(shí),則釋放該鎖。
如下:我們通過(guò)修改自旋鎖來(lái)實(shí)現(xiàn)一個(gè)可重入的自旋鎖
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();
}
}
輸出結(jié)果:
LoggingWidget calling doSomething
Widget calling doSomething