Java 深入淺出分析Synchronized原理與Callable接口
一、基本特點(diǎn)
1. 開始時(shí)是樂觀鎖, 如果鎖沖突頻繁, 就轉(zhuǎn)換為悲觀鎖.
2. 開始是輕量級(jí)鎖實(shí)現(xiàn), 如果鎖被持有的時(shí)間較長, 就轉(zhuǎn)換成重量級(jí)鎖.
3. 實(shí)現(xiàn)輕量級(jí)鎖的時(shí)候大概率用到的自旋鎖策略
4. 是一種不公平鎖
5. 是一種可重入鎖
6. 不是讀寫鎖
二、加鎖工作過程
JVM 將 synchronized 鎖分為 無鎖、偏向鎖、輕量級(jí)鎖、重量級(jí)鎖狀態(tài)。會(huì)根據(jù)情況,進(jìn)行依次升級(jí)。
偏向鎖
假設(shè)男主是一個(gè)鎖, 女主是一個(gè)線程. 如果只有這一個(gè)線程來使用這個(gè)鎖, 那么男主女主即使不領(lǐng)證 結(jié)婚(避免了高成本操作), 也可以一直幸福的生活下去. 但是女配出現(xiàn)了, 也嘗試競爭男主, 此時(shí)不管領(lǐng)證結(jié)婚這個(gè)操作成本多高, 女主也勢必要把這個(gè)動(dòng)作 完成了, 讓女配死心
偏向鎖不是真的 "加鎖", 只是給對(duì)象頭中做一個(gè) "偏向鎖的標(biāo)記", 記錄這個(gè)鎖屬于哪個(gè)線程. 如果后續(xù)沒有其他線程來競爭該鎖, 那么就不用進(jìn)行其他同步操作了(避免了加鎖解鎖的開銷) 如果后續(xù)有其他線程來競爭該鎖(剛才已經(jīng)在鎖對(duì)象中記錄了當(dāng)前鎖屬于哪個(gè)線程了, 很容易識(shí)別 當(dāng)前申請(qǐng)鎖的線程是不是之前記錄的線程), 那就取消原來的偏向鎖狀態(tài), 進(jìn)入一般的輕量級(jí)鎖狀態(tài)
偏向鎖本質(zhì)上相當(dāng)于 "延遲加鎖" . 能不加鎖就不加鎖, 盡量來避免不必要的加鎖開銷. 但是該做的標(biāo)記還是得做的, 否則無法區(qū)分何時(shí)需要真正加鎖
偏向鎖不是真的加鎖, 而只是在鎖的對(duì)象頭中記錄一個(gè)標(biāo)記(記錄該鎖所屬的線程). 如果沒有其他線 程參與競爭鎖, 那么就不會(huì)真正執(zhí)行加鎖操作, 從而降低程序開銷. 一旦真的涉及到其他的線程競 爭, 再取消偏向鎖狀態(tài), 進(jìn)入輕量級(jí)鎖狀態(tài)
輕量級(jí)鎖
隨著其他線程進(jìn)入競爭, 偏向鎖狀態(tài)被消除, 進(jìn)入輕量級(jí)鎖狀態(tài)(自適應(yīng)的自旋鎖). 此處的輕量級(jí)鎖就是通過 CAS 來實(shí)現(xiàn).
通過 CAS 檢查并更新一塊內(nèi)存 (比如 null => 該線程引用)
如果更新成功, 則認(rèn)為加鎖成功
如果更新失敗, 則認(rèn)為鎖被占用, 繼續(xù)自旋式的等待(并不放棄 CPU).
自旋操作是一直讓 CPU 空轉(zhuǎn), 比較浪費(fèi) CPU 資源. 因此此處的自旋不會(huì)一直持續(xù)進(jìn)行, 而是達(dá)到一定的時(shí)間/重試次數(shù), 就不再自旋了. 也就是所謂的 "自適應(yīng)"
重量級(jí)鎖
如果競爭進(jìn)一步激烈, 自旋不能快速獲取到鎖狀態(tài), 就會(huì)膨脹為重量級(jí)鎖 此處的重量級(jí)鎖就是指用到內(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)用都會(huì)涉及加鎖和解鎖. 但如果只是在單線程中執(zhí)行這個(gè)代碼, 那么這些加 鎖解鎖操作是沒有必要的, 白白浪費(fèi)了一些資源開銷.
鎖粗化
一段邏輯中如果出現(xiàn)多次加鎖解鎖, 編譯器 + JVM 會(huì)自動(dòng)進(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 相對(duì), 都是描述一個(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í)就會(huì)阻塞等待 //一直等到最終的線程把這個(gè)結(jié)果算出來,get返回 Integer result = futureTask.get(); System.out.println(result); } }
到此這篇關(guān)于Java 深入淺出分析Synchronized原理與Callable接口的文章就介紹到這了,更多相關(guān)Java Synchronized 內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Java多線程并發(fā)編程 Synchronized關(guān)鍵字
- Java中synchronized用法匯總
- Java并發(fā)系列之JUC中的Lock鎖與synchronized同步代碼塊問題
- Java中線程狀態(tài)+線程安全問題+synchronized的用法詳解
- Java中提供synchronized后為什么還要提供Lock
- Java對(duì)象級(jí)別與類級(jí)別的同步鎖synchronized語法示例
- Java synchronized同步方法詳解
- Java多線程之synchronized同步代碼塊詳解
- Java多線程并發(fā)synchronized?關(guān)鍵字
相關(guān)文章
Java StringBuilder類相關(guān)知識(shí)總結(jié)
這篇文章主要介紹了Java StringBuilder類相關(guān)知識(shí)總結(jié),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-02-02Java實(shí)現(xiàn)的上傳并壓縮圖片功能【可等比例壓縮或原尺寸壓縮】
這篇文章主要介紹了Java實(shí)現(xiàn)的上傳并壓縮圖片功能,可實(shí)現(xiàn)圖片的等比例壓縮或原尺寸壓縮,涉及java文件讀寫、轉(zhuǎn)換、傳輸?shù)认嚓P(guān)操作技巧,需要的朋友可以參考下2018-07-07SpringBoot整合阿里云OSS對(duì)象存儲(chǔ)服務(wù)的實(shí)現(xiàn)
這篇文章主要介紹了SpringBoot整合阿里云OSS對(duì)象存儲(chǔ)服務(wù)的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08聊聊@RequestParam,@PathParam,@PathVariable等注解的區(qū)別
這篇文章主要介紹了聊聊@RequestParam,@PathParam,@PathVariable等注解的區(qū)別,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2021-02-02spring boot在啟動(dòng)項(xiàng)目之后執(zhí)行的實(shí)現(xiàn)方法
在開發(fā)時(shí)有時(shí)候需要在整個(gè)應(yīng)用開始運(yùn)行時(shí)執(zhí)行一些特定代碼,比如初始化環(huán)境,下面這篇文章就來給大家介紹了關(guān)于spring boot在啟動(dòng)項(xiàng)目之后執(zhí)行自己要執(zhí)行的東西的實(shí)現(xiàn)方法,文中給出了詳細(xì)的示例代碼,需要的朋友可以參考下。2017-09-09詳解SpringBoot健康檢查的實(shí)現(xiàn)原理
這篇文章主要介紹了詳解SpringBoot健康檢查的實(shí)現(xiàn)原理,幫助大家更好的理解和學(xué)習(xí)使用SpringBoot框架,感興趣的朋友可以了解下2021-03-03Spring5使用JSR 330標(biāo)準(zhǔn)注解的方法
從Spring3.0之后,除了Spring自帶的注解,我們也可以使用JSR330的標(biāo)準(zhǔn)注解,本文主要介紹了Spring5使用JSR 330標(biāo)準(zhǔn)注解,感興趣的可以了解一下2021-09-09