Java線程問(wèn)題解析:如何保證線程B及時(shí)看到線程A的修改?
1.引言
大家好!今天我來(lái)和大家聊一聊一個(gè)在Java面試中常常出現(xiàn)的經(jīng)典問(wèn)題——線程B怎么知道線程A修改了變量?
這個(gè)問(wèn)題非常典型,面試官常常用這個(gè)問(wèn)題來(lái)考察候選人對(duì)Java線程之間通信和共享數(shù)據(jù)的理解。作為一個(gè)資深程序員,我也常常遇到這個(gè)問(wèn)題,今天就來(lái)和大家分享一下解決方案。接下來(lái),我們會(huì)從四個(gè)方面詳細(xì)探討這個(gè)問(wèn)題:volatile修飾變量、synchronized修飾修改變量的方法、wait/notify以及while輪詢。讓我們從一個(gè)簡(jiǎn)單的故事開(kāi)始,一步步解開(kāi)這個(gè)謎題。
圖片
2.線程 A 修改了變量,線程 B 怎么知道?
假設(shè)你和你的同事在一個(gè)項(xiàng)目中負(fù)責(zé)不同的任務(wù),你負(fù)責(zé)A模塊,他負(fù)責(zé)B模塊。你們倆需要共享一些數(shù)據(jù),每當(dāng)你修改了A模塊的數(shù)據(jù)時(shí),B模塊應(yīng)該能夠及時(shí)感知到這個(gè)變化并做出反應(yīng)。但如果不加任何同步機(jī)制,這種數(shù)據(jù)的共享就變得非常麻煩,因?yàn)镴ava內(nèi)存模型的存在,使得A線程修改的變量可能并不會(huì)立刻反映到B線程的視野中。
問(wèn)題:線程A修改了變量X,但線程B不一定能立即看到X的變化,這個(gè)時(shí)候,B該怎么辦呢?接下來(lái),我將從四個(gè)不同的角度,幫你全面理解這個(gè)問(wèn)題。
3.使用volatile修飾變量
volatile是最常見(jiàn)的解決方案之一。它的作用是確保一個(gè)變量的修改對(duì)其他線程是立即可見(jiàn)的。簡(jiǎn)單來(lái)說(shuō),當(dāng)線程A修改了某個(gè)被volatile修飾的變量時(shí),線程B能夠立刻看到變量的變化。
那么為什么會(huì)有這個(gè)效果呢?
當(dāng)你在一個(gè)變量前加上volatile關(guān)鍵字時(shí),它的變化會(huì)被寫(xiě)入主內(nèi)存,而不是保存在線程的本地緩存中。這樣,當(dāng)線程B去讀取這個(gè)變量時(shí),它會(huì)直接從主內(nèi)存讀取,而不是從緩存中讀取,因此B總能看到A線程對(duì)變量的最新修改。
代碼示例:
圖片
在這個(gè)例子中,線程A修改了flag變量,而線程B則通過(guò)while循環(huán)不斷檢測(cè)flag的值。當(dāng)線程A修改了flag時(shí),由于flag被volatile修飾,線程B能立刻看到變化。
總結(jié):volatile關(guān)鍵字確保了對(duì)變量的修改對(duì)其他線程是立刻可見(jiàn)的,但它僅僅適用于一些簡(jiǎn)單的共享變量場(chǎng)景。對(duì)于復(fù)雜的共享狀態(tài),它并不適用。
4.使用synchronized修飾修改變量的方法
我們知道,synchronized是Java中用于處理線程同步的關(guān)鍵字,能夠保證在同一時(shí)刻只有一個(gè)線程執(zhí)行被修飾的方法或代碼塊。當(dāng)一個(gè)線程獲得了某個(gè)對(duì)象的鎖后,其他線程就必須等待該鎖釋放才能繼續(xù)執(zhí)行。這對(duì)于解決線程之間的共享數(shù)據(jù)問(wèn)題非常有效。
代碼示例:
圖片
在這個(gè)例子中,increment方法被synchronized修飾,確保每次只有一個(gè)線程可以修改count。線程B在讀取count時(shí)需要獲取鎖,確保它讀取的值是最新的。
總結(jié):synchronized關(guān)鍵字不僅保證了對(duì)共享資源的同步訪問(wèn),也確保了線程B能夠讀取到線程A修改后的最新變量。
5.使用wait/notify實(shí)現(xiàn)線程間通信
在多線程編程中,wait和notify是一對(duì)非常有用的工具,可以實(shí)現(xiàn)線程間的等待和通知機(jī)制。這是一種更加靈活的線程間通信方式,能夠讓一個(gè)線程在滿足某些條件時(shí)等待,而另一個(gè)線程則可以通過(guò)notify或notifyAll來(lái)通知正在等待的線程。
代碼示例:
圖片
在這個(gè)例子中,線程A修改了flag后,通過(guò)notify方法通知線程B,而線程B則通過(guò)wait進(jìn)入等待狀態(tài),直到flag的值被修改為true。
總結(jié):使用wait/notify機(jī)制,線程間的通信更加高效,線程B能在適當(dāng)?shù)臅r(shí)機(jī)及時(shí)感知線程A的狀態(tài)變化。
6.使用while輪詢
最后,我們來(lái)看一下while輪詢的方式。雖然它不像volatile、synchronized和wait/notify那樣顯得優(yōu)雅和高效,但它在某些簡(jiǎn)單的場(chǎng)景中仍然有效。
在這種方法中,線程B通過(guò)一個(gè)while循環(huán)不斷地檢查某個(gè)條件是否滿足,直到條件滿足為止。需要注意的是,while輪詢很容易導(dǎo)致CPU的浪費(fèi),因此通常需要配合Thread.sleep()來(lái)減少不必要的資源占用。
代碼示例:
圖片
總結(jié):while輪詢是一種簡(jiǎn)單的方式,但它不如volatile和synchronized那樣高效,且容易造成性能問(wèn)題。
END
通過(guò)這四種方式:volatile、synchronized、wait/notify和while輪詢,我們可以讓線程B及時(shí)知道線程A修改了變量。每種方式都有其優(yōu)缺點(diǎn),選擇合適的方式取決于具體場(chǎng)景的需求。