一. Java多線程: Java給多線程編程提供了內置的支持。一條線程指的是進程中一個單一順序的控制流,一個進程中可以併發多個線程,每條線程並行執行不同的任務。 多線程是多任務的一種特別的形式,但多線程使用了更小的資源開銷。 這裡定義和線程相關的另一個術語--進程:一個進程包括由操作系統分配的記憶體空 ...
一. Java多線程:
Java給多線程編程提供了內置的支持。一條線程指的是進程中一個單一順序的控制流,一個進程中可以併發多個線程,每條線程並行執行不同的任務。
多線程是多任務的一種特別的形式,但多線程使用了更小的資源開銷。
這裡定義和線程相關的另一個術語--進程:一個進程包括由操作系統分配的記憶體空間,包含一個或多個線程。一個線程不能獨立的存在,它必須是進程的一部分。一個進程一直運行,直到所有的非守護線程都結束運行後才能結束。
多線程能滿足程式員編寫高效率的程式來達到充分利用CPU的目的。
二. 線程的生命周期:
線程是一個動態的執行過程,它也有從產生到死亡的過程
a) 新建狀態:
- 使用new關鍵字和Thread類或其子類建立一個線程對象後,該線程對象就處於新建狀態。它保持這個狀態直到程式start()這個線程。
b) 就緒狀態:
- 當線程對象調用了start()方法之後,該線程就進入就緒狀態。就緒狀態的線程處於就緒隊列中,要等待JVM里線程調度器的調度。
c) 運行狀態:
- 如果就緒狀態的線程獲取 CPU 資源,就可以執行 run(),此時線程便處於運行狀態。處於運行狀態的線程最為複雜,它可以變為阻塞狀態、就緒狀態和死亡狀態。
d) 阻塞狀態:
- 如果一個線程執行了sleep(睡眠)、suspend(掛起)等方法,失去所占用資源之後,該線程就從運行狀態進入阻塞狀態。在睡眠時間已到或獲得設備資源後可以重新進入就緒狀態。可以分為三種:
- 等待阻塞:運行狀態中的線程執行 wait() 方法,使線程進入到等待阻塞狀態。
- 同步阻塞:線程在獲取 synchronized 同步鎖失敗(因為同步鎖被其他線程占用)。
- 其他阻塞:通過調用線程的 sleep() 或 join() 發出了 I/O 請求時,線程就會進入到阻塞狀態。當sleep() 狀態超時,join() 等待線程終止或超時,或者 I/O 處理完畢,線程重新轉入就緒狀態。
e) 死亡狀態:
- 一個運行狀態的線程完成任務或者其他終止條件發生時,該線程就切換到終止狀態。
三. 線程的優先順序
每一個 Java 線程都有一個優先順序,這樣有助於操作系統確定線程的調度順序。Java 線程的優先順序是一個整數,其取值範圍是 1 (Thread.MIN_PRIORITY ) - 10 (Thread.MAX_PRIORITY )。
預設情況下,每一個線程都會分配一個優先順序 NORM_PRIORITY(5)。
具有較高優先順序的線程對程式更重要,並且應該在低優先順序的線程之前分配處理器資源。但是,線程優先順序不能保證線程執行的順序,而且非常依賴於平臺。
四. 創建一個線程
a) 通過繼承Thread類
b) 通過實現Runnable介面
c) 通過Callable和Future創建線程
- 創建Callable介面的實現類,並實現call()方法,該call()方法將作為線程執行體,並且有返回值。
- 創建Callable實現類的實例,使用FutureTask類來包裝Callable對象,該FutureTask對象封裝了該Callable對象的call()方法的返回值。
- 使用FutureTask對象作為Thread對象的target創建並啟動新線程。
- 調用FutureTask對象的get()方法來獲得子線程執行結束後的返回值。
五. 線程的常見方法
序號 |
方法描述 |
1 |
public void start() |
2 |
public void run() |
3 |
public final void setName(String name) |
4 |
public final void setPriority(int priority) |
5 |
public final void setDaemon(boolean on) |
6 |
public final void join(long millisec) |
7 |
public void interrupt() |
8 |
public final boolean isAlive() |
序號 |
方法描述 |
1 |
public static void yield() |
2 |
public static void sleep(long millisec) |
3 |
public static boolean holdsLock(Object x) |
4 |
public static Thread currentThread() |
六. 線程的幾個主要概念
a) 線程同步
b) 線程安全:如果你的代碼在多線程下執行和在單線程下執行永遠都能獲得一樣的結果,那麼你的代碼就是線程安全的。
- 不可變:像String、Integer、Long這些,都是final類型的類,任何一個線程都改變不了它們的值,要改變除非新創建一個,因此這些不可變對象不需要任何同步手段就可以直接在多線程環境下使用
- 絕對線程安全:不管運行時環境如何,調用者都不需要額外的同步措施。要做到這一點通常需要付出許多額外的代價,Java中標註自己是線程安全的類,實際上絕大多數都不是線程安全的,不過絕對線程安全的類,Java中也有,比方說CopyOnWriteArrayList、CopyOnWriteArraySet
- 相對線程安全:相對線程安全也就是我們通常意義上所說的線程安全,像Vector這種,add、remove方法都是原子操作,不會被打斷,但也僅限於此,如果有個線程在遍歷某個Vector、有個線程同時在add這個Vector,99%的情況下都會出現ConcurrentModificationException,也就是fail-fast機制。
- 線程非安全:這個就沒什麼好說的了,ArrayList、LinkedList、HashMap等都是線程非安全的類
a) 線程間通信
b) 線程死鎖
c) 線程式控制制:掛起、停止和恢復
七. 多線程中的常見問題
a) Run()和start()之間的區別?
只有調用了start()方法,才會表現出多線程的特性,不同線程的run()方法裡面的代碼交替執行。如果只是調用run()方法,那麼代碼還是同步執行的,必須等待一個線程的run()方法裡面的代碼全部執行完畢之後,另外一個線程才可以執行其run()方法裡面的代碼。
b) Runnable介面和Callable介面的區別?
Runnable介面中的run()方法的返回值是void,它做的事情只是純粹地去執行run()方法中的代碼而已;Callable介面中的call()方法是有返回值的,是一個泛型,和Future、FutureTask配合可以用來獲取非同步執行的結果。
這其實是很有用的一個特性,因為多線程相比單線程更難、更複雜的一個重要原因就是因為多線程充滿著未知性,某條線程是否執行了?某條線程執行了多久?某條線程執行的時候我們期望的數據是否已經賦值完畢?無法得知,我們能做的只是等待這條多線程的任務執行完畢而已。而Callable+Future/FutureTask卻可以獲取多線程運行的結果,可以在等待時間太長沒獲取到需要的數據的情況下取消該線程的任務,真的是非常有用。
c) Sleep()方法和wait()方法的區別?
sleep方法和wait方法都可以用來放棄CPU一定的時間,不同點在於如果線程持有某個對象的監視器(監視對象同步),sleep方法不會放棄這個對象的監視器,wait方法會放棄這個對象的監視器,並且wait()只能在同步中使用