摘要:假設(shè)不發(fā)生編譯器重排和指令重排,線程修改了的值,但是修改以后,的值可能還沒有寫回到主存中,那么線程得到就是很自然的事了。同理,線程對于的賦值操作也可能沒有及時(shí)刷新到主存中。線程的最后操作與線程發(fā)現(xiàn)線程已經(jīng)結(jié)束同步。
很久沒更新文章了,對隔三差五過來刷更新的讀者說聲抱歉。
關(guān)于 Java 并發(fā)也算是寫了好幾篇文章了,本文將介紹一些比較基礎(chǔ)的內(nèi)容,注意,閱讀本文需要一定的并發(fā)基礎(chǔ)。
本文的主要目的是讓大家對于并發(fā)程序中的重排序、內(nèi)存可見性以及原子性有一定的了解,同時(shí)要能準(zhǔn)確理解 synchronized、volatile、final 幾個(gè)關(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ā)程序?yàn)槭裁措y寫的原因。
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 這個(gè)結(jié)果,仔細(xì)看看代碼就會知道,如果不發(fā)生重排序的話,這個(gè)結(jié)果是不可能出現(xiàn)的。
重排序由以下幾種機(jī)制引起:
編譯器優(yōu)化:對于沒有數(shù)據(jù)依賴關(guān)系的操作,編譯器在編譯的過程中會進(jìn)行一定程度的重排。
大家仔細(xì)看看線程 1 中的代碼,編譯器是可以將 a = 1 和 x = b 換一下順序的,因?yàn)樗鼈冎g沒有數(shù)據(jù)依賴關(guān)系,同理,線程 2 也一樣,那就不難得到 x == y == 0 這種結(jié)果了。
指令重排序:CPU 優(yōu)化行為,也是會對不存在數(shù)據(jù)依賴關(guān)系的指令進(jìn)行一定程度的重排。
這個(gè)和編譯器優(yōu)化差不多,就算編譯器不發(fā)生重排,CPU 也可以對指令進(jìn)行重排,這個(gè)就不用多說了。
內(nèi)存系統(tǒng)重排序:內(nèi)存系統(tǒng)沒有重排序,但是由于有緩存的存在,使得程序整體上會表現(xiàn)出亂序的行為。
假設(shè)不發(fā)生編譯器重排和指令重排,線程 1 修改了 a 的值,但是修改以后,a 的值可能還沒有寫回到主存中,那么線程 2 得到 a == 0 就是很自然的事了。同理,線程 2 對于 b 的賦值操作也可能沒有及時(shí)刷新到主存中。
2. 內(nèi)存可見性前面在說重排序的時(shí)候,也說到了內(nèi)存可見性的問題,這里再啰嗦一下。
線程間的對于共享變量的可見性問題不是直接由多核引起的,而是由多緩存引起的。如果每個(gè)核心共享同一個(gè)緩存,那么也就不存在內(nèi)存可見性問題了。
現(xiàn)代多核 CPU 中每個(gè)核心擁有自己的一級緩存或一級緩存加上二級緩存等,問題就發(fā)生在每個(gè)核心的獨(dú)占緩存上。每個(gè)核心都會將自己需要的數(shù)據(jù)讀到獨(dú)占緩存中,數(shù)據(jù)修改后也是寫入到緩存中,然后等待刷入到主存中。所以會導(dǎo)致有些核心讀取的值是一個(gè)過期的值。
Java 作為高級語言,屏蔽了這些底層細(xì)節(jié),用 JMM 定義了一套讀寫內(nèi)存數(shù)據(jù)的規(guī)范,雖然我們不再需要關(guān)心一級緩存和二級緩存的問題,但是,JMM 抽象了主內(nèi)存和本地內(nèi)存的概念。
所有的共享變量存在于主內(nèi)存中,每個(gè)線程有自己的本地內(nèi)存,線程讀寫共享數(shù)據(jù)也是通過本地內(nèi)存交換的,所以可見性問題依然是存在的。這里說的本地內(nèi)存并不是真的是一塊給每個(gè)線程分配的內(nèi)存,而是 JMM 的一個(gè)抽象,是對于寄存器、一級緩存、二級緩存等的抽象。
3. 原子性在本文中,原子性不是重點(diǎn),它將作為并發(fā)編程中需要考慮的一部分進(jìn)行介紹。
說到原子性的時(shí)候,大家應(yīng)該都能想到 long 和 double,它們的值需要占用 64 位的內(nèi)存空間,Java 編程語言規(guī)范中提到,對于 64 位的值的寫入,可以分為兩個(gè) 32 位的操作進(jìn)行寫入。本來一個(gè)整體的賦值操作,被拆分為低 32 位賦值和高 32 位賦值兩個(gè)操作,中間如果發(fā)生了其他線程對于這個(gè)值的讀操作,必然就會讀到一個(gè)奇怪的值。
這個(gè)時(shí)候我們要使用 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 的讀同步
啟動線程的操作與線程中的第一個(gè)操作同步。
對于每個(gè)屬性寫入默認(rèn)值(0, false,null)與每個(gè)線程對其進(jìn)行的操作同步。
盡管在創(chuàng)建對象完成之前對對象屬性寫入默認(rèn)值有點(diǎn)奇怪,但從概念上來說,每個(gè)對象都是在程序啟動時(shí)用默認(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兩個(gè)操作可以用 happens-before 來確定它們的執(zhí)行順序,如果一個(gè)操作 happens-before 于另一個(gè)操作,那么我們說第一個(gè)操作對于第二個(gè)操作是可見的。
如果我們分別有操作 x 和操作 y,我們寫成 hb(x, y) 來表示 x happens-before y。以下幾個(gè)規(guī)則也是來自于 Java 8 語言規(guī)范:
如果操作 x 和操作 y 是同一個(gè)線程的兩個(gè)操作,并且在代碼上操作 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 這個(gè)關(guān)鍵字大家都用得很多了,這里不會教你怎么使用它,我們來看看它對于內(nèi)存可見性的影響。
一個(gè)線程在獲取到監(jiān)視器鎖以后才能進(jìn)入 synchronized 控制的代碼塊,一旦進(jìn)入代碼塊,首先,該線程對于共享變量的緩存就會失效,因此 synchronized 代碼塊中對于共享變量的讀取需要從主內(nèi)存中重新獲取,也就能獲取到最新的值。
退出代碼塊的時(shí)候的,會將該線程寫緩沖區(qū)中的數(shù)據(jù)刷到主內(nèi)存中,所以在 synchronized 代碼塊之前或 synchronized 代碼塊中對于共享變量的操作隨著該線程退出 synchronized 塊,會立即對其他線程可見(這句話的前提是其他讀取共享變量的線程會從主內(nèi)存讀取最新值)。
因此,我們可以總結(jié)一下:線程 a 對于進(jìn)入 synchronized 塊之前或在 synchronized 中對于共享變量的操作,對于后續(xù)的持有同一個(gè)監(jiān)視器鎖的線程 b 可見。雖然是挺簡單的一句話,請讀者好好體會。
注意一點(diǎn),在進(jìn)入 synchronized 的時(shí)候,并不會保證之前的寫操作刷入到主內(nèi)存中,synchronized 主要是保證退出的時(shí)候能將本地內(nèi)存的數(shù)據(jù)刷入到主內(nèi)存。
單例模式中的雙重檢查我們趁熱打鐵,為大家解決下單例模式中的雙重檢查問題。關(guān)于這個(gè)問題,大神們發(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è)有兩個(gè)線程 a 和 b 調(diào)用 getInstance() 方法,假設(shè) a 先走,一路走到 4 這一步,執(zhí)行 instance = new Singleton() 這句代碼。
instance = new Singleton() 這句代碼首先會申請一段空間,然后將各個(gè)屬性初始化為零值(0/null),執(zhí)行構(gòu)造方法中的屬性賦值[1],將這個(gè)對象的引用賦值給 instance[2]。在這個(gè)過程中,[1] 和 [2] 可能會發(fā)生重排序。
此時(shí),線程 b 剛剛進(jìn)來執(zhí)行到 1(看上面的代碼塊),就有可能會看到 instance 不為 null,然后線程 b 也就不會等待監(jiān)視器鎖,而是直接返回 instance。問題是這個(gè) instance 可能還沒執(zhí)行完構(gòu)造方法(線程 a 此時(shí)還在 4 這一步),所以線程 b 拿到的 instance 是不完整的,它里面的屬性值可能是初始化的零值(0/false/null),而不是線程 a 在構(gòu)造方法中指定的值。
回顧下前面的知識,分析下這里為什么會有這個(gè)問題。
1、編譯器可以將構(gòu)造方法內(nèi)聯(lián)過來,之后再發(fā)生重排序就很容易理解了。
2、即使不發(fā)生代碼重排序,線程 a 對于屬性的賦值寫入到了線程 a 的本地內(nèi)存中,此時(shí)對于線程 b 不可見。
最后提一點(diǎn),如果線程 a 從 synchronized 塊出來了,那么 instance 一定是正確構(gòu)造的完整實(shí)例,這是我們前面說過的 synchronized 的內(nèi)存可見性保證。
—————分割線—————
對于大部分讀者來說,這一小節(jié)其實(shí)可以結(jié)束了,很多讀者都知道,解決方案是使用 volatile 關(guān)鍵字,這個(gè)我們在介紹 volatile 的時(shí)候再說。當(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)嵌一個(gè) synchronized 塊 temp = new Singleton(); } instance = temp; // } } } return instance; }
上面這個(gè)代碼很有趣,想利用 synchronized 的內(nèi)存可見性語義,不過這個(gè)解決方案還是失敗了,我們分析下。
前面我們也說了,synchronized 在退出的時(shí)候,能保證 synchronized 塊中對于共享變量的寫入一定會刷入到主內(nèi)存中。也就是說,上述代碼中,內(nèi)嵌的 synchronized 結(jié)束的時(shí)候,temp 一定是完整構(gòu)造出來的,然后再賦給 instance 的值一定是好的。
可是,synchronized 保證了釋放監(jiān)視器鎖之前的代碼一定會在釋放鎖之前被執(zhí)行(如 temp 的初始化一定會在釋放鎖之前執(zhí)行完 ),但是沒有任何規(guī)則規(guī)定了,釋放鎖之后的代碼不可以在釋放鎖之前先執(zhí)行。
也就是說,代碼中釋放鎖之后的行為 instance = temp 完全可以被提前到前面的 synchronized 代碼塊中執(zhí)行,那么前面說的重排序問題就又出現(xiàn)了。
最后扯一點(diǎn),如果所有的屬性都是使用 final 修飾的,其實(shí)之前介紹的雙重檢查是可行的,不需要加 volatile,這個(gè)等到 final 那節(jié)再介紹。
volatile 關(guān)鍵字大部分開發(fā)者應(yīng)該都知道怎么使用這個(gè)關(guān)鍵字,只是可能不太了解個(gè)中緣由。
如果你下次面試的時(shí)候有人問你 volatile 的作用,記住兩點(diǎn):內(nèi)存可見性和禁止指令重排序。
volatile 的內(nèi)存可見性我們還是用 JMM 的主內(nèi)存和本地內(nèi)存抽象來描述,這樣比較準(zhǔn)確。還有,并不是只有 Java 語言才有 volatile 關(guān)鍵字,所以后面的描述一定要建立在 Java 跨平臺以后抽象出了內(nèi)存模型的這個(gè)大環(huán)境下。
還記得 synchronized 的語義嗎?進(jìn)入 synchronized 時(shí),使得本地緩存失效,synchronized 塊中對共享變量的讀取必須從主內(nèi)存讀?。煌顺?synchronized 時(shí),會將進(jìn)入 synchronized 塊之前和 synchronized 塊中的寫操作刷入到主存中。
volatile 有類似的語義,讀一個(gè) volatile 變量之前,需要先使相應(yīng)的本地緩存失效,這樣就必須到主內(nèi)存讀取最新值,寫一個(gè) volatile 屬性會立即刷入到主內(nèi)存。所以,volatile 讀和 monitorenter 有相同的語義,volatile 寫和 monitorexit 有相同的語義。
volatile 的禁止重排序大家還記得之前的雙重檢查的單例模式吧,前面提到,加個(gè) volatile 能解決問題。其實(shí)就是利用了 volatile 的禁止重排序功能。
volatile 的禁止重排序并不局限于兩個(gè) volatile 的屬性操作不能重排序,而且是 volatile 屬性操作和它周圍的普通屬性的操作也不能重排序。
之前 instance = new Singleton() 中,如果 instance 是 volatile 的,那么對于 instance 的賦值操作(賦一個(gè)引用給 instance 變量)就不會和構(gòu)造函數(shù)中的屬性賦值發(fā)生重排序,能保證構(gòu)造方法結(jié)束后,才將此對象引用賦值給 instance。
根據(jù) volatile 的內(nèi)存可見性和禁止重排序,那么我們不難得出一個(gè)推論:線程 a 如果寫入一個(gè) volatile 變量,此時(shí)線程 b 再讀取這個(gè)變量,那么此時(shí)對于線程 a 可見的所有屬性對于線程 b 都是可見的。
volatile 小結(jié)volatile 修飾符適用于以下場景:某個(gè)屬性被多個(gè)線程共享,其中有一個(gè)線程修改了此屬性,其他線程可以立即得到修改后的值。在并發(fā)包的源碼中,它使用得非常多。
volatile 屬性的讀寫操作都是無鎖的,它不能替代 synchronized,因?yàn)樗鼪]有提供原子性和互斥性。因?yàn)闊o鎖,不需要花費(fèi)時(shí)間在獲取鎖和釋放鎖上,所以說它是低成本的。
volatile 只能作用于屬性,我們用 volatile 修飾屬性,這樣 compilers 就不會對這個(gè)屬性做指令重排序。
volatile 提供了可見性,任何一個(gè)線程對其的修改將立馬對其他線程可見。volatile 屬性不會被線程緩存,始終從主存中讀取。
volatile 提供了 happens-before 保證,對 volatile 變量 v 的寫入 happens-before 所有其他線程后續(xù)對 v 的讀操作。
volatile 可以使得 long 和 double 的賦值是原子的,前面在說原子性的時(shí)候提到過。
用 final 修飾的類不可以被繼承,用 final 修飾的方法不可以被覆寫,用 final 修飾的屬性一旦初始化以后不可以被修改。當(dāng)然,我們不關(guān)心這些段子,這節(jié),我們來看看 final 帶來的內(nèi)存可見性影響。
之前在說雙重檢查的單例模式的時(shí)候,提過了一句,如果所有的屬性都使用了 final 修飾,那么 volatile 也是可以不要的,這就是 final 帶來的可見性影響。
在對象的構(gòu)造方法中設(shè)置 final 屬性,同時(shí)在對象初始化完成前,不要將此對象的引用寫入到其他線程可以訪問到的地方(不要讓引用在構(gòu)造函數(shù)中逸出)。如果這個(gè)條件滿足,當(dāng)其他線程看到這個(gè)對象的時(shí)候,那個(gè)線程始終可以看到正確初始化后的對象的 final 屬性。
上面說得很明白了,final 屬性的寫操作不會和此引用的賦值操作發(fā)生重排序,如:
x.finalField = v; ...; sharedRef = x;
如果你還想查看更多的關(guān)于 final 的介紹,可以移步到我之前翻譯的 Java 語言規(guī)范的 final屬性的語義 部分。
并發(fā)問題是程序員都離不開的話題,說到這里順便給大家推薦一個(gè)交流學(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ù)瓶頸的碼友,在這個(gè)群里一定有你需要的內(nèi)容。
小結(jié)之前看過 Java8 語言規(guī)范《深入分析 java 8 編程語言規(guī)范:Threads and Locks》,本文中的很多知識是和它相關(guān)的,不過那篇直譯的文章的可讀性差了些,希望本文能給讀者帶來更多的收獲。
描述該類知識需要非常嚴(yán)謹(jǐn)?shù)恼Z言描述,雖然我仔細(xì)檢查了好幾篇,但還是擔(dān)心有些地方會說錯(cuò),一來這些內(nèi)容的正誤非常受我自身的知識積累影響,二來也和我在行文中使用的話語有很大的關(guān)系。希望讀者能幫助指正我表述錯(cuò)誤的地方。
update:2018-07-06 留個(gè)小問題給讀者我們不難得出一個(gè)推論:線程 a 如果寫入一個(gè) volatile 變量,此時(shí)線程 b 再讀取這個(gè)變量,那么此時(shí)對于線程 a
可見的所有屬性對于線程 b 都是可見的。文中我寫了上面這么一句,讀者可以考慮下這個(gè)結(jié)論是怎么推出來的。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/71495.html
摘要:前言并發(fā)編程的目的是讓程序跑的更快,但并不是啟動更多的線程,這個(gè)程序就跑的更快。盡可能降低上下文切換的次數(shù),有助于提高并發(fā)效率。死鎖并發(fā)編程中的另一挑戰(zhàn)是死鎖,會造成系統(tǒng)功能不可用。 前言 并發(fā)編程的目的是讓程序跑的更快,但并不是啟動更多的線程,這個(gè)程序就跑的更快。有以下幾種挑戰(zhàn)。 挑戰(zhàn)及方案 上下文切換 單核CPU上執(zhí)行多線程任務(wù),通過給每個(gè)線程分配CPU時(shí)間片的方式來實(shí)現(xiàn)這個(gè)機(jī)制。...
摘要:目前看的部分主要是這個(gè)關(guān)鍵字。語言提供了,保證了所有線程能看到共享變量最新的值。前綴的指令在多核處理器下會做兩件事情將當(dāng)前處理器緩存行的數(shù)據(jù)寫回到系統(tǒng)內(nèi)存。 這一章節(jié)的話,主要是講一下在并發(fā)操作中常見的volatile、synchronized以及原子操作的相關(guān)知識。 目前看的部分主要是volatile這個(gè)關(guān)鍵字。 volatile 根據(jù)Java語言規(guī)范第3版中對volatile的定義...
摘要:我的是忙碌的一年,從年初備戰(zhàn)實(shí)習(xí)春招,年三十都在死磕源碼,三月份經(jīng)歷了阿里五次面試,四月順利收到實(shí)習(xí)。因?yàn)槲倚睦砗芮宄?,我的目?biāo)是阿里。所以在收到阿里之后的那晚,我重新規(guī)劃了接下來的學(xué)習(xí)計(jì)劃,將我的短期目標(biāo)更新成拿下阿里轉(zhuǎn)正。 我的2017是忙碌的一年,從年初備戰(zhàn)實(shí)習(xí)春招,年三十都在死磕JDK源碼,三月份經(jīng)歷了阿里五次面試,四月順利收到實(shí)習(xí)offer。然后五月懷著忐忑的心情開始了螞蟻金...
摘要:因?yàn)槎嗑€程競爭鎖時(shí)會引起上下文切換。減少線程的使用。舉個(gè)例子如果說服務(wù)器的帶寬只有,某個(gè)資源的下載速度是,系統(tǒng)啟動個(gè)線程下載該資源并不會導(dǎo)致下載速度編程,所以在并發(fā)編程時(shí),需要考慮這些資源的限制。 最近私下做一項(xiàng)目,一bug幾日未解決,總惶恐。一日頓悟,bug不可怕,怕的是項(xiàng)目不存在bug,與其懼怕,何不與其剛正面。 系列文章傳送門: Java多線程學(xué)習(xí)(一)Java多線程入門 Jav...
摘要:相比與其他操作系統(tǒng)包括其他類系統(tǒng)有很多的優(yōu)點(diǎn),其中有一項(xiàng)就是,其上下文切換和模式切換的時(shí)間消耗非常少。因?yàn)槎嗑€程競爭鎖時(shí)會引起上下文切換。減少線程的使用。很多編程語言中都有協(xié)程。所以如何避免死鎖的產(chǎn)生,在我們使用并發(fā)編程時(shí)至關(guān)重要。 系列文章傳送門: Java多線程學(xué)習(xí)(一)Java多線程入門 Java多線程學(xué)習(xí)(二)synchronized關(guān)鍵字(1) java多線程學(xué)習(xí)(二)syn...
摘要:線程的啟動與銷毀都與本地線程同步。操作系統(tǒng)會調(diào)度所有線程并將它們分配給可用的??蚣艿某蓡T主要成員線程池接口接口接口以及工具類。創(chuàng)建單個(gè)線程的接口與其實(shí)現(xiàn)類用于表示異步計(jì)算的結(jié)果。參考書籍并發(fā)編程的藝術(shù)方騰飛魏鵬程曉明著 在java中,直接使用線程來異步的執(zhí)行任務(wù),線程的每次創(chuàng)建與銷毀需要一定的計(jì)算機(jī)資源開銷。每個(gè)任務(wù)創(chuàng)建一個(gè)線程的話,當(dāng)任務(wù)數(shù)量多的時(shí)候,則對應(yīng)的創(chuàng)建銷毀開銷會消耗大量...
閱讀 797·2021-10-14 09:42
閱讀 2025·2021-09-22 15:04
閱讀 1666·2019-08-30 12:44
閱讀 2212·2019-08-29 13:29
閱讀 2789·2019-08-29 12:51
閱讀 605·2019-08-26 18:18
閱讀 777·2019-08-26 13:43
閱讀 2872·2019-08-26 13:38