前言
在多個線程,同時對共享資源進行操作時,會因爲線程不安全,造成數據錯誤。在java中有不同的鎖機制來避免這一問題,除此之外,還有一些線程安全的集合也供我們使用。
可能我們耳熟能詳的是某些集合是線程安全的,某些是線程不安全的。線程安全的集合保證線程安全的方法也不是都一樣的。
今天我們看看如何使用可重入鎖ReentrantLock
和synchronized
來保證線程安全。
線程不安全的情況
在java中,ArrayList是線程不安全的,我們先模擬一個線程不安全的場景。
/**
* @author 陽光大男孩!!!
*/
public class CopyOnWriteArrayListTest {
private static List<Integer> list = new ArrayList<>();
public static void add() {
list.add(1);
}
public static void main(String[] args) {
for (int i = 0; i < 10000; i++) {
new Thread(() -> add()).start();
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}
在上面的代碼中,我們開啓10000個線程,分別對ArrayList
對象進行add
操作,爲了讓其執行完,我們讓線程阻塞3s,最後打印ArrayList
對象的大小。
看看執行結果
第一次輸出了 9907
,第二次輸出了9998
,第三次輸出了10000
,很顯然這就是一個多線程操作同一個資源導致的線程不安全問題。
使用synchronized關鍵字來保證線程安全
synchronized
關鍵字的就是加鎖,通俗來講,就是你在網吧打遊戲,突然想小便,網吧廁所只能容納一個人方便,所以你得等,在廁所裏面的人把門鎖上了,你就進不去。 相當於在廁所裏面的人佔住了資源。
在上例中,多線程操作的是靜態的add方法,所以我們可以對add方法使用synchronized關鍵字來加鎖。
public synchronized static void add() {
list.add(1);
}
再次運行, 你會發現結果一直都是10000,即正確的,避免了多線程操作同一個資源帶來的線程不安全問題。
使用ReentrantLock鎖來保證線程安全
使用ReentrantLock鎖來保證線程安全也是一樣的,多線程操作的是add方法,所以我們要在add方法中加鎖
private static final Lock lock = new ReentrantLock();
public static void add() {
//加鎖
lock.lock();
try {
list.add(1);
}catch (Exception e)
{
e.printStackTrace();
}
finally {
//釋放鎖
lock.unlock();
}
}
ReentrantLock和synchronized的異同
- ReentrantLock是Lock接口的一個實現類,synchronized是一個java 關鍵字
- synchronized無法判斷是否獲取到鎖,ReentrantLock可以
- synchronized會自動釋放鎖,ReentrantLock需要顯式地手動加鎖和釋放鎖。如果不釋放鎖,可能會出現死鎖問題
- 廣義來講, synchronized和ReentrantLock都是可重入鎖,但是synchronized是非公平的、不可以中斷的。 ReentrantLock是一個類,其中有構造方法,可以設置非公平鎖和公平鎖。
總結
保證線程安全的一個有效方法就是互斥同步,互斥是實現同步的一種方法,當達到了同步,也就解決了線程安全的問題。