雖是讀書筆記,可是如轉載請註明出處 http://segmentfault.com/blog/exploring/
.. 拒絕伸手複製黨html
i++
爲何是非線程安全的?java
先來解釋下什麼叫「線程安全」 :算法
Thread Safe describe some code that can be called from multiple threads without corrupting the state of the object or simply doing the thing the code must do in right order.segmentfault
即一段代碼能夠被多個線程調用,調用過程當中對象的狀態不出現衝突,或者對象按照正確的順序進行了操做。數組
i++ 線程安全是指咱們讀取一個值但願的是每一次讀取到的值都是上一次+1 。緩存
i++是分爲三個步驟,獲取i的值;temp = i+1操做;temp寫入i; 若是存在兩個線程,都執行i++. 正常狀況應該是線程A 先執行,獲得1; 線程B再執行,獲得2.安全
可是又經常出現:多線程
或者更形象的舉例:線程A,B對i不停的進行操做,A執行i++, B執行打印。程序的邏輯是每次加1後就打,這樣應該輸出的結果是順序的不斷加1。因爲i++不是原子操做,在執行的過程當中發生了線程的切換,i+1沒有被回寫以前就被2訪問了,這時打印的仍是原來的數字,並非預期的+1。併發
線程的這種交叉操做會致使線程不安全。在Java中能夠有不少方法來保證線程安全,即原子化 —— 同步,使用原子類,實現併發鎖,使用volatile關鍵字,使用不變類和線程安全類。post
名詞解釋:何爲 Atomic?
Atomic 一詞跟原子有點關係,後者曾被人認爲是最小物質的單位。計算機中的 Atomic 是指不能分割成若干部分的意思。若是一段代碼被認爲是 Atomic, 原子操做是指一個不受其餘操做影響的操做任務單元,原子操做不能中斷。原子操做是在多線程環境下避免數據不一致必須的手段。一般來講,原子指令由硬件提供,供軟件來實現原子方法(某個線程進入該方法後,就不會被中斷,直到其執行完成)
爲了解決這個問題,必須保證增長操做是原子的,在 JDK1.5 以前咱們可使用同步技術(synchonized關鍵字, 鎖)來作到這一點。到 JDK1.5,java.util.concurrent.atomic 包提供了 int 和 long 類型的裝類,它們能夠自動的保證對於他們的操做是原子的而且不須要使用同步。
同步技術/鎖 :synchronized 關鍵字修飾,給方法自動獲取和釋放鎖
public class Example { private int value = 0; public synchronized int getNextValue(){ return value++; } }
或者
public class Example { private int value = 0; public int getNextValue() { synchronized (this) { return value++; } } }
或者想對其餘對象加鎖,而非當前對象
public class Example { private int value = 0; private final Object lock = new Object(); public int getNextValue() { synchronized (lock) { return value++; } } }
關鍵詞:可見性
volatile
的,JVM保證了每次讀變量都從內存中讀,跳過CPU cache這一步。volatile關鍵字爲實例域的同步訪問提供了一種免鎖機制。若是聲明一個域爲volatile. 一些狀況就能夠確保多線程訪問到的變量是最新的。(併發要求)
javapublic class SharedObject{ public volatile int counter = 0; }
The problem with multiple threads that do not see the latest value of a variable because that value has not yet been written back to main memory by another thread, is called a "visibility" problem. The updates of one thread are not visible to other threads.
一個線程對對象進行了操做,對象發生了變化,這種變化應該對其餘線程是可見的。可是默認對這點沒有任何保障。因此咱們使用了Synchonized. 另外一種方法是使用volatile關鍵字確保多線程對對象讀寫的可見性(可是隻是在某些狀況能夠保證同步,好比一個線程讀,而後寫在了volatile變量上,其餘線程只是進行讀操做; 若是多個線程都進行讀寫,那麼就必定要在用synchronized)。volatile只確保了可見性,並不能確保原子性。
當咱們使用 volatile 關鍵字去修飾變量的時候,因此線程都會直接讀取該變量而且不緩存它。這就確保了線程讀取到的變量是同內存中是一致的
幾乎 java.util.concurrent 包中的全部類都使用原子變量,而不使用同步。緣由是 同步(lock)機制並非一個輕量級的操做,它存在一些缺點。缺點以下
When several threads try to acquire the same lock, one or more threads will be suspended and they will be resumed later. When the critical section is little, the overhead is really heavy especially when the lock is often acquired and there is a lot of contention. Another disadvantage is that the other threads waiting of the lock cannot do something else during waiting and if the thread who has the lock is delayed (due to a page fault or the end of the time quanta by example), the others threads cannot take their turn.
JUC這包裏面提供了一組原子類。其基本的特性就是在多線程環境下,當有多個線程同時執行這些類的實例包含的方法時,具備排他性,即當某個線程進入方法,執行其中的指令時,不會被其餘線程打斷,而別的線程就像自旋鎖同樣,一直等到該方法執行完成,才由 JVM 從等待隊列中選擇一個另外一個線程進入,這只是一種邏輯上的理解。其實是藉助硬件的相關指令來實現的,不會阻塞線程 (或者說只是在硬件級別上阻塞了)。
根據修改的數據類型,能夠將 JUC 包中的原子操做類能夠分爲 4 類。
這些類都是基於CAS實現的。處理器提供了CAS操做來實現非加鎖的原子操做。
引用《Java Concurrency in Practice》裏的一段描述:
在這裏,CAS 指的是現代 CPU 普遍支持的一種對內存中的共享數據進行操做的一種特殊指令。這個指令會對內存中的共享數據作原子的讀寫操做。簡單介紹一下這個指令的操做過程:首先,CPU 會將內存中將要被更改的數據與指望的值作比較。而後,當這兩個值相等時,CPU 纔會將內存中的數值替換爲新的值。不然便不作操做。最後,CPU 會將舊的數值返回。這一系列的操做是原子的。它們雖然看似複雜,但倒是 Java 5 併發機制優於原有鎖機制的根本。簡單來講,CAS 的含義是 「我認爲原有的值應該是什麼,若是是,則將原有的值更新爲新值,不然不作修改,並告訴我原來的值是多少」。
CSA的優勢:Compare and Set 是一個非阻塞的算法,這是它的優點。由於使用的是 CPU 支持的指令,提供了比原有的併發機制更好的性能和伸縮性。能夠認爲通常狀況下性能更好,而且也更容易使用
使用原子類實現i++方法
public class AtomicCounter { private final AtomicInteger value = new AtomicInteger(0); public int getValue(){ return value.get(); } public int getNextValue(){ return value.incrementAndGet(); } public int getPreviousValue(){ return value.decrementAndGet(); } }
一個線程安全的棧
public class Stack { private final AtomicReference<Element> head = new AtomicReference<Element>(null); public void push(String value){ Element newElement = new Element(value); while(true){ Element oldHead = head.get(); newElement.next = oldHead; //Trying to set the new element as the head if(head.compareAndSet(oldHead, newElement)){ return; } } } public String pop(){ while(true){ Element oldHead = head.get(); //The stack is empty if(oldHead == null){ return null; } Element newHead = oldHead.next; //Trying to set the new element as the head if(head.compareAndSet(oldHead, newHead)){ return oldHead.value; } } } private static final class Element { private final String value; private Element next; private Element(String value) { this.value = value; } } }
總結說來,synchronized 實現的同步能確保線程安全,實現可見性和原子性;可是代價大,效率低,更慢;
volatile 可以實現多線程操做產生變化的可見性,可是不能實現原子性。
atomic 類 是一種更輕量級的方法實現可見性和原子性
想更一進步的支持我,請掃描下方的二維碼,你懂的~