Java 深入淺出分析Synchronized原理與Callable接口
一、基本特點(diǎn)
1. 開始時(shí)是樂觀鎖, 如果鎖沖突頻繁, 就轉(zhuǎn)換為悲觀鎖.
2. 開始是輕量級鎖實(shí)現(xiàn), 如果鎖被持有的時(shí)間較長, 就轉(zhuǎn)換成重量級鎖.
3. 實(shí)現(xiàn)輕量級鎖的時(shí)候大概率用到的自旋鎖策略
4. 是一種不公平鎖
5. 是一種可重入鎖
6. 不是讀寫鎖
二、加鎖工作過程
JVM 將 synchronized 鎖分為 無鎖、偏向鎖、輕量級鎖、重量級鎖狀態(tài)。會根據(jù)情況,進(jìn)行依次升級。

偏向鎖
假設(shè)男主是一個(gè)鎖, 女主是一個(gè)線程. 如果只有這一個(gè)線程來使用這個(gè)鎖, 那么男主女主即使不領(lǐng)證 結(jié)婚(避免了高成本操作), 也可以一直幸福的生活下去. 但是女配出現(xiàn)了, 也嘗試競爭男主, 此時(shí)不管領(lǐng)證結(jié)婚這個(gè)操作成本多高, 女主也勢必要把這個(gè)動作 完成了, 讓女配死心
偏向鎖不是真的 "加鎖", 只是給對象頭中做一個(gè) "偏向鎖的標(biāo)記", 記錄這個(gè)鎖屬于哪個(gè)線程. 如果后續(xù)沒有其他線程來競爭該鎖, 那么就不用進(jìn)行其他同步操作了(避免了加鎖解鎖的開銷) 如果后續(xù)有其他線程來競爭該鎖(剛才已經(jīng)在鎖對象中記錄了當(dāng)前鎖屬于哪個(gè)線程了, 很容易識別 當(dāng)前申請鎖的線程是不是之前記錄的線程), 那就取消原來的偏向鎖狀態(tài), 進(jìn)入一般的輕量級鎖狀態(tài)
偏向鎖本質(zhì)上相當(dāng)于 "延遲加鎖" . 能不加鎖就不加鎖, 盡量來避免不必要的加鎖開銷. 但是該做的標(biāo)記還是得做的, 否則無法區(qū)分何時(shí)需要真正加鎖
偏向鎖不是真的加鎖, 而只是在鎖的對象頭中記錄一個(gè)標(biāo)記(記錄該鎖所屬的線程). 如果沒有其他線 程參與競爭鎖, 那么就不會真正執(zhí)行加鎖操作, 從而降低程序開銷. 一旦真的涉及到其他的線程競 爭, 再取消偏向鎖狀態(tài), 進(jìn)入輕量級鎖狀態(tài)
輕量級鎖
隨著其他線程進(jìn)入競爭, 偏向鎖狀態(tài)被消除, 進(jìn)入輕量級鎖狀態(tài)(自適應(yīng)的自旋鎖). 此處的輕量級鎖就是通過 CAS 來實(shí)現(xiàn).
通過 CAS 檢查并更新一塊內(nèi)存 (比如 null => 該線程引用)
如果更新成功, 則認(rèn)為加鎖成功
如果更新失敗, 則認(rèn)為鎖被占用, 繼續(xù)自旋式的等待(并不放棄 CPU).
自旋操作是一直讓 CPU 空轉(zhuǎn), 比較浪費(fèi) CPU 資源. 因此此處的自旋不會一直持續(xù)進(jìn)行, 而是達(dá)到一定的時(shí)間/重試次數(shù), 就不再自旋了. 也就是所謂的 "自適應(yīng)"
重量級鎖
如果競爭進(jìn)一步激烈, 自旋不能快速獲取到鎖狀態(tài), 就會膨脹為重量級鎖 此處的重量級鎖就是指用到內(nèi)核提供的 mutex .
執(zhí)行加鎖操作, 先進(jìn)入內(nèi)核態(tài).
在內(nèi)核態(tài)判定當(dāng)前鎖是否已經(jīng)被占用
如果該鎖沒有占用, 則加鎖成功, 并切換回用戶態(tài).
如果該鎖被占用, 則加鎖失敗. 此時(shí)線程進(jìn)入鎖的等待隊(duì)列, 掛起. 等待被操作系統(tǒng)喚醒.
經(jīng)歷了一系列的滄海桑田, 這個(gè)鎖被其他線程釋放了, 操作系統(tǒng)也想起了這個(gè)掛起的線程, 于是喚醒 這個(gè)線程, 嘗試重新獲取鎖
三、其他的優(yōu)化操作
鎖消除
編譯器+JVM 判斷鎖是否可消除. 如果可以, 就直接消除
有些應(yīng)用程序的代碼中, 用到了 synchronized, 但其實(shí)沒有在多線程環(huán)境下. (例如 StringBuffer)
StringBuffer sb = new StringBuffer();
sb.append("a");
sb.append("b");
sb.append("c");
sb.append("d");
此時(shí)每個(gè) append 的調(diào)用都會涉及加鎖和解鎖. 但如果只是在單線程中執(zhí)行這個(gè)代碼, 那么這些加 鎖解鎖操作是沒有必要的, 白白浪費(fèi)了一些資源開銷.
鎖粗化
一段邏輯中如果出現(xiàn)多次加鎖解鎖, 編譯器 + JVM 會自動進(jìn)行鎖的粗化.

領(lǐng)導(dǎo), 給下屬交代工作任務(wù)
方式一:
打電話, 交代任務(wù)1, 掛電話.
打電話, 交代任務(wù)2, 掛電話.
打電話, 交代任務(wù)3, 掛電話
方式二:
打電話, 交代任務(wù)1, 任務(wù)2, 任務(wù)3, 掛電話
四、Callable 接口
Callable 是什么
Callable 是一個(gè) interface . 相當(dāng)于把線程封裝了一個(gè) "返回值". 方便程序猿借助多線程的方式計(jì)算 結(jié)果.
Callable 和 Runnable 相對, 都是描述一個(gè) "任務(wù)". Callable 描述的是帶有返回值的任務(wù), Runnable 描述的是不帶返回值的任務(wù).Callable 通常需要搭配 FutureTask 來使用. FutureTask 用來保存 Callable 的返回結(jié)果. 因?yàn)?Callable 往往是在另一個(gè)線程中執(zhí)行的, 啥時(shí)候執(zhí)行完并不確定. FutureTask 就可以負(fù)責(zé)這個(gè)等待結(jié)果出來的工作.
代碼示例: 創(chuàng)建線程計(jì)算 1 + 2 + 3 + ... + 1000, 不使用 Callable 版本
public class Text {
static class Result{
public int sum = 0;
public Object locker = new Object();
}
public static void main(String[] args) throws InterruptedException {
Result result = new Result();
Thread t = new Thread(){
@Override
public void run() {
int sum = 0;
for (int i = 0; i <=10000; i++){
sum += i;
}
result.sum = sum;
synchronized (result.locker){
result.locker.notify();
}
}
};
t.start();
synchronized (result.locker){
while (result.sum == 0){
result.locker.wait();
}
}
System.out.println(result.sum);
}
}
代碼示例: 創(chuàng)建線程計(jì)算 1 + 2 + 3 + ... + 1000, 使用 Callable 版本
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Text1 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable<Integer> callable = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i <=1000; i++){
sum += i;
}
return sum;
}
};
//由于Thread不能直接傳一個(gè)callable實(shí)例,就需要一個(gè)輔助類來包裝
FutureTask<Integer> futureTask = new FutureTask<>(callable);
Thread t = new Thread(futureTask);
t.start();
//嘗試在主線程獲取結(jié)果
//如果FutureTask中的結(jié)果還沒生成。此時(shí)就會阻塞等待
//一直等到最終的線程把這個(gè)結(jié)果算出來,get返回
Integer result = futureTask.get();
System.out.println(result);
}
}
到此這篇關(guān)于Java 深入淺出分析Synchronized原理與Callable接口的文章就介紹到這了,更多相關(guān)Java Synchronized 內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
centos7如何通過systemctl啟動springboot服務(wù)代替java -jar方式啟動
這篇文章主要介紹了centos7如何通過systemctl啟動springboot服務(wù)代替java -jar方式啟動,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2024-01-01
springboot集成spring cache緩存示例代碼
本篇文章主要介紹了springboot集成spring cache示例代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-05-05
Java 中如何使用 JavaFx 庫標(biāo)注文本顏色
這篇文章主要介紹了在 Java 中用 JavaFx 庫標(biāo)注文本顏色,在本文中,我們將了解如何更改標(biāo)簽的文本顏色,并且我們還將看到一個(gè)必要的示例和適當(dāng)?shù)慕忉?,以便更容易理解該主題,需要的朋友可以參考下2023-05-05
淺談java多態(tài)的實(shí)現(xiàn)主要體現(xiàn)在哪些方面
下面小編就為大家?guī)硪黄獪\談java多態(tài)的實(shí)現(xiàn)主要體現(xiàn)在哪些方面。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-09-09
MyBatis中XML 映射文件中常見的標(biāo)簽說明
這篇文章主要介紹了MyBatis中XML 映射文件中常見的標(biāo)簽說明,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-07-07
Swagger-boostrap-ui如何配置用戶名密碼訪問
這篇文章主要介紹了Swagger-boostrap-ui如何配置用戶名密碼訪問,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-05-05
Java RPC框架如何實(shí)現(xiàn)客戶端限流配置
這篇文章主要介紹了Java RPC框架如何實(shí)現(xiàn)客戶端限流配置,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-02-02
Java基本數(shù)據(jù)類型與類型轉(zhuǎn)換實(shí)例分析
這篇文章主要介紹了Java基本數(shù)據(jù)類型與類型轉(zhuǎn)換,結(jié)合實(shí)例形式分析了Java基本數(shù)據(jù)類型分類、用法,類型轉(zhuǎn)換及相關(guān)操作注意事項(xiàng),需要的朋友可以參考下2020-04-04

