<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    隨筆-204  評論-149  文章-0  trackbacks-0
    多線程
    線程:是指進(jìn)程中的一個(gè)執(zhí)行流程。
    線程與進(jìn)程的區(qū)別:每個(gè)進(jìn)程都需要操作系統(tǒng)為其分配獨(dú)立的內(nèi)存地址空間,而同一進(jìn)程中的所有線程在同一塊地址空間中工作,這些線程可以共享同一塊內(nèi)存和系統(tǒng)資源。


    如何創(chuàng)建一個(gè)線程?

    創(chuàng)建線程有兩種方式,如下:
    1、 擴(kuò)展java.lang.Thread類
    2、 實(shí)現(xiàn)Runnable接口
    Thread類代表線程類,它的兩個(gè)最主要的方法是:
    run()——包含線程運(yùn)行時(shí)所執(zhí)行的代碼
    Start()——用于啟動(dòng)線程

    一個(gè)線程只能被啟動(dòng)一次。第二次啟動(dòng)時(shí)將會(huì)拋出java.lang.IllegalThreadExcetpion異常

    線程間狀態(tài)的轉(zhuǎn)換(如圖示)

    新建狀態(tài):用new語句創(chuàng)建的線程對象處于新建狀態(tài),此時(shí)它和其它的java對象一樣,僅僅在堆中被分配了內(nèi)存
    就緒狀態(tài):當(dāng)一個(gè)線程創(chuàng)建了以后,其他的線程調(diào)用了它的start()方法,該線程就進(jìn)入了就緒狀態(tài)。處于這個(gè)狀態(tài)的線程位于可運(yùn)行池中,等待獲得CPU的使用權(quán)
    運(yùn)行狀態(tài):處于這個(gè)狀態(tài)的線程占用CPU,執(zhí)行程序的代碼
    阻塞狀態(tài):當(dāng)線程處于阻塞狀態(tài)時(shí),java虛擬機(jī)不會(huì)給線程分配CPU,直到線程重新進(jìn)入就緒狀態(tài),它才有機(jī)會(huì)轉(zhuǎn)到運(yùn)行狀態(tài)。
    阻塞狀態(tài)分為三種情況:
    1、 位于對象等待池中的阻塞狀態(tài):當(dāng)線程運(yùn)行時(shí),如果執(zhí)行了某個(gè)對象的wait()方法,java虛擬機(jī)就回把線程放到這個(gè)對象的等待池中
    2、 位于對象鎖中的阻塞狀態(tài),當(dāng)線程處于運(yùn)行狀態(tài)時(shí),試圖獲得某個(gè)對象的同步鎖時(shí),如果該對象的同步鎖已經(jīng)被其他的線程占用,JVM就會(huì)把這個(gè)線程放到這個(gè)對象的瑣池中。
    3、 其它的阻塞狀態(tài):當(dāng)前線程執(zhí)行了sleep()方法,或者調(diào)用了其它線程的join()方法,或者發(fā)出了I/O請求時(shí),就會(huì)進(jìn)入這個(gè)狀態(tài)中。

    死亡狀態(tài):當(dāng)線程退出了run()方法,就進(jìn)入了死亡狀態(tài),該線程結(jié)束了生命周期。
               或者正常退出
               或者遇到異常退出
               Thread類的isAlive()方法判斷一個(gè)線程是否活著,當(dāng)線程處于死亡狀態(tài)或者新建狀態(tài)時(shí),該方法返回false,在其余的狀態(tài)下,該方法返回true.

    線程調(diào)度
    線程調(diào)度模型:分時(shí)調(diào)度模型和搶占式調(diào)度模型
    JVM采用搶占式調(diào)度模型。
    所謂的多線程的并發(fā)運(yùn)行,其實(shí)是指宏觀上看,各個(gè)線程輪流獲得CPU的使用權(quán),分別執(zhí)行各自的任務(wù)。
    (線程的調(diào)度不是跨平臺(tái),它不僅取決于java虛擬機(jī),它還依賴于操作系統(tǒng))

    如果希望明確地讓一個(gè)線程給另外一個(gè)線程運(yùn)行的機(jī)會(huì),可以采取以下的辦法之一
    1、 調(diào)整各個(gè)線程的優(yōu)先級
    2、 讓處于運(yùn)行狀態(tài)的線程調(diào)用Thread.sleep()方法
    3、 讓處于運(yùn)行狀態(tài)的線程調(diào)用Thread.yield()方法
    4、 讓處于運(yùn)行狀態(tài)的線程調(diào)用另一個(gè)線程的join()方法

    調(diào)整各個(gè)線程的優(yōu)先級
    Thread類的setPriority(int)和getPriority()方法分別用來設(shè)置優(yōu)先級和讀取優(yōu)先級。
    如果希望程序能夠移值到各個(gè)操作系統(tǒng)中,應(yīng)該確保在設(shè)置線程的優(yōu)先級時(shí),只使用MAX_PRIORITY、NORM_PRIORITY、MIN_PRIORITY這3個(gè)優(yōu)先級。

    線程睡眠:當(dāng)線程在運(yùn)行中執(zhí)行了sleep()方法時(shí),它就會(huì)放棄CPU,轉(zhuǎn)到阻塞狀態(tài)。
    線程讓步:當(dāng)線程在運(yùn)行中執(zhí)行了Thread類的yield()靜態(tài)方法時(shí),如果此時(shí)具有相同優(yōu)先級的其它線程處于就緒狀態(tài),那么yield()方法將把當(dāng)前運(yùn)行的線程放到運(yùn)行池中并使另一個(gè)線程運(yùn)行。如果沒有相同優(yōu)先級的可運(yùn)行線程,則yield()方法什么也不做。
    Sleep()方法和yield()方法都是Thread類的靜態(tài)方法,都會(huì)使當(dāng)前處于運(yùn)行狀態(tài)的線程放棄CPU,把運(yùn)行機(jī)會(huì)讓給別的線程,兩者的區(qū)別在于:
             1、sleep()方法會(huì)給其他線程運(yùn)行的機(jī)會(huì),而不考慮其他線程的優(yōu)先級,因此會(huì)給較低線程一個(gè)運(yùn)行的機(jī)會(huì);yield()方法只會(huì)給相同優(yōu)先級或者更高優(yōu)先級的線程一個(gè)運(yùn)行的機(jī)會(huì)。
    2、當(dāng)線程執(zhí)行了sleep(long millis)方法后,將轉(zhuǎn)到阻塞狀態(tài),參數(shù)millis指定睡眠時(shí)間;當(dāng)線程執(zhí)行了yield()方法后,將轉(zhuǎn)到就緒狀態(tài)。
              3、sleep()方法聲明拋出InterruptedException異常,而yield()方法沒有聲明拋出任何異常
              4、sleep()方法比yield()方法具有更好的移植性

    等待其它線程的結(jié)束:join()
              當(dāng)前運(yùn)行的線程可以調(diào)用另一個(gè)線程的 join()方法,當(dāng)前運(yùn)行的線程將轉(zhuǎn)到阻塞狀態(tài),直到另一個(gè)線程運(yùn)行結(jié)束,它才恢復(fù)運(yùn)行。

    定時(shí)器Timer:在JDK的java.util包中提供了一個(gè)實(shí)用類Timer, 它能夠定時(shí)執(zhí)行特定的任務(wù)。

    線程的同步
    原子操作:根據(jù)Java規(guī)范,對于基本類型的賦值或者返回值操作,是原子操作。但這里的基本數(shù)據(jù)類型不包括long和double, 因?yàn)镴VM看到的基本存儲(chǔ)單位是32位,而long 和double都要用64位來表示。所以無法在一個(gè)時(shí)鐘周期內(nèi)完成。

    自增操作(++)不是原子操作,因?yàn)樗婕暗揭淮巫x和一次寫。

    原子操作:由一組相關(guān)的操作完成,這些操作可能會(huì)操縱與其它的線程共享的資源,為了保證得到正確的運(yùn)算結(jié)果,一個(gè)線程在執(zhí)行原子操作其間,應(yīng)該采取其他的措施使得其他的線程不能操縱共享資源。

    同步代碼塊:為了保證每個(gè)線程能夠正常執(zhí)行原子操作,Java引入了同步機(jī)制,具體的做法是在代表原子操作的程序代碼前加上synchronized標(biāo)記,這樣的代碼被稱為同步代碼塊。

    同步鎖:每個(gè)JAVA對象都有且只有一個(gè)同步鎖,在任何時(shí)刻,最多只允許一個(gè)線程擁有這把鎖。

    當(dāng)一個(gè)線程試圖訪問帶有synchronized(this)標(biāo)記的代碼塊時(shí),必須獲得 this關(guān)鍵字引用的對象的鎖,在以下的兩種情況下,本線程有著不同的命運(yùn)。
    1、 假如這個(gè)鎖已經(jīng)被其它的線程占用,JVM就會(huì)把這個(gè)線程放到本對象的鎖池中。本線程進(jìn)入阻塞狀態(tài)。鎖池中可能有很多的線程,等到其他的線程釋放了鎖,JVM就會(huì)從鎖池中隨機(jī)取出一個(gè)線程,使這個(gè)線程擁有鎖,并且轉(zhuǎn)到就緒狀態(tài)。
    2、 假如這個(gè)鎖沒有被其他線程占用,本線程會(huì)獲得這把鎖,開始執(zhí)行同步代碼塊。
    (一般情況下在執(zhí)行同步代碼塊時(shí)不會(huì)釋放同步鎖,但也有特殊情況會(huì)釋放對象鎖
    如在執(zhí)行同步代碼塊時(shí),遇到異常而導(dǎo)致線程終止,鎖會(huì)被釋放;在執(zhí)行代碼塊時(shí),執(zhí)行了鎖所屬對象的wait()方法,這個(gè)線程會(huì)釋放對象鎖,進(jìn)入對象的等待池中)

    線程同步的特征:
    1、 如果一個(gè)同步代碼塊和非同步代碼塊同時(shí)操作共享資源,仍然會(huì)造成對共享資源的競爭。因?yàn)楫?dāng)一個(gè)線程執(zhí)行一個(gè)對象的同步代碼塊時(shí),其他的線程仍然可以執(zhí)行對象的非同步代碼塊。(所謂的線程之間保持同步,是指不同的線程在執(zhí)行同一個(gè)對象的同步代碼塊時(shí),因?yàn)橐@得對象的同步鎖而互相牽制)
    2、 每個(gè)對象都有唯一的同步鎖
    3、 在靜態(tài)方法前面可以使用synchronized修飾符。
    4、 當(dāng)一個(gè)線程開始執(zhí)行同步代碼塊時(shí),并不意味著必須以不間斷的方式運(yùn)行,進(jìn)入同步代碼塊的線程可以執(zhí)行Thread.sleep()或者執(zhí)行Thread.yield()方法,此時(shí)它并不釋放對象鎖,只是把運(yùn)行的機(jī)會(huì)讓給其他的線程。
    5、 Synchronized聲明不會(huì)被繼承,如果一個(gè)用synchronized修飾的方法被子類覆蓋,那么子類中這個(gè)方法不在保持同步,除非用synchronized修飾。

    線程安全的類:
    1、 這個(gè)類的對象可以同時(shí)被多個(gè)線程安全的訪問。
    2、 每個(gè)線程都能正常的執(zhí)行原子操作,得到正確的結(jié)果。
    3、 在每個(gè)線程的原子操作都完成后,對象處于邏輯上合理的狀態(tài)。

    釋放對象的鎖:
    1、 執(zhí)行完同步代碼塊就會(huì)釋放對象的鎖
    2、 在執(zhí)行同步代碼塊的過程中,遇到異常而導(dǎo)致線程終止,鎖也會(huì)被釋放
    3、 在執(zhí)行同步代碼塊的過程中,執(zhí)行了鎖所屬對象的wait()方法,這個(gè)線程會(huì)釋放對象鎖,進(jìn)入對象的等待池。

    死鎖
    當(dāng)一個(gè)線程等待由另一個(gè)線程持有的鎖,而后者正在等待已被第一個(gè)線程持有的鎖時(shí),就會(huì)發(fā)生死鎖。JVM不監(jiān)測也不試圖避免這種情況,因此保證不發(fā)生死鎖就成了程序員的責(zé)任。

    如何避免死鎖
    一個(gè)通用的經(jīng)驗(yàn)法則是:當(dāng)幾個(gè)線程都要訪問共享資源A、B、C 時(shí),保證每個(gè)線程都按照同樣的順序去訪問他們。

    線程通信
    Java.lang.Object類中提供了兩個(gè)用于線程通信的方法
    1、 wait():執(zhí)行了該方法的線程釋放對象的鎖,JVM會(huì)把該線程放到對象的等待池中。該線程等待其它線程喚醒
    2、 notify():執(zhí)行該方法的線程喚醒在對象的等待池中等待的一個(gè)線程,JVM從對象的等待池中隨機(jī)選擇一個(gè)線程,把它轉(zhuǎn)到對象的鎖池中。
    posted on 2009-06-01 09:31 Frank_Fang 閱讀(795) 評論(4)  編輯  收藏 所屬分類: Java編程

    評論:
    # re: Java多線程小結(jié) 2009-06-02 14:33 | Frank_Fang
    wait和notify方法的使用一定要在同步代碼塊中
      回復(fù)  更多評論
      
    # re: Java多線程小結(jié) 2009-06-02 21:51 | Frank_Fang
    線程同步
    作者 : buaawhl

    我們可以在計(jì)算機(jī)上運(yùn)行各種計(jì)算機(jī)軟件程序。每一個(gè)運(yùn)行的程序可能包括多個(gè)獨(dú)立運(yùn)行的線程(Thread)。
    線程(Thread)是一份獨(dú)立運(yùn)行的程序,有自己專用的運(yùn)行棧。線程有可能和其他線程共享一些資源,比如,內(nèi)存,文件,數(shù)據(jù)庫等。
    當(dāng)多個(gè)線程同時(shí)讀寫同一份共享資源的時(shí)候,可能會(huì)引起沖突。這時(shí)候,我們需要引入線程“同步”機(jī)制,即各位線程之間要有個(gè)先來后到,不能一窩蜂擠上去搶作一團(tuán)。
    同步這個(gè)詞是從英文synchronize(使同時(shí)發(fā)生)翻譯過來的。我也不明白為什么要用這個(gè)很容易引起誤解的詞。既然大家都這么用,咱們也就只好這么將就。
    線程同步的真實(shí)意思和字面意思恰好相反。線程同步的真實(shí)意思,其實(shí)是“排隊(duì)”:幾個(gè)線程之間要排隊(duì),一個(gè)一個(gè)對共享資源進(jìn)行操作,而不是同時(shí)進(jìn)行操作。

    因此,關(guān)于線程同步,需要牢牢記住的第一點(diǎn)是:線程同步就是線程排隊(duì)。同步就是排隊(duì)。線程同步的目的就是避免線程“同步”執(zhí)行。這可真是個(gè)無聊的繞口令。
    關(guān)于線程同步,需要牢牢記住的第二點(diǎn)是 “共享”這兩個(gè)字。只有共享資源的讀寫訪問才需要同步。如果不是共享資源,那么就根本沒有同步的必要。
    關(guān)于線程同步,需要牢牢記住的第三點(diǎn)是,只有“變量”才需要同步訪問。如果共享的資源是固定不變的,那么就相當(dāng)于“常量”,線程同時(shí)讀取常量也不需要同步。至少一個(gè)線程修改共享資源,這樣的情況下,線程之間就需要同步。
    關(guān)于線程同步,需要牢牢記住的第四點(diǎn)是:多個(gè)線程訪問共享資源的代碼有可能是同一份代碼,也有可能是不同的代碼;無論是否執(zhí)行同一份代碼,只要這些線程的代碼訪問同一份可變的共享資源,這些線程之間就需要同步。

    為了加深理解,下面舉幾個(gè)例子。
    有兩個(gè)采購員,他們的工作內(nèi)容是相同的,都是遵循如下的步驟:
    (1)到市場上去,尋找并購買有潛力的樣品。
    (2)回到公司,寫報(bào)告。
    這兩個(gè)人的工作內(nèi)容雖然一樣,他們都需要購買樣品,他們可能買到同樣種類的樣品,但是他們絕對不會(huì)購買到同一件樣品,他們之間沒有任何共享資源。所以,他們可以各自進(jìn)行自己的工作,互不干擾。
    這兩個(gè)采購員就相當(dāng)于兩個(gè)線程;兩個(gè)采購員遵循相同的工作步驟,相當(dāng)于這兩個(gè)線程執(zhí)行同一段代碼。

    下面給這兩個(gè)采購員增加一個(gè)工作步驟。采購員需要根據(jù)公司的“布告欄”上面公布的信息,安排自己的工作計(jì)劃。
    這兩個(gè)采購員有可能同時(shí)走到布告欄的前面,同時(shí)觀看布告欄上的信息。這一點(diǎn)問題都沒有。因?yàn)椴几鏅谑侵蛔x的,這兩個(gè)采購員誰都不會(huì)去修改布告欄上寫的信息。

    下面增加一個(gè)角色。一個(gè)辦公室行政人員這個(gè)時(shí)候,也走到了布告欄前面,準(zhǔn)備修改布告欄上的信息。
    如果行政人員先到達(dá)布告欄,并且正在修改布告欄的內(nèi)容。兩個(gè)采購員這個(gè)時(shí)候,恰好也到了。這兩個(gè)采購員就必須等待行政人員完成修改之后,才能觀看修改后的信息。
    如果行政人員到達(dá)的時(shí)候,兩個(gè)采購員已經(jīng)在觀看布告欄了。那么行政人員需要等待兩個(gè)采購員把當(dāng)前信息記錄下來之后,才能夠?qū)懮闲碌男畔ⅰ?
    上述這兩種情況,行政人員和采購員對布告欄的訪問就需要進(jìn)行同步。因?yàn)槠渲幸粋€(gè)線程(行政人員)修改了共享資源(布告欄)。而且我們可以看到,行政人員的工作流程和采購員的工作流程(執(zhí)行代碼)完全不同,但是由于他們訪問了同一份可變共享資源(布告欄),所以他們之間需要同步。

    同步鎖

    前面講了為什么要線程同步,下面我們就來看如何才能線程同步。
    線程同步的基本實(shí)現(xiàn)思路還是比較容易理解的。我們可以給共享資源加一把鎖,這把鎖只有一把鑰匙。哪個(gè)線程獲取了這把鑰匙,才有權(quán)利訪問該共享資源。
    生活中,我們也可能會(huì)遇到這樣的例子。一些超市的外面提供了一些自動(dòng)儲(chǔ)物箱。每個(gè)儲(chǔ)物箱都有一把鎖,一把鑰匙。人們可以使用那些帶有鑰匙的儲(chǔ)物箱,把東西放到儲(chǔ)物箱里面,把儲(chǔ)物箱鎖上,然后把鑰匙拿走。這樣,該儲(chǔ)物箱就被鎖住了,其他人不能再訪問這個(gè)儲(chǔ)物箱。(當(dāng)然,真實(shí)的儲(chǔ)物箱鑰匙是可以被人拿走復(fù)制的,所以不要把貴重物品放在超市的儲(chǔ)物箱里面。于是很多超市都采用了電子密碼鎖。)
    線程同步鎖這個(gè)模型看起來很直觀。但是,還有一個(gè)嚴(yán)峻的問題沒有解決,這個(gè)同步鎖應(yīng)該加在哪里?
    當(dāng)然是加在共享資源上了。反應(yīng)快的讀者一定會(huì)搶先回答。
    沒錯(cuò),如果可能,我們當(dāng)然盡量把同步鎖加在共享資源上。一些比較完善的共享資源,比如,文件系統(tǒng),數(shù)據(jù)庫系統(tǒng)等,自身都提供了比較完善的同步鎖機(jī)制。我們不用另外給這些資源加鎖,這些資源自己就有鎖。
    但是,大部分情況下,我們在代碼中訪問的共享資源都是比較簡單的共享對象。這些對象里面沒有地方讓我們加鎖。
    讀者可能會(huì)提出建議:為什么不在每一個(gè)對象內(nèi)部都增加一個(gè)新的區(qū)域,專門用來加鎖呢?這種設(shè)計(jì)理論上當(dāng)然也是可行的。問題在于,線程同步的情況并不是很普遍。如果因?yàn)檫@小概率事件,在所有對象內(nèi)部都開辟一塊鎖空間,將會(huì)帶來極大的空間浪費(fèi)。得不償失。
    于是,現(xiàn)代的編程語言的設(shè)計(jì)思路都是把同步鎖加在代碼段上。確切的說,是把同步鎖加在“訪問共享資源的代碼段”上。這一點(diǎn)一定要記住,同步鎖是加在代碼段上的。
    同步鎖加在代碼段上,就很好地解決了上述的空間浪費(fèi)問題。但是卻增加了模型的復(fù)雜度,也增加了我們的理解難度。
    現(xiàn)在我們就來仔細(xì)分析“同步鎖加在代碼段上”的線程同步模型。
    首先,我們已經(jīng)解決了同步鎖加在哪里的問題。我們已經(jīng)確定,同步鎖不是加在共享資源上,而是加在訪問共享資源的代碼段上。
    其次,我們要解決的問題是,我們應(yīng)該在代碼段上加什么樣的鎖。這個(gè)問題是重點(diǎn)中的重點(diǎn)。這是我們尤其要注意的問題:訪問同一份共享資源的不同代碼段,應(yīng)該加上同一個(gè)同步鎖;如果加的是不同的同步鎖,那么根本就起不到同步的作用,沒有任何意義。
    這就是說,同步鎖本身也一定是多個(gè)線程之間的共享對象。

    Java語言的synchronized關(guān)鍵字

    為了加深理解,舉幾個(gè)代碼段同步的例子。
    不同語言的同步鎖模型都是一樣的。只是表達(dá)方式有些不同。這里我們以當(dāng)前最流行的Java語言為例。Java語言里面用synchronized關(guān)鍵字給代碼段加鎖。整個(gè)語法形式表現(xiàn)為
    synchronized(同步鎖) {
    // 訪問共享資源,需要同步的代碼段
    }

    這里尤其要注意的就是,同步鎖本身一定要是共享的對象。

    … f1() {

    Object lock1 = new Object(); // 產(chǎn)生一個(gè)同步鎖

    synchronized(lock1){
    // 代碼段 A
    // 訪問共享資源 resource1
    // 需要同步
    }
    }

    上面這段代碼沒有任何意義。因?yàn)槟莻€(gè)同步鎖是在函數(shù)體內(nèi)部產(chǎn)生的。每個(gè)線程調(diào)用這段代碼的時(shí)候,都會(huì)產(chǎn)生一個(gè)新的同步鎖。那么多個(gè)線程之間,使用的是不同的同步鎖。根本達(dá)不到同步的目的。
    同步代碼一定要寫成如下的形式,才有意義。

    public static final Object lock1 = new Object();

    … f1() {

    synchronized(lock1){ // lock1 是公用同步鎖
    // 代碼段 A
    // 訪問共享資源 resource1
    // 需要同步
    }

    你不一定要把同步鎖聲明為static或者public,但是你一定要保證相關(guān)的同步代碼之間,一定要使用同一個(gè)同步鎖。
    講到這里,你一定會(huì)好奇,這個(gè)同步鎖到底是個(gè)什么東西。為什么隨便聲明一個(gè)Object對象,就可以作為同步鎖?
    在Java里面,同步鎖的概念就是這樣的。任何一個(gè)Object Reference都可以作為同步鎖。我們可以把Object Reference理解為對象在內(nèi)存分配系統(tǒng)中的內(nèi)存地址。因此,要保證同步代碼段之間使用的是同一個(gè)同步鎖,我們就要保證這些同步代碼段的synchronized關(guān)鍵字使用的是同一個(gè)Object Reference,同一個(gè)內(nèi)存地址。這也是為什么我在前面的代碼中聲明lock1的時(shí)候,使用了final關(guān)鍵字,這就是為了保證lock1的Object Reference在整個(gè)系統(tǒng)運(yùn)行過程中都保持不變。
    一些求知欲強(qiáng)的讀者可能想要繼續(xù)深入了解synchronzied(同步鎖)的實(shí)際運(yùn)行機(jī)制。Java虛擬機(jī)規(guī)范中(你可以在google用“JVM Spec”等關(guān)鍵字進(jìn)行搜索),有對synchronized關(guān)鍵字的詳細(xì)解釋。synchronized會(huì)編譯成 monitor enter, … monitor exit之類的指令對。Monitor就是實(shí)際上的同步鎖。每一個(gè)Object Reference在概念上都對應(yīng)一個(gè)monitor。
    這些實(shí)現(xiàn)細(xì)節(jié)問題,并不是理解同步鎖模型的關(guān)鍵。我們繼續(xù)看幾個(gè)例子,加深對同步鎖模型的理解。

    public static final Object lock1 = new Object();

    … f1() {

    synchronized(lock1){ // lock1 是公用同步鎖
    // 代碼段 A
    // 訪問共享資源 resource1
    // 需要同步
    }
    }

    … f2() {

    synchronized(lock1){ // lock1 是公用同步鎖
    // 代碼段 B
    // 訪問共享資源 resource1
    // 需要同步
    }
    }

    上述的代碼中,代碼段A和代碼段B就是同步的。因?yàn)樗鼈兪褂玫氖峭粋€(gè)同步鎖lock1。
    如果有10個(gè)線程同時(shí)執(zhí)行代碼段A,同時(shí)還有20個(gè)線程同時(shí)執(zhí)行代碼段B,那么這30個(gè)線程之間都是要進(jìn)行同步的。
    這30個(gè)線程都要競爭一個(gè)同步鎖lock1。同一時(shí)刻,只有一個(gè)線程能夠獲得lock1的所有權(quán),只有一個(gè)線程可以執(zhí)行代碼段A或者代碼段B。其他競爭失敗的線程只能暫停運(yùn)行,進(jìn)入到該同步鎖的就緒(Ready)隊(duì)列。
    每一個(gè)同步鎖下面都掛了幾個(gè)線程隊(duì)列,包括就緒(Ready)隊(duì)列,待召(Waiting)隊(duì)列等。比如,lock1對應(yīng)的就緒隊(duì)列就可以叫做lock1 - ready queue。每個(gè)隊(duì)列里面都可能有多個(gè)暫停運(yùn)行的線程。
    注意,競爭同步鎖失敗的線程進(jìn)入的是該同步鎖的就緒(Ready)隊(duì)列,而不是后面要講述的待召隊(duì)列(Waiting Queue,也可以翻譯為等待隊(duì)列)。就緒隊(duì)列里面的線程總是時(shí)刻準(zhǔn)備著競爭同步鎖,時(shí)刻準(zhǔn)備著運(yùn)行。而待召隊(duì)列里面的線程則只能一直等待,直到等到某個(gè)信號的通知之后,才能夠轉(zhuǎn)移到就緒隊(duì)列中,準(zhǔn)備運(yùn)行。
    成功獲取同步鎖的線程,執(zhí)行完同步代碼段之后,會(huì)釋放同步鎖。該同步鎖的就緒隊(duì)列中的其他線程就繼續(xù)下一輪同步鎖的競爭。成功者就可以繼續(xù)運(yùn)行,失敗者還是要乖乖地待在就緒隊(duì)列中。
    因此,線程同步是非常耗費(fèi)資源的一種操作。我們要盡量控制線程同步的代碼段范圍。同步的代碼段范圍越小越好。我們用一個(gè)名詞“同步粒度”來表示同步代碼段的范圍。
    同步粒度
    在Java語言里面,我們可以直接把synchronized關(guān)鍵字直接加在函數(shù)的定義上。
    比如。
    … synchronized … f1() {
    // f1 代碼段
    }

    這段代碼就等價(jià)于
    … f1() {
    synchronized(this){ // 同步鎖就是對象本身
    // f1 代碼段
    }
    }

    同樣的原則適用于靜態(tài)(static)函數(shù)
    比如。
    … static synchronized … f1() {
    // f1 代碼段
    }

    這段代碼就等價(jià)于
    …static … f1() {
    synchronized(Class.forName(…)){ // 同步鎖是類定義本身
    // f1 代碼段
    }
    }

    但是,我們要盡量避免這種直接把synchronized加在函數(shù)定義上的偷懶做法。因?yàn)槲覀円刂仆搅6?。同步的代碼段越小越好。synchronized控制的范圍越小越好。
    我們不僅要在縮小同步代碼段的長度上下功夫,我們同時(shí)還要注意細(xì)分同步鎖。
    比如,下面的代碼

    public static final Object lock1 = new Object();

    … f1() {

    synchronized(lock1){ // lock1 是公用同步鎖
    // 代碼段 A
    // 訪問共享資源 resource1
    // 需要同步
    }
    }

    … f2() {

    synchronized(lock1){ // lock1 是公用同步鎖
    // 代碼段 B
    // 訪問共享資源 resource1
    // 需要同步
    }
    }

    … f3() {

    synchronized(lock1){ // lock1 是公用同步鎖
    // 代碼段 C
    // 訪問共享資源 resource2
    // 需要同步
    }
    }

    … f4() {

    synchronized(lock1){ // lock1 是公用同步鎖
    // 代碼段 D
    // 訪問共享資源 resource2
    // 需要同步
    }
    }

    上述的4段同步代碼,使用同一個(gè)同步鎖lock1。所有調(diào)用4段代碼中任何一段代碼的線程,都需要競爭同一個(gè)同步鎖lock1。
    我們仔細(xì)分析一下,發(fā)現(xiàn)這是沒有必要的。
    因?yàn)閒1()的代碼段A和f2()的代碼段B訪問的共享資源是resource1,f3()的代碼段C和f4()的代碼段D訪問的共享資源是resource2,它們沒有必要都競爭同一個(gè)同步鎖lock1。我們可以增加一個(gè)同步鎖lock2。f3()和f4()的代碼可以修改為:
    public static final Object lock2 = new Object();

    … f3() {

    synchronized(lock2){ // lock2 是公用同步鎖
    // 代碼段 C
    // 訪問共享資源 resource2
    // 需要同步
    }
    }

    … f4() {

    synchronized(lock2){ // lock2 是公用同步鎖
    // 代碼段 D
    // 訪問共享資源 resource2
    // 需要同步
    }
    }

    這樣,f1()和f2()就會(huì)競爭lock1,而f3()和f4()就會(huì)競爭lock2。這樣,分開來分別競爭兩個(gè)鎖,就可以大大較少同步鎖競爭的概率,從而減少系統(tǒng)的開銷。


      回復(fù)  更多評論
      
    # re: Java多線程小結(jié) 2009-06-02 21:51 | Frank_Fang
    信號量

    同步鎖模型只是最簡單的同步模型。同一時(shí)刻,只有一個(gè)線程能夠運(yùn)行同步代碼。
    有的時(shí)候,我們希望處理更加復(fù)雜的同步模型,比如生產(chǎn)者/消費(fèi)者模型、讀寫同步模型等。這種情況下,同步鎖模型就不夠用了。我們需要一個(gè)新的模型。這就是我們要講述的信號量模型。
    信號量模型的工作方式如下:線程在運(yùn)行的過程中,可以主動(dòng)停下來,等待某個(gè)信號量的通知;這時(shí)候,該線程就進(jìn)入到該信號量的待召(Waiting)隊(duì)列當(dāng)中;等到通知之后,再繼續(xù)運(yùn)行。
    很多語言里面,同步鎖都由專門的對象表示,對象名通常叫Monitor。
    同樣,在很多語言中,信號量通常也有專門的對象名來表示,比如,Mutex,Semphore。
    信號量模型要比同步鎖模型復(fù)雜許多。一些系統(tǒng)中,信號量甚至可以跨進(jìn)程進(jìn)行同步。另外一些信號量甚至還有計(jì)數(shù)功能,能夠控制同時(shí)運(yùn)行的線程數(shù)。
    我們沒有必要考慮那么復(fù)雜的模型。所有那些復(fù)雜的模型,都是最基本的模型衍生出來的。只要掌握了最基本的信號量模型——“等待/通知”模型,復(fù)雜模型也就迎刃而解了。
    我們還是以Java語言為例。Java語言里面的同步鎖和信號量概念都非常模糊,沒有專門的對象名詞來表示同步鎖和信號量,只有兩個(gè)同步鎖相關(guān)的關(guān)鍵字——volatile和synchronized。
    這種模糊雖然導(dǎo)致概念不清,但同時(shí)也避免了Monitor、Mutex、Semphore等名詞帶來的種種誤解。我們不必執(zhí)著于名詞之爭,可以專注于理解實(shí)際的運(yùn)行原理。
    在Java語言里面,任何一個(gè)Object Reference都可以作為同步鎖。同樣的道理,任何一個(gè)Object Reference也可以作為信號量。
    Object對象的wait()方法就是等待通知,Object對象的notify()方法就是發(fā)出通知。
    具體調(diào)用方法為
    (1)等待某個(gè)信號量的通知
    public static final Object signal = new Object();

    … f1() {
    synchronized(singal) { // 首先我們要獲取這個(gè)信號量。這個(gè)信號量同時(shí)也是一個(gè)同步鎖

    // 只有成功獲取了signal這個(gè)信號量兼同步鎖之后,我們才可能進(jìn)入這段代碼
    signal.wait(); // 這里要放棄信號量。本線程要進(jìn)入signal信號量的待召(Waiting)隊(duì)列

    // 可憐。辛辛苦苦爭取到手的信號量,就這么被放棄了

    // 等到通知之后,從待召(Waiting)隊(duì)列轉(zhuǎn)到就緒(Ready)隊(duì)列里面
    // 轉(zhuǎn)到了就緒隊(duì)列中,離CPU核心近了一步,就有機(jī)會(huì)繼續(xù)執(zhí)行下面的代碼了。
    // 仍然需要把signal同步鎖競爭到手,才能夠真正繼續(xù)執(zhí)行下面的代碼。命苦啊。

    }
    }

    需要注意的是,上述代碼中的signal.wait()的意思。signal.wait()很容易導(dǎo)致誤解。signal.wait()的意思并不是說,signal開始wait,而是說,運(yùn)行這段代碼的當(dāng)前線程開始wait這個(gè)signal對象,即進(jìn)入signal對象的待召(Waiting)隊(duì)列。

    (2)發(fā)出某個(gè)信號量的通知
    … f2() {
    synchronized(singal) { // 首先,我們同樣要獲取這個(gè)信號量。同時(shí)也是一個(gè)同步鎖。

    // 只有成功獲取了signal這個(gè)信號量兼同步鎖之后,我們才可能進(jìn)入這段代碼
    signal.notify(); // 這里,我們通知signal的待召隊(duì)列中的某個(gè)線程。

    // 如果某個(gè)線程等到了這個(gè)通知,那個(gè)線程就會(huì)轉(zhuǎn)到就緒隊(duì)列中
    // 但是本線程仍然繼續(xù)擁有signal這個(gè)同步鎖,本線程仍然繼續(xù)執(zhí)行
    // 嘿嘿,雖然本線程好心通知其他線程,
    // 但是,本線程可沒有那么高風(fēng)亮節(jié),放棄到手的同步鎖
    // 本線程繼續(xù)執(zhí)行下面的代碼
    一般的情況是signal.notify()是此段代碼的最后一條語句

    }
    }

    需要注意的是,signal.notify()的意思。signal.notify()并不是通知signal這個(gè)對象本身。而是通知正在等待signal信號量的其他線程。

    以上就是Object的wait()和notify()的基本用法。
    實(shí)際上,wait()還可以定義等待時(shí)間,當(dāng)線程在某信號量的待召隊(duì)列中,等到足夠長的時(shí)間,就會(huì)等無可等,無需再等,自己就從待召隊(duì)列轉(zhuǎn)移到就緒隊(duì)列中了。
    另外,還有一個(gè)notifyAll()方法,表示通知待召隊(duì)列里面的所有線程。
    這些細(xì)節(jié)問題,并不對大局產(chǎn)生影響。
      回復(fù)  更多評論
      
    # re: Java多線程小結(jié) 2009-06-02 21:52 | Frank_Fang
    綠色線程

    綠色線程(Green Thread)是一個(gè)相對于操作系統(tǒng)線程(Native Thread)的概念。
    操作系統(tǒng)線程(Native Thread)的意思就是,程序里面的線程會(huì)真正映射到操作系統(tǒng)的線程(內(nèi)核級線程),線程的運(yùn)行和調(diào)度都是由操作系統(tǒng)控制的
    綠色線程(Green Thread)的意思是,程序里面的線程不會(huì)真正映射到操作系統(tǒng)的線程,而是由語言運(yùn)行平臺(tái)自身來調(diào)度。
    當(dāng)前版本的Python語言的線程就可以映射到操作系統(tǒng)線程。當(dāng)前版本的Ruby語言的線程就屬于綠色線程,無法映射到操作系統(tǒng)的線程,因此Ruby語言的線程的運(yùn)行速度比較慢。
    難道說,綠色線程要比操作系統(tǒng)線程要慢嗎?當(dāng)然不是這樣。事實(shí)上,情況可能正好相反。Ruby是一個(gè)特殊的例子。線程調(diào)度器并不是很成熟。
    目前,線程的流行實(shí)現(xiàn)模型就是綠色線程。比如,stackless Python,就引入了更加輕量的綠色線程概念。在線程并發(fā)編程方面,無論是運(yùn)行速度還是并發(fā)負(fù)載上,都優(yōu)于Python。
    另一個(gè)更著名的例子就是ErLang(愛立信公司開發(fā)的一種開源語言)。
    ErLang的綠色線程概念非常徹底。ErLang的線程不叫Thread,而是叫做Process。這很容易和進(jìn)程混淆起來。這里要注意區(qū)分一下。
    ErLang Process之間根本就不需要同步。因?yàn)镋rLang語言的所有變量都是final的,不允許變量的值發(fā)生任何變化。因此根本就不需要同步。
    final變量的另一個(gè)好處就是,對象之間不可能出現(xiàn)交叉引用,不可能構(gòu)成一種環(huán)狀的關(guān)聯(lián),對象之間的關(guān)聯(lián)都是單向的,樹狀的。因此,內(nèi)存垃圾回收的算法效率也非常高。這就讓ErLang能夠達(dá)到Soft Real Time(軟實(shí)時(shí))的效果。這對于一門支持內(nèi)存垃圾回收的語言來說,可不是一件容易的事情。   回復(fù)  更多評論
      
    主站蜘蛛池模板: 国产精品二区三区免费播放心| 亚洲精品成人片在线播放| 一边摸一边桶一边脱免费视频| 亚洲欧洲中文日韩久久AV乱码| 两个人看的www免费视频| 亚洲国产精品成人精品软件 | 亚洲AV无码乱码在线观看裸奔| 国产a视频精品免费观看| 国产成人亚洲精品91专区高清 | 污污视频网站免费观看| 久久精品国产精品亚洲色婷婷| 成年女人18级毛片毛片免费| 精品国产污污免费网站入口| 亚洲国产精品综合久久网各| 亚洲一本大道无码av天堂| 在线永久看片免费的视频| jizz18免费视频| 亚洲乱码日产精品一二三| 亚洲高清国产拍精品26U| 成人毛片18女人毛片免费| 男女作爱在线播放免费网站| 在线观看亚洲免费视频| 亚洲精品国产专区91在线| 久久亚洲色一区二区三区| 四虎成人免费网站在线| 久久九九AV免费精品| 色噜噜狠狠色综合免费视频| 亚洲人成电影院在线观看| 亚洲国产精品高清久久久| 免费h成人黄漫画嘿咻破解版| 91黑丝国产线观看免费| 久久er国产精品免费观看2| 特黄aa级毛片免费视频播放| avtt天堂网手机版亚洲| 久久久久亚洲av无码专区导航| 亚洲综合亚洲综合网成人| 日日AV拍夜夜添久久免费| 国拍在线精品视频免费观看| 免费看搞黄视频网站| 中文字幕免费在线观看动作大片| 亚洲gay片在线gv网站|