一、線程的基本知識
1.1 線程知識
進程和線程的關系和區別
線程:
線程是進程的基本執行單元,進程想要執行任務,必須要有線程。程序啟動默認開啟一條線程,這個線程被稱為主線程。
進程:
進程是指在系統中正在運行的一個應用程序。每個進程之間是獨立的,每個進程均運行在其專用且受保護的內存里。
線程的六個狀態:
NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED
Thread流程圖:
Thread的方法:
方法 | 說明 |
---|---|
void join() | t.join() 當前線程調用其他線程的t.join()方法,當前線程進入等待狀態,當前線程不會釋放已經持有的鎖。線程t執行完畢后,當前線程進入就緒狀態。 |
static native void sleep() | 靜態方法,線程睡眠,並讓出CPU時間片 |
void wait() | 當前線程調用對象的wait()方法,當前線程釋放對象鎖,進入等待隊列。依靠notify()/notifyAll()喚醒。 |
native void notify() | 喚醒在此對象監視器上等待的單個線程,選擇是任意性的。 |
native void notifyAll() | 發送信號通知所有等待線程 |
1.2 線程安全
並發的相關性質:
-
原子性:原子操作。對基本數據類型的讀取和賦值操作是原子性操作,即這些操作是不可被中斷的,要么執行,要么不執行。
-
可見性:對於可見性,java提供了volatile關鍵字來保證可見性。
當一個共享變量被volatile修飾時,他會保證修改的值會立即被更新到主存,當有其他線程需要讀取時,他會去主存中讀取新值。
volatile 不保證原子性。
-
有序性:Java允許編譯器和處理器對指令重排序,但是重排序不會影響到單線程的執行,卻會影響到多線程並發執行的正確性。
synchronized
使用對象頭標記字實現
使用場景:
- 修飾方法:
一個對象中的加鎖方法只允許一個線程訪問。 - 修飾靜態方法:
由於靜態方法是類方法,所以多個線程不同對象訪問這個靜態方法,也是可以保證同步的。 - 修飾代碼塊:
如:synchronized(obj){...} 這里的obj可以是類中的一個屬性,也可以是對象,這時他跟修飾普通方法一樣,如果obj是Object.class這樣的,那么效果跟修飾靜態方法類似。
volatile
- 每次讀取都強制從主內存刷數據
- 適用場景:單個線程寫,多個線程讀
- 原則:能不用就不用,不確定的時候也不用
- 語義
- 可見性
- 禁止指令重排序(不完全保證有序性)
- 不能保證原子性。
為什么不保證有序性呢?舉個例子說明
上述代碼,語句1和2,不會被重排到3的后面,4和5也不會到3的前面。但是1和2的順序、4和5的順序無法保證。
final
final定義類型 | 說明 |
---|---|
final class XXX | 不允許繼承 |
final 方法 | 不允許Override |
final 局部變量 | 不允許修改 |
final 實例屬性 | 構造函數、初始化塊后不能變更。只能賦值一次。構造函數結束返回時,final域最新的值保證對其他線程可見。 |
final static 屬性 | 靜態塊執行后不允許變更,只能賦值一次 |
二、線程池
- Executor :執行者 -頂層接口
- ExecutorService :繼承於Executor,線程池的接口API
- ThreadFactory:線程工廠
- Executors:工具類
submit方法和execute方法的區別:
- submit方法:有返回值,用Future封裝,執行的方法異常了可以在主線程里catch到。
- execute方法:無返回值,方法執行異常是捕捉不到的
如下圖:
ExecutorService主要方法:
構造線程池的參數
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
corePoolSize :核心線程數
maximumPoolSize:最大線程數
keepAliveTime:當線程數超過了核心線程數,空閑線程需要等待多久等待不到新任務就終止。
workQueue:任務隊列
threadFactory:線程工廠
handler:拒絕策略
提交任務邏輯:
- 判斷核心線程數
- 加入workQueue
- 判斷最大線程數,沒達到就創建
- 執行拒絕策略(默認是拋異常)
緩沖隊列
- ArrayBlockingQueue:規定大小的BlockingQueue,構造時必須指定大小
- LinkedBlockingQueue:大小不固定的BlockingQueue,如果構造時指定大小,則有大小限制,不指定大小,則用Integer.MAX_VALUE來決定
- PriorityBlockingQueue:類似於LinkedBlockingQueue,不同在它根據對象的自然順序或者構造函數的Comparator進行排序,不是FIFO
- SynchronizedQueue:特殊的BlockingQueue,對其的操作必須是放和取交替完成。
拒絕策略:
- AbortPolicy:丟棄任務並拋異常
- DiscardPolicy:丟棄任務,不拋異常
- DiscardOldestPolicy:丟棄隊列最前面的任務,重新提交被拒絕的任務
- CallerRunsPolicy:由提交任務的線程處理該任務。
線程工廠(ThreadFactory):
自定義示例:
public class CustomThreadFactory implements ThreadFactory {
private AtomicInteger count=new AtomicInteger();
@Override
public Thread newThread(Runnable r) {
Thread thread=new Thread(r);
thread.setDaemon(false);
thread.setName("customThread-"+count.getAndIncrement());
return thread;
}
}
線程工具類:
-
newSingleThreadExecutor
創建一個單線程的線程池。如果這個線程因為異常結束,那么會有一個新的線程替代它。此線程池保證所有的任務的執行順序按任務提交順序執行。
-
newFixedThreadPool
創建固定大小的線程池。缺點:隊列使用的LinkedBlockingQueue,且沒有限制大小。
-
newCachedThreadPool
創建一個可緩存的隊列,如果線程池大小超過了處理任務需要的線程,那么就會回收部分空閑線程。缺點:此線程池不會對線程池大小做限制。
-
newScheduledThreadPool
創建一個大小無限的線程池。此線程池支持定時以及周期性執行任務的需求。
創建固定線程池的經驗:
假設服務器核心數為N
- 如果是CPU密集型應用,則線程池大小設置為N或N+1
- 如果是IO密集型應用,則線程池大小設置為2N或2N+2