在多線程編程中,鎖是常用地控制並發的機制,對於臨界區的資源,需要保證線程之間互斥地訪問。
1. 可重入鎖
可重入鎖,也叫做遞歸鎖,指的是多次對同一個鎖進行加鎖操作,都不會阻塞線程。實現思路:記錄當前鎖正在被哪個線程使用,采用計數來統計lock和unlock的調用次數。正常情況下,lock和unlock的調用次數應該相等,如果不相等就會死鎖。
public class Test implements Runnable {
ReentrantLock lock = new ReentrantLock(); //定義一個可重入鎖
public void get() {
lock.lock(); //第一次調用lock()
System.out.println(Thread.currentThread().getId());
set();
lock.unlock();
}
public void set() {
lock.lock(); //第二次調用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();
}
}
2. 自旋鎖
首先,看看初級的自旋鎖實現方式:
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);
}
}
實現思路:通過CAS(CompareAndSet)原子操作來更新變量。如果CAS返回true,表示獲得了鎖;否則,需要通過while循環檢查,直到獲得鎖為止,這也是為什么叫做自旋鎖的原因,需要不停的嘗試獲取鎖。
2.1 初級版本的問題
- 同一線程前后兩次調用lock(),會導致第二次調用lock時進行自旋,產生了死鎖(因為第一次調用lock()之后,還沒有unlock),說明這個鎖不是可重入的。
- 如果問題一已經解決,當第一次調用unlock()時,就已經將鎖釋放了。實際上不應釋放鎖。
2.2 解決方案
- 針對問題一:在lock函數內,應驗證線程是否為已經獲得鎖的線程
- 針對問題二:采用計數進行統計
public class SpinLock {
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);
}
}
}
}