多線程
- 多線程
- 創建線程
- 方式一:繼承Thread類
- 方式二:實現Runable接口
- 方式三:實現Callbale接口
- Thread的常用方法
- 線程安全
- 線程同步
- 方式一:同步代碼塊
- 同步方法
- 方式三:Lock鎖
- 線性池
- 創建線程池
- 處理Runnable任務
- 處理Callable任務
- 通過Executors創建線程池
- 并發/并行
- 并發
- 并行
多線程
- 線程(Thread)是一個程序內部的一條執行流程。
- 程序中如果只有一條執行流程,那這個程序就是單線程的程序。
- 多線程是指從軟硬件上實現的多條執行流程的技術(多條線程由CPU負責調度執行)。
創建線程
- 啟動線程必須是調用start方法,不是調用run方法。
- 直接調用run方法會當成普通方法執行,此時相當于還是單線程執行
- 只有調用start方法才是啟動一個新的線程執行。
- 不要把主線程任務放在啟動子線程之前。
- 這樣主線程一直是先跑完的,相當于是一個單線程的效果了。
方式一:繼承Thread類
- 定義一個子類MyThread繼承線程類java.lang.Thread,重寫run()方法
- 創建MyThread類的對象
- 調用線程對象的start()方法啟動線程(啟動后還是執行run方法的)
- 優缺點
- 優點:編碼簡單
- 缺點:線程類已經繼承Thread,無法繼承其他類,不利于功能的擴展。
方式二:實現Runable接口
- 定義一個線程任務類MyRunnable實現Runnable接口,重寫run()方法
- 創建MyRunnable任務對象
- 把MyRunnable任務對象交給Thread處理。
- 調用線程對象的start()方法啟動線程
- 優缺點
- 優點:任務類只是實現接口,可以繼續繼承其他類、實現其他接口,擴展性強。
- 缺點:需要多一個Runnable對象。
Thread****類提供的構造器 | 說明 |
---|---|
public Thread(Runnable target) | 封裝Runnable對象成為線程對象 |
- 匿名內部類寫法
- 可以創建Runnable的匿名內部類對象。
- 再交給Thread線程對象。
- 再調用線程對象的start()啟動線程。
方式三:實現Callbale接口
- 創建任務對象
- 定義一個類實現Callable接口,重寫call方法,封裝要做的事情,和要返回的數據。
- 把Callable類型的對象封裝成FutureTask(線程任務對象)。
- 把線程任務對象交給Thread對象。
- 調用Thread對象的start方法啟動線程。
- 線程執行完畢后、通過FutureTask對象的的get方法去獲取線程任務執行的結果。
- 優缺點
- 優點:線程任務類只是實現接口,可以繼續繼承類和實現接口,擴展性強;可以在線程執行完畢后去獲取線程執行的結果。
- 缺點:編碼復雜一點。
- FutureTask的API
FutureTask提供的構造器 | 說明 |
---|---|
public FutureTask<>(Callable call) | 把Callable對象封裝成FutureTask對象。 |
FutureTask提供的方法 | 說明 |
---|---|
public V get() throws Exception | 獲取線程執行call方法返回的結果。 |
Thread的常用方法
Thread提供的常用方法 | 說明 |
---|---|
public void run() | 線程的任務方法 |
public void start() | 啟動線程 |
public String getName() | 獲取當前線程的名稱,線程名稱默認是Thread-索引 |
public void setName (String name) | 為線程設置名稱 |
public static Thread currentThread () | 獲取當前執行的線程對象 |
public static void sleep(long time) | 讓當前執行的線程休眠多少毫秒后,再繼續執行 |
public final void join()… | 讓調用當前這個方法的線程先執行完! |
Thread 提供的常見構造器 | 說明 |
---|---|
public Thread(String name) | 可以為當前線程指定名稱 |
public Thread(Runnable target) | 封裝Runnable對象成為線程對象 |
public Thread(Runnable target, String name) | 封裝Runnable對象成為線程對象,并指定線程名稱 |
線程安全
- 多個線程,同時操作同一個共享資源的時候,可能會出現業務安全問題。
- 存在多個線程在同時執行
- 同時訪問一個共享資源
- 存在修改該共享資源
線程同步
- 線程同步是線程安全問題的解決方案。
- 線程同步的核心思想
- 讓多個線程先后依次訪問共享資源,這樣就可以避免出現線程安全問題。
- 線程同步的常見方案
- 加鎖:每次只允許一個線程加鎖,加鎖后才能進入訪問,訪問完畢后自動解鎖,然后其他線程才能再加鎖進來。
方式一:同步代碼塊
- 作用:把訪問共享資源的核心代碼給上鎖,以此保證線程安全。
- 對出現問題的核心代碼使用synchronized進行加鎖
- 每次只能一個線程占鎖進入訪問
- 原理:每次只允許一個線程加鎖后進入,執行完畢后自動解鎖,其他線程才可以進來執行。
- 對于當前同時執行的線程來說,同步鎖必須是同一把(同一個對象),否則會出bug。
- 鎖對象的使用規范
- 建議使用共享資源作為鎖對象,對于實例方法建議使用this作為鎖對象。
- 對于靜態方法建議使用字節碼(類名.class)對象作為鎖對象。
synchronized(同步鎖) {訪問共享資源的核心代碼
}
同步方法
-
作用:把訪問共享資源的核心方法給上鎖,以此保證線程安全。
-
原理:每次只能一個線程進入,執行完畢以后自動解鎖,其他線程才可以進來執行。
-
同步方法其實底層也是有隱式鎖對象的,只是鎖的范圍是整個方法代碼。
-
如果方法是實例方法:同步方法默認用this作為的鎖對象。
-
如果方法是靜態方法:同步方法默認用類名.class作為的鎖對象。
修飾符 synchronized 返回值類型 方法名稱(形參列表) {操作共享資源的代碼
}
方式三:Lock鎖
- Lock鎖是JDK5開始提供的一個新的鎖定操作,通過它可以創建出鎖對象進行加鎖和解鎖,更靈活、更方便、更強大。
- Lock是接口,不能直接實例化,可以采用它的實現類ReentrantLock來構建Lock鎖對象。
- 鎖對象建議使用final修飾,防止被別人篡改
- 釋放鎖建議將釋放鎖的操作放到finally代碼塊中,確保鎖用完了一定會被釋放
線性池
- 線程池就是一個可以復用線程的技術。
- 不使用線性池的問題
- 用戶每發起一個請求,后臺就需要創建一個新線程來處理,下次新任務來了肯定又要創建新線程處理的, 創建新線程的開銷是很大的,并且請求過多時,肯定會產生大量的線程出來,這樣會嚴重影響系統的性能。
- 線程池的工作原理
線程池的基本概念是,在應用程序啟動時創建一定數量的線程,并將它們保存在線程池中。當需要執行任務時,從線程池中獲取一個空閑的線程,將任務分配給該線程執行。當任務執行完畢后,線程將返回到線程池,可以被其他任務復用。
- 線程池:{有限的任務隊列(WorkQueue)}
- 工作線程WorkThread進行選擇任務隊列的對象。
- 任務接口
- Runable
- Callable
創建線程池
- JDK 5.0起提供了代表線程池的接口:ExecutorService。
- 方式一:使用ExecutorService的實現類ThreadPoolExecutor自創建一個線程池對象。
ExecutorService ——> ThreadPoolExecutor
- 通過ThreadPoolExecutor創建線程池。
- 參數一:corePoolSize : 指定線程池的核心線程的數量。
- 參數二:maximumPoolSize:指定線程池的最大線程數量。
- 參數三:keepAliveTime :指定臨時線程的存活時間。
- 參數四:unit:指定臨時線程存活的時間單位(秒、分、時、天)
- 參數五:workQueue:指定線程池的任務隊列。
- 參數六:threadFactory:指定線程池的線程工廠。
- 參數七:handler:指定線程池的任務拒絕策略(線程都在忙,任務隊列也滿了的時候,新任務來了該怎么處理)
- 通過ThreadPoolExecutor創建線程池。
ThreadPoolExecutor類提供的構造器 | 作用 |
---|---|
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) | 使用指定的初始化參數創建一個新的線程池對象 |
- 方式二:使用Executors(線程池的工具類)調用方法返回不同特點的線程池對象。
處理Runnable任務
- ExecutorService的常用方法
方法名稱 | 說明 |
---|---|
void execute(Runnable command) | 執行 Runnable 任務 |
Future submit(Callable task) | 執行 Callable 任務,返回未來任務對象,用于獲取線程返回的結果 |
void shutdown() | 等全部任務執行完畢后,再關閉線程池! |
List shutdownNow() | 立刻關閉線程池,停止正在執行的任務,并返回隊列中未執行的任務 |
- 線程池的注意事項
- 新任務提交時發現核心線程都在忙,任務隊列也滿了,并且還可以創建臨時線程,此時才會創建臨時線程。
什么時候會拒絕新任務? - 核心線程和臨時線程都在忙,任務隊列也滿了,新的任務過來的時候才會開始拒絕任務。
- 新任務提交時發現核心線程都在忙,任務隊列也滿了,并且還可以創建臨時線程,此時才會創建臨時線程。
- 任務拒絕策略
策略 | 說明 |
---|---|
ThreadPoolExecutor.AbortPolicy() | 丟棄任務并拋出RejectedExecutionException異常。是默認的策略 |
ThreadPoolExecutor. DiscardPolicy() | 丟棄任務,但是不拋出異常,這是不推薦的做法 |
ThreadPoolExecutor. DiscardOldestPolicy() | 拋棄隊列中等待最久的任務 然后把當前任務加入隊列中 |
ThreadPoolExecutor. CallerRunsPolicy() | 由主線程負責調用任務的run()方法從而繞過線程池直接執行 |
處理Callable任務
- 線程池使用ExecutorService的方法處理Callable任務,并得到任務執行完后返回的結果。
Future<T> submit(Callable<T> command)
通過Executors創建線程池
- Executors是一個線程池的工具類,提供了很多靜態方法用于返回不同特點的線程池對象。
- 方法的底層,都是通過線程池的實現類ThreadPoolExecutor創建的線程池對象。
方法名稱 | 說明 |
---|---|
public static ExecutorService newFixedThreadPool(int nThreads) | 創建固定線程數量的線程池,如果某個線程因為執行異常而結束,那么線程池會補充一個新線程替代它。 |
public static ExecutorService newSingleThreadExecutor() | 創建只有一個線程的線程池對象,如果該線程出現異常而結束,那么線程池會補充一個新線程。 |
public static ExecutorService newCachedThreadPool() | 線程數量隨著任務增加而增加,如果線程任務執行完畢且空閑了60s則會被回收掉。 |
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) | 創建一個線程池,可以實現在給定的延遲后運行任務,或者定期執行任務。 |
- Executors不是適合做大型互聯網場景的線程池方案。
- 建議使用ThreadPoolExecutor來指定線程池參數,這樣可以明確線程池的運行規則,規避資源耗盡的風險。
并發/并行
- 正在運行的程序(軟件)就是一個獨立的進程。
- 線程是屬于進程的,一個進程中可以同時運行很多個線程。
- 進程中的多個線程其實是并發和并行執行的。
并發
- 進程中的線程是由CPU負責調度執行的,但CPU能同時處理線程的數量有限,為了保證全部線程都能往前執行,CPU會輪詢為系統的每個線程服務,由于CPU切換的速度很快,給我們的感覺這些線程在同時執行,這就是并發。
并行
- 在同一個時刻上,同時有多個線程在被CPU調度執行。