摘要:簡(jiǎn)介從創(chuàng)建以來,就支持核心的并發(fā)概念如線程和鎖。這篇文章會(huì)幫助從事多線程編程的開發(fā)人員理解核心的并發(fā)概念以及如何使用它們。請(qǐng)求操作系統(tǒng)互斥,并讓操作系統(tǒng)調(diào)度程序處理線程停放和喚醒。
簡(jiǎn)介
從創(chuàng)建以來,JAVA就支持核心的并發(fā)概念如線程和鎖。這篇文章會(huì)幫助從事多線程編程的JAVA開發(fā)人員理解核心的并發(fā)概念以及如何使用它們。
(博主將在其中加上自己的理解以及自己想出的例子作為補(bǔ)充)
概念原子性:原子操作是指該系列操作要么全部執(zhí)行,要么全部不執(zhí)行,因此不存在部分執(zhí)行的狀態(tài)。競(jìng)爭(zhēng)情況
可見性:一個(gè)線程能夠看見另一個(gè)線程所帶來的改變。
當(dāng)多個(gè)線程在一個(gè)共享的資源上執(zhí)行一組操作時(shí),會(huì)產(chǎn)生競(jìng)爭(zhēng)。根據(jù)各個(gè)線程執(zhí)行操作的順序可能產(chǎn)生多個(gè)不同結(jié)果。下面的代碼不是線程安全的,value可能會(huì)被初始化多次,因?yàn)?b>check-then-act型(先判斷是否為null,然后初始化)的惰性初始化并非原子性操作。
class Lazy數(shù)據(jù)沖突{ private volatile T value; T get() { if (value == null) value = initialize(); return value; } }
當(dāng)兩個(gè)或多個(gè)線程在沒有同步的情況下試圖訪問同一個(gè)非final變量時(shí),會(huì)產(chǎn)生數(shù)據(jù)沖突。不使用同步可能使數(shù)據(jù)的改變對(duì)別的線程不可見,從而可能讀取過期的數(shù)據(jù),并導(dǎo)致如無限循環(huán),數(shù)據(jù)結(jié)構(gòu)損壞和不準(zhǔn)確的計(jì)算等后果。下面這段代碼可能會(huì)導(dǎo)致無限循環(huán),因?yàn)樽x者線程可能永遠(yuǎn)都沒有看到寫入者線程做出的更改:
class Waiter implements Runnable { private boolean shouldFinish; void finish() { shouldFinish = true; } public void run() { long iteration = 0; while (!shouldFinish) { iteration++; } System.out.println("Finished after: " + iteration); } } class DataRace { public static void main(String[] args) throws InterruptedException { Waiter waiter = new Waiter(); Thread waiterThread = new Thread(waiter); waiterThread.start(); waiter.finish(); waiterThread.join(); } }JAVA內(nèi)存模型:happens-before關(guān)系
JAVA內(nèi)存模型是根據(jù)讀寫字段等操作來定義的,并在控制器上進(jìn)行同步。操作根據(jù)happens-before關(guān)聯(lián)排序,這解釋了一個(gè)線程何時(shí)能夠看到另一個(gè)線程操作的結(jié)果,以及是什么構(gòu)成了一個(gè)同步良好的程序。
happens-before關(guān)聯(lián)有以下屬性:
Thread#start的方法在線程的所有操作之前執(zhí)行
在釋放當(dāng)前控制器之后,后序的請(qǐng)求才可以獲取控制器。(Releasing a monitor happens before any subsequent acquisition of the same monitor.)
寫入volatile變量的操作在所有后序讀取該變量的操作之前執(zhí)行。
寫入final型變量的操作在發(fā)布該對(duì)象的引用之前執(zhí)行
線程的所有操作在從Thread#join方法返回之前執(zhí)行
上圖中,Action X在Action Y之前執(zhí)行,因此線程1在Action X以前執(zhí)行的所有操作對(duì)線程2在Action Y之后的所有操作可見。
標(biāo)注的同步功能 synchronized關(guān)鍵字synchronized關(guān)鍵字用來防止不同的線程同時(shí)進(jìn)入一段代碼。它確保了你的操作的原子性,因?yàn)槟阒挥蝎@得了這段代碼的鎖才能進(jìn)入這段代碼,使得該鎖所保護(hù)的數(shù)據(jù)可以在獨(dú)占模式下操作。除此以外,它還確保了別的線程在獲得了同樣的鎖之后,能夠觀察到之前線程的操作。
class AtomicOperation { private int counter0; private int counter1; void increment() { synchronized (this) { counter0++; counter1++; } } }
synchronized關(guān)鍵字也可以在方法層上聲明。
靜態(tài)方法:將持有該方法的類作為加鎖對(duì)象
非靜態(tài)方法:加鎖this指針
鎖是可重入的。所以如果一個(gè)線程已經(jīng)持有了該鎖,它可以一直訪問該鎖下的任何內(nèi)容:
class Reentrantcy { synchronized void doAll() { doFirst(); doSecond(); } synchronized void doFirst() { System.out.println("First operation is successful."); } synchronized void doSecond() { System.out.println("Second operation is successful."); } }
爭(zhēng)用程度影響如何獲得控制器:
初始化:剛剛創(chuàng)建,沒有被獲取wait/notify
biased:鎖下的代碼只被一個(gè)線程執(zhí)行,不會(huì)產(chǎn)生沖突
thin:控制器被幾個(gè)線程無沖突的獲取。使用CAS(compare and swap)來管理這個(gè)鎖
fat:產(chǎn)生沖突。JVM請(qǐng)求操作系統(tǒng)互斥,并讓操作系統(tǒng)調(diào)度程序處理線程停放和喚醒。
wait/notify/notifyAll方法在Object類中聲明。wait方法用來將線程狀態(tài)改變?yōu)?b>WAITING或是TIMED_WAITING(如果傳入了超時(shí)時(shí)間值)。要想喚醒一個(gè)線程,下列的操作都可以實(shí)現(xiàn):
另一個(gè)線程調(diào)用notify方法,喚醒在控制器上等待的任意的一個(gè)線程
另一個(gè)線程調(diào)用notifyAll方法,喚醒在該控制器上等待的所有線程
Thread#interrupt方法被調(diào)用,在這種情況下,會(huì)拋出InterruptedException
最常用的一個(gè)模式是一個(gè)條件性循環(huán):
class ConditionLoop { private boolean condition; synchronized void waitForCondition() throws InterruptedException { while (!condition) { wait(); } } synchronized void satisfyCondition() { condition = true; notifyAll(); } }
記住,要想使用對(duì)象上的wait/notify/notifyAll方法,你首先需要獲取對(duì)象的鎖
總是在一個(gè)條件性循環(huán)中等待,從而解決如果另一個(gè)線程在wait開始之前滿足條件并且調(diào)用了notifyAll而導(dǎo)致的順序問題。而且它還防止線程由于偽喚起繼續(xù)執(zhí)行。
時(shí)刻確保你在調(diào)用notify/notifyAll之前已經(jīng)滿足了等待條件。如果不這樣的話,將只會(huì)發(fā)出一個(gè)喚醒通知,但是在該等待條件上的線程永遠(yuǎn)無法跳出其等待循環(huán)。
博主備注:這里解釋一下為何建議將wait放在條件性循環(huán)中、假設(shè)現(xiàn)在有一個(gè)線程,并沒有將wait放入條件性循環(huán)中,代碼如下:
class UnconditionLoop{ private boolean condition; synchronized void waitForCondition() throws InterruptedException{ //.... wait(); } synchronized void satisfyCondition(){ condition = true; notifyAll(); } }
假設(shè)現(xiàn)在有兩個(gè)線程分別同時(shí)調(diào)用waitForCondition和satisfyCondition(),而調(diào)用satisfyCondition的方法先調(diào)用完成,并且發(fā)出了notifyAll通知。鑒于waitForCondition方法根本沒有進(jìn)入wait方法,因此它就錯(cuò)過了這個(gè)解掛信號(hào),從而永遠(yuǎn)無法被喚醒。
這時(shí)你可能會(huì)想,那就使用if判斷一下條件唄,如果條件還沒滿足,就進(jìn)入掛起狀態(tài),一旦接收到信號(hào),就可以直接執(zhí)行后序程序。代碼如下:
class UnconditionLoop{ private boolean condition; private boolean condition2; synchronized void waitForCondition() throws InterruptedException{ //.... if(!condition){ wait(); } } synchronized void waitForCondition2() throws InterruptedException{ //.... if(!condition2){ wait(); } } synchronized void satisfyCondition(){ condition = true; notifyAll(); } synchronized void satisfyCondition2(){ condition2 = true; notifyAll(); } }
那讓我們?cè)偌僭O(shè)這個(gè) 方法中還存在另一個(gè)condition,并且也有其對(duì)應(yīng)的等待和喚醒方法。假設(shè)這時(shí)satisfyConsition2被滿足并發(fā)出nofityAll喚醒所有等待的線程,那么waitForCondition和waitForCondition2都將會(huì)被喚醒繼續(xù)執(zhí)行。而waitForCondition的條件并沒有被滿足!
因此在條件中循環(huán)等待信號(hào)是有必要的。
volatile關(guān)鍵字volatile關(guān)鍵字解決了可見性問題,并且使值的更改原子化,因?yàn)檫@里存在一個(gè)happens-before關(guān)聯(lián):對(duì)volatile值的更改會(huì)在所有后續(xù)讀取該值的操作之前執(zhí)行。因此,它確保了后序所有的讀取操作能夠看到之前的更改。
class VolatileFlag implements Runnable { private volatile boolean shouldStop; public void run() { while (!shouldStop) { //do smth } System.out.println("Stopped."); } void stop() { shouldStop = true; } public static void main(String[] args) throws InterruptedException { VolatileFlag flag = new VolatileFlag(); Thread thread = new Thread(flag); thread.start(); flag.stop(); thread.join(); } }Atomics
java.util.concurrent.atomic包中包含了一組支持在單一值上進(jìn)行多種原子性操作的類,從而從加鎖中解脫出來。
使用AtomicXXX類,可以實(shí)現(xiàn)原子性的check-then-act操作:
class CheckThenAct { private final AtomicReferencevalue = new AtomicReference<>(); void initialize() { if (value.compareAndSet(null, "Initialized value")) { System.out.println("Initialized only once."); } } }
AtomicInteger和AtomicLong都用increment/decrement操作:
class Increment { private final AtomicInteger state = new AtomicInteger(); void advance() { int oldState = state.getAndIncrement(); System.out.println("Advanced: "" + oldState + "" -> "" + (oldState + 1) + ""."); } }
如果你想要?jiǎng)?chuàng)建一個(gè)計(jì)數(shù)器,但是并不需要原子性的讀操作,可以使用LongAdder替代AtomicLong/AtomicInteger,LongAdder在多個(gè)單元格中維護(hù)該值,并在需要時(shí)對(duì)這些值同時(shí)遞增,從而在高并發(fā)的情況下性能更好。ThreadLocal
在線程中包含數(shù)據(jù)并且不需要鎖定的一種方法是使用ThreadLocal存儲(chǔ)。從概念上將,ThreadLocal就好像是在每個(gè)線程中都有自己版本的變量。ThreadLocal常用來存儲(chǔ)只屬于線程自己的值,比如當(dāng)前的事務(wù)以及其它資源。而且,它還能用來維護(hù)單個(gè)線程專有的計(jì)數(shù)器,統(tǒng)計(jì)或是ID生成器。
class TransactionManager { private final ThreadLocalcurrentTransaction = ThreadLocal.withInitial(NullTransaction::new); Transaction currentTransaction() { Transaction current = currentTransaction.get(); if (current.isNull()) { current = new TransactionImpl(); currentTransaction.set(current); } return current; } }
想要了解更多開發(fā)技術(shù),面試教程以及互聯(lián)網(wǎng)公司內(nèi)推,歡迎關(guān)注我的微信公眾號(hào)!將會(huì)不定期的發(fā)放福利哦~
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/70996.html
摘要:前言上一篇文章請(qǐng)參考貓頭鷹的深夜翻譯核心并發(fā)一安全發(fā)布發(fā)布一個(gè)對(duì)象是指該對(duì)象的引用對(duì)當(dāng)前的域之外也可見比如,從方法中獲取一個(gè)引用。任務(wù)的功能性接口表示一個(gè)沒有返回值的任務(wù)表示一個(gè)包含返回值的計(jì)算。 前言 上一篇文章請(qǐng)參考貓頭鷹的深夜翻譯:核心JAVA并發(fā)(一) 安全發(fā)布 發(fā)布一個(gè)對(duì)象是指該對(duì)象的引用對(duì)當(dāng)前的域之外也可見(比如,從getter方法中獲取一個(gè)引用)。要確保一個(gè)對(duì)象被安全的發(fā)...
摘要:有可能一個(gè)線程中的動(dòng)作相對(duì)于另一個(gè)線程出現(xiàn)亂序。當(dāng)實(shí)際輸出取決于線程交錯(cuò)的結(jié)果時(shí),這種情況被稱為競(jìng)爭(zhēng)條件。這里的問題在于代碼塊不是原子性的,而且實(shí)例的變化對(duì)別的線程不可見。這種不能同時(shí)在多個(gè)線程上執(zhí)行的部分被稱為關(guān)鍵部分。 為什么要額外寫一篇文章來研究volatile呢?是因?yàn)檫@可能是并發(fā)中最令人困惑以及最被誤解的結(jié)構(gòu)。我看過不少解釋volatile的博客,但是大多數(shù)要么不完整,要么難...
摘要:由于需要跨進(jìn)程訪問網(wǎng)絡(luò)上的高速緩存,因此延遲,故障和對(duì)象序列化會(huì)導(dǎo)致性能下降。應(yīng)用程序高速緩存會(huì)自動(dòng)清除條目以保持其內(nèi)存占用。緩存統(tǒng)計(jì)高速緩存統(tǒng)計(jì)信息可幫助識(shí)別高速緩存的運(yùn)行狀況并提供有關(guān)高速緩存行為和性能的信息。 前言 這篇文章探索了現(xiàn)有的各種JAVA緩存基數(shù),它們對(duì)各種場(chǎng)景下提高應(yīng)用的性能起著重要的作用。 近十年來,信息技術(shù)極高的提升了業(yè)務(wù)流程,它已經(jīng)成為了全球企業(yè)的戰(zhàn)略性方案。它...
摘要:什么是為執(zhí)行字節(jié)碼提供一個(gè)運(yùn)行環(huán)境。它的實(shí)現(xiàn)主要包含三個(gè)部分,描述實(shí)現(xiàn)規(guī)格的文檔,具體實(shí)現(xiàn)和滿足要求的計(jì)算機(jī)程序以及實(shí)例具體執(zhí)行字節(jié)碼。該類先被轉(zhuǎn)化為一組字節(jié)碼并放入文件中。字節(jié)碼校驗(yàn)器通過字節(jié)碼校驗(yàn)器檢查格式并找出非法代碼。 什么是Java Development Kit (JDK)? JDK通常用來開發(fā)Java應(yīng)用和插件?;旧峡梢哉J(rèn)為是一個(gè)軟件開發(fā)環(huán)境。JDK包含Java Run...
閱讀 3072·2021-11-23 09:51
閱讀 2918·2021-11-11 16:55
閱讀 3019·2021-10-14 09:43
閱讀 1462·2021-09-23 11:22
閱讀 1102·2019-08-30 11:04
閱讀 1786·2019-08-29 11:10
閱讀 1021·2019-08-27 10:56
閱讀 3209·2019-08-26 12:01