場景引入 可見性問題
先來看一張圖:
上面的圖,是簡化版的Java內存模型,一個線程有自己的工作內存,同時還有一個共享的主內存。
線程1和線程2讀取數據data時,先從主內存里加載data變量的值到工作內存,然后才可以使用那個值。
假設現在線程1修改了data變量的值為1,然后將這個修改寫入到自己的工作內存。那么此時,線程1的工作內存里data的值為1,而主內存里data的值還是0。線程2的工作內存data值也是0。
這就尷尬了,線程1和線程2操作的是用一個變量data,但由於線程本地緩存的存在,導致線程1對data變量的修改,線程2不能及時看到。
這就是Java並發編程中的可見性問題:當一個線程修改某個共享變量的值,其他線程是否能夠立即知道這個修改。
值得注意的是,上面的Java內存模型是極其簡化的,真實的情況遠比上面復雜。
volatile的作用及實現原理
要解決上述可見性問題,我們可以使用volatile關鍵字。
在加入volatile關鍵字后,線程1只要修改data變量的值,就會在修改工作內存data變量值的同時,強制將修改刷新到主內存中。與此同時,線程2需要讀取data變量時,先強制將主內存的值刷新到工作內存中,從而保證線程2每次讀取都是最新的值。如下圖:
volatile工作原理如上所述,其在JVM底層的實現原理,涉及到內存屏障相關概念。簡單來說,JVM在在遇到volatile變量時,在其寫操作之后插入一個store屏障,在其讀操作之前插入一個load屏障。