雖然多執行緒程式設計極大地提高了效率,但是也會帶來一定的隱患。比如說兩個線程同時往一個資料庫錶中插入不重複的數據,就可能會導致資料庫中插入了相同的數據。今天我們就來一起討論下線程安全問題,以及Java中提供了什麼機制來解决線程安全問題。源碼:http://www.binbaodaonew.com synchronized & Volatile JSR133http://www.cs.umd.edu/~pugh/java/memoryModel FIFO(First Input First Output)簡單說就是指先進先出。 什麼是對象鎖 對象鎖也叫方法鎖,是針對一個對象實例的,它只在該對象的某個記憶體位置聲明一個標識該對象是否擁有鎖,所有它只會鎖住當前的對象,而並不會對其他對象實例的鎖產生任何影響,不同對象訪問同一個被synchronized修飾的方法的時候不會阻塞, 什麼是類瑣 類鎖是鎖住整個類,當有多個線程來聲明這個類的對象時候將會被阻塞,直到擁有這個類鎖的對象唄銷毀或者主動釋放了類鎖,這個時候在被阻塞的線程被挑選出一個佔有該類鎖,聲明該類的對象。其他線程繼續被阻塞住。 (上面百度的),即一句話,不管多少個對象,多少個對象,共用一把多,且只有一把,不管怎麼調用,都會同步 synchronized 在瞭解synchronized關鍵字的使用方法之前,我們先來看一個概念:互斥鎖,顧名思義:能到達到互斥訪問目的的鎖。 1.舉個簡單的例子:如果對臨界資源加上互斥鎖,當一個線程在訪問該臨界資源時,其他線程便只能等待。 2.在Java中,每一個對象都擁有一個鎖標記(monitor),也稱為監視器,多執行緒同時訪問某個對象時,線程只有獲取了該對象的鎖才能訪問。 3.在Java中,可以使用synchronized關鍵字來標記一個方法或者程式碼塊,當某個線程調用該對象的synchronized方法或者訪問synchronized程式碼塊時,這個線程便獲得了該對象的鎖,其他線程暫時無法訪問這個方法,只有等待這個方法執行完畢或者程式碼塊執行完畢,這個線程才會釋放該對象的鎖,其他線程才能執行這個方法或者程式碼塊。 不過有幾點需要注意: 1)當一個線程正在訪問一個對象的synchronized方法,那麼其他線程不能訪問該對象的其他synchronized方法。這個原因很簡單,因為一個對象只有一把鎖,當一個線程獲取了該對象的鎖之後,其他線程無法獲取該對象的鎖,所以無法訪問該對象的其他synchronized方法。 2)當一個線程正在訪問一個對象的synchronized方法,那麼其他線程能訪問該對象的非synchronized方法。這個原因很簡單,訪問非synchronized方法不需要獲得該對象的鎖,假如一個方法沒用synchronized關鍵字修飾,說明它不會使用到臨界資源,那麼其他線程是可以訪問這個方法的, 3)如果一個線程A需要訪問對象object1的synchronized方法fun1,另外一個線程B需要訪問對象object2的synchronized方法fun1,即使object1和object2是同一類型),也不會產生線程安全問題,因為他們訪問的是不同的對象,所以不存在互斥問題。 對於synchronized方法或者synchronized程式碼塊,當出現異常時,JVM會自動釋放當前線程佔用的鎖,囙此不會由於异常導致出現鎖死現象 volatile 一旦一個共亯變數(類的成員變數、類的靜態成員變數)被volatile修飾之後,那麼就具備了兩層語義: 1)保證了不同線程對這個變數進行操作時的可見性,即一個線程修改了某個變數的值,這新值對其他線程來說是立即可見的。 2)禁止進行指令重排序。 synchronized關鍵字是防止多個線程同時執行一段程式碼,那麼就會很影響程式執行效率,而volatile關鍵字在某些情况下效能要優於synchronized,但是要注意volatile關鍵字是無法替代synchronized關鍵字的,因為volatile關鍵字無法保證操作的原子性。通常來說,使用volatile必須具備以下2個條件: 1)對變數的寫操作不依賴於當前值 2)該變數沒有包含在具有其他變數的不變式中 實際上,這些條件表明,可以被寫入volatile變數的這些有效值獨立於任何程式的狀態,包括變數的當前狀態。 ReentrantLock 在JDK5.0版本之前,重入鎖的效能遠遠好於synchronized關鍵字,JDK6.0版本之後synchronized得到了大量的優化,二者效能也不分伯仲,但是重入鎖是可以完全替代synchronized關鍵字的。除此之外,重入鎖又自帶一系列高逼格UBFF:可中斷響應、鎖申請等待限時、公平鎖。另外可以結合Condition來使用,使其更是逼格滿滿。 公平鎖 公平鎖每次都是從同步隊列中的第一個節點獲取到鎖,公平鎖每次獲取到鎖為同步隊列中的第一個節點,保證請求資源時間上的絕對順序。公平鎖為了保證時間上的絕對順序,需要頻繁的上下文切換,而非公平鎖會降低一定的上下文切換,降低效能開銷。囙此,ReentrantLock默認選擇的是非公平鎖,則是為了减少一部分上下文切換,保證了系統更大的輸送量。 非公平鎖 非公平性鎖則不一定,有可能剛釋放鎖的線程能再次獲取到鎖。公平鎖有可能剛釋放鎖的線程下次繼續獲取該鎖,則有可能導致其他線程永遠無法獲取到鎖,造成“饑餓”現象。 提供了一個基於FIFO隊列,可以用於構建鎖或者其他相關同步裝置的基礎框架。該同步器(以下簡稱同步器)利用了一個int來表示狀態,期望它能够成為實現大部分同步需求的基礎。使用的方法是繼承,子類通過繼承同步器並需要實現它的方法來管理其狀態,管理的管道就是通過類似acquire和release的管道來操縱狀態。然而多執行緒環境中對狀態的操縱必須確保原子性,囙此子類對於狀態的把握,需要使用這個同步器提供的以下三個方法對狀態進行操作: java.util.concurrent.locks.AbstractQueuedSynchronizer.getState() java.util.concurrent.locks.AbstractQueuedSynchronizer.setState(int) java.util.concurrent.locks.AbstractQueuedSynchronizer.compareAndSetState(int,int) 子類推薦被定義為自定義同步裝置的內部類,同步器自身沒有實現任何同步介面,它僅僅是定義了若干acquire之類的方法來供使用。該同步器即可以作為排他模式也可以作為共亯模式,當它被定義為一個排他模式時,其他線程對其的獲取就被封锁,而共亯模式對於多個線程獲取都可以成功。 同步器是實現鎖的關鍵,利用同步器將鎖的語義實現,然後在鎖的實現中聚合同步器。可以這樣理解:鎖的API是面向使用者的,它定義了與鎖互動的公共行為,而每個鎖需要完成特定的操作也是透過這些行為來完成的(比如:可以允許兩個線程進行加鎖,排除兩個以上的線程),但是實現是依託給同步器來完成;同步器面向的是線程訪問和資源控制,它定義了線程對資源是否能够獲取以及線程的排隊等操作。鎖和同步器很好的隔離了二者所需要關注的領域,嚴格意義上講,同步器可以適用於除了鎖以外的其他同步設施上(包括鎖)。 AbstractQueuedSynchronizer又稱為隊列同步器(後面簡稱AQS),它是用來構建鎖或其他同步組件的基礎框架內部通過一個int類型的成員變數state來控制同步狀態,當state=0時,則說明沒有任何線程佔有共亯資源的鎖,當state=1時,則說明有線程目前正在使用共亯變數,其他線程必須加入同步隊列進行等待,AQS內部通過內部類Node構成FIFO的同步隊列來完成線程獲取鎖的排隊工作,同時利用內部類ConditionObject構建等待隊列,當Condition調用wait()方法後,線程將會加入等待隊列中,而當Condition調用signal()方法後,線程將從等待隊列轉移動同步隊列中進行鎖競爭。注意這裡涉及到兩種隊列,一種的同步隊列,當線程請求鎖而等待的後將加入同步隊列等待,而另一種則是等待隊列(可有多個),通過Condition調用await()方法釋放鎖後,將加入等待隊列。 PS:理論比較多,重點好好看下,源碼中分享的那個pdf,官網關於線程這塊的介紹,國人已經翻譯成了中文,很容易理解