摘要:假設(shè)不發(fā)生編譯器重排和指令重排,線程修改了的值,但是修改以后,的值可能還沒有寫回到主存中,那么線程得到就是很自然的事了。同理,線程對于的賦值操作也可能沒有及時刷新到主存中。線程的最后操作與線程發(fā)現(xiàn)線程已經(jīng)結(jié)束同步。
很久沒更新文章了,對隔三差五過來刷更新的讀者說聲抱歉。
關(guān)于 Java 并發(fā)也算是寫了好幾篇文章了,本文將介紹一些比較基礎(chǔ)的內(nèi)容,注意,閱讀本文需要一定的并發(fā)基礎(chǔ)。
本文的主要目的是讓大家對于并發(fā)程序中的重排序、內(nèi)存可見性以及原子性有一定的了解,同時要能準(zhǔn)確理解 synchronized、volatile、final 幾個關(guān)鍵字的作用。
另外,本文還對雙重檢查形式的單例模式為什么需要使用 volatile 做了深入的解釋。
并發(fā)三問題
重排序
內(nèi)存可見性
原子性
Java 對于并發(fā)的規(guī)范約束
1.Synchronization Order
2.Happens-before Order
3.synchronized 關(guān)鍵字
4.單例模式中的雙重檢查
volatile 關(guān)鍵字
1.volatile 的內(nèi)存可見性
2.volatile 的禁止重排序
3.volatile 小結(jié)
6.final 關(guān)鍵字
7.小結(jié)
這節(jié)將介紹重排序、內(nèi)存可見性以及原子性相關(guān)的知識,這些也是并發(fā)程序為什么難寫的原因。
1. 重排序請讀者先在自己的電腦上運(yùn)行一下以下程序:
public class Test { private static int x = 0, y = 0; private static int a = 0, b =0; public static void main(String[] args) throws InterruptedException { int i = 0; for(;;) { i++; x = 0; y = 0; a = 0; b = 0; CountDownLatch latch = new CountDownLatch(1); Thread one = new Thread(() -> { try { latch.await(); } catch (InterruptedException e) { } a = 1; x = b; }); Thread other = new Thread(() -> { try { latch.await(); } catch (InterruptedException e) { } b = 1; y = a; }); one.start();other.start(); latch.countDown(); one.join();other.join(); String result = "第" + i + "次 (" + x + "," + y + ")"; if(x == 0 && y == 0) { System.err.println(result); break; } else { System.out.println(result); } } } }
幾秒后,我們就可以得到 x == 0 && y == 0 這個結(jié)果,仔細(xì)看看代碼就會知道,如果不發(fā)生重排序的話,這個結(jié)果是不可能出現(xiàn)的。
重排序由以下幾種機(jī)制引起:
編譯器優(yōu)化:對于沒有數(shù)據(jù)依賴關(guān)系的操作,編譯器在編譯的過程中會進(jìn)行一定程度的重排。
大家仔細(xì)看看線程 1 中的代碼,編譯器是可以將 a = 1 和 x = b 換一下順序的,因為它們之間沒有數(shù)據(jù)依賴關(guān)系,同理,線程 2 也一樣,那就不難得到 x == y == 0 這種結(jié)果了。
指令重排序:CPU 優(yōu)化行為,也是會對不存在數(shù)據(jù)依賴關(guān)系的指令進(jìn)行一定程度的重排。
這個和編譯器優(yōu)化差不多,就算編譯器不發(fā)生重排,CPU 也可以對指令進(jìn)行重排,這個就不用多說了。
內(nèi)存系統(tǒng)重排序:內(nèi)存系統(tǒng)沒有重排序,但是由于有緩存的存在,使得程序整體上會表現(xiàn)出亂序的行為。
假設(shè)不發(fā)生編譯器重排和指令重排,線程 1 修改了 a 的值,但是修改以后,a 的值可能還沒有寫回到主存中,那么線程 2 得到 a == 0 就是很自然的事了。同理,線程 2 對于 b 的賦值操作也可能沒有及時刷新到主存中。
2. 內(nèi)存可見性前面在說重排序的時候,也說到了內(nèi)存可見性的問題,這里再啰嗦一下。
線程間的對于共享變量的可見性問題不是直接由多核引起的,而是由多緩存引起的。如果每個核心共享同一個緩存,那么也就不存在內(nèi)存可見性問題了。
現(xiàn)代多核 CPU 中每個核心擁有自己的一級緩存或一級緩存加上二級緩存等,問題就發(fā)生在每個核心的獨(dú)占緩存上。每個核心都會將自己需要的數(shù)據(jù)讀到獨(dú)占緩存中,數(shù)據(jù)修改后也是寫入到緩存中,然后等待刷入到主存中。所以會導(dǎo)致有些核心讀取的值是一個過期的值。
Java 作為高級語言,屏蔽了這些底層細(xì)節(jié),用 JMM 定義了一套讀寫內(nèi)存數(shù)據(jù)的規(guī)范,雖然我們不再需要關(guān)心一級緩存和二級緩存的問題,但是,JMM 抽象了主內(nèi)存和本地內(nèi)存的概念。
所有的共享變量存在于主內(nèi)存中,每個線程有自己的本地內(nèi)存,線程讀寫共享數(shù)據(jù)也是通過本地內(nèi)存交換的,所以可見性問題依然是存在的。這里說的本地內(nèi)存并不是真的是一塊給每個線程分配的內(nèi)存,而是 JMM 的一個抽象,是對于寄存器、一級緩存、二級緩存等的抽象。
3. 原子性在本文中,原子性不是重點(diǎn),它將作為并發(fā)編程中需要考慮的一部分進(jìn)行介紹。
說到原子性的時候,大家應(yīng)該都能想到 long 和 double,它們的值需要占用 64 位的內(nèi)存空間,Java 編程語言規(guī)范中提到,對于 64 位的值的寫入,可以分為兩個 32 位的操作進(jìn)行寫入。本來一個整體的賦值操作,被拆分為低 32 位賦值和高 32 位賦值兩個操作,中間如果發(fā)生了其他線程對于這個值的讀操作,必然就會讀到一個奇怪的值。
這個時候我們要使用 volatile 關(guān)鍵字進(jìn)行控制了,JMM 規(guī)定了對于 volatile long 和 volatile double,JVM 需要保證寫入操作的原子性。
另外,對于引用的讀寫操作始終是原子的,不管是 32 位的機(jī)器還是 64 位的機(jī)器。
Java 編程語言規(guī)范同樣提到,鼓勵 JVM 的開發(fā)者能保證 64 位值操作的原子性,也鼓勵使用者盡量使用 volatile 或使用正確的同步方式。關(guān)鍵詞是”鼓勵“。
在 64 位的 JVM 中,不加 volatile 也是可以的,同樣能保證對于 long 和 double 寫操作的原子性。關(guān)于這一點(diǎn),我沒有找到官方的材料描述它,如果讀者有相關(guān)的信息,希望可以給我反饋一下。
Java 對于并發(fā)的規(guī)范約束并發(fā)問題使得我們的代碼有可能會產(chǎn)生各種各樣的執(zhí)行結(jié)果,顯然這是我們不能接受的,所以 Java 編程語言規(guī)范需要規(guī)定一些基本規(guī)則,JVM 實(shí)現(xiàn)者會在這些規(guī)則的約束下來實(shí)現(xiàn) JVM,然后開發(fā)者也要按照規(guī)則來寫代碼,這樣寫出來的并發(fā)代碼我們才能準(zhǔn)確預(yù)測執(zhí)行結(jié)果。下面進(jìn)行一些簡單的介紹。
Synchronization OrderJava 語言規(guī)范對于同步定義了一系列的規(guī)則:17.4.4. Synchronization Order,包括了如下同步關(guān)系:
對于監(jiān)視器 m 的解鎖與所有后續(xù)操作對于 m 的加鎖同步
對 volatile 變量 v 的寫入,與所有其他線程后續(xù)對 v 的讀同步
啟動線程的操作與線程中的第一個操作同步。
對于每個屬性寫入默認(rèn)值(0, false,null)與每個線程對其進(jìn)行的操作同步。
盡管在創(chuàng)建對象完成之前對對象屬性寫入默認(rèn)值有點(diǎn)奇怪,但從概念上來說,每個對象都是在程序啟動時用默認(rèn)值初始化來創(chuàng)建的。
線程 T1 的最后操作與線程 T2 發(fā)現(xiàn)線程 T1 已經(jīng)結(jié)束同步。
線程 T2 可以通過 T1.isAlive() 或 T1.join() 方法來判斷 T1 是否已經(jīng)終結(jié)。
如果線程 T1 中斷了 T2,那么線程 T1 的中斷操作與其他所有線程發(fā)現(xiàn) T2 被中斷了同步(通過拋出 InterruptedException 異常,或者調(diào)用 Thread.interrupted 或 Thread.isInterrupted )
Happens-before Order兩個操作可以用 happens-before 來確定它們的執(zhí)行順序,如果一個操作 happens-before 于另一個操作,那么我們說第一個操作對于第二個操作是可見的。
如果我們分別有操作 x 和操作 y,我們寫成 hb(x, y) 來表示 x happens-before y。以下幾個規(guī)則也是來自于 Java 8 語言規(guī)范:
如果操作 x 和操作 y 是同一個線程的兩個操作,并且在代碼上操作 x 先于操作 y 出現(xiàn),那么有 hb(x, y)
對象構(gòu)造方法的最后一行指令 happens-before 于 finalize() 方法的第一行指令。
如果操作 x 與隨后的操作 y 構(gòu)成同步,那么 hb(x, y)。這條說的是前面一小節(jié)的內(nèi)容。
hb(x, y) 和 hb(y, z),那么可以推斷出 hb(x, z)
這里再提一點(diǎn),x happens-before y,并不是說 x 操作一定要在 y 操作之前被執(zhí)行,而是說 x 的執(zhí)行結(jié)果對于 y 是可見的,只要滿足可見性,發(fā)生了重排序也是可以的。
monitor,這里翻譯成監(jiān)視器鎖,為了大家理解方便。
synchronized 這個關(guān)鍵字大家都用得很多了,這里不會教你怎么使用它,我們來看看它對于內(nèi)存可見性的影響。
一個線程在獲取到監(jiān)視器鎖以后才能進(jìn)入 synchronized 控制的代碼塊,一旦進(jìn)入代碼塊,首先,該線程對于共享變量的緩存就會失效,因此 synchronized 代碼塊中對于共享變量的讀取需要從主內(nèi)存中重新獲取,也就能獲取到最新的值。
退出代碼塊的時候的,會將該線程寫緩沖區(qū)中的數(shù)據(jù)刷到主內(nèi)存中,所以在 synchronized 代碼塊之前或 synchronized 代碼塊中對于共享變量的操作隨著該線程退出 synchronized 塊,會立即對其他線程可見(這句話的前提是其他讀取共享變量的線程會從主內(nèi)存讀取最新值)。
因此,我們可以總結(jié)一下:線程 a 對于進(jìn)入 synchronized 塊之前或在 synchronized 中對于共享變量的操作,對于后續(xù)的持有同一個監(jiān)視器鎖的線程 b 可見。雖然是挺簡單的一句話,請讀者好好體會。
注意一點(diǎn),在進(jìn)入 synchronized 的時候,并不會保證之前的寫操作刷入到主內(nèi)存中,synchronized 主要是保證退出的時候能將本地內(nèi)存的數(shù)據(jù)刷入到主內(nèi)存。
單例模式中的雙重檢查我們趁熱打鐵,為大家解決下單例模式中的雙重檢查問題。關(guān)于這個問題,大神們發(fā)過文章對此進(jìn)行闡述了,這里搬運(yùn)一下。
來膜拜下文章署名中的大神們:David Bacon (IBM Research) Joshua Bloch (Javasoft), Jeff Bogda, Cliff Click (Hotspot JVM project), Paul Haahr, Doug Lea, Tom May, Jan-Willem Maessen, Jeremy Manson, John D. Mitchell (jGuru) Kelvin Nilsen, Bill Pugh, Emin Gun Sirer,至少 Joshua Bloch 和 Doug Lea 大家都不陌生吧。
廢話少說,看以下單例模式的寫法:
public class Singleton { private static Singleton instance = null; private int v; private Singleton() { this.v = 3; } public static Singleton getInstance() { if (instance == null) { // 1. 第一次檢查 synchronized (Singleton.class) { // 2 if (instance == null) { // 3. 第二次檢查 instance = new Singleton(); // 4 } } } return instance; } }
很多人都知道上述的寫法是不對的,但是可能會說不清楚到底為什么不對。
我們假設(shè)有兩個線程 a 和 b 調(diào)用 getInstance() 方法,假設(shè) a 先走,一路走到 4 這一步,執(zhí)行 instance = new Singleton() 這句代碼。
instance = new Singleton() 這句代碼首先會申請一段空間,然后將各個屬性初始化為零值(0/null),執(zhí)行構(gòu)造方法中的屬性賦值[1],將這個對象的引用賦值給 instance[2]。在這個過程中,[1] 和 [2] 可能會發(fā)生重排序。
此時,線程 b 剛剛進(jìn)來執(zhí)行到 1(看上面的代碼塊),就有可能會看到 instance 不為 null,然后線程 b 也就不會等待監(jiān)視器鎖,而是直接返回 instance。問題是這個 instance 可能還沒執(zhí)行完構(gòu)造方法(線程 a 此時還在 4 這一步),所以線程 b 拿到的 instance 是不完整的,它里面的屬性值可能是初始化的零值(0/false/null),而不是線程 a 在構(gòu)造方法中指定的值。
回顧下前面的知識,分析下這里為什么會有這個問題。
1、編譯器可以將構(gòu)造方法內(nèi)聯(lián)過來,之后再發(fā)生重排序就很容易理解了。
2、即使不發(fā)生代碼重排序,線程 a 對于屬性的賦值寫入到了線程 a 的本地內(nèi)存中,此時對于線程 b 不可見。
最后提一點(diǎn),如果線程 a 從 synchronized 塊出來了,那么 instance 一定是正確構(gòu)造的完整實(shí)例,這是我們前面說過的 synchronized 的內(nèi)存可見性保證。
—————分割線—————
對于大部分讀者來說,這一小節(jié)其實(shí)可以結(jié)束了,很多讀者都知道,解決方案是使用 volatile 關(guān)鍵字,這個我們在介紹 volatile 的時候再說。當(dāng)然,如果你還有耐心,也可以繼續(xù)看看本小節(jié)。
我們看下下面這段代碼,看看它能不能解決我們之前碰到的問題。
public static Singleton getInstance() { if (instance == null) { // Singleton temp; synchronized (Singleton.class) { // temp = instance; if (temp == null) { // synchronized (Singleton.class) { // 內(nèi)嵌一個 synchronized 塊 temp = new Singleton(); } instance = temp; // } } } return instance; }
上面這個代碼很有趣,想利用 synchronized 的內(nèi)存可見性語義,不過這個解決方案還是失敗了,我們分析下。
前面我們也說了,synchronized 在退出的時候,能保證 synchronized 塊中對于共享變量的寫入一定會刷入到主內(nèi)存中。也就是說,上述代碼中,內(nèi)嵌的 synchronized 結(jié)束的時候,temp 一定是完整構(gòu)造出來的,然后再賦給 instance 的值一定是好的。
可是,synchronized 保證了釋放監(jiān)視器鎖之前的代碼一定會在釋放鎖之前被執(zhí)行(如 temp 的初始化一定會在釋放鎖之前執(zhí)行完 ),但是沒有任何規(guī)則規(guī)定了,釋放鎖之后的代碼不可以在釋放鎖之前先執(zhí)行。
也就是說,代碼中釋放鎖之后的行為 instance = temp 完全可以被提前到前面的 synchronized 代碼塊中執(zhí)行,那么前面說的重排序問題就又出現(xiàn)了。
最后扯一點(diǎn),如果所有的屬性都是使用 final 修飾的,其實(shí)之前介紹的雙重檢查是可行的,不需要加 volatile,這個等到 final 那節(jié)再介紹。
volatile 關(guān)鍵字大部分開發(fā)者應(yīng)該都知道怎么使用這個關(guān)鍵字,只是可能不太了解個中緣由。
如果你下次面試的時候有人問你 volatile 的作用,記住兩點(diǎn):內(nèi)存可見性和禁止指令重排序。
volatile 的內(nèi)存可見性我們還是用 JMM 的主內(nèi)存和本地內(nèi)存抽象來描述,這樣比較準(zhǔn)確。還有,并不是只有 Java 語言才有 volatile 關(guān)鍵字,所以后面的描述一定要建立在 Java 跨平臺以后抽象出了內(nèi)存模型的這個大環(huán)境下。
還記得 synchronized 的語義嗎?進(jìn)入 synchronized 時,使得本地緩存失效,synchronized 塊中對共享變量的讀取必須從主內(nèi)存讀取;退出 synchronized 時,會將進(jìn)入 synchronized 塊之前和 synchronized 塊中的寫操作刷入到主存中。
volatile 有類似的語義,讀一個 volatile 變量之前,需要先使相應(yīng)的本地緩存失效,這樣就必須到主內(nèi)存讀取最新值,寫一個 volatile 屬性會立即刷入到主內(nèi)存。所以,volatile 讀和 monitorenter 有相同的語義,volatile 寫和 monitorexit 有相同的語義。
volatile 的禁止重排序大家還記得之前的雙重檢查的單例模式吧,前面提到,加個 volatile 能解決問題。其實(shí)就是利用了 volatile 的禁止重排序功能。
volatile 的禁止重排序并不局限于兩個 volatile 的屬性操作不能重排序,而且是 volatile 屬性操作和它周圍的普通屬性的操作也不能重排序。
之前 instance = new Singleton() 中,如果 instance 是 volatile 的,那么對于 instance 的賦值操作(賦一個引用給 instance 變量)就不會和構(gòu)造函數(shù)中的屬性賦值發(fā)生重排序,能保證構(gòu)造方法結(jié)束后,才將此對象引用賦值給 instance。
根據(jù) volatile 的內(nèi)存可見性和禁止重排序,那么我們不難得出一個推論:線程 a 如果寫入一個 volatile 變量,此時線程 b 再讀取這個變量,那么此時對于線程 a 可見的所有屬性對于線程 b 都是可見的。
volatile 小結(jié)volatile 修飾符適用于以下場景:某個屬性被多個線程共享,其中有一個線程修改了此屬性,其他線程可以立即得到修改后的值。在并發(fā)包的源碼中,它使用得非常多。
volatile 屬性的讀寫操作都是無鎖的,它不能替代 synchronized,因為它沒有提供原子性和互斥性。因為無鎖,不需要花費(fèi)時間在獲取鎖和釋放鎖上,所以說它是低成本的。
volatile 只能作用于屬性,我們用 volatile 修飾屬性,這樣 compilers 就不會對這個屬性做指令重排序。
volatile 提供了可見性,任何一個線程對其的修改將立馬對其他線程可見。volatile 屬性不會被線程緩存,始終從主存中讀取。
volatile 提供了 happens-before 保證,對 volatile 變量 v 的寫入 happens-before 所有其他線程后續(xù)對 v 的讀操作。
volatile 可以使得 long 和 double 的賦值是原子的,前面在說原子性的時候提到過。
用 final 修飾的類不可以被繼承,用 final 修飾的方法不可以被覆寫,用 final 修飾的屬性一旦初始化以后不可以被修改。當(dāng)然,我們不關(guān)心這些段子,這節(jié),我們來看看 final 帶來的內(nèi)存可見性影響。
之前在說雙重檢查的單例模式的時候,提過了一句,如果所有的屬性都使用了 final 修飾,那么 volatile 也是可以不要的,這就是 final 帶來的可見性影響。
在對象的構(gòu)造方法中設(shè)置 final 屬性,同時在對象初始化完成前,不要將此對象的引用寫入到其他線程可以訪問到的地方(不要讓引用在構(gòu)造函數(shù)中逸出)。如果這個條件滿足,當(dāng)其他線程看到這個對象的時候,那個線程始終可以看到正確初始化后的對象的 final 屬性。
上面說得很明白了,final 屬性的寫操作不會和此引用的賦值操作發(fā)生重排序,如:
x.finalField = v; ...; sharedRef = x;
如果你還想查看更多的關(guān)于 final 的介紹,可以移步到我之前翻譯的 Java 語言規(guī)范的 final屬性的語義 部分。
并發(fā)問題是程序員都離不開的話題,說到這里順便給大家推薦一個交流學(xué)習(xí)群:650385180,里面會分享一些資深架構(gòu)師錄制的視頻錄像:有Spring,MyBatis,Netty源碼分析,高并發(fā)、高性能、分布式、微服務(wù)架構(gòu)的原理,JVM性能優(yōu)化這些成為架構(gòu)師必備的知識體系。還能領(lǐng)取免費(fèi)的學(xué)習(xí)資源,相信對于已經(jīng)工作和遇到技術(shù)瓶頸的碼友,在這個群里一定有你需要的內(nèi)容。
小結(jié)之前看過 Java8 語言規(guī)范《深入分析 java 8 編程語言規(guī)范:Threads and Locks》,本文中的很多知識是和它相關(guān)的,不過那篇直譯的文章的可讀性差了些,希望本文能給讀者帶來更多的收獲。
描述該類知識需要非常嚴(yán)謹(jǐn)?shù)恼Z言描述,雖然我仔細(xì)檢查了好幾篇,但還是擔(dān)心有些地方會說錯,一來這些內(nèi)容的正誤非常受我自身的知識積累影響,二來也和我在行文中使用的話語有很大的關(guān)系。希望讀者能幫助指正我表述錯誤的地方。
update:2018-07-06 留個小問題給讀者我們不難得出一個推論:線程 a 如果寫入一個 volatile 變量,此時線程 b 再讀取這個變量,那么此時對于線程 a
可見的所有屬性對于線程 b 都是可見的。文中我寫了上面這么一句,讀者可以考慮下這個結(jié)論是怎么推出來的。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/71495.html
摘要:前言并發(fā)編程的目的是讓程序跑的更快,但并不是啟動更多的線程,這個程序就跑的更快。盡可能降低上下文切換的次數(shù),有助于提高并發(fā)效率。死鎖并發(fā)編程中的另一挑戰(zhàn)是死鎖,會造成系統(tǒng)功能不可用。 前言 并發(fā)編程的目的是讓程序跑的更快,但并不是啟動更多的線程,這個程序就跑的更快。有以下幾種挑戰(zhàn)。 挑戰(zhàn)及方案 上下文切換 單核CPU上執(zhí)行多線程任務(wù),通過給每個線程分配CPU時間片的方式來實(shí)現(xiàn)這個機(jī)制。...
摘要:目前看的部分主要是這個關(guān)鍵字。語言提供了,保證了所有線程能看到共享變量最新的值。前綴的指令在多核處理器下會做兩件事情將當(dāng)前處理器緩存行的數(shù)據(jù)寫回到系統(tǒng)內(nèi)存。 這一章節(jié)的話,主要是講一下在并發(fā)操作中常見的volatile、synchronized以及原子操作的相關(guān)知識。 目前看的部分主要是volatile這個關(guān)鍵字。 volatile 根據(jù)Java語言規(guī)范第3版中對volatile的定義...
摘要:我的是忙碌的一年,從年初備戰(zhàn)實(shí)習(xí)春招,年三十都在死磕源碼,三月份經(jīng)歷了阿里五次面試,四月順利收到實(shí)習(xí)。因為我心理很清楚,我的目標(biāo)是阿里。所以在收到阿里之后的那晚,我重新規(guī)劃了接下來的學(xué)習(xí)計劃,將我的短期目標(biāo)更新成拿下阿里轉(zhuǎn)正。 我的2017是忙碌的一年,從年初備戰(zhàn)實(shí)習(xí)春招,年三十都在死磕JDK源碼,三月份經(jīng)歷了阿里五次面試,四月順利收到實(shí)習(xí)offer。然后五月懷著忐忑的心情開始了螞蟻金...
摘要:因為多線程競爭鎖時會引起上下文切換。減少線程的使用。舉個例子如果說服務(wù)器的帶寬只有,某個資源的下載速度是,系統(tǒng)啟動個線程下載該資源并不會導(dǎo)致下載速度編程,所以在并發(fā)編程時,需要考慮這些資源的限制。 最近私下做一項目,一bug幾日未解決,總惶恐。一日頓悟,bug不可怕,怕的是項目不存在bug,與其懼怕,何不與其剛正面。 系列文章傳送門: Java多線程學(xué)習(xí)(一)Java多線程入門 Jav...
摘要:相比與其他操作系統(tǒng)包括其他類系統(tǒng)有很多的優(yōu)點(diǎn),其中有一項就是,其上下文切換和模式切換的時間消耗非常少。因為多線程競爭鎖時會引起上下文切換。減少線程的使用。很多編程語言中都有協(xié)程。所以如何避免死鎖的產(chǎn)生,在我們使用并發(fā)編程時至關(guān)重要。 系列文章傳送門: Java多線程學(xué)習(xí)(一)Java多線程入門 Java多線程學(xué)習(xí)(二)synchronized關(guān)鍵字(1) java多線程學(xué)習(xí)(二)syn...
摘要:線程的啟動與銷毀都與本地線程同步。操作系統(tǒng)會調(diào)度所有線程并將它們分配給可用的。框架的成員主要成員線程池接口接口接口以及工具類。創(chuàng)建單個線程的接口與其實(shí)現(xiàn)類用于表示異步計算的結(jié)果。參考書籍并發(fā)編程的藝術(shù)方騰飛魏鵬程曉明著 在java中,直接使用線程來異步的執(zhí)行任務(wù),線程的每次創(chuàng)建與銷毀需要一定的計算機(jī)資源開銷。每個任務(wù)創(chuàng)建一個線程的話,當(dāng)任務(wù)數(shù)量多的時候,則對應(yīng)的創(chuàng)建銷毀開銷會消耗大量...
閱讀 712·2021-10-14 09:42
閱讀 1966·2021-09-22 15:04
閱讀 1571·2019-08-30 12:44
閱讀 2134·2019-08-29 13:29
閱讀 2730·2019-08-29 12:51
閱讀 542·2019-08-26 18:18
閱讀 698·2019-08-26 13:43
閱讀 2803·2019-08-26 13:38