一個大學生的編程問題,差點難倒了我這個老司機

 小夥伴遇到的問題

前幾天羣裏有個小夥伴,年紀確實小,畢竟仍是大學生,很是好學,固然不是妹子~
我但是不只僅喜歡幫助妹子
先看他的代碼:程序員

watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

類的命名,咱們就忽略了,畢竟小夥伴是真的在練習和測試。隨意起的類名。面試

類Test2

類Test2繼承JFrame,是的,很古老的Java UI。忽略這個有年代感的類,瞧重點:編程

用Java畫了一個窗口,窗口上有個Big Button,鋪滿窗口的。這個類有一個變量b默認false,並給按鈕增長了一個事件,將b修改成true:緩存

public boolean b = false;
...忽略其餘代碼
button.addActionListener(new ActionListener(){
 // 按鈕事件
 public void actionPerformed(ActionEvent e){
     // 修改b的值
     b = true;
 }

});

類Test1

類Test1繼承的是一個線程,該線程進行死循環,判斷test2.b==true,則打印Hello world:ide

public void run(){
  while(true){
     if(test2.b){
        System.out.println("Hello world");
     }
  }

}

啓動類Test2的main

main的實現邏輯是,實例化Test2,並傳入Test1構造函數,啓動Test1線程函數

public static void main(String[] args){
  Test2 test2 = new Test2();
  new Test1(test2).start();

}

小夥伴的疑問 ???

下圖是這個代碼啓動後的全屏按鈕:性能

watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

小夥伴說:爲何啓動後控制檯沒有打印Hello world?測試

我也同樣看了幾遍產生和小夥伴同樣的疑問---Hello world跑哪去了呢?spa

一切都是那麼完美無缺!線程

這個結果纔是咱們期待的奇蹟:
watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

但是奇蹟並未發生!

隱蔽的角落

Main線程修改了變量b的值,Test1線程讀取變量b的值

b爲mian線程和Test1線程共享變量 對,問題就處在共享變量上!!!!!

共享變量你們可以理解,畢竟共享時代也持續好久了~

但是這個變量真的共享嗎?

既然這麼問,那確定不是真的!納尼!!!!

CPU是如何訪問內存變量

watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

CPU能挖礦,咱們知道性能特別快,若是頻繁跨硬件訪問主存,而且內存讀寫數據的速度要比CPU慢不少,若是是這樣的話,那CPU再快也是浪費!

因此CPU訪問內存變量採用了CPU內部的高速緩存來解決!

watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

當程序在運行過程當中,會將運算須要的數據從主存複製一份到CPU的高速緩存當中,那麼CPU進行計算時就能夠直接從它的高速緩存讀取數據和向其中寫入數據,當運算結束以後,再將高速緩存中的數據刷新到主存當中。

每一個線程都有變量b的副本

標題如圖:

watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

按照程序邏輯走一走:

  • 1.Main線程啓動,初始化實例Test2,拷貝了變量b的副本到本身的高速緩存。
  • 2.Test1線程啓動,拷貝了變量b的副本到本身的高速緩存。
  • 3.點擊按鈕,Main線程的高速緩存中b修改成true
  • 4.而後Test1線程高速緩存什麼都沒作。b依然是false

@11 同窗,如今能理解了嗎?

緩存一致性

不用擔憂,你遇到的問題,偉人已經爲咱們鋪好了路。
有兩種方式解決線程間的緩存一致性,專業表述是這樣的:

  • 經過在總線加LOCK鎖的方式
  • 經過緩存一致性協議
    CPU和其餘部件通信是經過總線完成的。這點先了解下。 看看下圖:

watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

其實上面兩點通俗一點理解是這樣的:

  • 第1點可理解爲,多個線程一個一個依次來讀取或修改變量,一個讀寫,另外的就要等待,也就是阻塞。明顯方案太out了。
    偉人是接受不了的。

  • 第二點是經過一個xx協議完成。這個xx協議就是我沒據說過的Intel 的MESI協議。 不想之後裝b,能夠不記下它的大名。
    這個協議是幹嗎的呢?一個線程若是修改了一個共享變量的值,注意是共享變量,若是其餘cpu的高速緩存有這個共享變量的副本,那麼就將它變成無效,若是其餘CPU發現無效,就從新從主存讀取。

TIP:若是以爲有點繞,能夠讀一遍。

我是個無辜的程序員,該怎麼辦呢?

使用Volatile關鍵字。姿式是這樣的!

public volatile boolean b = false;

到這裏,老司機能夠離場,這個是面試80%必問題。

固然,公衆號回覆msgq1能夠下載《面試怪圈內卷手冊》會教給你怎麼給面試官擺2小時的。

volatile關鍵字,有兩個重要的做用:

  • 保證線程間內存的可見性,即一個線程修改了某個變量的值,這新值對其餘線程來講是當即可見的。
  • 防止指令重排

百度搜索volatile第一個,真心講的不錯,若是你有耐心花2個小時,把它讀完:

watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

迴歸正題了,仍是說一下volatile內存可見性是怎麼作到的:
1.若是Main線程修改了b的值爲true
2.會致使線程Test1中高速緩存的b的值失效
3.Test1讀取b的值得時候,發現已經失效,從新從主存中獲取最新值。

好了,花了兩個小時,解決了@11 朋友心中的困惑,但願他在編程的路上再也不迷路。

無論怎麼樣,可以在大學的時候,遇到這樣的問題,真的是給編程人生是很好的開始!

最後

最近我整理了整套《JAVA核心知識點總結》,說實話 ,做爲一名Java程序員,不論你需不須要面試都應該好好看下這份資料。拿到手老是不虧的~個人很多粉絲也所以拿到騰訊字節快手等公司的Offer

Java進階之路羣,找管理員獲取哦-!

watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

相關文章
相關標籤/搜索