文章目錄
線程
1.什麼是線程?
進程:是程序的一次執行過程,是操作系統資源分配的基本單位
線程:是計算機中獨立運行的最小單位,運行時佔用很少的系統資源。
由於每個線程佔用的CPU時間是系統分配的,因此可以把線程看成操作系統分配CPU時間的基本單位。
線程基本上不擁有系統資源,它與同屬於一個進程的線程共享進程擁有的全部資源。
2.線程的創建方式
①繼承Thread類重寫run方法②參數傳遞Runnable對象③參數傳遞Callable對象,返回FutureTask對象
3.線程常用方法
3.1 sleep:使當前線程睡眠一段時間
//1.,當前線程不會釋放任何監視器的所有權(不釋放對象鎖)
//2.被打斷後,在拋出異常時,打斷狀態會被clear,也就是調用isInterrupted會返回fasle
public static native void sleep(long millis) throws InterruptedException;
3.2 join:等待某個線程運行結束
//(比如main線程中 調用t1線程的start(),join()後,則main線程需要等待t1線程運行完之後繼續執行,main線程進入 WAITING態)
public final void join() throws InterruptedException {...}
- 可以實現同步
3.3 yield :讓出線程執行權
//1.調用 yield 會讓調用線程從 Running 進入 Runnable 就緒狀態,然後調度執行其它線程
//2.很少用,可能對調試和測試的目的有用
public static native void yield();
3.4 interrupt: 打斷某個(阻塞)線程
//1.打斷由於調用了wait,join,sleep而陷入阻塞的線程時,會拋出InterruptedException,並且清除打斷狀態
//2.如果一個線程阻塞在一個IO操作(InterruptibleChannel),調用此方法會關閉channel,並設置線程的打斷狀態,並且此線程會收到1個ClosedByInterruptException
public void interrupt() {...}
3.5 setPriority:
線程的切換是由線程調度控制的,我們無法通過代碼來干涉,但是我們可以通過提高線程的優先級來最大程度的改善線程優先獲取時間片的機率
Java中線程的優先級被劃分爲10級,值分別爲1-10,1最低,10最高。Thread類提供了3個常量來表示最低最高和默認優先級.
Thread.MIN_PRIORITY = 1;
Thread.NORM_PRIORITY = 5;
Thread.MAX_PRIORITY = 10;
public final void setPriority(int newPriority) {...}
3.6 setDaemon
在Java中有兩類線程:User Thread(用戶線程)、Daemon Thread(守護線程)
只要當前JVM實例中尚存在任何一個非守護線程沒有結束,守護線程就全部工作;只有當最後一個非守護線程結束時,守護線程隨着JVM一同結束工作。
Daemon的作用是爲其他線程的運行提供便利服務,守護線程最典型的應用就是 GC (垃圾回收器),它就是一個很稱職的守護者。
public final void setDaemon(boolean on) {...}
4. 線程的狀態
4.1 NEW
: new
出來後 start
()前
4.2 RUNNABLE
: 在Java虛擬機中執行但可能在等操作系統的某個資源,比如CPU
4.3 BLOCKED
: 等一個monitor鎖時以便進入同步塊/方法中
4.4 WAITING
: 調用了Object
#wait
(),Thread
#join
(),LockSupport
#park
()後, 在等待其他線程執行一個特殊的操作
4.5 TIMED_WAITTING
: 調用了這些Thread.sleep
,Object
#wait(long),join
(long),LockSupport
#parkNanos
,LockSupport
#parkUntil
方法後。
4.6 TERMINATED
:run方法執行完後。
5. 線程安全問題的解決synchronized
5.1同步與互斥
- 互斥是保證臨界區的競態條件發生,同一時刻只能有一個線程執行臨界區代碼
- 同步是由於線程執行的先後、順序不同、需要一個線程等待其它線程運行到某個點
5.2 synchronized
用法,代碼塊和方法上,爲什麼需要類鎖?
- 鎖靜態方法,有些東西是屬於一個類的。
5.3 JDK1.6的優化 偏向鎖和輕量級鎖
線程池
1.什麼是線程池,爲什麼要有線程池?
就是事先創建若干個可執行的線程放入一個池(容器)中,需要的時候從池中獲取線程不用自行創建,使用完畢不需要銷燬線程而是放回池中,從而減少創建和銷燬線程對象的開銷.
JDK中用HashSet workers 表示線程池執行器中的線程池。
2.核心線程是如何保持不被銷燬的?
去任務時阻塞,BlockQueue
#getTask
(),如果沒有任務會調用條件變量的await方法等待,直到有爲止。
非核心線程不是阻塞獲取,是超時獲取的null,也就會任務結束,線程終止。
核心方法getTask(),參加分析https://www.cnblogs.com/DDiamondd/p/11362164.html
3.Executors返回的4種線程池
3.1 newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
}
特點
- 核心線程數 == 最大線程數(沒有救急線程被創建),因此也無需超時時間
- 阻塞隊列是無界的,可以放任意數量的任務
適用於任務量已知,相對耗時的任務
3.2 newCachedThreadPool
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new
SynchronousQueue<Runnable>());
}
-
核心線程數是 0, 最大線程數是 Integer.MAX_VALUE,救急線程的空閒生存時間是 60s,意味着
全部都是救急線程(60s 後可以回收),救急線程可以無限創建
-
隊列採用了 SynchronousQueue 實現特點是,它沒有容量,沒有線程來取是放不進去的(一手交錢、一手交 貨)
整個線程池表現爲線程數會根據任務量不斷增長,沒有上限,當任務執行完畢,空閒 1分鐘後釋放線 程。 適合任務數比較密集,但每個任務執行時間較短的情況
3.3 newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() { return new
FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L,
TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); }
使用場景: 希望多個任務排隊執行。線程數固定爲 1,任務數多於 1 時,會放入無界隊列排隊。任務執行完畢,這唯一的線程 也不會被釋放。 區別:
-
自己創建一個單線程串行執行任務,如果任務執行失敗而終止那麼沒有任何補救措施,而線程池還會新建一 個線程,保證池的正常工作
-
Executors.newSingleThreadExecutor
() 線程個數始終爲1,不能修改
FinalizableDelegatedExecutorService
應用的是裝飾器模式,只對外暴露了 ExecutorService
接口,因此不能調用 ThreadPoolExecutor
中特有的方法
Executors.newFixedThreadPool
(1) 初始時爲1,以後還可以修改對外暴露的是ThreadPoolExecutor
對象,可以強轉後調用setCorePoolSize
等方法進行修改
3.4 任務調度線程池
在『任務調度線程池』功能加入之前,可以使用 java.util.Timer 來實現定時功能,Timer 的優點在於簡單易用,但 由於所有任務都是由同一個線程來調度,因此所有任務都是串行執行的,同一時間只能有一個任務在執行,前一個 任務的延遲或異常都將會影響到之後的任務。
ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);
- 可以延時執行任務
executor.schedule
() - 也可以定時執行任務
scheduleAtFixedRate
scheduleWithFixedDelay
4. 線程池異常處理
- 任務主動自己try catch
- 使用Future,Callable處理
5. 一些概念的區分
5.1 核心線程 和非核心線程?
>=ThreadPoolExecutor
#corePoolSize
後的線程是非核心線程(也叫救急線程)
5.2 線程池當前pool size(池子的大小)?
private final HashSet<Worker> workers = new HashSet<Worker>();
// workers.size()表示
5.3 active thread 數量(活動線程數量)
爲正在執行任務的線程,通過遍歷workers , worker.isLocked()返回true的是active thread
6.線程池機制的總結
6.1
6.2 線程池不終止,核心線程啓動後就不會主動終止,也就是說當poolSize
超過corePoolSize
時,線程池中沒有任何任務時,pool
size 爲corePoolSize
的大小 ,通過ThreadPoolExecutor
#toString
方法可以看
public String toString() {
long ncompleted;
int nworkers, nactive;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
ncompleted = completedTaskCount;
nactive = 0;
nworkers = workers.size();
for (Worker w : workers) {
ncompleted += w.completedTasks;
if (w.isLocked())
++nactive;
}
} finally {
mainLock.unlock();
}
int c = ctl.get();
String rs = (runStateLessThan(c, SHUTDOWN) ? "Running" :
(runStateAtLeast(c, TERMINATED) ? "Terminated" :
"Shutting down"));
return super.toString() +
"[" + rs +
", pool size = " + nworkers +
", active threads = " + nactive +
", queued tasks = " + workQueue.size() +
", completed tasks = " + ncompleted +
"]";
}
7.阿里巴巴Android規範對於線程池的要求
【強制】線程池不允許使用 Executors 去創建,而是通過 ThreadPoolExecutor 的方 式,這樣的處理方式讓寫的同學更加明確線程池的運行規則,規避資源耗盡的風險。
Executors
返回的線程池對象的弊端如下:
1)FixedThreadPool
和SingleThreadPool
:允 許 的 請 求 隊 列 長 度 爲Integer.MAX_VALUE,可能會堆積大量的請求,從而導致;OOM
2)CachedThreadPool
和ScheduledThreadPool
:允許的創建線程數量爲Integer.MAX_VALUE,可能會創建大量的線程,從而導致OOM。
正例
int NUMBER_OF_CORES = Runtime.getRuntime().availableProcessors();
int KEEP_ALIVE_TIME = 1;
TimeUnit KEEP_ALIVE_TIME_UNIT = TimeUnit.SECONDS;
BlockingQueue<Runnable> taskQueue = new LinkedBlockingQueue<Runnable>();
ExecutorService executorService = new ThreadPoolExecutor(NUMBER_OF_CORES,
NUMBER_OF_CORES*2, KEEP_ALIVE_TIME, KEEP_ALIVE_TIME_UNIT,
taskQueue, new BackgroundThreadFactory(), new DefaultRejectedExecutionHandler());
反例
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();