【多線程】什么是悲觀鎖和樂觀鎖
悲觀鎖
概念
悲觀鎖在已最壞的打算來考慮結果,它會在每次資源操作的同時,都需要對他進行加鎖,避免其他的線程來搶占。在絕對上保證我這次執行是沒有問題的。
適用場景
悲觀鎖適用于競爭激勵的場景,例如高并發的讀寫操作。
典型案例
synchronized 關鍵字
public class TestLock implements Runnable{
private static Object object = new Object();
public static void main(String[] args) throws InterruptedException {
Thread th01 = new Thread(new TestLock());
Thread th02 = new Thread(new TestLock());
th01.start();
th02.start();
}
@Override
public void run() {
synchronized (object){
System.out.println(Thread.currentThread().getName()+"獲取到了鎖");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"釋放了鎖");
}
}
輸出結果
Thread-0獲取到了鎖
Thread-0釋放了鎖
Thread-1獲取到了鎖
Thread-1釋放了鎖
可以看到
當線程Thread-0獲取到鎖的時候,Thread-1會陷入到阻塞狀態,因為這個時候synchronized發揮的是悲觀鎖,所以Thread-0在執行進入到鎖的同時,其他線程是無法獲取到鎖的狀態的
Lock 接口
public class TestLock implements Runnable{
private static Lock lock = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
Thread th01 = new Thread(new TestLock());
Thread th02 = new Thread(new TestLock());
th01.start();
th02.start();
}
@Override
public void run() {
lock.lock();
System.out.println(Thread.currentThread().getName()+"獲取到了鎖");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.unlock();
System.out.println(Thread.currentThread().getName()+"釋放了鎖");
}
}
Lock與synchronized的作用是一樣的,都是悲觀鎖,但是Lock的優勢要比
synchronized強。平時我們要實現一個悲觀鎖,推薦使用Lock來使用。具體的優勢可以我這里就不再過多的闡述,后續會再會出一片這個文章來專門講。
樂觀鎖
概念
樂觀鎖在Java的線程中,并不會真正的上鎖,而是通過以判斷初始值的方式來判斷當前操作的線程是否有被其他線程來操作過,當樂觀鎖判斷本次執行結束后發現初始值和當時開始執行的初始值一致的時候,那么就代表這次線程執行中,修改的值沒有被其他線程修改過,如果不一致那么就是被修改過。
適用場景
樂觀鎖適用于讀比較多,但是寫操作比較少的情況,并且并發也不會很高的時候。
典型案例
原子類
AtomicInteger
public class TestLock{
private static AtomicInteger integer = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
Thread th01 = new Thread(()->{
while (integer.get()<10000){
System.out.println(Thread.currentThread().getName()+"輸出"+integer.addAndGet(1));
}
});
Thread th02 = new Thread(()->{
while (integer.get()<10000){
System.out.println(Thread.currentThread().getName()+"輸出"+integer.addAndGet(1));
}
});
th01.start();
th02.start();
th01.join();
th02.join();
System.out.println(integer.get());
}
}
上面這段代碼其實就是簡單的實現了一個類似于i + + 的操作,但是為什么需要用到
AtomicInteger來代替i + + 來實現呢,使用i + + 豈不是更簡單快捷一點的嗎?其實這里用AtomicInteger的原因就是因為i + + 它不是原子操作,在多線程的環境下會出現少加的情況,但是AtomicInteger就不一樣,他是基于CAS算法來實現樂觀鎖的,并且保證了每次在進行i + + 的情況下會得倒自己想要的值,接下來我們可以來看一下AtomicInteger的實現 + + i的流程。

在AtomicInteger的代碼中,是通過調用compareAndSwapInt方法來實現上面這個流程的,這個方法用了一種比較并交換的機制(Compare And Swap),在這個方法中有幾個參數:
var1:傳入AtomicInteger對象
var2:AtomicInteger中變量的偏移地址
var5:修改之前的AtomicInteger中的值
var5+var4:預期結果
在compareAndSwapInt開始執行的時候,會先根據傳入的AtomicInteger對象和AtomicInteger中變量的偏移地址來獲取現在AtomicInteger內存中保存的正確的值,然后和修改之前獲取到的AtomicInteger值比較,如果一致那么就開始執行var5+var4,獲取預期結果,如果不一致,就會重頭再來。
++例如上面的流程,我們再用文字闡述一遍++
步驟一:初始的AtomicInteger的值為0
步驟二:線程A開始執行,獲取到AtomicInteger的值為0;
步驟三:線程A執行暫停,線程B開始獲取AtomicInteger的值為0;
步驟四:線程B開始執行 + +
i操作,獲取到值為1,并修改了AtomicInteger的值
步驟五:線程B執行完畢,切換到線程A。
步驟六:線程A開始執行 + + i操作,這個時候線程A會再獲取一次AtomicInteger的值,發現AtomicInteger的值為1,與修改之前獲得的AtomicInteger的值0不同了。比較失敗,返回false,繼續循環。直到下次只執行的AtomicInteger的值等于修改之前的值,便執行成功。

浙公網安備 33010602011771號