多線程的宏觀和微觀視角

首先咱們在作併發編程的的時候會考慮到原子性丶可見性和有序性,在宏觀上會考慮到安全性丶活躍性和性能;git

微觀視角

  • 可見性: 一個線程對共享變量的修改,另一個線程可以馬上感知到,咱們稱爲可見性;
  • 原子性: 一個或者多個操做在 CPU 執行的過程當中不被中斷的特性稱爲原子性;
  • 有序性: 就是咱們代碼的執行順序,依賴等。(指令重排致使順序被打亂);

  線程工做內存: 是指 Cpu 的 '寄存器''高速緩存',線程的 工做內存/本地內存 是指cpu的寄存器和高速緩存的抽象描述,數據讀取順序優先級 是:寄存器->高速緩存->內存github


宏觀視角

  • 安全性: 安全性我認爲實際上是包含了原子性丶可見性和有序性的,是一個總的概念,在程序開發的時候首先要注重安全性,會在下面詳細解釋這三點;
  • 活躍性 活躍性告訴咱們的是要避免死鎖,飢餓和活鎖; --- 死鎖:這個都不陌生,線程A持有1鎖,等待獲取2鎖,線程B持有2鎖,等待獲取1鎖,這就是個典型的死鎖,就就是阻塞了。 --- 飢餓:飢餓當多線程獲取鎖都得時候,總有線程沒有機會獲取到鎖,出現飢餓的三中狀況:1-高優先級的線程吞噬了低優先級線程的CPU使用權 2-線程被一直阻塞(好比Thread.Sleep) 3-等待線程永遠不被喚醒,也能夠理解爲鎖的優先級,咱們經常使用的synchronized就是非公平鎖,例如線程A,B,C按順序獲取鎖1,首先是A獲取到了鎖,執行完臨界區代碼釋放了鎖,這是線程D來了直接獲取到了鎖,這就是非公平鎖;ReentrantLock()默認是非公平鎖,能夠在ReentractLock(true)建立公平鎖; --- 活鎖:在生活中A和B同時進入左手門,爲了避免發生碰撞,A和B互相禮讓同時進入了右手門,爲了避免發生碰撞又進入了左手們會一直循環下去,實例代碼找到適用的場景在增長
  • 性能 1:延遲: 延遲就是指一個請求調用到返回所使用的時間,時間越短,程序的處理的就越快,性能也就會高; 2:吞吐量: 吞吐量就是值在單位時間內(秒)處理的請求數量,吞吐量越大,程序處理的請求就越多,性能也越好;

可見性:線程工做空間致使可見性問題

  例如:線程A在主存中年將變量age=0拉去到本身的工做內存中,而後作了age = 5,固然這個操做是在cpu的寄存器中進行的,而後寫會高速緩存中,這時線程A的高速緩存還未執行同步主內存的操做,線程B又將age=0從主存拉取到了線程B的工做內存中,致使A線程已經更新可是B線程看不到的可見性問題;編程

原子性:線程切換致使原子性問題 ++count

  例如:當線程A從主內存中將共享變量Count加載到線程A的工做內存後,發生了線程切換,這個時候線程B也將共享變量Count從主內存加載到了線程B的工做內存,這時線程A和B的工做內存中count都是0,線程B執行了Count = Count + 1,而後寫回到主內存,這時候線程切換完成,回到了線程A再次執行 Count = Count + 1,再將線程A工做內存計算過的count寫回主內存,如今咱們獲得的主內存呢中Count值是1而不是2。緩存

有序性:指令重排致使有序性問題;

在這裏講一個例子,就是獲取單例雙重檢查鎖(double-checked locking)判斷:安全

/**
 * @Auther: lantao
 * @Date: 2019-03-28 14:32
 * @Company: 隨行付支付有限公司
 * @maill: lan_tao@suixingpay.com
 * @Description: TODO
 */
public class Test1 {
    
    private DoMain doMain;
    
    public DoMain getDoMain(){
        if(doMain == null){
            synchronized (this.getClass()){
                if(doMain == null){
                    doMain = new DoMain("");
                }
                return doMain;
            }
        }else{
            return doMain;
        }
    }
}

複製代碼

  在上邊的代碼中在synchronized內和外都有一個if判斷,判斷doMain是否爲null操做,有不少人對synchronized中的if null判斷不理解,其實能夠這樣想,線程A和線程B都執行到了synchronized這裏進行競爭鎖,結果A獲得鎖,判斷if null,結果還未實例化,繼續進行實例化,而後return對象並釋放鎖,這時線程B獲取到了鎖進入if null判斷,發現doMain已經被線程A實例化過了,直接返回實例便可,第二個if null的做用就在這裏;bash

看上去上邊的代碼是完美的,可是new的操做上咱們理解是:多線程

  • 建立內存M
  • 在內存M上初始化doMain對象
  • 將內存M的地址指向變量doMain

可是實際上優化後(指令重排)的執行路徑多是這樣的:併發

  • 建立內存M
  • 將內存M的地址指向變量doMain
  • 將內存M的地址指向變量doMain

%E6%9C%89%E5%BA%8F%E6%80%A7%E9%97%AE%E9%A2%98.png

博客地址:lantaoblog.site性能

相關文章
相關標籤/搜索