常見的鎖的概念
- 可重入鎖
- 公平鎖/非公平鎖
- 獨享鎖/共享鎖
- 互斥鎖/讀寫鎖
- 樂觀鎖/悲觀鎖
- 分段鎖
- 偏向鎖/輕量級鎖/重量級鎖
- 自旋鎖
修改 使用鎖 或者同步機制
僅僅給變量添加volatile 是不行的 還會出現多賣少買狀況
synchronized 簡介 :非常經典的處理手段,具體使用有多種形式,它的核心思想就是修飾一個方法或者一段代碼,這段代碼不能同時兩個以上的線程同時運作。
代碼塊 中的this 是調用該方法的對象 一般都是使用代碼塊
了解一下synchronized 直譯過來 是同步的意思 但是我們京城稱呼其為一種鎖,為什麼叫鎖,原則上每個對象都可以持有一把鎖,當某一個線程操作這個對象時, 這個線程就獲得該對象的鎖,在此期間其他線程就無法操作該對象了
Lock
簡介:Lock是自JDK1.5 之後推出的一個接口,當熱也是一系列子實作類,接口非常重要,定義了Java所認定的鎖的概念,跟synchronized有顯著差別,
synchronized是一個關鍵字,使用它可以實作類似鎖的效果,但是嚴格的來說它并不是鎖,Lock是一個接口,較長的描述了鎖的概念,其中重點包含若幹方法,這些方法就是核心内容了。
方法:
lock():這個方法用來加鎖,或者可以了解為擷取對象的鎖,如果擷取成功,就執行下面的代碼,若擷取失敗則一直等待,重複嘗試擷取
lockInterruptibly(); 也是嘗試擷取鎖,但是對應帶了Interrupter這個部分,是以對于擷取鎖的結果會進行進一步工作;
tryLock(); 嘗試擷取鎖,傳回值boolean類型,會傳回擷取鎖的結果,跟lock()不一樣,如果擷取失敗,傳回false ,并且不會繼續嘗試擷取;
unlock():解鎖,運作之後即可釋放鎖,就能讓其他線程擷取了;
ReentrantLock:
是最常用的子實作類,加強對它的熟悉字面翻譯它叫重入鎖,确實它也是符合重入鎖的要求,但是它僅僅可以表示重入鎖。
可重入鎖:
是一個概念,可重用鎖在大部分時候并不直接表示RenntrantLock,可重用鎖強調的是一種類型,這個類型關鍵特點是可重入,什麼是可重入?
就是一個已經獲得鎖的線程還能繼續調用加鎖的代碼。
這裡我們強調可重入是一種典型的鎖的類型,我們自己可以編碼實作一些可重入鎖,在實際項目中它的運用可以展現靈活性等等諸多優勢點,就不一一展開。
ThreadDemo類
/**
* @Author: Jiangjun
* @Date: 2019/10/7 11:53
* @Description: 描述購票的邏輯
*/
public class ThreadDemo implements Runnable{
Lock lock = new ReentrantLock();
volatile int num = 20;
@Override
public void run() {
lock.lock();
while (num>0){
addlock();
System.out.println(Thread.currentThread().getName() + "認為此時還有票可選");
num = num - 1;
System.out.println(Thread.currentThread().getName() + "買到了一張票,還剩" + num);
}
lock.unlock();
}
public void addlock(){
lock.lock();
}
}
主函數:
public class TestMain {
public static void main(String[] args) {
ThreadDemo td = new ThreadDemo();
Thread t1 = new Thread(td,"掌上程式設計");
Thread t2 = new Thread(td,"公衆号");
t1.start();
t2.start();
}
}
公平鎖:
公平=先來後到,先來的擷取鎖,就來的後擷取鎖。
同樣公平鎖也是一種概念,也是一種鎖的類型,如果設計鎖的機制符合公平要求,那麼這個鎖就是公平的鎖。
若适用購票場景,如果強調先來後到,(從性能角度考慮)就必須采取特備的方案來實作這種先後順序,這樣必然會帶來性能損失,是以公平鎖的性能會劣于非公平鎖。
如果我們使用非公平鎖也會帶帶來一定風險,容易造成真實意義上的不公平,就有一些線程老早就在等待了,但是運氣不好一直拿不到鎖,這個可以成為鎖饑餓現象。
是以我們強調根據情況來選擇鎖是否公平,一般如果要避免掉鎖饑餓現象,就要考慮使用公平鎖,否則可以使用非公平鎖。
Synchronized 隻能是非公平鎖
ReentrantLock 是後來加入的類,是以在設計上更加完善,可以直接通過構造函數來制定鎖是否公平。
細節注意:
養成比較規範的編碼習慣,一旦使了Lock,要充分考慮到可能出現的報錯情況,是以一般編碼會這樣寫try…catch…fianlly
public class LockFairThread implements Runnable{
//建立公平鎖
private static ReentrantLock lock = new ReentrantLock(true);
@Override
public void run() {
lock.lock();
try{
System.out.println(Thread.currentThread().getName() + "獲得鎖");
}catch (Exception e){
System.out.println("異常相關提示");
}finally {
lock.unlock();
}
}
}
獨享鎖/共享鎖:
獨享鎖隻能提供一個線程所享用,共享鎖表示鎖可以提供多個線程享用。
這裡會覺得比較奇怪,感覺鎖好像就是提供一個線程使用,這個其實不絕對,比如一些操作是可以共享的,比如我們在修改資料時,必須要獨享,不允許多個地方同時對資料進行修改,容易導緻一些錯誤,但是在讀取資料時,其實可以多線程一起讀。共享鎖主要在一些場合使用,提升一下靈活性。
互斥鎖/讀寫鎖
樂觀鎖/悲觀鎖:
樂觀鎖強調認為并發不一定會導緻資料出現不一緻等問題,是以原則來說就是允許并發的發生,比如允許多個線程同時讀寫同一個變量,但是單純的樂觀相信也是不行的,還要想辦法控制一下。
是以樂觀鎖強調一種思路和理念通過方式來允許并發出現又能夠規避掉出錯的情況,典型的方案就是使用版本号控制,下面就較長的描述一下:
- 線程A檢視庫存,此時讀取的版本号,發現是0.
- 線程B檢視庫存,此時讀取的版本号,發現是0.
- 線程A此時想要修改庫存,是以檢查下庫存了,這時可以把檢查版本号和扣減庫存一次性完成,比如執行如下sql:UPDATE 表明 SET 庫存=庫存-20,version=version+1 WHERE 商品id=…AND version=0
- 執行第三步之後,趕緊檢查一下自己修改是否成功,如果修改失敗,意味着什麼?意味着此時在查詢資料時,根據版本号=0沒有查詢出來,表示在第一步和第三步之間,商品已經被修改了,是以就要撤銷重做。如果修改成功,表示第一步和第三步之間沒有出現問題,商品庫存沒有被其他線程修改過,就可以順利的成功修改。
- 讓B修改商品,此時就會失敗,因為線程A在過程中已經修改過商品庫存了,之前N讀取的庫存已經失效,它需要重新讀取。
- 多線程基礎(線程狀态 、run/start 、 Thread/Runnable)
- 常用方法和經典問題(wait notify join 生産消費者)
- 同步基礎 (volatile synchronized)
- 核心類庫(Callable 集合)
- JUC原子類(LongAdder AtomicReference)
- JUC鎖 (Lock AQS RenntrantLock ReadWriteLock)
- 鎖概念(重入鎖、公平鎖、讀寫鎖、樂觀鎖等等)