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

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

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

    Sky's blog

    我和我追逐的夢

    常用鏈接

    統計

    其他鏈接

    友情鏈接

    最新評論

    編碼最佳實踐(2)--推薦使用concurrent包中的Atomic類

        這是一個真實案例,曾經惹出碩大風波,故事的起因卻很簡單,就是需要實現一個簡單的計數器,每次取值然后加1,于是就有了下面這段代碼:
              private int counter = 0;
              public int getCount ( ) {
                       return counter++;
              }
        這個計數器被用于生成一個sessionId,這個sessionID用于和外部計費系統交互,這個sessionId理所當然的要求保證全局唯一而不重復。但是很遺憾,上面的代碼最終被發現會產生相同的id,因此會造成一些請求莫名其妙的報錯.....更痛苦的是,上面這段代碼是一個來自其他部門開發的工具類,我們當時只是拿了它的jar包來調用,沒有源碼,更沒有想這里面會有如此低級而可怕的錯誤。
        由于重復的sessionId,造成有個別請求失敗,雖然出現概率極低,經常跑一天測試都不見得能重現一次。因為是和計費相關,因此哪怕是再低的概率出錯,也不得不要求解決。實際情況是,項目開發到最后階段,都開始做發布前最后的穩定性測試了,在7*24小時的連續測試中,這個問題往往在測試開始幾天后才重現,將當時負責trouble shooting的同事折騰的很慘......經過反復的查找,終于有人懷疑到這里,反編譯了那個jar包,才看到上面這段出問題的代碼。
        這個低級的錯誤,源于一個java的基本知識:
        ++操作,無論是i++還是++i,都不是原子操作!  
        而一個非原子操作,在多線程并發下會有線程安全的問題:這里稍微解釋一下,上面的"++"操作符,從原理上講它其實包含以下:計算加1之后的新值,然后將這個新值賦值給原變量,返回原值。類似于下面的代碼
              private int counter = 0;
              public int getCount ( ) {
                       int result = counter;
                       int newValue = counter + 1; // 1. 計算新值
                       counter = newValue;         // 2. 將新值賦值給原變量
                       return result;
              }
        多線程并發時,如果兩個線程同時調用getCount()方法,則他們可能得到相同的counter值。為了保證安全,一個最簡單的方法就是在getCount()方法上做同步:
              private int counter = 0;
              public synchronized int getCount ( ) {
                       return counter++;
              }
        這樣就可以避免因++操作符的非原子性而造成的并發危險。
        我們在這個案例基礎上稍微再擴展一下,如果這里的操作是原子操作,就可以不用同步而安全的并發訪問嗎?我們將這個代碼稍作修改:
              private int something = 0;
              public int getSomething ( ) {
                       return something;
              }
              public void setSomething (int something) {
                       this.something = something;
              }
        假設有多線程同時并發訪問getSomething()和setSomething()方法,那么當一個線程通過調用setSomething()方法設置一個新的值時,其他調用getSomething()的方法是不是立即可以讀到這個新值呢?這里的"this.something = something;" 是一個對int 類型的賦值,按照java 語言規范,對int的賦值是原子操作,這里不存在上面案例中的非原子操作的隱患。
        但是這里還是有一個重要問題,稱為"內存可見性"。這里涉及到java內存模型的一系列知識,限于篇幅,不詳盡講述,不清楚這些知識點的可以自己翻翻資料,最簡單的辦法就是google一下這兩個關鍵詞"java 內存模型", "java 內存可見性"。或者,可以參考這個帖子"java線程安全總結", http://www.iteye.com/topic/806990。
        解決這里的"內存可見性"問題的方式有兩個,一個是繼續使用 synchronized 關鍵字,代碼如下
              private int something = 0;
              public synchronized  int getSomething ( ) {
                       return something;
              }
              public synchronized  void setSomething (int something) {
                       this.something = something;
              }
         另一個是使用volatile 關鍵字,
              private volatile int something = 0;
              public int getSomething ( ) {
                       return something;
              }
              public void setSomething (int something) {
                       this.something = something;
              }
        使用volatile 關鍵字的方案,在性能上要好很多,因為volatile是一個輕量級的同步,只能保證多線程的內存可見性,不能保證多線程的執行有序性。因此開銷遠比synchronized要小。
        讓我們再回到開始的案例,因為我們采用直接在 getCount() 方法前加synchronized 的修改方式,因此不僅僅避免了非原子性操作帶來的多線程的執行有序性問題,也"順帶"解決了內存可見性問題。
        OK,現在可以繼續了,前面講到可以通過在 getCount() 方法前加synchronized 的方式來解決問題,但是其實還有更方便的方式,可以使用jdk 5.0之后引入的concurrent包中提供的原子類,java.util.concurrent.atomic.Atomic***,如AtomicInteger,AtomicLong等。
            private AtomicInteger  counter = new AtomicInteger(0);
            public int getCount ( ) {
                 return counter.incrementAndGet();
            }
        Atomic類不僅僅提供了對數據操作的線程安全保證,而且提供了一系列的語義清晰的方法如incrementAndGet(),getAndIncrement,addAndGet(),getAndAdd(),使用方便。更重要的是,Atomic類不是一個簡單的同步封裝,其內部實現不是簡單的使用synchronized,而是一個更為高效的方式CAS (compare and swap) + volatile,從而避免了synchronized的高開銷,執行效率大為提升。限于篇幅,關于“CAS”原理就不在這里講訴。
        因此,出于性能考慮,強烈建議盡量使用Atomic類,而不要去寫基于synchronized關鍵字的代碼實現。
        最后總結一下,在這個帖子中我們講訴了一下幾個問題:
        1. ++操作不是原子操作
        2. 非原子操作有線程安全問題
        3. 并發下的內存可見性
        4. Atomic類通過CAS + volatile可以比synchronized做的更高效,推薦使用

    posted on 2012-06-16 17:54 sky ao 閱讀(2897) 評論(5)  編輯  收藏 所屬分類: java

    評論

    # re: 編碼最佳實踐(2)--推薦使用concurrent包中的Atomic類[未登錄] 2012-06-17 08:33 stevenfrog

    感謝!
    清晰,實用。
    特別是“++操作符”原理那段,我很喜歡。
    一下就說清楚了,為什么++會引起非同步。  回復  更多評論   

    # re: 編碼最佳實踐(2)--推薦使用concurrent包中的Atomic類 2012-06-17 22:18 Yiding He

    生成全局值的操作標記為 synchronized 應該作為編碼規范來執行。  回復  更多評論   

    # re: 編碼最佳實踐(2)--推薦使用concurrent包中的Atomic類 2012-06-17 23:18 stevenfrog

    @Yiding He
    我覺得還是用Atomic類劃算些,synchronized代價大了些。
    不過synchronized簡單,不會出錯。  回復  更多評論   

    # re: 編碼最佳實踐(2)--推薦使用concurrent包中的Atomic類 2012-06-19 09:36 allenny

    嗯,樓主最好出一個性能測試比較,這樣比較有說服力。  回復  更多評論   

    # re: 編碼最佳實踐(2)--推薦使用concurrent包中的Atomic類[未登錄] 2016-08-22 12:53 飛飛

    原子類操作并不能完全保證多線程下正確啊,執行一下 看看結果就知道了啊  回復  更多評論   

    主站蜘蛛池模板: 亚洲国产av一区二区三区丶| 国产精品福利在线观看免费不卡| 日本v片免费一区二区三区 | 色噜噜狠狠色综合免费视频| 亚洲日韩欧洲无码av夜夜摸| 青娱分类视频精品免费2| 国产成人+综合亚洲+天堂| 亚洲区小说区图片区QVOD| 青青青国产在线观看免费| 一级一级一片免费高清| 亚洲一卡2卡4卡5卡6卡残暴在线| 又黄又爽的视频免费看| 91福利视频免费观看| 鲁啊鲁在线视频免费播放| 亚洲国产成人91精品| 久久精品国产精品亚洲人人| 日韩一区二区a片免费观看| 最近免费字幕中文大全| 亚洲精品无码国产片| 亚洲国产综合专区电影在线| 免费a级毛片在线观看| 成年黄网站色大免费全看| 免费人成视频在线观看免费| 亚洲国产日韩在线一区| 亚洲熟女少妇一区二区| 国产成人涩涩涩视频在线观看免费| 亚洲精品免费在线观看| 国产精品偷伦视频免费观看了| 亚洲熟妇无码AV| 亚洲男女一区二区三区| 国产成人亚洲综合无码精品| 四虎国产精品免费视| 成人免费福利电影| 120秒男女动态视频免费| a级毛片黄免费a级毛片| 日韩毛片免费一二三| 日韩精品亚洲专区在线影视| 亚洲人成在久久综合网站| 亚洲电影中文字幕| 国产偷v国产偷v亚洲高清| 亚洲性日韩精品国产一区二区|