<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    隨筆-67  評論-522  文章-0  trackbacks-0
        Java并發一直都是開發中比較難也比較有挑戰性的技術,對于很多新手來說是很容易掉進這個并發陷阱的,其中尤以共享變量最具代表性,其實關于講這個知識點網上也不少,但大象想講講自己對這個概念的理解。
        共享變量比較典型的就是指類的成員變量,在類中定義了很多方法對成員變量的使用,如果是單實例,當有多個線程同時來調用這些方法,方法又沒加控制,那么這些方法對成員變量的操作就會使得該成員變量的值變得不準確了。
        大象用一個最典型的i++例子來說明:
        public class Test {
            private int i = 0;
            private final CountDownLatch mainLatch = new CountDownLatch(1);

            public void add(){
                i++;
            }

            private class Work extends Thread{
                private CountDownLatch threadLatch;

                public Work(CountDownLatch latch){
                    threadLatch = latch;
                }

                @Override
                public void run() {
                    try {
                        mainLatch.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    for (int j = 0; j < 1000; j++) {
                        add();
                    }
                    threadLatch.countDown();
                }
            }

            public static void main(String[] args) throws InterruptedException {
                for(int k = 0; k < 10; k++){
                    Test test = new Test();
                    CountDownLatch threadLatch = new CountDownLatch(10);
                    for (int i = 0; i < 10; i++) {
                        test.new Work(threadLatch).start();
                    }
                    test.mainLatch.countDown();
                    threadLatch.await();
                    System.out.println(test.i);
                }
            }
        }
        java.util.concurrent.CountDownLatchJDK5.0提供的關于并發的一個新API,它的作用就像一個門閂或是閘門那樣。上面這段代碼一共執行10次,每次啟動10個線程同時執行。mainLatch.await()相當于門閂擋著線程,讓準備好的線程處于等待狀態,當所有的線程都準備好時再調用mainLatch.countDown()方法,打開門閂讓線程同時執行。我在這里用這個類的原因,是想讓我創建的10個線程都準備好后再一起并發執行,這樣才能很明顯的看出add方法里面的i++效果。如果不引入CountDownLatch,只執行test.new Work(threadLatch).start(),則獲得的結果可能看不出來線程競爭共享變量產生的錯誤情況。threadLatch這個CountDownLatch的作用是讓10個線程都執行完run方法的for循環后通知主線程的threadLatch.await()停止等待打印出當前i的值。
        這段代碼我加了-server參數運行了多次,每次結果都不一樣,我取了幾個比較明顯的結果。當然,你也可以多運行幾次看看效果。
        
        
        
        共享變量i沒做任何同步操作,當有多個線程都要讀取并修改它時,問題就產生了。正確的結果應該是10000,但是我們看到了,不是每次結果都是10000。我這段代碼最初的版本不是這樣的,因為現在的CPU哪怕是家用級PCCPU核心頻率都非常高,所以完全看不出效果,在和一個朋友的討論中,他給出了修改的建議,最后改為上面的代碼,在這里謝謝Sunny君。run方法中的循環次數越大,i的并發問題就越明顯,大家可以動手試下。對于上圖的運行結果,和硬件平臺有關,也和-server參數有關。
        有同學會有疑問了,既然共享變量沒加同步處理,那為什么還是會出現10000的結果呢?關于這點我想這可能是JVM優化的結果,對于JVM(HotSpot)大象還沒有很深入的研究,不敢隨便下結論,請知道的朋友幫忙解答一下。
        Java中,線程是怎么操作共享變量的呢?我們都知道,Java代碼在編譯后會變成字節碼,然后在JVM里面運行,而像實例域(i)這樣的變量是存儲在堆內存(Heap Memory)中的,堆內存是內存中的一塊區域。線程的執行其實說到底就是CPU的執行,當今的CPU(Intel)基本上都是多核的,因此多線程都是由多核CPU來處理,并且都有L1L2L3CPU緩存,CPU為了提高處理速度,在執行的時候,會從內存中把數據讀到緩存后再操作,而每個線程執行add方法操作i++的過程是這樣的:
            1、線程從堆內存中讀取i的值,將它復制到緩存中
            2、在緩存中執行i++操作,并將結果賦給變量i
            3、再用緩存中的值刷新堆內存中的變量i的值
        我上面寫的這三步并不是嚴格按照JVMCPU指令的步驟來的,但過程就是這么一回事,方便大家理解。通過上面這個過程我們可以看出問題了,如果有多個線程同時要修改i,那么都需要先讀取堆內存中的變量i值,然后把它復制到緩存后執行i++操作,再將結果寫回到堆內存的變量i中。這個執行的時間非常短,可能只有零點幾納秒(主要還是跟硬件平臺有關),但還是出現了錯誤。產生這種錯誤的原因是共享變量的可見性,線程1在讀取變量i的值的時候,線程2正在更新變量i的值,而線程1這時看不到線程2修改的值。這種現象就是常說的共享變量可見性。
        下圖是線程執行的抽象圖,也可以說是Java內存模型的抽象示意圖,可能不嚴謹,但大意是這樣的。
        

        現在選用開發框架一般都會選擇Spring,或是類似Spring這樣的東西,而代碼中經常用到的依賴注入的Bean如果沒做處理一般都會是單例模式。試想一下,按下面這個方式引用Service或其它類似的Bean,在UserService中又不小心用到了共享變量,同時沒有處理它的共享可見性,即同步,那將會產生意想不到的結果。不光Service是單例的,Spring MVC中的Controller也是單例的,所以編寫代碼的時候一定要注意共享變量的問題。 
        @Autowired
        private UserService userService;
        所以我們要盡可能的不使用共享變量,避開它,因為處理好共享變量可見性不是一個很簡單的問題。如果有非用不可的理由,請使用java.util.concurrent.atomic包下面的原子類來代替常用變量類型。比如用AtomicInteger代替intAtomicLong代替long等等,具體可以參考API文檔。如果需求比這更復雜,那還得想其它解決辦法。
        以上是大象關于共享變量的一些淺薄見解,有什么不對的,還請各位指出來。
        本文為菠蘿大象原創,如要轉載請注明出處。http://www.tkk7.com/bolo
    posted on 2014-06-10 16:09 菠蘿大象 閱讀(11311) 評論(5)  編輯  收藏 所屬分類: Concurrency

    評論:
    # re: 淺談Java共享變量[未登錄] 2014-06-17 21:33 | Gospel
    大象可以試一下在i前面加上關鍵字volatile試一下  回復  更多評論
      
    # re: 淺談Java共享變量 2014-06-18 16:39 | 菠蘿大象
    @Gospel
    volatile的變量可不是什么情況都適用的呦,不要亂用呦。我下一篇正準備談談volatile  回復  更多評論
      
    # re: 淺談Java共享變量[未登錄] 2014-06-19 23:26 | Gospel
    @菠蘿大象
    是的 感覺在用多線程的時候就像在走鋼絲   回復  更多評論
      
    # re: 淺談Java共享變量 2015-01-25 22:46 | Yaya
    參考你的博客 收益頗多 不過有一點不是很清楚 你說的 多核CPU情況下的共享變量那個圖 不是很理解 CPU多核 的情況下 每個CPU都有自己的緩存Cache CPU將計算的數據交給Cache之后刷新給內存 如果是多核多線程 那么針對某一個核CPU的所有線程 是共享變量 那么其他線程 結果可能會不如預期 這個顯然和我們所接受的共享變量不太一樣 不清楚這個地方 你是如何證明的(剛入門Java一年 想法有點幼稚 莫怪)   回復  更多評論
      
    # re: 淺談Java共享變量 2015-01-26 09:04 | 菠蘿大象
    @Yaya
    你可以看看我另一篇文章"淺談volatile變量的理解"  回復  更多評論
      
    主站蜘蛛池模板: 国产精品久久久久久久久久免费 | 免费一看一级毛片| 国产成人精品亚洲日本在线| 蜜桃成人无码区免费视频网站 | 亚洲国产精品免费视频| 亚洲AⅤ永久无码精品AA| 亚洲国产精品无码专区在线观看| 色哟哟国产精品免费观看| mm1313亚洲精品无码又大又粗| 老司机午夜性生免费福利| 免费一级毛片在线播放不收费| 四虎成人精品国产永久免费无码| 亚洲AV无码成H人在线观看 | 七色永久性tv网站免费看| 亚洲欧洲国产精品香蕉网| 日本中文字幕免费高清视频| 亚洲久本草在线中文字幕| 久久久久久夜精品精品免费啦| 亚洲欧洲第一a在线观看| 国产1000部成人免费视频| 最新亚洲卡一卡二卡三新区| 国产日产成人免费视频在线观看| 国产成人综合亚洲一区| 国产亚洲一区二区三区在线不卡| 免费在线看污视频| 亚洲国产精品线观看不卡| 日韩免费在线观看| 三年片免费观看大全国语| 久久精品九九亚洲精品| 女人18毛片水最多免费观看| 日韩电影免费在线观看网址| 亚洲男人的天堂www| 国产成人yy免费视频| 美女羞羞喷液视频免费| 久久精品国产亚洲av麻| 成年免费大片黄在线观看岛国| 四虎国产精品成人免费久久| 亚洲精品在线网站| 全亚洲最新黄色特级网站 | 亚洲中文字幕久久精品无码A | 亚洲天堂中文字幕在线观看|