摘要:線程的這種交叉操作會導致線程不安全。原子操作是在多線程環境下避免數據不一致必須的手段。如果聲明一個域為一些情況就可以確保多線程訪問到的變量是最新的。并發要求一個線程對對象進行了操作,對象發生了變化,這種變化應該對其他線程是可見的。
雖是讀書筆記,但是如轉載請注明出處 http://segmentfault.com/blog/exploring/
.. 拒絕伸手復制黨
i++為什么是非線程安全的?
先來解釋下什么叫“線程安全” :
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.
即一段代碼可以被多個線程調用,調用過程中對象的狀態不出現沖突,或者對象按照正確的順序進行了操作。
i++ 線程安全是指我們讀取一個值希望的是每一次讀取到的值都是上一次+1 。
i++是分為三個步驟,獲取i的值;temp = i+1操作;temp寫入i; 如果存在兩個線程,都執行i++. 正常情況應該是線程A 先執行,得到1; 線程B再執行,得到2.
但是又常常出現:
線程A : 獲取i的值;得到0;temp = i+1操作;得到i= 1;
線程B : 獲取i的值;得到0;temp = i+1操作;得到i= 1;
線程A : i = temp 賦值 i =1 被寫入;
線程B :i = temp 賦值 i =1 被寫入;
或者更形象的舉例:線程A,B對i不停的進行操作,A執行i++, B執行打印。程序的邏輯是每次加1后就打,這樣應該輸出的結果是順序的不斷加1。由于i++不是原子操作,在執行的過程中發生了線程的切換,i+1沒有被回寫之前就被2訪問了,這時打印的還是原來的數字,并不是預期的+1。
線程的這種交叉操作會導致線程不安全。在Java中可以有很多方法來保證線程安全,即原子化 —— 同步,使用原子類,實現并發鎖,使用volatile關鍵字,使用不變類和線程安全類。
名詞解釋:何為 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
關鍵詞:可見性
當對非volatile變量進行讀寫的時候,每個線程先從內存拷貝變量到CPU緩存中。如果計算機有多個CPU,每個線程可能在不同的CPU上被處理,這意味著每個線程可以考慮到不同的CPU cache中。
而聲明變量是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)機制并不是一個輕量級的操作,它存在一些缺點。缺點如下
via Baptiste Vicht
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 類。
基本類型: AtomicInteger, AtomicLong, AtomicBoolean ;
數組類型: AtomicIntegerArray, AtomicLongArray, AtomicReferenceArray ;
引用類型: AtomicReference, AtomicStampedRerence, AtomicMarkableReference ;
對象的屬性修改類型: AtomicIntegerFieldUpdater, AtomicLongFieldUpdater, AtomicReferenceFieldUpdater 。
這些類都是基于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總結head = new AtomicReference (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 類 是一種更輕量級的方法實現可見性和原子性
想更一進步的支持我,請掃描下方的二維碼,你懂的~
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/64313.html
摘要:今天給大家總結一下,面試中出鏡率很高的幾個多線程面試題,希望對大家學習和面試都能有所幫助。指令重排在單線程環境下不會出先問題,但是在多線程環境下會導致一個線程獲得還沒有初始化的實例。使用可以禁止的指令重排,保證在多線程環境下也能正常運行。 下面最近發的一些并發編程的文章匯總,通過閱讀這些文章大家再看大廠面試中的并發編程問題就沒有那么頭疼了。今天給大家總結一下,面試中出鏡率很高的幾個多線...
摘要:另一個是使用鎖的機制來處理線程之間的原子性。依賴于去實現鎖,因此在這個關鍵字作用對象的作用范圍內,都是同一時刻只能有一個線程對其進行操作的。 線程安全性 定義:當多個線程訪問某個類時,不管運行時環境采用何種調度方式或者這些線程將如何交替執行,并且在主調代碼中不需要任何額外的同步或協同,這個類都能表現出正確的行為,那么就稱這個類是線程安全的。 線程安全性主要體現在三個方面:原子性、可見性...
摘要:簡介從創建以來,就支持核心的并發概念如線程和鎖。這篇文章會幫助從事多線程編程的開發人員理解核心的并發概念以及如何使用它們。請求操作系統互斥,并讓操作系統調度程序處理線程停放和喚醒。 簡介 從創建以來,JAVA就支持核心的并發概念如線程和鎖。這篇文章會幫助從事多線程編程的JAVA開發人員理解核心的并發概念以及如何使用它們。 (博主將在其中加上自己的理解以及自己想出的例子作為補充) 概念 ...
摘要:方法由兩個參數,表示期望的值,表示要給設置的新值。操作包含三個操作數內存位置預期原值和新值。如果處的值尚未同時更改,則操作成功。中就使用了這樣的操作。上面操作還有一點是將事務范圍縮小了,也提升了系統并發處理的性能。 這是java高并發系列第21篇文章。 本文主要內容 從網站計數器實現中一步步引出CAS操作 介紹java中的CAS及CAS可能存在的問題 悲觀鎖和樂觀鎖的一些介紹及數據庫...
閱讀 1644·2021-09-02 15:11
閱讀 1978·2019-08-30 14:04
閱讀 2566·2019-08-27 10:52
閱讀 1585·2019-08-26 11:52
閱讀 1207·2019-08-23 15:26
閱讀 2624·2019-08-23 15:09
閱讀 2607·2019-08-23 12:07
閱讀 2237·2019-08-22 18:41