程式猿必須要掌握的多執行緒安全問題之鎖策略詳解

2022-10-04 10:09:10 字數 3886 閱讀 1221

樂觀鎖:樂觀鎖假設認為資料一般情況下不會產生併發衝突,所以在資料進行提交更新的時候,才會正 式對資料是否產生併發衝突進行檢測,如果發現併發衝突了,則讓返回使用者錯誤的資訊,讓使用者決定如 何去做。樂觀鎖的效能比較高。

悲觀鎖:總是假設最壞的情況,每次去拿資料的時候都認為別人會修改,所以每次在拿資料的時候都會 上鎖,這樣別人想拿這個資料就會阻塞直到它拿到鎖。

悲觀鎖的問題:總是需要競爭鎖,進而導致發生執行緒切換,掛起其他執行緒;所以效能不高。 樂觀鎖的問題:並不總是能處理所有問題,所以會引入一定的系統複雜度。

樂觀鎖的使用場景:

import j**a.util.concurrent.atomic.atomicinteger;

public class happylock

}公平鎖:鎖的獲取順序必須合執行緒方法的先後順序是儲存一致的,就叫公平鎖 優點:執行時順序的,所以結果是可以預期的

非公平鎖:鎖的獲取方式循序和執行緒獲取鎖的順序無關。優點:效能比較高

按之間的方式處理下,執行緒在搶鎖失敗後進入阻塞狀態,放棄 cpu,需要過很久才能再次被排程。但經過測算,實際的生活中,大部分情況下,雖然當前搶鎖失敗,但過不了很久,鎖就會被釋放。基於這個 事實,自旋鎖誕生了。

你可以簡單的認為自旋鎖就是下面的**

只要沒搶到鎖,就死等。

自旋鎖的缺點:

缺點其實非常明顯,就是如果之前的假設(鎖很快會被釋放)沒有滿足,則執行緒其實是光在消耗 cpu 資源,長期在做無用功的。

可重入鎖的字面意思是「可以重新進入的鎖」,即允許同乙個執行緒多次獲取同一把鎖。比如乙個遞迴函式 裡有加鎖操作,遞迴過程中www.cppcns.com這個鎖會阻塞自己嗎?如果不會,那麼這個鎖就是可重入鎖(因為這個原因 可重入鎖也叫做遞迴鎖)。

j**a裡只要以reentrant開頭命名的鎖都是可重入鎖,而且jdk提供的所有現成的lock實現類,包括

synchronized關鍵字鎖都是可重入的。

面試題:

1.你是怎麼理解樂觀鎖和悲觀鎖的,具體怎麼實現呢?

樂觀鎖——> cas ——> atomic.(cas是由v(記憶體值) a(預期值)b(新值))組成,然後執行的時候是使用v=a對比,如果結果為true,這表明沒有併發衝突,則可以直接進行修改,否則返回錯誤資訊。*

2.有了解什麼讀寫鎖麼?

多執行緒之間,資料的讀取方之間不會產生執行緒安全問題,但資料的寫入方互相之間以及和讀者之間都需 要進行互斥。如果兩種場景下都用同乙個鎖,就會產生極大的效能損耗。所以讀寫鎖因此而產生。

讀寫鎖(readers-writer lock),看英文可以顧名思義,在執行加鎖操作時需要額外表明讀寫意圖,複數讀者之間並不互斥,而寫者則要求與任何人互斥。

把鎖分成兩個鎖,乙個是讀鎖,乙個是寫鎖,其中讀鎖可以多個執行緒擁有,而寫鎖是乙個執行緒擁有

3.什麼是自旋鎖,為什麼要使用自旋鎖策略呢,缺點是什麼?

按之間的方式處理下,執行緒在搶鎖失敗後進入阻塞狀態,放棄 cpu,需要過很久才能再次被排程。但經過測算,實際的生活中,大部分情況下,雖然當前搶鎖失敗,但過不了很久,鎖就會被釋放。基於這個 事實,自旋鎖誕生了。

你可以簡單的認為自旋鎖就是下面的**

只要沒搶到鎖,就死等。

自旋鎖的缺點:

缺點其實非常明顯,就是如果之前的假設(鎖很快會被釋放)沒有滿足,則執行緒其實是光在消耗 cpu 資源,長期在做無用功的。

4.synchronized 是可重入鎖麼?

synchronized 是可重入鎖,

**如下:

public class chonglock }}

}cas: 全稱compare and swap,字面意思:」比較並交換「,乙個 cas 涉及到以下操作:

我們假設記憶體中的原資料v,舊的預期值a,需要修改的新值b。 1. 比較 a 與 v 是否相等。(比較) 2. 如果比較相等,將 b 寫入 v。(交換) 3. 返回操作是否成功。

當多個執行緒同時對某個資源進行cas操作,只能有乙個執行緒操作成功,但是並不會阻塞其他執行緒,其他執行緒只會收到操作失敗的訊號。可見 cas 其實是乙個樂觀鎖。

針對不同的作業系統,jvm 用到了不同的 cas 實現原理,簡單來講:

j**a 的 cas 利用的的是 unsafe 這個類提供的 cas 操作;

unsafe 的 cas 依 賴 了 的 是 jvm 針 對 不 同 的 操 作 系 統 實 現 的 atomic::cmpxchg(乙個原子性的指令)

/atomic::cmpxchg 的實現使用了彙編的 cas 操作,並使用 cpu 硬體提供的 lock 機制保證其原子性。

簡而言之,是因為硬體予以了支援,軟體層面才能做到。

2.3.1 實現自旋鎖

public class spinlock

}public void unlock ()

}用於實現原子類

示例**:

public class atomicinteger

}public class unsafe while(!this.compareandswapint(var1, var2, var5, var5 + var4));

return var5;

}}aba 的問題,就是乙個值從a變成了b又變成了a,而這個期間我們不清楚這個過程。

我來舉乙個例子,如果你向別人轉錢,你需要轉100元,但是你點選了兩次轉錢,第一次會成功,但是第二次肯定會失敗,但是,在你點選第二次轉錢的同一時刻,你的公司給你轉了100元工資,那麼你就會莫名其妙的把100又轉了出去,你丟失了100,別人也沒有獲得100.

**演示:

1.正常轉錢流程

import j**a.util.concurrent.atomic.atomicreference;

public class aba

});t1.start();

//轉賬執行緒2

thread t2 = new thread(new runnable()

}});

t2.start();}}

2.錯誤操作後:

import j**a.util.concurrent.atomic.atomicreference;

public class abas

});t1.start();

t1.join();

//轉入100

thread t3 = new thread(new runnable()

});t3.start();

//轉賬執行緒2

t3.join();

thread t2 = new thread(new runnable()

});t2.start();}}

解決aba方法

解決方法:加入版本資訊,例如攜帶 atomicstampedreference 之類的時間戳作為版本資訊,保證不會

出現老的值。

**實現:

import j**a.util.concurrent.atomic.atomicreference;

import j**a.util.concurrent.akhywutzptomic.atomicstampedreference;

public class abaack

});t1.start();

t1.join();

//轉入100

thread t3 = new thread(new runnable()

});t3.start();

//轉賬執行緒2

t3.join();

thread t2 = new thread(new runnable()

});t2.start();

//integer的快取記憶體是-128--127(atomicstampedreference)

//如果大於127,那麼就開始new物件了

/** 解決方法,調整邊界值*/}}

以上就是今天要講的內容,本文僅僅簡單介紹了鎖策略,解決執行緒安全。

程式猿必須掌握的git命令

git status s m readme m表示修改過的檔案,沒有add mm rakefile 第乙個m表示修改過沒有add,第二個m表示 add了但是沒有commit a lib git.rb a表示新新增的檔案,已經add但是還沒有commit m lib git.rb m表示修改過但是沒有...

程式設計師必須要掌握的語言

在有許多程式開發工具可供選擇的今天,強調 c c 是程式設計師所必須掌握的語言 難道就沒有人真的敢站出來提出質疑嗎?下面就由卓躍教育為您做乙個介紹 誠然,我們不能否認c c 語言的超凡魅力。然而我們不禁要設問,在windows流行的今天,用windowsc編制出介面獨特 功能強大的應用程式,你能嗎?...

設計安全的多執行緒應用程式 執行緒安全

以前常聽高手告誡mfc物件不要跨執行緒使用,因為mfc不是執行緒安全的。比如cwnd物件不要跨執行緒使用,可以用視窗控制代碼 hwnd 代替。csocket casyncsocket物件不要跨執行緒使用,用socket控制代碼代替.那麼到底什麼是執行緒安全呢?什麼時候需要考慮?如果程式涉及到多 執行...