1、并行和并發的差別
2、多線程(高并發程式設計)的優點
3、多線程程式需要注意事項
4、線程的啟動與安全中止
5、怎麼安全中止線程(interrupt())
并行和并發的差別:
一個是同時執行,一個是交替執行(線程切換)。例:把線程比做高速公路,如果有條高速公路并排有8條車道,那麼最大的并行車輛就是8輛,隻要車輛<=8,車輛就可以并行運作。機關時間内通過的車輛,就是并發。
多線程(高并發程式設計)的優點:
(1)充分利用CPU的資源。
(2)加快響應使用者的時間。
(3)可以使你的代碼子產品化,異步化,簡單化。
例如我們在做 Android程式開發的時候,主線程的UI展示部分是一塊主代碼程式部分,但是UI上的按鈕用相應事件的處理程式就可以做個單獨的子產品程式拿出來。這樣既增加了異步的操做,又使程式子產品化,清晰化和簡單化。
多線程程式需要注意事項:
(1)線程之間的安全性
在同一個程序裡面的多線程是資源共享的,也就是都可以通路同一個記憶體位址當中的一個變量。例如:若每個線程中對全局變量、靜态變量隻有讀操作,而無寫操作,一般來說,這個全局變量是線程安全的:若有多個線程同時執行寫操作,一般都需要考慮線程同步,否則就可能影響線程安全。
(2)線程之間的死循環過程
為了解決線程之間的安全性引入了Java的鎖機制,而一不小心就會産生Java線程死鎖的多線程問題,因為不同的線程都在等待那些根本不可能被釋放的鎖,進而導緻所有的工作都無法完成。假設有兩個線程,分别代表兩個饑餓的人,他們必須共享刀叉并輪流吃飯。他們都需要獲得兩個鎖:共享刀和共享叉的鎖。假如線程A獲得了刀,而線程B獲得了叉。線程A就會進入阻塞狀态來等待獲得叉,而線程B則阻塞來等待線程A所擁有的刀。這隻是人為設計的例子,但盡管在運作時很難探測到,這類情況卻時常發生。
(3)線程太多了會将伺服器資源耗盡形成當機當機
線程數太多有可能造成系統建立大量線程而導緻消耗完系統記憶體以及CPU的“過渡切換”,造成系統的當機,那麼我們該如何解決這類問題呢?
某些系統資源是有限的,如檔案描述符。多線程程式可能耗盡資源,因為每個線程都可能希望有一個這樣的資源。如果線程數相當大,或者某個資源的侯選線程數遠遠超過了可用的資源數則最好使用資源池。一個最好的示例是資料庫連接配接池。隻要線程需要使用一個資料庫連接配接,它就從池中取出一個,使用以後再将它傳回池中。資源池也稱為資源庫。
Java天生就是多線程的:
一個Java程式從main()方法開始執行,然後按照既定的代碼邏輯執行,看似沒有其他線程參與,但實際上Java程式天生就是多線程程式,因為執行main()方法的是一個名稱為main的線程。
[1] main //main線程,使用者程式入口
[2] Reference Handler//清除Reference的線程
[3] Finalizer // 調用對象finalize方法的線程
[4] Signal Dispatcher // 分發處理發送給JVM信号的線程
[5] Attach Listener //記憶體dump,線程dump,類資訊統計,擷取系統屬性等
[6] Monitor Ctrl-Break //監控Ctrl-Break中斷信号的
線程的啟動與中止:
啟動線程的方式有:
1、X extends Thread;,然後X.run
2、X implements Runnable;然後交給Thread運作
3、X implements Callable;然後交給Thread運作
第1、2方式都有一個缺陷就是:在執行完任務之後無法擷取執行結果。從Java 1.5開始,就提供了Callable和Future,通過它們可以在任務執行完畢之後得到任務執行結果。
Callable、Future和FutureTask
Runnable是一個接口,在它裡面隻聲明了一個run()方法,由于run()方法傳回值為void類型,是以在執行完任務之後無法傳回任何結果。
Callable位于java.util.concurrent包下,它也是一個接口,在它裡面也隻聲明了一個方法,隻不過這個方法叫做call(),這是一個泛型接口,call()函數傳回的類型就是傳遞進來的V類型。
Future就是對于具體的Runnable或者Callable任務的執行結果進行取消、查詢是否完成、擷取結果。必要時可以通過get方法擷取執行結果,該方法會阻塞直到任務傳回結果。

因為Future隻是一個接口,是以是無法直接用來建立對象使用的,是以就有了下面的FutureTask。
FutureTask類實作了RunnableFuture接口,RunnableFuture繼承了Runnable接口和Future接口。是以它既可以作為Runnable被線程執行,又可以作Future得到Callable的傳回值。
事實上,FutureTask是Future接口的一個唯一實作類。
要new一個FutureTask的執行個體,有兩種方法
中止線程的方式:
線程自然終止:要麼是run執行完成了,要麼是抛出了一個未處理的異常導緻線程提前結束。
手動中止:暫停、恢複和停止操作對應線上程Thread的API就是suspend()、resume()和stop()。但是這些API是過期的,也就是不建議使用的。不建議使用的原因主要有:以suspend()方法為例,在調用後,線程不會釋放已經占有的資源(比如鎖),而是占有着資源進入睡眠狀态,這樣容易引發死鎖問題。同樣,stop()方法在終結一個線程時不會保證線程的資源正常釋放,通常是沒有給予線程完成資源釋放工作的機會,是以會導緻程式可能工作在不确定狀态下。正因為suspend()、resume()和stop()方法帶來的副作用,這些方法才被标注為不建議使用的過期方法。
安全的中止:則是其他線程通過調用某個線程A的interrupt()方法對其進行中斷操作, 中斷好比其他線程對該線程打了個招呼,“A,你要中斷了”,不代表線程A會立即停止自己的工作,同樣的A線程完全可以不理會這種中斷請求。因為java裡的線程是協作式的,不是搶占式的。線程通過檢查自身的中斷标志位是否被置為true來進行響應,線程通過方法isInterrupted()來進行判斷是否被中斷,也可以調用靜态方法Thread.interrupted()來進行判斷目前線程是否被中斷,不過Thread.interrupted()會同時将中斷辨別位改寫為false。
如果一個線程處于了阻塞狀态(如線程調用了thread.sleep、thread.join、thread.wait、),則線上程在檢查中斷标示時如果發現中斷标示為true,則會在這些阻塞方法調用處抛出InterruptedException異常,并且在抛出異常後會立即将線程的中斷标示位清除,即重新設定為false。
不建議自定義一個取消标志位來中止線程的運作。因為run方法裡有阻塞調用時會無法很快檢測到取消标志,線程必須從阻塞調用傳回後,才會檢查這個取消标志。這種情況下,使用中斷會更好,因為,一、一般的阻塞方法,如sleep等本身就支援中斷的檢查,二、檢查中斷位的狀态和檢查取消标志位沒什麼差別,用中斷位的狀态還可以避免聲明取消标志位,減少資源的消耗。
注意:處于死鎖狀态的線程無法被中斷
深入了解run()和start()
Thread類是Java裡對線程概念的抽象,可以這樣了解:我們通過new Thread()其實隻是new出一個Thread的執行個體,還沒有作業系統中真正的線程挂起鈎來。隻有執行了start()方法後,才實作了真正意義上的啟動線程。
start()方法讓一個線程進入就緒隊列等待配置設定cpu,分到cpu後才調用實作的run()方法,start()方法不能重複調用。
而run方法是業務邏輯實作的地方,本質上和任意一個類的任意一個成員方法并沒有任何差別,可以重複執行,可以被單獨調用。
其他的線程方法
yield()方法:使目前線程讓出CPU占有權,但讓出的時間是不可設定的。也不會釋放鎖資源,所有執行yield()的線程有可能在進入到可執行狀态後馬上又被執行。
join方法:把指定的線程加入到目前線程,可以将兩個交替執行的線程合并為順序執行的線程。比如線上程B中調用了線程A的Join()方法,直到線程A執行完畢後,才會繼續執行線程B。