一、執行緒的基本知識
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