非同步程式設計

lwx_R發表於2024-04-22

1.非同步程式設計

同步程式設計:執行緒等待返回
非同步程式設計:

2.顯式使用執行緒和執行緒池實現

2.1 顯式使用

  • 1.實現Runnable介面
public class SyncExample {

    public static void main(String[] args) throws Exception {
        long start = System.currentTimeMillis();
        // 第一種
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    task1();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
        // Java8寫法
//        Thread thread = new Thread( () -> {
//            try {
//                task1();
//            } catch (Exception e) {
//                e.printStackTrace();
//            }
//        }, "SyncExample");

        task2();
        // 同步等待1任務結束
        thread.join();
        System.out.println(System.currentTimeMillis() - start);
    }

    public static void task1(){
        try {
            Thread.sleep(2000);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("Task1");
    }

    public static void task2() {
        try {
            Thread.sleep(2000);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("Task2");
    }
}
  • 2.實現Thread類重寫run方法
public class SyncExample2 {

    public static void main(String[] args) throws Exception {
        long start = System.currentTimeMillis();
        Thread thread = new Thread("Example"){
            @Override
            public void run() {
                try {
                    task1();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        };
        task2();
        // 同步等待1任務結束
        thread.join();
        System.out.println(System.currentTimeMillis() - start);
    }

    public static void task1(){
        try {
            Thread.sleep(2000);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("Task1");
    }

    public static void task2() {
        try {
            Thread.sleep(2000);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("Task2");
    }
}

問題:

  • 每當執行非同步任務時,直接建立一個Thread,執行緒建立與銷燬有開銷,並且沒有限制執行緒個數,應用執行緒池
  • Thread執行非同步任務沒有返回值,需要用Future
  • 每次都建立是指令式程式設計方式,需要宣告式程式設計方法,即告訴程式我們要非同步執行,但如何實現應該對我們透明

2.2 執行緒池實現

  • 無返回結果
public class SyncExample3ThreadPool {

    /**
     * 執行緒池核心執行緒個數為當前CPU核數
     */
    private final static int AVALIABLE_PROCESSORS = Runtime.getRuntime().availableProcessors();

    /**
     * 最大執行緒個數為CPU核數2倍
     * 拒絕策略為CallerRunsPolicy,當執行緒池任務飽和,執行拒絕策略不會丟棄新任務,使用呼叫執行緒執行
     * 使用命名的執行緒建立工廠來追溯業務
     */
    public static final ThreadPoolExecutor POOL_EXECUTOR = new ThreadPoolExecutor(AVALIABLE_PROCESSORS,
            AVALIABLE_PROCESSORS * 2, 1, TimeUnit.MINUTES,
            new LinkedBlockingQueue<>(5), new ThreadPoolExecutor.CallerRunsPolicy());

    public static void main(String[] args) throws Exception {
        long start = System.currentTimeMillis();
        POOL_EXECUTOR.execute( () -> {
            try {
                task1();
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
        task2();
        System.out.println(System.currentTimeMillis() - start);
        // 掛起當前執行緒
        Thread.currentThread().join();
    }
}
  • 帶返回值
    使用Callable型別任務提交到執行緒,返回Future物件,以阻塞的方式獲取結果
public class SyncExample3Return {

    /**
     * 執行緒池核心執行緒個數為當前CPU核數
     */
    private final static int AVALIABLE_PROCESSORS = Runtime.getRuntime().availableProcessors();

    /**
     * 最大執行緒個數為CPU核數2倍
     * 拒絕策略為CallerRunsPolicy,當執行緒池任務飽和,執行拒絕策略不會丟棄新任務,使用呼叫執行緒執行
     * 使用命名的執行緒建立工廠來追溯業務
     */
    public static final ThreadPoolExecutor POOL_EXECUTOR = new ThreadPoolExecutor(AVALIABLE_PROCESSORS,
            AVALIABLE_PROCESSORS * 2, 1, TimeUnit.MINUTES,
            new LinkedBlockingQueue<>(5), new ThreadPoolExecutor.CallerRunsPolicy());

    public static void main(String[] args) throws Exception {
        // 必須阻塞執行緒才能獲取結果
        Future<?> taskReturn = POOL_EXECUTOR.submit(new Callable<String>() {
            @Override
            public String call() throws Exception {
                return task1();
            }
        });
        // Lambda寫法
//        Future<?> taskReturn = POOL_EXECUTOR.submit(() -> task1());
        System.out.println(taskReturn.get());
    }

    public static String task1(){
        try {
            Thread.sleep(2000);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("Task1");
        return "Task1";
    }
}

3 執行緒池原理

3.1 ctl

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

是原子變數,同時記錄執行緒池狀態和執行緒池執行緒個數
以32位為例,高三位表示狀態,後面表示個數

3.2 狀態

Running:接受新任務並處理阻塞佇列裡任務
shutdown:拒絕新任務但是處理佇列任務
stop:拒絕並拋棄阻塞佇列任務,同時中斷處理任務
tidying:所有任務都執行完,活動執行緒為0,將要呼叫terminated方法
terminated:終止狀態

  • 狀態轉換
    run->shut:顯式呼叫shutdown,隱式呼叫finalize
    run/shut->stop:顯式呼叫shutdownNow
    shut->tidy:執行緒池和任務佇列都為空
    stop->tidy:執行緒池為空
    tidy->terminated:執行terminated

3.3 執行緒池七個引數

  • corePoolSize:核心執行緒數
    執行緒池維護的最小執行緒數量,核心執行緒建立後不會被回收(注意:設定allowCoreThreadTimeout=true後,空閒的核心執行緒超過存活時間也會被回收)。
    大於核心執行緒數的執行緒,在空閒時間超過keepAliveTime後會被回收。
    執行緒池剛建立時,裡面沒有一個執行緒,當呼叫 execute() 方法新增一個任務時,如果正在執行的執行緒數量小於corePoolSize,則馬上建立新執行緒並執行這個任務。

  • maximumPoolSize:最大執行緒數
    執行緒池允許建立的最大執行緒數量。
    當新增一個任務時,核心執行緒數已滿,執行緒池還沒達到最大執行緒數,並且沒有空閒執行緒,工作佇列已滿的情況下,建立一個新執行緒並執行。

  • keepAliveTime:空閒執行緒存活時間
    當一個可被回收的執行緒的空閒時間大於keepAliveTime,就會被回收。
    可被回收的執行緒:設定allowCoreThreadTimeout=true的核心執行緒和大於核心執行緒數的執行緒(非核心執行緒)。
    unit:時間單位:keepAliveTime的時間單位:
    TimeUnit.NANOSECONDS
    TimeUnit.MICROSECONDS
    TimeUnit.MILLISECONDS // 毫秒
    TimeUnit.SECONDS
    TimeUnit.MINUTES
    TimeUnit.HOURS
    TimeUnit.DAYS

  • workQueue:工作佇列
    存放待執行任務的佇列:當提交的任務數超過核心執行緒數大小後,再提交的任務就存放在工作佇列,任務排程時再從佇列中取出任務。
    它僅僅用來存放被execute()方法提交的Runnable任務。工作佇列實現了BlockingQueue介面。
    JDK預設的工作佇列有五種:
    ArrayBlockingQueue 陣列型阻塞佇列:陣列結構,初始化時傳入大小,有界,FIFO,使用一個重入鎖,預設使用非公平鎖,入隊和出隊共用一個鎖,互斥。
    LinkedBlockingQueue 連結串列型阻塞佇列:連結串列結構,預設初始化大小為Integer.MAX_VALUE,有界(近似無解),FIFO,使用兩個重入鎖分別控制元素的入隊和出隊,用Condition進行執行緒間的喚醒和等待。
    SynchronousQueue 同步佇列:容量為0,新增任務必須等待取出任務,這個佇列相當於通道,不儲存元素。
    PriorityBlockingQueue 優先阻塞佇列:無界,預設採用元素自然順序升序排列。
    DelayQueue 延時佇列:無界,元素有過期時間,過期的元素才能被取出。

  • threadFactory:執行緒工廠
    建立執行緒的工廠,可以設定執行緒名、執行緒編號等。

  • dafaultHandler:拒絕策略
    當執行緒池執行緒數已滿,並且工作佇列達到限制,新提交的任務使用拒絕策略處理。可以自定義拒絕策略,拒絕策略需要實現RejectedExecutionHandler介面。
    JDK預設的拒絕策略有四種:
    AbortPolicy:丟棄任務並丟擲RejectedExecutionException異常。
    DiscardPolicy:丟棄任務,但是不丟擲異常。可能導致無法發現系統的異常狀態。
    DiscardOldestPolicy:丟棄佇列最前面的任務,然後重新提交被拒絕的任務。
    CallerRunsPolicy:由呼叫執行緒處理該任務。

3.4 mainLock成員變數

獨佔鎖,termination是

相關文章