亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

一文搞懂Java中的線程安全與線程同步

 更新時間:2022年06月23日 14:05:02   作者:淵渟岳  
線程安全指在被多個線程訪問時,程序可以持續(xù)進行正確的處理。線程同步是指程序中用于控制不同線程間操作發(fā)生相對順序的機制。本文將通過示例詳細為大家講講二者的使用,需要的可以參考一下

1.為什么需要線程同步

什么是線程安全:指在被多個線程訪問時,程序可以持續(xù)進行正確的處理。

線程安全問題

案例:通過搶優(yōu)惠例子說明線程安全問題

圖片

public?class?Demo1?{

????public?static?void?main(String[]?args)?{
????????//?簡單模擬20人搶優(yōu)惠
????????for(int?i=0;i<20;i++){
????????????new?Thread(new?ThreadDemo()).start();
????????}
????}

}
//?前十位可以獲取優(yōu)惠,憑號碼兌換優(yōu)惠
class?ThreadDemo?implements?Runnable{
????private?static?Integer?num?=?10;
????@Override
????public?void?run()?{
????????try?{
????????????Thread.sleep(10);
????????}?catch?(InterruptedException?e)?{
????????????e.printStackTrace();
????????}
????????if(num<=0){
????????????System.out.println("已被搶完,下次再來");
????????????return;
????????}
????????System.out.println(Thread.currentThread().getName()+"用戶搶到的號碼:"+num--);
????}
}

執(zhí)行結(jié)果:出現(xiàn)的問題

  • 同一個優(yōu)惠號碼可能被多次獲取到;
  • 優(yōu)惠號碼可能獲取到0 和負數(shù),類似超買超賣

并發(fā)訪問線程不安全的共享變量時,會出現(xiàn)如上的常見問題。

避免問題的產(chǎn)生

  • 共享變量設(shè)計為不可變的(final)
  • 編程過程不修改共享變量(不修改)
  • 將對象設(shè)計為無狀態(tài)的(無狀態(tài))
  • 在修改共享變量時使用線程同步(通過鎖實現(xiàn))

2.怎么實現(xiàn)線程同步

線程同步是指程序中用于控制不同線程間操作發(fā)生相對順序的機制。

2.1.使用volatile關(guān)鍵字

volatile是輕量級的synchronized,對于共享變量可以通過volatile關(guān)鍵字來實現(xiàn)線程同步。

共享變量處理情況:總線先從主內(nèi)存拷貝共享變量到線程私有的工作內(nèi)存,再交由處理器進行處理,處理完成后再從工作內(nèi)存將運算結(jié)果回寫主內(nèi)存。工作內(nèi)存起到臨時緩存數(shù)據(jù)和指令的作用、并且線程私有,這就會存在緩存不一致問題。

實現(xiàn)原理:有volatile變量修飾的共享變量進行寫操作的時候會多出一行l(wèi)ock 前綴指令的匯編代碼,lock前綴指令會直接鎖緩存行,起到內(nèi)存屏障的效果,并使處理器立即執(zhí)行緩存回寫到主內(nèi)存的操作,并且導(dǎo)致其他處理器的緩存失效,需要重新從主內(nèi)存獲取最新值。

附上一張圖便于理解

圖片

Java線程需要由操作系統(tǒng)內(nèi)核線程調(diào)度器進行調(diào)度,并不是直接訪問處理器資源,圖中僅展示關(guān)鍵的幾個點。

怎么輸出匯編指令

windows:下載hsdis-amd64.dll 提取碼:w5a2,并放在目錄 jdk1.8.0_181\jre\bin\server

在idea 工具中配置VM:-Xcomp -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -XX:CompileCommand=compileonly,*TestVolatile.main

//?測試代碼
public?class?TestVolatile?{
????private?static?volatile?int?num?=?0;

????public?static?void?main(String[]?args)?{
????????num--;
????????System.out.println("結(jié)果為:"?+?num);
????}
}

執(zhí)行結(jié)果:lock add dword ptr [rsp]

0x000000000320606d: lock add dword ptr [rsp],0h ;*putstatic num ; - com.yty.concurrent.synchronizeddemo.TestVolatile::main@5 (line 7)

通過緩存鎖定和緩存一致性協(xié)議實現(xiàn)可見性,確保多線程程序讀寫共享變量的時候,每個CPU看到的都是最新值。

MESI 高速緩存一致性協(xié)議,處理器使用嗅探技術(shù)保證緩存、主內(nèi)存和其他處理器緩存的數(shù)據(jù)一致。還有其他的緩存一致性協(xié)議,比如:AMD的MOESI協(xié)議、Intel的MOSIF協(xié)議。

MESI 分別表示:

  • M(Modify):修改
  • E(Exclusive):獨占、互斥
  • S(Shared):共享
  • I(Invalid):無效

volatile 關(guān)鍵字的另一個作用是禁止編譯器或處理器對指令進行重排序優(yōu)化。

什么是重排序:重排序是指編譯器和處理器為了優(yōu)化程序性能而對指令序列進行重新排序的一種手段。說白了,如果在編譯或運行期間發(fā)生重排序,那么程序就可能不按照編寫的順序執(zhí)行,會出現(xiàn)意料之外的執(zhí)行結(jié)果。

編譯器和處理器不會對存在數(shù)據(jù)依賴關(guān)系的操作做重排序,一般重排序可能發(fā)生在沒有數(shù)據(jù)依賴關(guān)系的指令之中。就算數(shù)據(jù)之間沒有依賴關(guān)系,在多線程場景中若發(fā)生指令重排序優(yōu)化,依然存在影響到最終執(zhí)行結(jié)果的情況。當(dāng)多線程場景下存在這種情況時,就需要使用volatile關(guān)鍵字等其他手段去禁止編譯器和處理器對指令重排序優(yōu)化。

實現(xiàn)原理是在編譯器在生成字節(jié)碼時,會在指令序列中插入內(nèi)存屏障禁止在內(nèi)存屏障前后的指令執(zhí)行重排序優(yōu)化。

回來到一開始的案例問題,給線程類的共享變量 private static Integer num = 10; 加上volatile關(guān)鍵字能實現(xiàn)線程安全嗎?

private?static?volatile?Integer?num?=?10;

答案是:不能

原因是:指令【num--】看似一條指令,其實分成三步執(zhí)行:先獲取、再計算、后保存,所以自減和自增都不是原子性操作,而volatile 無法確保其原子性操作,所以使用volatile關(guān)鍵字無法確保該情況下的線程安全,需要使用鎖來實現(xiàn)原子性操作。

2.2.使用synchronized關(guān)鍵字

synchronized塊是Java提供的一種原子性內(nèi)置鎖,Java中的每個對象都可以把它當(dāng)作一個同步鎖來使用,也稱為監(jiān)視器鎖。synchronized 同步代碼塊中的操作被看為原子性操作。同步鎖是一種排它鎖、獨占鎖、可重入鎖,當(dāng)一個線程獲取這個鎖后,其他線程必須等待該線程釋放鎖后才能獲取該鎖,并支持鎖釋放后可以再次獲取鎖。

使用方式

private?Integer?num?=?1;
private?static?Integer?num2?=?1;

public?synchronized?void?test1(){
????System.out.println("普通方法"?+?num++);
}
public?static?synchronized?void?test2(){
????System.out.println("靜態(tài)方法"?+?num2++);
}

public?void?test1_1(){
????synchronized?(this){
????????System.out.println("鎖當(dāng)前對象"+?num++);
????}
????synchronized?(TestSynchronized.class){
????????System.out.println("鎖當(dāng)前類"?+?num++);
????}
????synchronized?(num){
????????System.out.println("鎖指定變量"?+?num++);
????}
}

synchronized 關(guān)鍵字加在普通方法時,是給當(dāng)前實例對象上鎖;

synchronized 關(guān)鍵字加在靜態(tài)方法時,是給當(dāng)前Class類對象上鎖;

synchronized 關(guān)鍵字加在同步代碼塊時,是給括號里配置的對象上鎖。

注意:這個Class類對象指的是每個類在類加載過程中生成的Class類。

原理簡析

在synchronized的同步代碼塊中,入口處執(zhí)行了monitorenter,在其出口執(zhí)行了monitorexit,虛擬機中的每個Object實例都有一個monitor(監(jiān)視器鎖);而同步方法通過字節(jié)碼flags 標記該方法為ACC_SYNCHRONIZED,表明執(zhí)行該方法時需要獲取到監(jiān)視器鎖才可以執(zhí)行。兩者的本質(zhì)都是對一個對象的監(jiān)視器(monitor)的獲取和釋放。

可以使用javap -c -v xxx.class 查看字節(jié)碼信息

同步代碼塊

圖片

同步方法

圖片

關(guān)于一開始的例子,可以寫成

public?class?Demo1?{

????public?static?void?main(String[]?args)?{
????????//?簡單模擬20人搶優(yōu)惠
????????for(int?i=0;i<20;i++){
????????????new?Thread(new?ThreadDemo()).start();
????????}
????}

}
//?前十位可以獲取優(yōu)惠,憑號碼兌換優(yōu)惠
class?ThreadDemo?implements?Runnable{
????private?static?Integer?num?=?10;
????@Override
????public?void?run()?{
????????try?{
????????????Thread.sleep(10);
????????}?catch?(InterruptedException?e)?{
????????????e.printStackTrace();
????????}
????????synchronized?(num){
????????????if(num<=0){
????????????????System.out.println("已被搶完,下次再來");
????????????????return;
????????????}
????????????System.out.println(Thread.currentThread().getName()+"用戶搶到的號碼:"+num--);
????????}
????}
}

或者改為

synchronized?(ThreadDemo.class){
????if(num<=0){
????????System.out.println("已被搶完,下次再來");
????????return;
????}
????System.out.println(Thread.currentThread().getName()+"用戶搶到的號碼:"+num--);
}

注意:num 變量是static變量,是屬于類的變量,需要鎖住變量對象或Class類,單獨或組合使用synchronized (this) 和volatile都無法確保其線程安全,這個可自行驗證。

案例:對象單例實現(xiàn)

public?class?SingletonDemo?{
????private?static?volatile?SingletonDemo?singletonDemo;
????public?SingletonDemo(){
????}
????public?static?SingletonDemo?getInstance(){
????????if(singletonDemo==null)
????????????synchronized?(SingletonDemo.class){
????????????????if?(singletonDemo==null)
????????????????????singletonDemo?=?new?SingletonDemo();
????????????}
????????return?singletonDemo;
????}
}
//?測試
class?Demo{
????public?static?void?main(String[]?args)?{
????????Demo?demo?=?new?Demo();
????????for(int?i=0;i<10000;i++){
????????????demo.test();
????????}
????}
????public?void?test(){
????????new?Thread(()->{
????????????SingletonDemo?instance?=?SingletonDemo.getInstance();
????????????System.out.println(Thread.currentThread().getName()+"="+instance);
????????}).start();
????}
}

說明:volatile 和 synchronized實現(xiàn)雙重鎖校驗。synchronized 起到指令執(zhí)行的原子性和同一時間只能單個線程執(zhí)行;synchronized和volatile都起到內(nèi)存可見性保證;volatile起到禁止指令重排序,指令重排序?qū)е戮€程不安全的可能性較小,但存在可能發(fā)生。

除了簡單易用的synchronized 同步鎖之外,還有其他更靈活的鎖。

篇幅原因,將在下一篇講述關(guān)于Java中的鎖:

圖片

以上就是一文搞懂Java中的線程安全與線程同步的詳細內(nèi)容,更多關(guān)于Java線程安全 線程同步的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評論