<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                企業??AI智能體構建引擎,智能編排和調試,一鍵部署,支持知識庫和私有化部署方案 廣告
                ## 11 眼見不實—可見性 > 人生的價值,并不是用時間,而是用深度去衡量的。 > ——列夫·托爾斯泰 本節介紹并發三大特性的可見性。并發編程路上可謂困難重重。不過沒有關系,道高一尺,魔高一丈。我們現在講解的所有問題,都有能降伏住他的武器。但要想做常勝將軍,那就要做到知己知彼。我們只要搞清楚有哪些問題,問題的根本原因是什么,困難才會迎刃而解。 由于我們的程序在絕大多數情況下是單線程運行的,另外即使是多線程,如果對象是無狀態的,也不會有線程安全的問題。所以 JVM 更多會考慮單線程的需求。這也就造就了多線程程序在共享資源訪的訪問上存在問題。比如本節所討論的可見性。 ## 1\. 什么是可見性 可見性指的是,某個線程對共享變量進行了修改,其它線程能夠立刻看到修改后的最新值。乍一聽這個定義,你可能會覺得這不是廢話嗎?變量被修改了,線程當然能夠立刻讀取到!否則即使單線程的程序也會出問題啊!沒錯,變量被修改后,在本線程中確實能夠立刻被看到,但并不保證別的線程會立刻看到。原因就是編程領域經典的兩大難題之一----緩存一致性。 我們看一個例子,代碼如下: ~~~java public class visibility { private static class ShowVisibility implements Runnable{ public static Object o = new Object(); private Boolean flag = false; @Override public void run() { while (true) { if (flag) { System.out.println(Thread.currentThread().getName()+":"+flag); } } } } public static void main(String[] args) throws InterruptedException { ShowVisibility showVisibility = new ShowVisibility(); Thread blindThread = new Thread(showVisibility); blindThread.start(); //給線程啟動的時間 Thread.sleep(500); //更新flag showVisibility.flag=true; System.out.println("flag is true, thread should print"); Thread.sleep(1000); System.out.println("I have slept 1 seconds. I guess there was nothing printed "); } } ~~~ 這段代碼很簡單,ShowVisibility 實現 Runnable 接口,在 run 方法中判斷成員變量 flag 值為 true 時進行打印。main 方法中通過 showVisibility 對象啟動一個線程。主線程等待 0.5 秒后,改變 showVisibility 中 flag 的值為 true。按正常思路,此時 blindThread 應該開始打印。但是,實際情況并非如此。運行此程序,輸出如下: ~~~ flag is true, thread should print I have slept 1 seconds. I guess there was nothing printed ~~~ 沒錯,flag 改為 true 后,blindThread 沒有任何打印。也就是說 blindThread 并沒有觀察到到 flag 的值變化。為了測試 blindThread 到底多久能看到 flag 的變化,我決定先看會電視,可是等我刷完一集《樂隊的夏天》回來,還是沒有任何輸出。 ![圖片描述](https://img.mukewang.com/5d898b9b0001361f09020442.jpg) 是不是很神奇?是不是很玄學?作為程序員,你一定碰到過怎么都找不出原因的 bug,最后歸于玄學。其實作為代碼來說,不會有什么玄學。遇到的所有問題一定有其原因。只不過有些隱藏得很深,我們很難發現。或者也可能限于自己的認知,苦苦思考也找不到答案。 回到例子的問題本身來,執行結果完全違背我們的直覺。如果是單線程程序,做了一個變量的修改,那么程序是立即就能看到的。然而在多線程程序中并非如此。原因是 CPU 為提高計算的速度,使用了緩存。 ## 2\. CPU 緩存模型 大家一定都知道摩爾定律。根據定律,CPU 每 18 個月速度將會翻一番。CPU 的計算速度提升了,但是內存的訪問速度卻沒有什么大幅度的提升。這就好比一個腦瓜很聰明程序員,接到需求后很快就想好程序怎么寫了。但是他的電腦性能很差,每敲一行代碼都要反應好久,導致完成編碼的時間依舊很長。所以人再聰明沒有用,瓶頸在計算機的速度上。CPU 計算也是同樣的道理,瓶頸出現在對內存的訪問上。沒關系,我們可以使用緩存啊,這已經是路人皆知的手段了。CPU 更狠一點,用了 L1、L2、L3,一共三級緩存。其中 L1 緩存根據用途不同,還分為 L1i 和 L1d 兩種緩存。如下圖: ![圖片描述](https://img.mukewang.com/5d898bad0001bcad04920422.jpg) 緩存的訪問速度是主存的幾分之一,甚至幾十分之一。通過緩存,極大的提高了 CPU 計算速度。CPU 會先從主存中復制數據到緩存,CPU 在計算的時候就可以從緩存讀取數據了,在計算完成后再把數據從緩存更新回主存。這樣在計算期間,就無須訪問主存了,速度大大提升。加上緩存后,CPU 的數據訪問如下: ![圖片描述](https://img.mukewang.com/5d898bb90001752107950443.jpg) 我們再回頭看上文的例子。blindThread 線程啟動后,就進入 while 循環中,一直進行運算,運算時把 flag 從主存拿到了自己線程中的緩存,此后就會一直從緩存中讀取 flag 的值。即便是main線程修改了 flag 的值。但是 blindThread 線程的緩存并未更新,所以取到的還一直是之前的值。導致 blindThread 線程一致也不會有輸出。 ## 3\. 最低安全性 在前面的例子中,blindThread 線程讀取到flag的值是之前有效的 false。但其現在已經失效了。也就是說 blindThread 讀取到了失效數據。雖然線程在未做同步的時候會讀取到失效值,但是起碼這個值是曾經存在過的。這稱之為最低安全性。我猜你一定會問,難道線程還能讀取到從來沒有設置過的值嗎?是的,對于 64 位類型的變量 long 和 double,JVM 會把讀寫操作分解為兩個 32 位的操作。如果兩個線程分別去讀和寫,那么在讀的時候,可能寫線程只修改了一個 32 位的數據。此時讀線程會讀取到原來數值一個 32 位的數值和新的數值一個 32 位的數值。兩個不同數值各自的一個 32 位數值合在一起會產生一個新的數值,沒有任何線程設置過的數值。這就好比馬和驢各一半的基因,會生出騾子一樣。此時,就違背了最低安全性。 ## 4\. 初識 volatile 關鍵字 要想解決可見性問題其實很簡單。第一種方法就是解決一切并發問題的方法–同步。不過讀和寫都需要同步。 此外還有一個方法會簡單很多,使用 volatile 關鍵字。 我們把例子中下面這行代碼做一下修改。 ~~~java private Boolean flag = false; ~~~ 改為: ~~~java private volatile Boolean flag = false; ~~~ 我們再次運行。現在程序居然可以正常輸出了!是不是很簡單的修改? volatile 修飾的變量,在發生變化的時候,其它線程會立刻覺察到,然后從主存中取得更新后的值。volatile 除了簡潔外,還有個好處就是它不會加鎖,所以不會阻塞代碼。關于 volatile 更多的知識我們后面還會做詳細講解。現在我們只要知道他能夠以輕量級的方式實現同步就可以了。 ## 5\. 總結 本節我們學習了可見性。如果不了解可見性,我們寫出的并發代碼,可能會出現各種違背邏輯的現象。現在我們已經弄清了問題產生的原因以及如何去解決,所以可見性的問題也沒什么可怕的。開發遇到問題時不要慌,所有的問題都有其產生的原因,找到原因再對癥下藥,保準藥到病除。 開發工作中,我會遇到一些同事,遇到問題后不去分析問題產生的原因,先是自己猜測,試著亂改。發現自己不能解決后,網上搜索。找到相關帖子或文章,也不看原因是什么,直接復制粘貼代碼,又是一頓試。即使這樣最后解決了問題,我想對于他來說也是毫無收獲的。我們不管遇到什么難題,一定不能亂了陣腳,還是從分析問題入手。最終解決問題一定是基于你分析出的原因。而不是靠猜測和盲目亂試。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看