java併發(二)java線程安全 原子性操作

synchronized和volatile

對於併發編程不能避免的就是內存可見性和線程安全的問題。
比如這個例子:

class unsafeClass {
        private int mark;

        public int getMark() {
            return mark;
        }

        public void setMark(int mark) {
            this.mark = mark;
        }
    }

在多線程操作和讀取mark變量的時候就會遇到問題,線程1讀取mark,然後線程2讀取mark又寫入mark,但是這時線程1操作的mark就已經過期了。這樣就會出現髒數據或者其他不可見的問題。

所以使用synchronized和volatile解決這個問題。

package day0128;

/**
 * @Author Braylon
 * @Date 2020/1/28 9:35
 */
public class JavaAtomicity {
    class unsafeClass {
        private int mark;

        public int getMark() {
            return mark;
        }

        public void setMark(int mark) {
            this.mark = mark;
        }
    }

    class safeClass {
        private int mark;

        public synchronized int getMark() {
            return mark;
        }

        public synchronized void setMark(int mark) {
            this.mark = mark;
        }
    }

    class safeClass2 {
        private volatile int mark;

        public int getMark() {
            return mark;
        }

        public void setMark(int mark) {
            this.mark = mark;
        }
    }
}

synchronized時java提供的一種原子性內置鎖,線程的執行代碼在進入synchronized代碼塊前會自動獲取內部鎖,這時候同步代碼塊會被阻塞掛起。
但是由於java中的線城市與操作系統的原生線程一一對應 ,所以當阻塞一個線程時需要從用戶態切換到內核態,非常的耗費時間。如何避免呢?


這裏我們就提到了volatile關鍵字。
由於使用synchronized鎖太笨重了。於是java提供了一種弱的同步,這個關鍵字確保對一個變量的更新對其他線程馬上可見。當一個變量被用volatile聲明,線程在寫入變量的時候不會把值緩存在寄存器或者別的地方,而是直接寫穿,刷新回內存。

兩種關鍵字優缺點比較

相同點是,synchronized和volatile都解決了共享變量的內存可見性問題。
不同點是,前者是獨佔鎖同時只能有一個線程調用get方法,其他調用的線程都會被阻塞。缺點就是增加了切換上下文的開銷。
可是volatile雖然沒有上下文切換的開銷,卻不能保證原子性。

怎麼選擇使用這兩種關鍵字

  • 當寫入變量值不依賴於變量的當前值的時候。我們可以使用volatile來提高效率
  • 當對線程安全要求較高同時需要依賴於變量的當前值的時候,就是用synchronized。

有沒有更好的方法呢?

我之前學習的時候就疑惑過,依然這兩種方法在實際應用中都不太靠譜,那麼現在以先的技術是如何解決這個問題的呢。
我在下一章中將會給大家分享,CAS操作。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章