java併發編程(1) --併發基礎及其鎖的原理

引言

多線程的知識點是一個龐大的體現,對此也是隻知其一;不知其二。一直想系統的深刻的學習多線程的知識,奈何一直沒有找到機會,好吧,其實就是懶。最近在項目中接觸到一個多併發的項目,在項目中踩了無數的坑。在此下定決心作一個併發的學習筆記。html

爲何併發會有安全問題

當兩個線程同時對一個共享可變變量進行操做時,例如:緩存

兩個線程對變量i=1同時執行i++操做。執行完畢後i可能並不等於3而是等於2。由於i++不是原子性的操做,i++其實是有三個步驟安全

第一步:讀取,從主內存中將i=1讀取到本地內存中。多線程

第二步:修改,i自增。併發

第三部:寫入,將i=2寫會到緩存中。性能

因此當兩個線程同時將i讀取到工做內存中,並分別將變量i賦值爲2。學習

原子性

原子性是指一個操做是不可中斷的,要麼所有執行成功要麼所有執行失敗,有着「同生共死」的感受。及時在多個線程一塊兒執行的時候,一個操做一旦開始,就不會被其餘線程所幹擾。測試

可見性

可見性是指當一個線程修改了共享變量後,其餘線程可以當即得知這個修改。爲何要這樣說?難道一個線程修改了共享變量其餘線程不必定會當即得知這個變量的修改?沒錯事實確實如此。 簡單的舉一個例子。 在這裏插入圖片描述spa

數據 i 是存儲在主內存中的,當一個線程執行 i++ 操做的時候首先將 i 從主內存讀取到本身線程的工做內存中(也就是緩衝行),而後將工做內存的 i 執行+1操做。若是是單線程程序,在沒有其餘寫入操做的狀況下讀取這個值,首先會讀取緩衝行,緩存命中。那麼總能獲得 +1 操做以後的值。操作系統

可是多線程環境結果則會違背咱們的直覺。

因爲操做系統的執行,咱們並不知道工做內存中的值什麼時候才能被寫入到主內存中(理由很簡單,咱們不可能每次修改了緩存,操做系統就會將值瞬間刷入到主內存吧?這樣效率會多低呀)。因此若是這以前另外一個線程從主內存讀取 i 的值到本地工做內存中。那麼他可能並不會感知到另外一個線程其實已經修改了 i 的值。

爲何synchronized和volatile能夠實現可見性咱們在後續會繼續介紹。

有序性

在執行程序時,爲了提升性能,編譯器和處理器經常會對指令作重排序

爲何要進行重排序?

好比三個操做之間是沒有邏輯關係的,那麼是一個cpu串行執行三個操做快仍是將三個操做分別給三個cpu同時執行快呢?答案顯而易見。

可是帶來的一個弊端就是,可能代碼的執行順序與咱們的意願相違背。

如何讓程序具有有序性,咱們在後續會繼續介紹。

如何避免併發問題

1.不在線程之間共享該狀態變量。

2.將狀態變量修改成不可變的變量。

3.在訪問狀態變量時使用同步。

鎖的原理

在介紹原理以前咱們須要瞭解什麼是CAS自旋轉,CAS自旋也就是咱們常說的樂觀鎖,他不會發生線程阻塞,當咱們將修改後的共享變量寫回內存的時候,會檢查在此期間這個共享變量是否被別的線程操做,若是被別的線程操做了,那麼就回寫內存失敗,從新執行代碼。(這樣的好處在於對於同步塊執行時間較短,上下文切換的代價是很是大的)

鎖一共有4個狀態,級別從低到高依次是:無所狀態、偏向鎖狀態、輕量級鎖狀態和重量級鎖狀態,這幾個狀態會隨着競爭逐漸升級,可是不能降級。而且這4個狀態是存儲在對象頭中的。對象頭中的Mark Word信息以下圖所示(每一行表明一個狀態) 在這裏插入圖片描述

  1. 一個對象剛建立的時候是無鎖狀態,當第一個線程a打算訪問同步塊應獲取鎖的時候,會檢查是否偏向鎖,發現此時爲0,則使用CAS操做將Mark Word中的來線程ID設置爲本身的的線程ID。而後將是否偏向鎖設置爲1。之後該線程在進入和退出同步塊的時候不須要進行CAS操做來加鎖和解鎖,只須要簡單的測試一下線程ID是否指向本身便可。
  2. 當另外一個線程b打算訪問同步塊,此時與以前的線程a發生競爭,此時就要執行偏向鎖的撤銷。首先發出暫停線程a的指令,若是線程a還存活而且在代碼塊中。那麼線程a在執行到安全點的時候會安全退出。 線程b檢查是否存活或者是否已經退出同步塊:
    • 若是不存活或者已退出同步塊則將對像頭設置爲無鎖狀態(也就是將是否偏向鎖設置爲0)。而後重複步驟一,使用CAS操做將Mark Word中的來線程ID設置爲本身的的線程ID。
    • 若是存活而且在還在同步塊當中,則將鎖升級爲輕量級鎖。
  3. 輕量級鎖輕量級鎖是相對於重量級鎖而言的。使用輕量級鎖時,不須要申請互斥量,僅僅將Mark Word中的部分字節CAS更新指向線程棧中的Lock Record,若是更新成功,則輕量級鎖獲取成功,記錄鎖狀態爲輕量級鎖;若是CAS更新失敗,說明已經有線程得到了輕量級鎖,目前發生了鎖競爭(不適合繼續使用輕量級鎖),接下來膨脹爲重量級鎖。
  4. 重量級別的鎖底層直接與操做系統打交道,也就是咱們日常說的阻塞,線程會發生阻塞,當競爭線程釋放鎖的時候,纔會喚醒阻塞線程。

在博客中發現一個大佬畫的圖仍是蠻詳細的,你們能夠參考參考 在這裏插入圖片描述

參考:淺談偏向鎖、輕量級鎖、重量級鎖

原文出處:https://www.cnblogs.com/zhxiansheng/p/10611994.html

相關文章
相關標籤/搜索