一.實現多線程的方式
1.繼承Thread類,重寫其run方法
2.實現Runnable接口,實現run方法
3.實現Callable接口,實現call方法
由於Java的設計,只支持單繼承,但是支持多實現形式,所以一般面向接口開發,Runnable接口與Callable接口的區別在於Callable接口中的call方法是帶返回值的,其返回一個Future的異步類,我們可以通過Future的get方法獲取結果,如果線程還沒有執行完,get方法會阻塞。
Future還提供了很多有用的方法,像用於判斷線程是否執行完成的方法(isDone)
取消任務執行的方法 cancle()
二.線程的狀態
1. 新建狀態(New):新創建了一個線程對象。
2. 就緒狀態(Runnable):線程對象創建後,其他線程調用了該對象的start()方法。該狀態的線程位於可運行線程池中,變得可運行,等待獲取CPU的使用權。
3. 運行狀態(Running):就緒狀態的線程獲取了CPU,執行程序代碼。
4. 阻塞狀態(Blocked):阻塞狀態是線程因為某種原因放棄CPU使用權,暫時停止運行。直到線程進入就緒狀態,才有機會轉到運行狀態。阻塞的情況分三種:
(一)、等待阻塞:運行的線程執行wait()方法,JVM會把該線程放入等待池中。
(二)、同步阻塞:運行的線程在獲取對象的同步鎖時,若該同步鎖被別的線程佔用,則JVM會把該線程放入鎖池中。
(三)、其他阻塞:運行的線程執行sleep()或join()方法,或者發出了I/O請求時,JVM會把該線程置為阻塞狀態。當sleep()狀態超時、join()等待線程終止或者超時、或者I/O處理完畢時,線程重新轉入就緒狀態。
5. 死亡狀態(Dead):線程執行完了或者因異常退出了run()方法,該線程結束生命週期
三.java併發開發中的最佳實踐
1.創建線程時給每個線程起一個名字,便於之後的問題查找
2.縮小併發區域
3.儘量使用JUC下的鎖
4.儘量使用併發集合而非同步集合
5.儘量使用併發鎖而非同步器鎖
6.如果可以不共享數據,多線程下儘量不共享
7.可以使用volatile修飾long和double
8.使用線程池
四.線程池
合理利用線程池能夠帶來三個好處。第一:降低資源消耗。通過重複利用已創建的線程降低線程創建和銷燬造成的消耗。第二:提高響應速度。當任務到達時,任務可以不需要的等到線程創建就能立即執行。第三:提高線程的可管理性。線程是稀缺資源,如果無限制的創建,不僅會消耗系統資源,還會降低系統的穩定性,使用線程池可以進行統一的分配,調優和監控
我們可以通過ThreadPoolExecutor來創建一個線程,ThreadPoolExecutor需要的參數如下:
線程池參數詳細說明如下:
corePoolSize(線程池的基本大小):當提交一個任務到線程池時,線程池會創建一個線程來執行任務,即使其他空閒的基本線程能夠執行新任務也會創建線程,等到需要執行的任務數大於線程池基本大小時就不再創建。如果調用了線程池的prestartAllCoreThreads方法,線程池會提前創建並啟動所有基本線程
runnableTaskQueue(任務隊列):用於保存等待執行的任務的阻塞隊列。可以選擇以下幾個阻塞隊列。
1. ArrayBlockingQueue:是一個基於數組結構的有界阻塞隊列,此隊列按 FIFO(先進先出)原則對元素進行排序。
2. LinkedBlockingQueue:一個基於鏈表結構的阻塞隊列,此隊列按FIFO (先進先出) 排序元素,吞吐量通常要高於ArrayBlockingQueue。靜態工廠方法Executors.newFixedThreadPool()使用了這個隊列。
3. SynchronousQueue:一個不存儲元素的阻塞隊列。每個插入操作必須等到另一個線程調用移除操作,否則插入操作一直處於阻塞狀態,吞吐量通常要高於LinkedBlockingQueue,靜態工廠方法Executors.newCachedThreadPool使用了這個隊列。
4. PriorityBlockingQueue:一個具有優先級得無限阻塞隊列
maximumPoolSize(線程池最大大小):線程池允許創建的最大線程數。如果隊列滿了,並且已創建的線程數小於最大線程數,則線程池會再創建新的線程執行任務。值得注意的是如果使用了無界的任務隊列這個參數就沒什麼效果。
ThreadFactory:用於設置創建線程的工廠,可以通過線程工廠給每個創建出來的線程設置更有意義的名字,Debug和定位問題時非常又幫助
RejectedExecutionHandler(飽和策略):當隊列和線程池都滿了,說明線程池處於飽和狀態,那麼必須採取一種策略處理提交的新任務。這個策略默認情況下是AbortPolicy,表示無法處理新任務時拋出異常。以下是JDK1.5提供的四種策略。
1.AbortPolicy:直接拋出異常。
2.CallerRunsPolicy:只用調用者所在線程來運行任務。
3.DiscardOldestPolicy:丟棄隊列裡最近的一個任務,並執行當前任務。
4.DiscardPolicy:不處理,丟棄掉。
當然也可以根據應用場景需要來實現RejectedExecutionHandler接口自定義策略。如記錄日誌或持久化不能處理的任務,如果某個任務被提交到一個已經關閉的Executor也會執行拒絕策略
keepAliveTime(線程活動保持時間):線程池的工作線程空閒後,保持存活的時間。所以如果任務很多,並且每個任務執行的時間比較短,可以調大這個時間,提高線程的利用率。
TimeUnit(線程活動保持時間的單位):可選的單位有天(DAYS),小時(HOURS),分鐘(MINUTES),毫秒(MILLISECONDS),微秒(MICROSECONDS, 千分之一毫秒)和毫微秒(NANOSECONDS, 千分之一微秒)
五.線程池的工作流程
· 首先線程池判斷基本線程池是否已滿?沒滿,創建一個工作線程來執行任務。滿了,則進入下個流程。
· 其次線程池判斷工作隊列是否已滿?沒滿,則將新提交的任務存儲在工作隊列裡。滿了,則進入下個流程。
· 最後線程池判斷整個線程池是否已滿?沒滿,則創建一個新的工作線程來執行任務,滿了,則交給飽和策略來處理這個任務
今天先寫到這裡,有什麼不同見解記得關注我【不愛八阿哥】歡迎私信交流
閱讀更多 不愛八阿哥 的文章