【Java 并发编程】线程操作原子性问题 ( 问题业务场景分析 | 使用 synchronized 解决线程原子性问题 )
文章目錄
- 總結(jié)
- 一、原子性問題示例
- 二、線程操作原子性問題分析
- 三、使用 synchronized 解決線程原子性問題
總結(jié)
原子操作問題 : 線程中 , 對變量副本 count 進(jìn)行自增操作 , 不是原子操作 , 首先 從工作內(nèi)存中讀取變量副本到執(zhí)行引擎 ( 操作數(shù)棧 ) 中 , 然后 再 進(jìn)行自增運(yùn)算 , 最后 寫回到線程工作內(nèi)存中 , 這是 333 個操作 , 如果變量 在這 333 個操作的空檔時間進(jìn)行了修改 , 那么就會產(chǎn)生無法預(yù)知的效果 ;
總結(jié)一下 : 線程 A 的變量副本入操作數(shù)棧的時刻 , 該共享變量被線程 B 修改并且同步更新 , 此時入棧的這個變量自增是無效的 , 但是也算自增了 111 次 , 因此這里就丟失了 111 次計算機(jī)會 ;
一、原子性問題示例
開啟 202020 個線程 , 對某個線程共享 int 類型變量進(jìn)行自增 , 每個線程自增 100001000010000 次 , 那么按照正常執(zhí)行 , 202020 個線程執(zhí)行完畢后的變量值應(yīng)該是 200000200000200000 ;
代碼示例 :
public class Main {private volatile static int count = 0;private static void increase() {count++;}public static void main(String[] args) {for (int i = 0; i < 20; i ++) {new Thread(){@Overridepublic void run() {for (int i = 0; i < 10000; i ++) {increase();System.out.println(count);}}}.start();}} }執(zhí)行結(jié)果 : 多運(yùn)行幾次 , 有的時候會出現(xiàn)結(jié)果不是 200000 的情況 , 這就是出現(xiàn)問題的情景 ;
二、線程操作原子性問題分析
上述程序中 , 將變量 int count 設(shè)置成 volatile 類型的 , 只能保證其 可見性 和 有序性 , 無法保證 線程操作的 原子性 ;
在線程中對 int count = 0 進(jìn)行累加操作 , 首先將變量 int count = 0 加載到線程工作內(nèi)存的變量副本中 , 這里創(chuàng)建了 202020 個線程 , 就會有 202020 個線程對應(yīng)的工作內(nèi)存空間 , 需要將 count 變量拷貝 202020 份到相應(yīng)的線程工作內(nèi)存中 ;
有這樣一種極端情況 , 當(dāng)某個線程 A , 將 變量副本 加載到 線程執(zhí)行引擎 時 , 就是 線程棧 中的 棧幀 的的 操作數(shù)棧 中 , 此時將要開始執(zhí)行相關(guān)操作 , 在線程執(zhí)行引擎沒有執(zhí)行之前 ,
與此同時 , 線程 B 修改了 count 副本變量 , 并進(jìn)行了同步 , 主內(nèi)存 , 包括 線程 A 的副本變量也已經(jīng)更新了最新的值 ,
當(dāng)前 線程棧中的棧幀中的操作數(shù)棧 中 , 還壓著一個副本變量 , 雖然 該變量已經(jīng)過時 , 該 count++ 操作無效 , 這樣就 丟失了 111 次 count 變量自增的操作 , 導(dǎo)致 最終輸出的值是 199991999919999 ;
原子操作問題 : 線程中 , 對變量副本 count 進(jìn)行自增操作 , 不是原子操作 , 首先 從工作內(nèi)存中讀取變量副本到執(zhí)行引擎 ( 操作數(shù)棧 ) 中 , 然后 再 進(jìn)行自增運(yùn)算 , 最后 寫回到線程工作內(nèi)存中 , 這是 333 個操作 , 如果變量 在這 333 個操作的空檔時間進(jìn)行了修改 , 那么就會產(chǎn)生無法預(yù)知的效果 ;
總結(jié)一下 : 線程 A 的變量副本入操作數(shù)棧的時刻 , 該共享變量被線程 B 修改并且同步更新 , 此時入棧的這個變量自增是無效的 , 但是也算自增了 111 次 , 因此這里就丟失了 111 次計算機(jī)會 ;
三、使用 synchronized 解決線程原子性問題
使用 synchronized 修飾 increase 方法 ;
private static void increase() {count++;}方法 , 相當(dāng)于在方法體重添加了 synchronized 代碼塊 ;
private static void increase() {synchronized (Main.class) {count++;}}一旦某個線程執(zhí)行 synchronized 方法或代碼塊中的代碼 , 則當(dāng)前線程持有互斥鎖 , 只能由當(dāng)前線程訪問 count 變量 ;
代碼示例 :
public class Main {private volatile static int count = 0;private synchronized static void increase() {count++;}public static void main(String[] args) {for (int i = 0; i < 20; i ++) {new Thread(){@Overridepublic void run() {for (int i = 0; i < 10000; i ++) {increase();System.out.println(count);}}}.start();}} }執(zhí)行結(jié)果 :
總結(jié)
以上是生活随笔為你收集整理的【Java 并发编程】线程操作原子性问题 ( 问题业务场景分析 | 使用 synchronized 解决线程原子性问题 )的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【Java 并发编程】线程指令重排序问题
- 下一篇: 【Java 并发编程】指令重排序规范 (