欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久

打開(kāi)APP
userphoto
未登錄

開(kāi)通VIP,暢享免費電子書(shū)等14項超值服

開(kāi)通VIP
線(xiàn)程同步(使用了synchronized)和線(xiàn)程通訊(使用了wait,notify)

線(xiàn)程同步


什么是線(xiàn)程同步?

當使用多個(gè)線(xiàn)程來(lái)訪(fǎng)問(wèn)同一個(gè)數據時(shí),非常容易出現線(xiàn)程安全問(wèn)題(比如多個(gè)線(xiàn)程都在操作同一數據導致數據不一致),所以我們用同步機制來(lái)解決這些問(wèn)題。

實(shí)現同步機制有兩個(gè)方法:
1。
同步代碼塊:
synchronized(
同一個(gè)數據){} 同一個(gè)數據:就是N條線(xiàn)程同時(shí)訪(fǎng)問(wèn)一個(gè)數據。

 

2。同步方法:

public synchronized 數據返回類(lèi)型 方法名(){}
就是使用 synchronized 來(lái)修飾某個(gè)方法,則該方法稱(chēng)為同步方法。對于同步方法而言,無(wú)需顯示指定同步監視器,同步方法的同步監視器是 this 也就是該對象的本身(這里指的對象本身有點(diǎn)含糊,其實(shí)就是調用該同步方法的對象)通過(guò)使用同步方法,可非常方便的將某類(lèi)變成線(xiàn)程安全的類(lèi),具有如下特征:
1
,該類(lèi)的對象可以被多個(gè)線(xiàn)程安全的訪(fǎng)問(wèn)。
2
,每個(gè)線(xiàn)程調用該對象的任意方法之后,都將得到正確的結果。
3
,每個(gè)線(xiàn)程調用該對象的任意方法之后,該對象狀態(tài)依然保持合理狀態(tài)。
注:synchronized關(guān)鍵字可以修飾方法,也可以修飾代碼塊,但不能修飾構造器,屬性等。

實(shí)現同步機制注意以下幾點(diǎn):   安全性高,性能低,在多線(xiàn)程用。性能高,安全性低,在單線(xiàn)程用。
1
,不要對線(xiàn)程安全類(lèi)的所有方法都進(jìn)行同步,只對那些會(huì )改變共享資源方法的進(jìn)行同步。
2
,如果可變類(lèi)有兩種運行環(huán)境,當線(xiàn)程環(huán)境和多線(xiàn)程環(huán)境則應該為該可變類(lèi)提供兩種版本:線(xiàn)程安全版本和線(xiàn)程不安全版本(沒(méi)有同步方法和同步塊)。在單線(xiàn)程中環(huán)境中,使用線(xiàn)程不安全版本以保證性能,在多線(xiàn)程中使用線(xiàn)程安全版本.

 

線(xiàn)程通訊:

為什么要使用線(xiàn)程通訊?

當使用synchronized 來(lái)修飾某個(gè)共享資源時(shí)(分同步代碼塊和同步方法兩種情況),當某個(gè)線(xiàn)程獲得共享資源的鎖后就可以執行相應的代碼段,直到該線(xiàn)程運行完該代碼段后才釋放對該共享資源的鎖,讓其他線(xiàn)程有機會(huì )執行對該共享資源的修改。當某個(gè)線(xiàn)程占有某個(gè)共享資源的鎖時(shí),如果另外一個(gè)線(xiàn)程也想獲得這把鎖運行就需要使用wait() 和notify()/notifyAll()方法來(lái)進(jìn)行線(xiàn)程通訊了。
Java.lang.object
里的三個(gè)方法wait() notify()  notifyAll()
wait
方法導致當前線(xiàn)程等待,直到其他線(xiàn)程調用同步監視器的notify方法或notifyAll方法來(lái)喚醒該線(xiàn)程。
wait(mills)
方法
都是等待指定時(shí)間后自動(dòng)蘇醒,調用wait方法的當前線(xiàn)程會(huì )釋放該同步監視器的鎖定,可以不用notifynotifyAll方法把它喚醒。

notify()
喚醒在同步監視器上等待的單個(gè)線(xiàn)程,如果所有線(xiàn)程都在同步監視器上等待,則會(huì )選擇喚醒其中一個(gè)線(xiàn)程,選擇是任意性的,只有當前線(xiàn)程放棄對該同步監視器的鎖定后,也就是使用wait方法后,才可以執行被喚醒的線(xiàn)程。

notifyAll()
方法
喚醒在同步監視器上等待的所有的線(xiàn)程。只用當前線(xiàn)程放棄對該同步監視器的鎖定后,才可以執行被喚醒的線(xiàn)程。

 

 

 

-------------------------------------------------另外的總結2--------------------------------------------

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

自增操作(++)不是原子操作,因為它涉及到一次讀和一次寫(xiě)。

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

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

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

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

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

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

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

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

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

線(xiàn)程通信
Java.lang.Object
類(lèi)中提供了兩個(gè)用于線(xiàn)程通信的方法
1
、 wait():執行了該方法的線(xiàn)程釋放對象的鎖,JVM會(huì )把該線(xiàn)程放到對象的等待池中。該線(xiàn)程等待其它線(xiàn)程喚醒
2
、 notify():執行該方法的線(xiàn)程喚醒在對象的等待池中等待的一個(gè)線(xiàn)程,JVM從對象的等待池中隨機選擇一個(gè)線(xiàn)程,把它轉到對象的鎖池中。

 

 

----------------------------------------------------線(xiàn)程同步的總結3---------------------------------------

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

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

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

下面給這兩個(gè)采購員增加一個(gè)工作步驟。采購員需要根據公司的“布告欄”上面公布的信息,安排自己的工作計劃。
這兩個(gè)采購員有可能同時(shí)走到布告欄的前面,同時(shí)觀(guān)看布告欄上的信息。這一點(diǎn)問(wèn)題都沒(méi)有。因為布告欄是只讀的,這兩個(gè)采購員誰(shuí)都不會(huì )去修改布告欄上寫(xiě)的信息。

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

同步鎖

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

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

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

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

… f1() {

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

synchronized(lock1){
  // 代碼段 A
// 訪(fǎng)問(wèn)共享資源 resource1
// 需要同步
}
}

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

public static final Object lock1 = new Object();

… f1() {

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

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

public static final Object lock1 = new Object();

… f1() {

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

… f2() {

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

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

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

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

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

但是,我們要盡量避免這種直接把synchronized加在函數定義上的偷懶做法。因為我們要控制同步粒度。同步的代碼段越小越好。synchronized控制的范圍越小越好。
我們不僅要在縮小同步代碼段的長(cháng)度上下功夫,我們同時(shí)還要注意細分同步鎖。
比如,下面的代碼

public static final Object lock1 = new Object();

… f1() {

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

… f2() {

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

… f3() {

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

… f4() {

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

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

… f3() {

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

… f4() {

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

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

信號量

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

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

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

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

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

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

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

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

// 如果某個(gè)線(xiàn)程等到了這個(gè)通知,那個(gè)線(xiàn)程就會(huì )轉到就緒隊列中
// 但是本線(xiàn)程仍然繼續擁有signal這個(gè)同步鎖,本線(xiàn)程仍然繼續執行
// 嘿嘿,雖然本線(xiàn)程好心通知其他線(xiàn)程,
// 但是,本線(xiàn)程可沒(méi)有那么高風(fēng)亮節,放棄到手的同步鎖
// 本線(xiàn)程繼續執行下面的代碼
    …
}
}

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

以上就是Object的wait()和notify()的基本用法。
實(shí)際上,wait()還可以定義等待時(shí)間,當線(xiàn)程在某信號量的待召隊列中,等到足夠長(cháng)的時(shí)間,就會(huì )等無(wú)可等,無(wú)需再等,自己就從待召隊列轉移到就緒隊列中了。
另外,還有一個(gè)notifyAll()方法,表示通知待召隊列里面的所有線(xiàn)程。
這些細節問(wèn)題,并不對大局產(chǎn)生影響。

----------------------------------------------------------------------------------------線(xiàn)程同步的總結4-------------------------------------------------------------------------

 

總的說(shuō)來(lái),synchronized關(guān)鍵字可以作為函數的修飾符,也可作為函數內的語(yǔ)句,也就是平時(shí)說(shuō)的同步方法和同步語(yǔ)句塊。如果再細的分類(lèi),synchronized可作用于instance變量、object reference(對象引用)、static函數和class literals(類(lèi)名稱(chēng)字面常量)身上。

在進(jìn)一步闡述之前,我們需要明確幾點(diǎn):

A.無(wú)論synchronized關(guān)鍵字加在方法上還是對象上,它取得的鎖都是對象,而不是把一段代碼或函數當作鎖――而且同步方法很可能還會(huì )被其他線(xiàn)程的對象訪(fǎng)問(wèn)。

B.每個(gè)對象只有一個(gè)鎖(lock)與之相關(guān)聯(lián)。

C.實(shí)現同步是要很大的系統開(kāi)銷(xiāo)作為代價(jià)的,甚至可能造成死鎖,所以盡量避免無(wú)謂的同步控制。

接著(zhù)來(lái)討論synchronized用到不同地方對代碼產(chǎn)生的影響:

 

假設P1、P2是同一個(gè)類(lèi)的不同對象,這個(gè)類(lèi)中定義了以下幾種情況的同步塊或同步方法,P1、P2就都可以調用它們。

 

1.  synchronized當作函數修飾符時(shí),示例代碼如下:

Public synchronized void methodAAA()

{

//….

}

這也就是同步方法,那這時(shí)synchronized鎖定的是哪個(gè)對象呢?它鎖定的是調用這個(gè)同步方法對象。也就是說(shuō),當一個(gè)對象P1在不同的線(xiàn)程中執行這個(gè)同步方法時(shí),它們之間會(huì )形成互斥,達到同步的效果。但是這個(gè)對象所屬的Class所產(chǎn)生的另一對象P2卻可以任意調用這個(gè)被加了synchronized關(guān)鍵字的方法。

上邊的示例代碼等同于如下代碼:

public void methodAAA()

{

synchronized (this)      //  (1)

{

       //…..

}

}

 (1)處的this指的是什么呢?它指的就是調用這個(gè)方法的對象,如P1??梢?jiàn)同步方法實(shí)質(zhì)是將synchronized作用于object reference。――那個(gè)拿到了P1對象鎖的線(xiàn)程,才可以調用P1的同步方法,而對P2而言,P1這個(gè)鎖與它毫不相干,程序也可能在這種情形下擺脫同步機制的控制,造成數據混亂:(

2.同步塊,示例代碼如下:

            public void method3(SomeObject so)

              {

                     synchronized(so)

{

       //…..

}

}

這時(shí),鎖就是so這個(gè)對象,誰(shuí)拿到這個(gè)鎖誰(shuí)就可以運行它所控制的那段代碼。當有一個(gè)明確的對象作為鎖時(shí),就可以這樣寫(xiě)程序,但當沒(méi)有明確的對象作為鎖,只是想讓一段代碼同步時(shí),可以創(chuàng )建一個(gè)特殊的instance變量(它得是一個(gè)對象)來(lái)充當鎖:

class Foo implements Runnable

{

       private byte[] lock = new byte[0];  // 特殊的instance變量

    Public void methodA()

{

       synchronized(lock) { //… }

}

//…..

}

注:零長(cháng)度的byte數組對象創(chuàng )建起來(lái)將比任何對象都經(jīng)濟――查看編譯后的字節碼:生成零長(cháng)度的byte[]對象只需3條操作碼,而Object lock = new Object()則需要7行操作碼。

3.將synchronized作用于static 函數,示例代碼如下:

      Class Foo

{

public synchronized static void methodAAA()   // 同步的static 函數

{

//….

}

public void methodBBB()

{

       synchronized(Foo.class)   //  class literal(類(lèi)名稱(chēng)字面常量)

}

       }

   代碼中的methodBBB()方法是把class literal作為鎖的情況,它和同步的static函數產(chǎn)生的效果是一樣的,取得的鎖很特別,是當前調用這個(gè)方法的對象所屬的類(lèi)(Class,而不再是由這個(gè)Class產(chǎn)生的某個(gè)具體對象了)。

記得在《Effective Java》一書(shū)中看到過(guò)將 Foo.class P1.getClass()用于作同步鎖還不一樣,不能用P1.getClass()來(lái)達到鎖這個(gè)Class的目的。P1指的是由Foo類(lèi)產(chǎn)生的對象。

可以推斷:如果一個(gè)類(lèi)中定義了一個(gè)synchronizedstatic函數A,也定義了一個(gè)synchronized instance函數B,那么這個(gè)類(lèi)的同一對象Obj在多線(xiàn)程中分別訪(fǎng)問(wèn)AB兩個(gè)方法時(shí),不會(huì )構成同步,因為它們的鎖都不一樣。A方法的鎖是Obj這個(gè)對象,而B的鎖是Obj所屬的那個(gè)Class。

 

小結如下:

搞清楚synchronized鎖定的是哪個(gè)對象,就能幫助我們設計更安全的多線(xiàn)程程序。

 

 

還有一些技巧可以讓我們對共享資源的同步訪(fǎng)問(wèn)更加安全:

1.  定義private instance變量+它的 get方法,而不要定義public/protectedinstance變量。如果將變量定義為public,對象在外界可以繞過(guò)同步方法的控制而直接取得它,并改動(dòng)它。這也是JavaBean的標準實(shí)現方式之一。

2.  如果instance變量是一個(gè)對象,如數組或ArrayList什么的,那上述方法仍然不安全,因為當外界對象通過(guò)get方法拿到這個(gè)instance對象的引用后,又將其指向另一個(gè)對象,那么這個(gè)private變量也就變了,豈不是很危險。 這個(gè)時(shí)候就需要將get方法也加上synchronized同步,并且,只返回這個(gè)private對象的clone()――這樣,調用端得到的就是對象副本的引用了。

 

 

 

 

 


本站僅提供存儲服務(wù),所有內容均由用戶(hù)發(fā)布,如發(fā)現有害或侵權內容,請點(diǎn)擊舉報。
打開(kāi)APP,閱讀全文并永久保存 查看更多類(lèi)似文章
猜你喜歡
類(lèi)似文章
JavaThread應該注意的問(wèn)題
java線(xiàn)程的作用 - houxueyongmonkey的日志 - 網(wǎng)易博客
Java并發(fā)-對象監視器
Java并發(fā)編程實(shí)戰(2):synchronized
Java鎖機制了解一下
Java 并發(fā)核心編程
更多類(lèi)似文章 >>
生活服務(wù)
分享 收藏 導長(cháng)圖 關(guān)注 下載文章
綁定賬號成功
后續可登錄賬號暢享VIP特權!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服

欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久