Java中的并發(fā)

什么是并發(fā)


并發(fā)灿椅,實(shí)際上是多個(gè)程序之間或者同一個(gè)程序內(nèi)不同部分之間能夠并行執(zhí)行的一種能力套蒂。
它極大的提升了CPU的吞吐量,特別是對(duì)于多核CPU尤為明顯茫蛹。(隨著并發(fā)量的增加會(huì)達(dá)到性能上限操刀,并發(fā)量過(guò)大,致使運(yùn)行上下文切換過(guò)于頻繁麻惶,甚至有可能會(huì)降低性能)
同時(shí)馍刮,也提升了程序的交互性,比如在App開發(fā)過(guò)程中窃蹋,同一個(gè)頁(yè)面需要若干線程同時(shí)執(zhí)行卡啰,繪制UI線程静稻,后臺(tái)線程等等,分工提供高質(zhì)量的用戶交互需求匈辱。

進(jìn)程并發(fā)和線程并發(fā)

多個(gè)程序之間的并行振湾,表現(xiàn)為進(jìn)程的并行,這一點(diǎn)很普遍亡脸,也不會(huì)存在什么問(wèn)題押搪。并行的進(jìn)程之間,一般不會(huì)相互影響浅碾,它們是相互獨(dú)立的大州,相互不可見,進(jìn)程的資源比如內(nèi)存和CPU時(shí)間被操作系統(tǒng)管理垂谢。在應(yīng)用層的表現(xiàn)厦画,在電腦上,你可以在聽音樂的同時(shí)打字滥朱。
線程也被稱為是輕量級(jí)的進(jìn)程根暑。同一個(gè)進(jìn)程可以擁有多個(gè)線程,每個(gè)線程有自己的本地內(nèi)存(調(diào)用棧和緩沖內(nèi)存等)徙邻。它的控制排嫌,是由進(jìn)程管理的,并且缰犁,重要的是多個(gè)線程之間可以共享進(jìn)程中的變量以達(dá)到通信的目的淳地。

線程并發(fā)中的問(wèn)題

根據(jù)JVM的內(nèi)存模型,一個(gè)線程訪問(wèn)主內(nèi)存中的共享變量的步驟是:

  1. 總是先將該內(nèi)存變量的內(nèi)容副本拷貝進(jìn)自己的本地內(nèi)存中民鼓;
  2. 對(duì)于基礎(chǔ)類型的變量薇芝,在更改了副本中之后,會(huì)將更改后的值同步刷新到主內(nèi)存丰嘉;引用類型則不存在這種同步操作夯到。
    同時(shí),我們知道:
  3. 對(duì)一個(gè)變量的基本操作饮亏,哪怕是簡(jiǎn)單的變量自增操作耍贾,雖然代碼上只有一行,但其實(shí)CPU是分幾步完成的:得到變量副本路幸,加一荐开,生成臨時(shí)變量接收結(jié)果,然后將結(jié)果寫會(huì)到主內(nèi)存刷新變量值简肴。##Java中原子操作包括:除了long和double之外的賦值操作(long和double是64位的晃听,在32位機(jī)器上將分兩次進(jìn)行操作);對(duì)于引用的賦值操作##)##
  4. JVM在不影響最終執(zhí)行結(jié)果的情況下,會(huì)進(jìn)行優(yōu)化能扒,完成指令重排佣渴,這對(duì)同一個(gè)線程不會(huì)產(chǎn)生什么影響,但是在不同的線程之間就不同了初斑。
    因此辛润,最終會(huì)產(chǎn)生的問(wèn)題是:
  5. 如何在高層級(jí)上保證操作的原子性,對(duì)于加一操作而言见秤,最終目的就是保證這句代碼已經(jīng)完成之后砂竖,再允許其他線程訪問(wèn)該變量,否則可能導(dǎo)致的結(jié)果是加一操作并沒有完成鹃答,其他線程讀取到了未進(jìn)行該操作之前的值乎澄;
  6. 當(dāng)一個(gè)線程更改了變量(基礎(chǔ)類型變量)的值后,如何保證立刻對(duì)其他線程是可見性挣跋,即當(dāng)其他線程再訪問(wèn)該變量的時(shí)候三圆,獲取到的是最新的該變量的副本狞换;
  7. 如何消除由于指令重排引發(fā)的多線程問(wèn)題避咆?
    總結(jié)起來(lái),其實(shí)就是三個(gè)方面:變量操作的原子性修噪、可見性(對(duì)其它線程)查库、順序執(zhí)行。

Java中的同步


每個(gè)Java程序默認(rèn)運(yùn)行在它自己進(jìn)程的線程中黄琼,Java同時(shí)支持多個(gè)線程并發(fā)執(zhí)行樊销,這一點(diǎn)是通過(guò)Thread對(duì)象實(shí)現(xiàn)的。Java應(yīng)用可以通過(guò)Thread類實(shí)現(xiàn)多線程開發(fā)脏款。在使用過(guò)程中围苫,為了保證程序的正常運(yùn)行,它同時(shí)提供了一些安全機(jī)制撤师,來(lái)實(shí)現(xiàn)線程操作的原子性剂府、可見性和順序性。

鎖和線程同步

Java提供了鎖來(lái)控制某段代碼在不同線程之間的互斥執(zhí)行剃盾。使用鎖的一個(gè)最簡(jiǎn)單的方式腺占,是通過(guò) synchronized 關(guān)鍵字修飾方法或者代碼塊。
synchronized 關(guān)鍵字保證了:

  1. 同時(shí)只能由一個(gè)線程執(zhí)行代碼塊痒谴,實(shí)現(xiàn)多個(gè)線程對(duì)同一代碼塊或者方法的互斥訪問(wèn)衰伯;(對(duì)于自增操作而言,實(shí)際上仍然并非是原子的积蔚,但是通過(guò)互斥訪問(wèn)達(dá)到了這樣的目的)
  2. 每個(gè)線程在進(jìn)入同步代碼塊之后意鲸,會(huì)看到先前所有對(duì)代碼塊中變量的修改。(保證可見性和順序)
//同步方法
public synchronized void add() {
}
public void delete() {
  //同步代碼塊
  syschronized(this){
  }
}

上述在使用 synchronized 時(shí),使用的鎖實(shí)際上是通過(guò)該類實(shí)例化出來(lái)的對(duì)象怎顾。當(dāng)然论矾,也可以使用其他對(duì)象作為鎖。

private Object lock = new Object();
public void add(){
  synchronized(lock){
  }
}

對(duì)于靜態(tài)方法杆勇,需要使用類對(duì)象

class Test{
            
}
public static void add(){
    synchronized(Test.class){
    }
}
總結(jié)

使用鎖進(jìn)行操作無(wú)疑是最安全最可靠的做法贪壳。但是它最大的弊端在于對(duì)CPU資源的消耗。

Volatile使用

Volatile的特點(diǎn)就是在蚜退,任何一個(gè)線程讀取主內(nèi)存的變量時(shí)闰靴,將讀取的是該變量最新的值。即滿足線程對(duì)變量修改后钻注,在其他線程中的可見性蚂且。即如果A和B同時(shí)讀取了變量param的值為1,此時(shí)如果在A中設(shè)置param為2.
下面一段代碼可以證明(代碼來(lái)源)

public class Test extends Thread {
     boolean keepRunning = true;
        public static void main(String[] args) throws InterruptedException {
            Test t = new Test();
            t.start();
            Thread.sleep(1000);
            t.keepRunning = false;
            System.out.println("keepRunning is false");
        }
        public void run() {
            int x = 10;
            while (keepRunning) 
            {
                x++;
            }
            System.out.println("x:"+x);
        }
}
-------------------------------------------------------------------------------------------------------
實(shí)測(cè)結(jié)果:
執(zhí)行 x++ 的while循環(huán)一直沒有停下來(lái)過(guò)幅恋。

我對(duì)于一直處于while死循環(huán)也感到有點(diǎn)不解杏死。可以肯定是捆交,這一定和共享變量的可變性有關(guān)淑翼。查了一些資料,一個(gè)可以接受的說(shuō)法是:
由于對(duì)keepRunning變量沒有使用任何同步措施品追,比如volatile和synchronized玄括,編譯器認(rèn)為這個(gè)變量不會(huì)被多個(gè)線程共享,從而進(jìn)行了循環(huán)不變式的優(yōu)化肉瓦,優(yōu)化結(jié)果為:

if(keepRunning){
  while(true){
    x++ ;
  }
}

而這種優(yōu)化實(shí)際上跟具體的運(yùn)行環(huán)境有關(guān)遭京。也就是說(shuō),在有些設(shè)備上泞莉,不一定會(huì)出現(xiàn)這種死循環(huán)哪雕。
使用volatile是可以保證共享變量的可見性,然而這并不能完全解決線程之間的同步問(wèn)題鲫趁。

static volatile int counter = 0;
for (int i = 0; i < 10; i++) {
            new Thread() {
                public void run() {
                    for (int j = 0; j < 1000; j++)
                        counter++;
                };
            }.start();
}

預(yù)計(jì)結(jié)果應(yīng)該是 10000斯嚎,然而實(shí)際上,運(yùn)行結(jié)果每次都不一樣饮寞,最終的結(jié)果很少有到10000的孝扛。問(wèn)題就在于,多個(gè)線程同時(shí)對(duì) counter 進(jìn)行了修改幽崩,只是保證了 counter 在每個(gè)線程中的可見性苦始,但是并不能保證它的同步。
以上慌申,我們開了十個(gè)線程同時(shí)修改 counter

總結(jié)

volatile只能作為一個(gè)輕量級(jí)的synchronized陌选,而不能完全替代synchronized理郑。volatile適用于那些只會(huì)在一個(gè)線程中進(jìn)行更改數(shù)據(jù)的情況,因?yàn)榇蠖鄶?shù)情況下對(duì)數(shù)據(jù)變量的操作 都不是原子的咨油。更加安全的做法是使用synchronized您炉,但是synchronized由于需要使用鎖,這將更加消耗CPU資源役电。

AtomicInteger

這是java.util.concurrent.atomic種的類赚爵,其中還包括: AtomicBoolean; AtomicLong;AtomicReference等。具體我們來(lái)看一看AtomicInteger法瑟,其他的大同小異冀膝。
AtomicInteger內(nèi)部其實(shí)有一個(gè)volatile的value。

private volatile int value;

而它內(nèi)部的操作霎挟,大都使用了 sun.misc.Unsafe 這個(gè)類窝剖。這個(gè)類被稱為魔術(shù)類,會(huì)繞過(guò)Java的安全檢查機(jī)制酥夭,直接操作內(nèi)存以獲取高性能赐纱,而對(duì)于它的set、get熬北、compareAndSet操作都是原子性的疙描。
對(duì)于這個(gè)類有多強(qiáng)大,可以看這篇文章蒜埋,這是個(gè)強(qiáng)大到即將在JDK9中不被支持淫痰。太強(qiáng)大的東西往往都是一把雙刃劍,用不好就是災(zāi)難整份。

總結(jié)

AtomicX中借用volatile和Unsafe實(shí)現(xiàn)了對(duì)共享變量操作的互斥性。是一種很安全的使用方式籽孙。注意烈评,單純的AtomicInteger的set和get方法僅僅是返回上面的value,并沒有使用Unsafe操作犯建。

感想

在項(xiàng)目過(guò)程中由于使用了多線程操作讲冠,涉及到多線程同步的問(wèn)題,所以對(duì)此進(jìn)行了一次梳理适瓦,記錄在此竿开。
其實(shí)在使用過(guò)程中,更多的是使用synchronized玻熙,這也是非常安全的一個(gè)做法否彩,但是問(wèn)題就在于性能消耗大,但是對(duì)于多線程讀寫共享變量時(shí)嗦随,又不得不這么做列荔。
后來(lái)查看RxJava的源碼實(shí)現(xiàn),發(fā)現(xiàn)其使用AtomicX(AtomicInteger、AtomicReference等)實(shí)現(xiàn)了一個(gè)多線程共享的隊(duì)列贴浙,它借此完全做到了對(duì)變量原子性砂吞、可見性、順序性的功能崎溃,同時(shí)性能消耗比synchronized要低蜻直。我覺得對(duì)于不需要復(fù)雜同步操作時(shí),使用AtomicX是一種很好的選擇袁串。
當(dāng)然袭蝗,volatile的性能相對(duì)于synchronized和AtomicX應(yīng)該是最高的,它不涉及到鎖的操作般婆。但是卻只能保證共享變量的可見性到腥,而無(wú)法保證原子性。所以蔚袍,它的應(yīng)用場(chǎng)景在于那些只有一個(gè)線程需要對(duì)共享變量做復(fù)合賦值操作的情況乡范。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市啤咽,隨后出現(xiàn)的幾起案子晋辆,更是在濱河造成了極大的恐慌,老刑警劉巖宇整,帶你破解...
    沈念sama閱讀 217,907評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件瓶佳,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡鳞青,警方通過(guò)查閱死者的電腦和手機(jī)霸饲,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)臂拓,“玉大人厚脉,你說(shuō)我怎么就攤上這事〗憾瑁” “怎么了傻工?”我有些...
    開封第一講書人閱讀 164,298評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)孵滞。 經(jīng)常有香客問(wèn)我中捆,道長(zhǎng),這世上最難降的妖魔是什么坊饶? 我笑而不...
    開封第一講書人閱讀 58,586評(píng)論 1 293
  • 正文 為了忘掉前任泄伪,我火速辦了婚禮,結(jié)果婚禮上幼东,老公的妹妹穿的比我還像新娘臂容。我一直安慰自己科雳,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,633評(píng)論 6 392
  • 文/花漫 我一把揭開白布脓杉。 她就那樣靜靜地躺著糟秘,像睡著了一般。 火紅的嫁衣襯著肌膚如雪球散。 梳的紋絲不亂的頭發(fā)上尿赚,一...
    開封第一講書人閱讀 51,488評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音蕉堰,去河邊找鬼凌净。 笑死,一個(gè)胖子當(dāng)著我的面吹牛屋讶,可吹牛的內(nèi)容都是我干的冰寻。 我是一名探鬼主播,決...
    沈念sama閱讀 40,275評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼皿渗,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼斩芭!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起乐疆,我...
    開封第一講書人閱讀 39,176評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤划乖,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后挤土,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體琴庵,經(jīng)...
    沈念sama閱讀 45,619評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,819評(píng)論 3 336
  • 正文 我和宋清朗相戀三年仰美,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了迷殿。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,932評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡筒占,死狀恐怖贪庙,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情翰苫,我是刑警寧澤,帶...
    沈念sama閱讀 35,655評(píng)論 5 346
  • 正文 年R本政府宣布这橙,位于F島的核電站奏窑,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏屈扎。R本人自食惡果不足惜埃唯,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,265評(píng)論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望鹰晨。 院中可真熱鬧墨叛,春花似錦止毕、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,871評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至闯传,卻和暖如春谨朝,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背甥绿。 一陣腳步聲響...
    開封第一講書人閱讀 32,994評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工字币, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人共缕。 一個(gè)月前我還...
    沈念sama閱讀 48,095評(píng)論 3 370
  • 正文 我出身青樓洗出,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親图谷。 傳聞我的和親對(duì)象是個(gè)殘疾皇子翩活,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,884評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容