1. 程式人生 > >讀書筆記:java多執行緒程式設計核心技術

讀書筆記:java多執行緒程式設計核心技術

讀書筆記,簡單記錄....(都是從我的有道雲筆記直接複製的,沒有進行發表修改, 讀者見諒!)

第一章

掌握執行緒的啟動 暫停 停止 優先順序 安全性等

1.1程序 與 執行緒

程序 作業系統結構的基礎,是一次程式的執行,是系統進行資源分配和排程的獨立單位,簡單理解:一個執行的exe程式就是一個程序

執行緒 可理解成程序中獨立執行的子任務。 使用多執行緒可以在同一時間內執行更多不同型別的任務

單任務的特點就是排隊執行,即同步。使用多執行緒就是在使用非同步,多執行緒是非同步的。

1.2 多執行緒實現

1.繼承Thread類

缺點:無法多繼承

public class Mythread extends Thread{

@override

public void run(){

super.run();

syso("mythread---");

main(){

Mythread s = new Mythread();

s.start(); //啟動執行緒

syso("-----main完")

}

2.實現Runnable介面

其實Thread也繼承了Runnable介面,他們之間有多型的關係

public class Myrun implements Runnable{

@override

public void run(){

syso("Myrun ---");

main(){

Myrun my = new Myrun ();

Thread s = new Thread(my);

s.start(); //啟動執行緒

syso("-----main完")

}

啟動執行緒:

呼叫start()方法,非同步執行,使用多執行緒,程式碼執行結果與程式碼呼叫順序是無關的

若是直接呼叫run()方法,就不是非同步執行,與呼叫普通方法無異。

例項變數與安全性

自定義執行緒類中的例項變數針對其他執行緒可以有共享與不共享之分,

1.不共享資料的情況

2.共享資料的情況

在某些jvm中,i-- 的操作要分成如下3步:

l )取得原有 i 值。

2 )計算 i-1。

3.對 i 賦值

在這3個步驟中, 如果有多個執行緒同時訪問, 那麼一定會出現非執行緒安全問題。

解決辦法就是 同步 synchronized 使多個執行緒在執行run方法時, 以排隊的方式進行處理。當一個執行緒呼叫run前, 先判斷m方法有沒有被上鎖, 如果上鎖, 說明有其他執行緒正在呼叫m方法, 必須等其他執行緒對run方法呼叫結束後才可以執行 run方法。這個執行緒就會不斷嘗試拿這把鎖, 直到能夠拿到為止, 而且是有多個執行緒 同時去爭搶這把鎖

synchronized可以在任意物件及方法上加鎖, 而加鎖的這段程式碼稱為 “互斥區” 或 “臨界區”。

非執行緒安全主要是指多個執行緒對同一個物件中的同一個例項變數進行操作時會出現值被更改、 值不同步的情況, 進而影響程式的執行流程

println()方法在內部是同步的, i-- 的操作卻是在進入print In()之前發生的,應該加同步

1.3一些方法

currentThread()方法

可返回程式碼段正在被哪個錢程呼叫的資訊

isAlive()方法

判斷當前的執行緒是否處於活動狀態。活動狀態就是執行緒已經啟動且尚未終止。但此值是不準確的。

sleep()方法

作用是在指定的毫秒數內讓當前“正在執行的執行緒”休眠(暫停執行)。“正在執行的執行緒”是指this.currentThread()返回的執行緒。

getld()方法

取得執行緒的唯一標識。

1.4停止執行緒

停止一個執行緒可以使用Thread.stop()方法,但最好不用它。雖然它確實可以停止一個正在執行的執行緒,但是這個方法是不安全的(unsafe),而且是已被棄用作廢的(deprecated),在將來的Java版本中,這個方法將不可用或不被支援。

大多數停止一個執行緒的操作使用Thread.interrupt()方法,儘管方法的名稱是“停止,中的意思,但這個方法不會終止一個正在執行的執行緒,還需要加入一個判斷才-可以完成線

在Java中有以下3種方法可以終止正在執行的執行緒:

l )使用退出標誌,使執行緒正常退出,也就是當run方法完成後執行緒終止。

2)使用stop方法強行終止執行緒,但是不推薦使用這個方法,因為stop和suspend及resume一樣,都是作廢過期的方法,使用它們可能產生不可預料的結果

3)使用interrupt方法中斷執行緒。

stop()會強制停止,使一些清理性工作無法完成。會釋放鎖,可能造成資料不同步。呼叫stop會丟擲java.lang.ThreadDeath異常,通常不捕獲。

intenrupt()方法僅僅是在當前執行緒中打了一個停 止的標記,並不是真的停止執行緒。

判斷執行緒是否是停止狀態

l ) this.interrupted(): 測試當前執行緒是否已經中斷。具有清理狀態的功能,連續2次呼叫

2 ) this. islnterrupted():該方法不是static的 測試執行緒是否已經中斷,不清除狀態標誌

1.拋異常方式停止

先判斷執行緒是否停止,根據判斷條件來停止執行緒,,,出現異常時,鎖會自動釋放。

2.使用return停止執行緒

停止睡眠的執行緒

try catch 中,如果在sleep狀態下停止某一執行緒,會進入catch語句,並且清除 停止狀態值.使之變成false。java. lang. IncerrupdExcepcion:’sleep interrnpced

暫停 / 恢復執行緒

suspend()方法暫 停執行緒,過期方法

resume()方法恢復執行緒的執行。過期方法

缺陷:獨佔,不同步

1.如果使用不當,極易造成公共的同步物件的獨佔,使其他執行緒無法訪問公共同步物件。如:當程式執行到println()方法內部停止時,同步鎖未被釋放,這導致當前PrintStrearn物件的println()方法一直呈“暫停”狀態,並且“鎖未釋放

2.

yield方法

放棄當前的 CPU 資源. 將它讓給其他的任務去佔用 CPU 執行時間。 但放棄的時間不確定, 有可能剛剛放棄, 馬上又獲得 CPU 時間片。

1.5執行緒優先順序

執行緒可以劃分優先順序,優先順序較高的執行緒得到的CPU資源較多,也就是CPU優先執行優先順序較高的執行緒物件中的任務。設定執行緒優先順序有助於幫“執行緒規劃器”確定在下一次選擇哪一個執行緒來優先執行。

設定執行緒的優先順序

setPriority()方法,此方法在JDK的原始碼如下:

優先順序的繼承特性

執行緒的優先順序具有繼承性,比如A執行緒啟動B執行緒則B執行緒的優先順序與A是一樣的。

優先順序具有規則性

高優先順序的執行緒總是大部分先執行完, 但不代表高優先順序的執行緒全部先執行完,誰先執行完和程式碼的呼叫順序無關

優先順序具有隨機性

優先順序較高的執行緒不一定每一次都先執行完。

1.6守護執行緒

Java執行緒中有兩種執行緒,一種是使用者執行緒,另一種是守護執行緒。

守護執行緒是一種特殊的執行緒,它的特性有“陪伴”的含義,當程序中不存在非守護執行緒了,則守護執行緒自動銷燬。

典型的守護執行緒就是垃圾回收執行緒,當程序中沒有非守護執行緒了,則垃圾回收執行緒也就沒有存在的必要了,自動銷燬。

第二章 物件及變數的併發訪問

“非執行緒安全” 其實會在多個執行緒對同一個物件中的例項變數進行併發訪問時發生, 產生的後果就是 “髒讀”,也就是取到的資料其實是被更改過的。

方法內的變數是執行緒安全的

非執行緒安全”問題存在於“例項變數”中,如果是方法內部的私有變數,則永遠是“執行緒安全”的。

1.synchronized

用執行緒訪問的物件中如果有多個例項變數, 則執行的結果有可能出現交叉的情況,方法前加同步即可。synchronized可以使多個執行緒訪問同一個資源具有同步性, 而且它還具有將執行緒工作記憶體中的私有變數與公共記憶體中的變數同步的功能,即具有volatile同步的功能。

它包含兩個特徵:互斥性和可見性

在兩個執行緒訪問同一個物件中的同步方法時一定是執行緒安全的

1.synchronized同步方法

1.多個物件多個鎖

兩個執行緒分別訪問同一個類兩個不同例項相同名稱的同步方法,效果是以非同步的方式執行的,

關鍵字synchronized取得的鎖都是物件鎖 ,因為new一個例項產生了一個鎖。鎖不統一

一些結論

1.執行緒A先持有了object物件的鎖,但執行緒B完全可以非同步呼叫非synchronized型別的方法。

2. A執行緒先持有o物件的Lock鎖,B執行緒如果在這時呼叫o物件中的synchronized 型別的方法則需等待,也就是同步。

髒讀

多個執行緒呼叫同一個方法時, 為了避免資料出現交叉的情況,使用synchronized關鍵字來進行同步。

問題:get 和set 其中一個加同步時,如在賦值時進行了同步, 但在取值時有可能出現一些意想不到的意外, 這種情況就是髒讀( dirty Read)。 發生髒讀的情況是在讀取例項變數時, 此值已經被其他執行緒更改過了。原因是get方法沒有同步

解決方法:set get都加同步

鎖重入

synchronized擁有鎖重人的功能,也就是在使用synchronized時,當一個執行緒 得到一個物件鎖後,再次請求此物件鎖時是可以再次得到該物件的鎖的。這也證明在一個 Synchronized方法/塊的內部呼叫本類的其他synchronized方法/塊時,是永遠可以得到鎖的。

“可重人鎖” 的概念是:自己可以再次獲取自己的內部鎖。如有l條執行緒獲得了某個物件的鎖, 此時這個物件鎖還沒有釋放, 當其再次想要獲取這個物件的鎖的時候還是可以獲取的, 如果不可鎖重人的話, 就會造成死鎖。

存在繼承時:當存在父子類繼承關係時,子類是完全可以通過可重人鎖呼叫父類的同步方法的。

同步不具有繼承性

同步不能繼承,父類方法加同步時,若呼叫子類方法還得在子類的方法中新增synchronized關鍵,才能確保安全。

2. synchronized同步語句塊

synchronized宣告方法在某些情況下是有弊端的,若執行時間過長,會造成長時間等待。可以使用 synchronized同步語句塊來解決。

synchronized(this)程式碼塊是鎖定當前物件

結論:

1.兩個併發執行緒訪問同一物件中的synchronized(this)同步程式碼塊時,一段時間內只能有一個執行緒被執行,另一執行緒必須等待當前執行緒執行完程式碼塊以後才能執行該程式碼塊。

2.當一個執行緒訪問object的一 個synchronized同步代 碼塊時, 另一執行緒仍可以訪問該object物件中的synchronized(this)同步程式碼塊。

3.當一個執行緒訪問object的一個 synchronjzed(this)同步程式碼塊時,其他執行緒對同一個object中所有其他synchronized(this)同 步程式碼塊的訪問將被阻塞,這說明synchronized使用的“物件監視器”是一個。

不在synchronized塊中就是非同步執行, 在synchronized塊中就是同步執行。

將任意物件作為物件監視器

多個執行緒呼叫同一個物件中的不同名稱的 synchronized同步方法或 synchronized(this)同 步程式碼塊時, 呼叫 的效果就是按順序執行, 也就是同步的, 阻塞的。

這說明 synchronized同步方法或 synchronized(th.is)同步程式碼塊分別有兩種作用。

( I ) synchronized同步方法

l )對其他 synchronized同步方法或 synchronized(this)同步程式碼塊呼叫呈阻塞狀態。

2 )同一時間只有一個線程可以執行 synchronized同步方法中的程式碼。

( 2 ) synchronized(由is)同步程式碼塊

I )對其他 synchronized同步方法或 synchronized(this)同步程式碼塊呼叫呈阻塞狀態。

2 )同一時間只有一個執行緒可以執行synchronized(this)同步程式碼塊中的程式碼。

使用 synchronized(th.is)格式來同步程式碼塊, 其實Java 還支援對 “任意物件” 作為 物件監視器 來實現同步的功能。 這個 任意物件” 大多數是例項變數及方法的引數, 使用格式為 synchronized(非 this物件)。

根據前面對 synchronized(this)同步程式碼塊的作用總結可知,

synchronized(非 this物件)格式的作用只有 l 種: synchronized(非 this物件)同步程式碼塊

非this物件鎖具有一定的優點:如果在一個類中有很多個synchronized方法,雖然能實現同步,但會受到阻塞,所以影響執行效率;

但如果使用同步程式碼塊鎖非this物件,則synchronized(非this)程式碼塊中的程式與同步方法是非同步的,不與其他鎖this同步方法爭搶this鎖,則可大大提高執行效率。

物件監視器不同,執行結果也是非同步的。

“synchronized(非this物件x)” 格式的寫法是將 x物件本身作為 “物件監視器” ,這樣就可以得出以下 3 個結論:

1 )當多個執行緒同時執行synchronized(x) {}同步程式碼塊時呈同步效果

2) 當其他執行緒執行x物件中synchronized同步方法時呈同步效果。

3) 當其他執行緒執行x物件方法裡面的synchronized(this)程式碼塊時也呈現同步效果。

但需要注意:如果其他執行緒呼叫不加synchronized關鍵字的方法時, 還是非同步呼叫

3.靜態同步synchronized方法與synchronized(xxx.class)程式碼塊

synchronized應用在static靜態方法上,這樣寫是對當前的*.java檔案對應的Class類進行持鎖,Class鎖可以對類的所有物件例項起作用。同步synchronized(xx.class)程式碼塊的作用其實和synchronized static方法的作用一樣。

synchronized關鍵字加到非static靜態方法上是給物件上鎖。

4.String型別資料的常量池特性

在 JVM 中具有 String 常量池快取的功能,將 synchronized(string)同步塊與 String 聯合使用時, 要注意常量池以帶來的一些例外。如取到的鎖相同造成一個執行緒不能執行。

大多數的情況下, 同步synchronized 程式碼塊都不使用 String 作為鎖物件, 而改用其他, 比如 new Object()例項化Object 物件, 但它並不放入快取中。

5.死迴圈

同步方法容易造成死迴圈

如下圖:執行緒B永遠得不到執行的機會,鎖死了。

解決方法: 可以使用同步塊來解決這樣的問題。。下下圖

6.死鎖

不同的執行緒都在等待根本不可能被釋放的鎖 從而導致所有的任務都無法繼續完成,造成執行緒的 “假死” 。在設計程式時就要避免雙方互相持有對方的鎖的情況。

最經典的巢狀造成死鎖

使用JDK自帶的工具來監測是否有死鎖的現象。 首先進入CMD工具, 再進入JDK的安裝資料夾中的bin目錄,執行jps命令,得到執行的執行緒Run的id值是3244。 再執行jstack命令, 檢視結果。

7.內建類與靜態內建類

1.在內建類中有兩個同步方法,但使用的卻是不同的鎖,列印的結果 也是非同步的。

2.同步程式碼塊synchronized(class2)對class2上鎖後,其他執行緒只能以同步的方式呼叫class2中的靜態同步方法

8.鎖物件的改變

只要物件不變,既使物件的屬性被改變,執行的結果還是同步

如 果同時持有相同的鎖物件, 則這些執行緒之間就是同步的;如果分別獲得鎖物件, 這些執行緒之 間就是非同步的。

2.volatile關鍵字

volatile的主要作用是使變數在多個執行緒間可見。強制的從公共記憶體中讀取變數 的值

2.1關鍵字volatile與死迴圈

停不下來的原因主要就是main執行緒 一直在處理while()迴圈,條件一直成立.導致程式不能繼續執行後面的程式碼。解決的辦法當然是用多執行緒技術。(讓另一個執行緒改變條件)

是什麼原因造成將jvm設定為-server時就出現死迴圈呢?

在啟動RunThread.java執行緒時, 變數private boolean isRunning = true;存在於公共堆疊及執行緒 的私有堆疊中。 在jvm被設定為-server模式時,為了執行緒執行的效率, 執行緒一直在私有堆疊 中取得isRunning的值是 true。 而程式碼 thread.setRunning( false);,雖然被執行, 更新的卻是公共堆疊中的isRunning變數值false, 所以一 直就是死迴圈的狀態。

問題其實就是私有堆戰中的值和公共堆桔中的值不同步造成的,解決這樣的問題就要使用volatile關鍵字了,它主要的作用就是當執行緒訪問isRunning這個變數時,強制性從公 共堆戰中進行取值。

2.2 synchronized與volatile進行一下比較:

volatile關鍵字特點 增加了例項變數在多個執行緒之間的可見性。 但最致命 的缺點是不支援原子性即不具備同步性,

l )關鍵字volatile是執行緒同步的輕量級實現, 所以volatile效能肯定比synchronized要好。並且volatile只能修飾於變數,而synchronized可以修飾方法, 以及程式碼塊。 隨著JDK新版本的釋出,synchronized關鍵字在執行效率上得到很大提升, 在開發中使用synchronized 關鍵字的比率還是比較大的。

2) 多錢程訪問volatile不會發生阻塞,而synchronized會出現阻塞。

3) volatile能保證資料的可見性, 但不能保證原子性;

synchronized可以保證原子性,也可以間接保證可見性, 因為它會將私有記憶體和公共記憶體中的資料做同步。使用synchronized同步關鍵字也就沒有必要再使用volatile關鍵字來宣告變量了。

4.關鍵字volatile解決的是變數在多個執行緒之間的可見性;而synchronized關鍵字解決的是多個執行緒之間訪問資源的同步性

但在這裡需要注意 的是: 如果修改例項變數中的資料, 比如i++, 也就是 i=i十l, 則這樣的操作其實’並不是原子操作, 也就是非執行緒安全的。 表示式i++的操作步驟分解如下:

l )從記憶體中取出 i 的值

2)計算 i的值;

3) 將 i 的值寫到記憶體中

假如在第2步計算值的時候, 另外一個執行緒也修改 i的值, 那麼這個時候就會出現髒資料。解決的辦法其實就是使用 synchronized關鍵字

變數在記憶體中的工作過程

在多執行緒環境中,use和assign是多次出現的,但這一操作並不是原子性,也就是在 read和load之後,如果主記憶體count變數發生修改之後,執行緒工作記憶體中的值由於已經載入,不會產生對應的變化,也就是私有記憶體和公共記憶體中的變數不同步,也就出現了非執行緒安全問題。

2.3 使用原子類進行i++操作

除了在i++操作時使用synchronized關鍵字實現同步外,還可以使用Atomiclnteger原子

進行實現。原子操作是不能分割的整體,沒有其他執行緒能夠中斷或檢查正在原子操作中的變數。一個原子(atomic)型別就是一個原子操作可用的型別,它可以在沒有鎖的情況下做到執行緒安全。

但也不完全安全。原子類在具有有邏輯性的情況下輸出結果也具有隨機性。

addAndGet()方法是原子的,但方法和方法之間的呼叫卻不是原子的。解決這樣的問題必須要用同步。

第三章 執行緒間通訊-----------------

待更..........