多線程編程六-線程池的使用

目錄

 

1 JDK自帶的線程池

2 七大參數簡介:

3 線程池工作流程

4 自定義拒絕策略

5 和spring整合

6 合理配置線程數


1 JDK自帶的線程池

我們知道JDK可以通過Executors類來創建線程池,但是這些線程池都有缺點,所以在生產環境中我們要自定義線程池來使用
Executors.newFixedThreadPool(),缺點:可能創建大量任務,導致oom
Executors.newCachedThreadPool(),缺點,可能創建大量線程,導致oom
Executors.newSingleThreadExecutor(),缺點:可能創建大量任務,導致oom

2 七大參數簡介:

corePoolSize:線程池的大小。線程池創建之後不會立即去創建線程,而是等待線程的到來。噹噹前執行的線程數大於該值是,線程會加入到緩衝隊列;
maximumPoolSize:線程池中創建的最大線程數;
keepAliveTime:空閒的線程多久時間後被銷燬。默認情況下,該值在線程數大於corePoolSize時,對超出corePoolSize值得這些線程起作用。默認60
unit:TimeUnit枚舉類型的值,代表keepAliveTime時間單位,可以取下列值:
TimeUnit.DAYS; //天
  TimeUnit.HOURS; //小時
  TimeUnit.MINUTES; //分鐘
  TimeUnit.SECONDS; //秒(默認)
  TimeUnit.MILLISECONDS; //毫秒
  TimeUnit.MICROSECONDS; //微妙
  TimeUnit.NANOSECONDS; //納秒
workQueue:阻塞隊列,用來存儲等待執行的任務,決定了線程池的排隊策略,有以下取值:
  ArrayBlockingQueue;
  LinkedBlockingQueue;
  SynchronousQueue;
threadFactory:線程工廠,是用來創建線程的。默認new Executors.DefaultThreadFactory();
handler:線程拒絕策略。當創建的線程超出maximumPoolSize,且緩衝隊列已滿時,新任務會拒絕,有以下取值:
  ThreadPoolExecutor.AbortPolicy(默認):丟棄任務並拋出RejectedExecutionException異常
  ThreadPoolExecutor.DiscardPolicy:也是丟棄任務,但是不拋出異常。
  ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊列最前面的任務,然後重新嘗試執行任務(重複此過程)
  ThreadPoolExecutor.CallerRunsPolicy:由調用線程處理該任務

3 線程池工作流程

  • 線程池執行任務的時候如果核心線程數未被佔用則創建新的線程來執行任務
  • 如果當前任務量大於核心線程數則開始把任務添加到工作隊列
  • 如果工作隊列也滿了則在不超過最大線程數的情況下創建線程執行任務
  • 如果最大線程數也滿了則啓用拒絕策略

代碼實驗

@Test
public void testThreadPool() throws InterruptedException {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    // 核心線程數爲5
    int corePoolSize = 5;
    executor.setCorePoolSize(corePoolSize);
    // 最大線程數爲10
    executor.setMaxPoolSize(corePoolSize * 2);
    // 等待隊列容量爲10
    executor.setQueueCapacity(10);
    executor.setThreadNamePrefix("my-pool");
    // 優雅停止
    executor.setWaitForTasksToCompleteOnShutdown(true);
    executor.initialize();
    for (int i = 0; i < 25; i ++) {
        // 睡10ms,確保先加的任務先執行
        Thread.sleep(10);
        int finalI = i;
        try {
            executor.execute(() -> {
                System.out.println(DateUtil.datetimeToString(new Date()) + ":" + Thread.currentThread() + "->" + finalI);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }catch (Exception e) {
            System.out.println("task " + i + " error");
            continue;
        }
    }
    Thread.sleep(1000000);
}

輸出
2020-07-01 22:30:36:Thread[my-pool1,5,main]->0
2020-07-01 22:30:36:Thread[my-pool2,5,main]->1
2020-07-01 22:30:36:Thread[my-pool3,5,main]->2
2020-07-01 22:30:36:Thread[my-pool4,5,main]->3
2020-07-01 22:30:36:Thread[my-pool5,5,main]->4
2020-07-01 22:30:36:Thread[my-pool6,5,main]->15
2020-07-01 22:30:36:Thread[my-pool7,5,main]->16
2020-07-01 22:30:36:Thread[my-pool8,5,main]->17
2020-07-01 22:30:36:Thread[my-pool9,5,main]->18
2020-07-01 22:30:36:Thread[my-pool10,5,main]->19
task 20 error
task 21 error
task 22 error
task 23 error
task 24 error
2020-07-01 22:30:37:Thread[my-pool1,5,main]->5
2020-07-01 22:30:37:Thread[my-pool2,5,main]->6
2020-07-01 22:30:37:Thread[my-pool3,5,main]->7
2020-07-01 22:30:37:Thread[my-pool4,5,main]->8
2020-07-01 22:30:37:Thread[my-pool5,5,main]->9
2020-07-01 22:30:37:Thread[my-pool6,5,main]->10
2020-07-01 22:30:37:Thread[my-pool7,5,main]->11
2020-07-01 22:30:37:Thread[my-pool8,5,main]->12
2020-07-01 22:30:37:Thread[my-pool9,5,main]->13
2020-07-01 22:30:37:Thread[my-pool10,5,main]->14

4 自定義拒絕策略

手動設置拒絕策略
executor.setRejectedExecutionHandler((r, executor1) -> System.out.println("丟棄任務:" + r));
測試
2020-07-01 22:37:18:Thread[my-pool1,5,main]->0
2020-07-01 22:37:18:Thread[my-pool2,5,main]->1
2020-07-01 22:37:18:Thread[my-pool3,5,main]->2
2020-07-01 22:37:18:Thread[my-pool4,5,main]->3
2020-07-01 22:37:18:Thread[my-pool5,5,main]->4
2020-07-01 22:37:18:Thread[my-pool6,5,main]->15
2020-07-01 22:37:18:Thread[my-pool7,5,main]->16
2020-07-01 22:37:18:Thread[my-pool8,5,main]->17
2020-07-01 22:37:18:Thread[my-pool9,5,main]->18
2020-07-01 22:37:18:Thread[my-pool10,5,main]->19
丟棄任務:com.demo.util.FTPUtilTest$$Lambda$3/334203599@396e2f39
丟棄任務:com.demo.util.FTPUtilTest$$Lambda$3/334203599@a74868d
丟棄任務:com.demo.util.FTPUtilTest$$Lambda$3/334203599@12c8a2c0
丟棄任務:com.demo.util.FTPUtilTest$$Lambda$3/334203599@7e0e6aa2
丟棄任務:com.demo.util.FTPUtilTest$$Lambda$3/334203599@365185bd
2020-07-01 22:37:19:Thread[my-pool1,5,main]->5
2020-07-01 22:37:19:Thread[my-pool2,5,main]->6
2020-07-01 22:37:19:Thread[my-pool3,5,main]->7
2020-07-01 22:37:19:Thread[my-pool4,5,main]->8
2020-07-01 22:37:19:Thread[my-pool5,5,main]->9
2020-07-01 22:37:19:Thread[my-pool6,5,main]->10
2020-07-01 22:37:19:Thread[my-pool7,5,main]->11
2020-07-01 22:37:19:Thread[my-pool8,5,main]->12
2020-07-01 22:37:19:Thread[my-pool9,5,main]->13
2020-07-01 22:37:20:Thread[my-pool10,5,main]->14

5 和spring整合

spring中可以使用@Async來輕鬆實現異步調用使用的時候務必配置value屬性,確保用的是自定義的線程池
// 創建自定義線程池

@Bean("threadPool")
public Executor taskExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    int corePoolSize = Runtime.getRuntime().availableProcessors();
    executor.setCorePoolSize(corePoolSize);
    executor.setMaxPoolSize(corePoolSize * 2);
    executor.setQueueCapacity(20);
    executor.setThreadNamePrefix("thread-poll-");
    executor.setWaitForTasksToCompleteOnShutdown(true);
    executor.setRejectedExecutionHandler((r, executor1) -> System.out.println("拒絕……"));
    executor.initialize();
    return executor;
}

// 異步執行類

@Slf4j
@Component
public class AsyncClass{

    @Async("threadPool")
    public void test1() throws InterruptedException {
        Thread.sleep(500L);
        log.info("test1...");
    }

    @Async("threadPool")
    public void test2() throws InterruptedException {
        Thread.sleep(200L);
        log.info("test2...");
    }

    @Async("threadPool")
    public void test3() throws InterruptedException {
        Thread.sleep(300L);
        log.info("test3...");
    }
}

// 測試代碼

@Autowired
private AsyncClass asyncClass;

@Test
public void test() throws InterruptedException {
    asyncClass.test1();
    asyncClass.test2();
    asyncClass.test3();
    log.info("main...");
    Thread.sleep(5000L);
}

輸出
2020-07-01 22:46:30.272 - [main] INFO  com.demo.thread.ThreadPoolTest : 30 - main...
2020-07-01 22:46:30.475 - [thread-poll-2] INFO  com.demo.threadpool.AsyncClass : 23 - test2...
2020-07-01 22:46:30.584 - [thread-poll-3] INFO  com.demo.threadpool.AsyncClass : 29 - test3...
2020-07-01 22:46:30.787 - [thread-poll-1] INFO  com.demo.threadpool.AsyncClass : 17 - test1...
由此可見我們的方法都是異步執行的,根據線程的名稱可知使用的是我們自定義的線程池

6 合理配置線程數

CPU核數:Runtime.getRuntime().availableProcessors();
如果是cpu密集型業務,則儘量少配置線程數,減少線程切換。一般公式:cpu核數+1
如果是io密集型,任務線程並不是一直在執行任務,大量io導致大量阻塞,這種加速主要是利用了浪費掉的阻塞時間。
儘可能多配置線程數,一般公式:cpu核數 / (1-阻塞係數),阻塞係數一般是0.8-0.9

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章