微吼云上線多路互動直播服務 加速多場景互動直播落地
619
2025-03-31
1.Volatile簡介
Volatile是Java虛擬機提供的輕量級的同步機制。
1.1 Volatile理解
volatile變量是Java語言提供的一種的同步機制,即用來確保變量的變更的操作時通知到其他的線程。當使用volatile關(guān)鍵字修飾變量的時候,編譯期以及運行期都會注意到這個變量是共享的,因此不會將這個變量上的操作與其他內(nèi)存操作一起重新排序。
并且volatile關(guān)鍵字修飾的變量不會被緩存進入寄存器或者其他處理器不可見的地方,在讀取volatile關(guān)鍵字修飾的變量時總會返回最新寫入的值。
1.2 Volatile的可見性和原子性
1.2.1 保證可見性
如上面代碼中,如果不加volatile關(guān)鍵字,當前線程無法感知主線程中的num的變化,當主線程中tem變量已經(jīng)變?yōu)?的時候,線程A中的tem變量并沒有發(fā)現(xiàn)tem已經(jīng)發(fā)生了變化,會繼續(xù)執(zhí)行while中的循環(huán),一直一直循環(huán)下去。。。但是當加了volatile關(guān)鍵詞后,tem變量就會對線程A保證了可見性,當發(fā)現(xiàn)tem變量發(fā)生變化不為0后,就自動跳出循環(huán)結(jié)束執(zhí)行任務。
private volatile static int tem = 0; public static void main(String[] args) throws InterruptedException { new Thread(()->{ while (tem == 0){ // System.out.println("變量num==0,執(zhí)行"+Thread.currentThread().getName()); } },"線程A").start(); TimeUnit.SECONDS.sleep(1); tem = 1; System.out.println(tem); }
注:while循環(huán)中不能加println打印方法,因為println方法是加鎖的,有同步代碼塊會保證變量修改的可見性,在變量num修改后會立刻刷回到主存中,因此要測試就不能加打印語句。
public void println(String x) { synchronized (this) { print(x); newLine(); } }
1.2.2 不保證原子性
1.2.2.1 不保證原子性的小案例
原子性是表示不可分割,執(zhí)行任務的時候不能被打擾,要么同事成功要么同時失敗。
注:yieled()是禮讓方法,是讓當前線程讓出給其他線程來執(zhí)行,由于java語言中有兩個線程是默認執(zhí)行的,一個是main線程,一個是gc線程,由于Thread.yieled()方法是在主線程中執(zhí)行的,只要有其它線程存在,就讓main線程和gc線程讓出cpu給其他線程,這樣main線程就不會執(zhí)行下去,而是禮讓后讓其他線程執(zhí)行,等其他線程執(zhí)行完了后再執(zhí)行。
private static int tem = 0; public static void add(){ tem++; } public static void main(String[] args) { for (int i = 1; i <= 20; i++) { new Thread(()->{ for (int j = 0; j < 100; j++) { add(); // System.out.println("for循環(huán)中"+Thread.currentThread().getName()); } },"線程"+i).start(); } while (Thread.activeCount() > 2){ Thread.yield(); } System.out.println(Thread.currentThread().getName()+ " "+ tem); }
打印結(jié)果為:main 1990
可以看出在不做任何處理的情況下多線程自由執(zhí)行的結(jié)果不為20*100=2000;
1.2.2.2 案例源碼探析
為什么會出現(xiàn)結(jié)果不同的情況?
因為add()方法中只有一行自增的執(zhí)行語句,我們可以通過javap反編譯一下成字節(jié)碼文件,可以看出add()方法實際上會有有四步:
1.2.2.2.1 獲取靜態(tài)變量,或得值
1.2.2.2.2 常量不用管
1.2.2.2.3 自增,+1的操作
1.2.2.2.4 返回改變后的變量,寫回這個變量的值
根據(jù)分析我們可以得出num++;這一行自增代碼并不是原子性操作,而是分為很多步執(zhí)行的,所以多線程的情況下會受到影響。而之所以會比理論值小的結(jié)果是因為線程回寫數(shù)據(jù)到主存中,覆蓋了其他更快執(zhí)行的結(jié)果導致的。
1.2.2.3 那么如何保證結(jié)果為我們想要的2000呢?(如何保證原子性)
1.2.2.3.1 可以通過加synchronized關(guān)鍵字或者加lock鎖
1.2.2.3.2 使用juc中的原子類來處理(推薦)
由于synchronized關(guān)鍵字或者加lock鎖解決更耗費資源,所以我們找到了一種給省資源的方式就是使用juc中的原子類來處理。底層是通過CAS來處理的(直接和操作系統(tǒng)掛鉤,在內(nèi)存中修改值),有很高的的效率。
volatitle和AtomicInteger需要同時使用,不一起使用的話,多運行幾次會出現(xiàn)可見性會體現(xiàn)不出來的情況;
// private volatile static int tem = 0; private volatile static AtomicInteger tem = new AtomicInteger(); public static void add(){ // tem++; tem.getAndIncrement(); } public static void main(String[] args) { for (int i = 1; i <= 20; i++) { new Thread(()->{ for (int j = 0; j < 100; j++) { add(); } }).start(); } while (Thread.activeCount() > 2){ Thread.yield(); } System.out.println(Thread.currentThread().getName()+ " "+ tem); }
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔相應法律責任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實后本網(wǎng)站將在24小時內(nèi)刪除侵權(quán)內(nèi)容。
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔相應法律責任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實后本網(wǎng)站將在24小時內(nèi)刪除侵權(quán)內(nèi)容。