Java併發基礎知識(一)

()一、Java裏的程序天生就是多線程的,那麼有幾種新啓線程的方式?

正確的答案應該是2種:1、類Thread;2、接口Runnable,有些人覺得應該是3個,還有一個是Callable接口,其實這個和Runnable是重複的,我們看源碼分析

首先源碼中明確說明創建Thread只有2種方式;

其次,Thread的構造方法中不接受任何的Callable參數

我們繼續看Callable,再使用的過程中,把它包裝成FutureTask,而FutureTask是實現了RunnableFuture接口,RunnableFuture繼承了Runnable,現在我們明白了,Callable交給線程執行的時候,是把它包裝成了一個Runnable交給線程執行,所以本質上還是實現了Runable接口,是不是瞬間明白!

二、線程的狀態

Java中線程的狀態分爲6種:

1. 初始(NEW):新創建了一個線程對象,但還沒有調用start()方法。

2. 運行(RUNNABLE):Java線程中將就緒(ready)和運行中(running)兩種狀態籠統的稱爲“運行”。

線程對象創建後,其他線程(比如main線程)調用了該對象的start()方法。該狀態的線程位於可運行線程池中,等待被線程調度選中,獲取CPU的使用權,此時處於就緒狀態(ready)。就緒狀態的線程在獲得CPU時間片後變爲運行中狀態(running)。

3. 阻塞(BLOCKED):表示線程阻塞於鎖。

4. 等待(WAITING):進入該狀態的線程需要等待其他線程做出一些特定動作(通知或中斷)。1

5. 超時等待(TIMED_WAITING):該狀態不同於WAITING,它可以在指定的時間後自行返回。

6. 終止(TERMINATED):表示該線程已經執行完畢。

狀態之間的變遷如下圖所示

三、死鎖

定義:是指兩個或兩個以上的進程在執行過程中,由於競爭資源或者由於彼此通信而造成的一種阻塞的現象,若無外力作用,它們都將無法推進下去。此時稱系統處於死鎖狀態或系統產生了死鎖。

舉個例子:A和B去按摩洗腳,都想在洗腳的時候,同時順便做個頭部按摩,13技師擅長足底按摩,14擅長頭部按摩。

這個時候A先搶到14,B先搶到13,兩個人都想同時洗腳和頭部按摩,於是就互不相讓,揚言我死也不讓你,這樣的話,A搶到14,想要13,B搶到13,想要14,在這個想同時洗腳和頭部按摩的事情上A和B就產生了死鎖。怎麼解決這個問題呢?

第一種,假如這個時候,來了個15,剛好也是擅長頭部按摩的,A又沒有兩個腦袋,自然就歸了B,於是B就美滋滋的洗腳和做頭部按摩,剩下A在旁邊氣鼓鼓的,這個時候死鎖這種情況就被打破了,不存在了。

第二種,C出場了,用武力強迫A和B,必須先做洗腳,再頭部按摩,這種情況下,A和B誰先搶到13,誰就可以進行下去,另外一個沒搶到的,就等着,這種情況下,也不會產生死鎖。

所以總結一下:

死鎖是必然發生在多操作者(M>=2個)情況下,爭奪多個資源(N>=2個,且N<=M)纔會發生這種情況。很明顯,單線程自然不會有死鎖,只有B一個去,不要2個,打十個都沒問題;單資源呢?只有13,A和B也只會產生激烈競爭,打得不可開交,誰搶到就是誰的,但不會產生死鎖。同時,死鎖還有幾個要求:

1、爭奪資源的順序不對,如果爭奪資源的順序是一樣的,也不會產生死鎖;

2、爭奪者拿到資源不放手。

學術化的定義

死鎖的發生必須具備以下四個必要條件。

1)互斥條件:指進程對所分配到的資源進行排它性使用,即在一段時間內某資源只由一個進程佔用。如果此時還有其它進程請求資源,則請求者只能等待,直至佔有資源的進程用畢釋放。

2)請求和保持條件:指進程已經保持至少一個資源,但又提出了新的資源請求,而該資源已被其它進程佔有,此時請求進程阻塞,但又對自己已獲得的其它資源保持不放。

3)不剝奪條件:指進程已獲得的資源,在未使用完之前,不能被剝奪,只能在使用完時由自己釋放。

4)環路等待條件:指在發生死鎖時,必然存在一個進程——資源的環形鏈,即進程集合{P0,P1,P2,···,Pn}中的P0正在等待一個P1佔用的資源;P1正在等待P2佔用的資源,……,Pn正在等待已被P0佔用的資源。

理解了死鎖的原因,尤其是產生死鎖的四個必要條件,就可以最大可能地避免、預防和解除死鎖。

只要打破四個必要條件之一就能有效預防死鎖的發生。

打破互斥條件:改造獨佔性資源爲虛擬資源,大部分資源已無法改造。

打破不可搶佔條件:當一進程佔有一獨佔性資源後又申請一獨佔性資源而無法滿足,則退出原佔有的資源。

打破佔有且申請條件:採用資源預先分配策略,即進程運行前申請全部資源,滿足則運行,不然就等待,這樣就不會佔有且申請。

打破循環等待條件:實現資源有序分配策略,對所有設備實現分類編號,所有進程只能採用按序號遞增的形式申請資源。

避免死鎖常見的算法有有序資源分配法、銀行家算法。

危害

1、線程不工作了,但是整個程序還是活着的2、沒有任何的異常信息可以供我們檢查。3、一旦程序發生了死鎖,是沒有任何的辦法恢復的,只能重啓程序,對正式已發佈程序來說,這是個很嚴重的問題。

解決

關鍵是保證拿鎖的順序一致

兩種解決方式

1、內部通過順序比較,確定拿鎖的順序;

2、採用嘗試拿鎖的機制。

其他線程安全問題

活鎖

兩個線程在嘗試拿鎖的機制中,發生多個線程之間互相謙讓,不斷髮生同一個線程總是拿到同一把鎖,在嘗試拿另一把鎖時因爲拿不到,而將本來已經持有的鎖釋放的過程。

解決辦法:每個線程休眠隨機數,錯開拿鎖的時間。

線程飢餓

低優先級的線程,總是拿不到執行時間

下一篇繼續講併發基礎知識ThreadLocal介紹!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章