Java必會之多執行緒

女友在高考發表於2021-06-01

一、執行緒的基本知識

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. 語義
  • 可見性
  • 禁止指令重排序(不完全保證有序性)
  • 不能保證原子性。

為什麼不保證有序性呢?舉個例子說明

上述程式碼,語句1和2,不會被重排到3的後面,4和5也不會到3的前面。但是1和2的順序、4和5的順序無法保證。

final

final定義型別 說明
final class XXX 不允許繼承
final 方法 不允許Override
final 區域性變數 不允許修改
final 例項屬性 建構函式、初始化塊後不能變更。只能賦值一次。建構函式結束返回時,final域最新的值保證對其他執行緒可見。
final static 屬性 靜態塊執行後不允許變更,只能賦值一次

二、執行緒池

  1. Executor :執行者 -頂層介面
  2. ExecutorService :繼承於Executor,執行緒池的介面API
  3. ThreadFactory:執行緒工才
  4. 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:拒絕策略

提交任務邏輯:

  1. 判斷核心執行緒數
  2. 加入workQueue
  3. 判斷最大執行緒數,沒達到就建立
  4. 執行拒絕策略(預設是拋異常)

緩衝佇列

  1. ArrayBlockingQueue:規定大小的BlockingQueue,構造時必須指定大小
  2. LinkedBlockingQueue:大小不固定的BlockingQueue,如果構造時指定大小,則有大小限制,不指定大小,則用Integer.MAX_VALUE來決定
  3. PriorityBlockingQueue:類似於LinkedBlockingQueue,不同在它根據物件的自然順序或者建構函式的Comparator進行排序,不是FIFO
  4. SynchronizedQueue:特殊的BlockingQueue,對其的操作必須是放和取交替完成。

拒絕策略:

  1. AbortPolicy:丟棄任務並拋異常
  2. DiscardPolicy:丟棄任務,不拋異常
  3. DiscardOldestPolicy:丟棄佇列最前面的任務,重新提交被拒絕的任務
  4. 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;
    }
}

執行緒工具類:

  1. newSingleThreadExecutor

    建立一個單執行緒的執行緒池。如果這個執行緒因為異常結束,那麼會有一個新的執行緒替代它。此執行緒池保證所有的任務的執行順序按任務提交順序執行。

  2. newFixedThreadPool

    建立固定大小的執行緒池。缺點:佇列使用的LinkedBlockingQueue,且沒有限制大小。

  3. newCachedThreadPool

    建立一個可快取的佇列,如果執行緒池大小超過了處理任務需要的執行緒,那麼就會回收部分空閒執行緒。缺點:此執行緒池不會對執行緒池大小做限制。

  4. newScheduledThreadPool

    建立一個大小無限的執行緒池。此執行緒池支援定時以及週期性執行任務的需求。

建立固定執行緒池的經驗:

假設伺服器核心數為N

  1. 如果是CPU密集型應用,則執行緒池大小設定為N或N+1
  2. 如果是IO密集型應用,則執行緒池大小設定為2N或2N+2

相關文章