@Async異步線程池以及線程的命名方式

本文記錄@Async的基本使用以及通過實現ThreadFactory來實現對線程的命名。

@Async的基本使用

近日有一個道友提出到一個問題,大意如下:

業務場景需要進行批量更新,已有數據id主鍵、更新的狀態。單條更新性能太慢,所以使用in進行批量更新。但是會導致鎖表使得其他業務無法訪問該表,in的量級太低又導致性能太慢。

龍道友提出瞭一個解決方案,把要處理的數據分成幾個list之後使用多線程進行數據更新。提到多線程可直接使用@Async註解來進行異步操作。

好的,接下來上面的問題我們不予解答,來說下@Async的簡單使用

@Async在SpringBoot中的使用較為簡單,所在位置如下:

一.啟動類加入註解@EnableAsync

第一步就是在啟動類中加入@EnableAsync註解 啟動類代碼如下:

@SpringBootApplication
@EnableAsync
public class EurekaApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaApplication.class, args);
    }
}

二.編寫MyAsyncConfigurer類

MyAsyncConfigurer類實現瞭AsyncConfigurer接口,重寫AsyncConfigurer接口的兩個重要方法:

1.getAsyncExecutor:自定義線程池,若不重寫會使用默認的線程池。

2.getAsyncUncaughtExceptionHandler:捕捉IllegalArgumentException異常.

一方法很好理解。二方法中提到的IllegalArgumentException異常在之後會說明。代碼如下:

/**
 * @author hsw
 * @Date 20:12 2018/8/23
 */
@Slf4j
@Component
public class MyAsyncConfigurer implements AsyncConfigurer {
 
    @Override
    public Executor getAsyncExecutor() {
        //定義一個最大為10個線程數量的線程池
        ExecutorService service = Executors.newFixedThreadPool(10);
        return service;
    }
 
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new MyAsyncExceptionHandler();
    }
 
    class MyAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {
 
        @Override
        public void handleUncaughtException(Throwable throwable, Method method, Object... objects) {
            log.info("Exception message - " + throwable.getMessage());
            log.info("Method name - " + method.getName());
            for (Object param : objects) {
                log.info("Parameter value - " + param);
            }
        }
    }
}</code>

三.三種測試方法

1.無參無返回值方法

2.有參無返回值方法

3.有參有返回值方法

具體代碼如下:

/**
 * @author hsw
 * @Date 20:07 2018/8/23
 */
@Slf4j
@Component
public class AsyncExceptionDemo {
 
    @Async
    public void simple() {
        log.info("this is a void method");
    }
 
    @Async
    public void inputDemo (String s) {
        log.info("this is a input method,{}",s);
        throw new IllegalArgumentException("inputError");
    }
 
    @Async
    public Future hardDemo (String s) {
        log.info("this is a hard method,{}",s);
        Future future;
        try {
            Thread.sleep(3000);
            throw new IllegalArgumentException();
        }catch (InterruptedException e){
            future = new AsyncResult("InterruptedException error");
        }catch (IllegalArgumentException e){
            future = new AsyncResult("i am throw IllegalArgumentException error");
        }
        return future;
    }
}

在第二種方法中,拋出瞭一種名為IllegalArgumentException的異常,在上述第二步中,我們已經通過重寫getAsyncUncaughtExceptionHandler方法,完成瞭對產生該異常時的處理。

在處理方法中我們簡單列出瞭異常的各個信息。

ok,現在該做的準備工作都已完成,加個測試方法看看結果如何

/**
 * @author hsw
 * @Date 20:16 2018/8/23
 */
@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
public class AsyncExceptionDemoTest {
 
    @Autowired
    private AsyncExceptionDemo asyncExceptionDemo;
 
    @Test
    public void simple() {
        for (int i=0;i<3;i++){
            try {
                asyncExceptionDemo.simple();
                asyncExceptionDemo.inputDemo("input");
                Future future = asyncExceptionDemo.hardDemo("hard");
                log.info(future.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
    }
}

測試結果如下:

2018-08-25 16:25:03.856 INFO 4396 — [ main] c.hsw.test.async.AsyncExceptionDemoTest : Started AsyncExceptionDemoTest in 3.315 seconds (JVM running for 4.184)
2018-08-25 16:25:03.947 INFO 4396 — [pool-1-thread-1] com.hsw.test.async.AsyncExceptionDemo : this is a void method
2018-08-25 16:25:03.947 INFO 4396 — [pool-1-thread-3] com.hsw.test.async.AsyncExceptionDemo : this is a hard method,hard
2018-08-25 16:25:03.947 INFO 4396 — [pool-1-thread-2] com.hsw.test.async.AsyncExceptionDemo : this is a input method,input
2018-08-25 16:25:03.947 INFO 4396 — [pool-1-thread-2] com.hsw.test.async.MyAsyncConfigurer : Exception message – inputError
2018-08-25 16:25:03.947 INFO 4396 — [pool-1-thread-2] com.hsw.test.async.MyAsyncConfigurer : Method name – inputDemo
2018-08-25 16:25:03.947 INFO 4396 — [pool-1-thread-2] com.hsw.test.async.MyAsyncConfigurer : Parameter value – input
2018-08-25 16:25:06.947 INFO 4396 — [ main] c.hsw.test.async.AsyncExceptionDemoTest : i am throw IllegalArgumentException error
2018-08-25 16:25:06.948 INFO 4396 — [pool-1-thread-4] com.hsw.test.async.AsyncExceptionDemo : this is a void method
2018-08-25 16:25:06.948 INFO 4396 — [pool-1-thread-6] com.hsw.test.async.AsyncExceptionDemo : this is a hard method,hard
2018-08-25 16:25:06.948 INFO 4396 — [pool-1-thread-5] com.hsw.test.async.AsyncExceptionDemo : this is a input method,input
2018-08-25 16:25:06.948 INFO 4396 — [pool-1-thread-5] com.hsw.test.async.MyAsyncConfigurer : Exception message – inputError
2018-08-25 16:25:06.948 INFO 4396 — [pool-1-thread-5] com.hsw.test.async.MyAsyncConfigurer : Method name – inputDemo
2018-08-25 16:25:06.948 INFO 4396 — [pool-1-thread-5] com.hsw.test.async.MyAsyncConfigurer : Parameter value – input
2018-08-25 16:25:09.949 INFO 4396 — [ main] c.hsw.test.async.AsyncExceptionDemoTest : i am throw IllegalArgumentException error
2018-08-25 16:25:09.950 INFO 4396 — [pool-1-thread-7] com.hsw.test.async.AsyncExceptionDemo : this is a void method
2018-08-25 16:25:09.950 INFO 4396 — [pool-1-thread-8] com.hsw.test.async.AsyncExceptionDemo : this is a input method,input
2018-08-25 16:25:09.950 INFO 4396 — [pool-1-thread-8] com.hsw.test.async.MyAsyncConfigurer : Exception message – inputError
2018-08-25 16:25:09.950 INFO 4396 — [pool-1-thread-9] com.hsw.test.async.AsyncExceptionDemo : this is a hard method,hard
2018-08-25 16:25:09.950 INFO 4396 — [pool-1-thread-8] com.hsw.test.async.MyAsyncConfigurer : Method name – inputDemo
2018-08-25 16:25:09.950 INFO 4396 — [pool-1-thread-8] com.hsw.test.async.MyAsyncConfigurer : Parameter value – input
2018-08-25 16:25:12.950 INFO 4396 — [ main] c.hsw.test.async.AsyncExceptionDemoTest : i am throw IllegalArgumentException error
2018-08-25 16:25:12.953 INFO 4396 — [ Thread-2] o.s.w.c.s.GenericWebApplicationContext : Closing org.s[email protected]71075444: startup date [Sat Aug 25 16:25:01 CST 2018]; root of context hierarchy

測試成功。

到此為止,關於@Async註解的基本使用內容結束。但是在測試過程中,我註意到線程名的命名為默認的pool-1-thread-X,雖然可以分辨出不同的線程在進行作業,但依然很不方便,為什麼默認會以這個命名方式進行命名呢?

線程池的命名

點進Executors.newFixedThreadPool,發現在初始化線程池的時候有另一種方法

public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory)

那麼這個ThreadFactory為何方神聖?繼續ctrl往裡點,在類中發現這麼一個實現類。

破案瞭破案瞭。原來是在這裡對線程池進行瞭命名。那我們隻需自己實現ThreadFactory接口,對命名方法進行重寫即可,完成代碼如下:

static class MyNamedThreadFactory implements ThreadFactory {
    private static final AtomicInteger poolNumber = new AtomicInteger(1);
    private final ThreadGroup group;
    private final AtomicInteger threadNumber = new AtomicInteger(1);
    private final String namePrefix;
    MyNamedThreadFactory(String name) {
 
        SecurityManager s = System.getSecurityManager();
        group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
        if (null == name || name.isEmpty()) {
            name = "pool";
        }
 
        namePrefix = name + "-" + poolNumber.getAndIncrement() + "-thread-";
    }
 
    public Thread newThread(Runnable r) {
        Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0);
        if (t.isDaemon())
            t.setDaemon(false);
        if (t.getPriority() != Thread.NORM_PRIORITY)
            t.setPriority(Thread.NORM_PRIORITY);
        return t;
    }
}

然後在創建線程池的時候加上新寫的類即可對線程名進行自定義。

@Override
public Executor getAsyncExecutor() {
    ExecutorService service = Executors.newFixedThreadPool(10,new MyNamedThreadFactory("HSW"));
    return service;
}

看看重命名後的執行結果。

2018-08-25 16:42:44.068 INFO 46028 — [ main] c.hsw.test.async.AsyncExceptionDemoTest : Started AsyncExceptionDemoTest in 3.038 seconds (JVM running for 3.83)
2018-08-25 16:42:44.155 INFO 46028 — [ HSW-1-thread-3] com.hsw.test.async.AsyncExceptionDemo : this is a hard method,hard
2018-08-25 16:42:44.155 INFO 46028 — [ HSW-1-thread-2] com.hsw.test.async.AsyncExceptionDemo : this is a input method,input
2018-08-25 16:42:44.155 INFO 46028 — [ HSW-1-thread-1] com.hsw.test.async.AsyncExceptionDemo : this is a void method
2018-08-25 16:42:44.155 INFO 46028 — [ HSW-1-thread-2] com.hsw.test.async.MyAsyncConfigurer : Exception message – inputError
2018-08-25 16:42:44.155 INFO 46028 — [ HSW-1-thread-2] com.hsw.test.async.MyAsyncConfigurer : Method name – inputDemo
2018-08-25 16:42:44.155 INFO 46028 — [ HSW-1-thread-2] com.hsw.test.async.MyAsyncConfigurer : Parameter value – input
2018-08-25 16:42:47.155 INFO 46028 — [ main] c.hsw.test.async.AsyncExceptionDemoTest : i am throw IllegalArgumentException error
2018-08-25 16:42:47.156 INFO 46028 — [ HSW-1-thread-4] com.hsw.test.async.AsyncExceptionDemo : this is a void method
2018-08-25 16:42:47.156 INFO 46028 — [ HSW-1-thread-5] com.hsw.test.async.AsyncExceptionDemo : this is a input method,input
2018-08-25 16:42:47.156 INFO 46028 — [ HSW-1-thread-6] com.hsw.test.async.AsyncExceptionDemo : this is a hard method,hard
2018-08-25 16:42:47.156 INFO 46028 — [ HSW-1-thread-5] com.hsw.test.async.MyAsyncConfigurer : Exception message – inputError
2018-08-25 16:42:47.156 INFO 46028 — [ HSW-1-thread-5] com.hsw.test.async.MyAsyncConfigurer : Method name – inputDemo
2018-08-25 16:42:47.156 INFO 46028 — [ HSW-1-thread-5] com.hsw.test.async.MyAsyncConfigurer : Parameter value – input
2018-08-25 16:42:50.156 INFO 46028 — [ main] c.hsw.test.async.AsyncExceptionDemoTest : i am throw IllegalArgumentException error
2018-08-25 16:42:50.157 INFO 46028 — [ HSW-1-thread-7] com.hsw.test.async.AsyncExceptionDemo : this is a void method
2018-08-25 16:42:50.157 INFO 46028 — [ HSW-1-thread-8] com.hsw.test.async.AsyncExceptionDemo : this is a input method,input
2018-08-25 16:42:50.157 INFO 46028 — [ HSW-1-thread-9] com.hsw.test.async.AsyncExceptionDemo : this is a hard method,hard
2018-08-25 16:42:50.157 INFO 46028 — [ HSW-1-thread-8] com.hsw.test.async.MyAsyncConfigurer : Exception message – inputError
2018-08-25 16:42:50.157 INFO 46028 — [ HSW-1-thread-8] com.hsw.test.async.MyAsyncConfigurer : Method name – inputDemo
2018-08-25 16:42:50.157 INFO 46028 — [ HSW-1-thread-8] com.hsw.test.async.MyAsyncConfigurer : Parameter value – input
2018-08-25 16:42:53.157 INFO 46028 — [ main] c.hsw.test.async.AsyncExceptionDemoTest : i am throw IllegalArgumentException error
2018-08-25 16:42:53.161 INFO 46028 — [ Thread-2] o.s.w.c.s.GenericWebApplicationContext : Closing org.s[email protected]71075444: startup date [Sat Aug 25 16:42:41 CST 2018]; root of context hierarchy

Spring Boot使用@Async實現異步調用:自定義線程池

定義線程池

第一步,先在Spring Boot主類中定義一個線程池,比如:

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
    @EnableAsync
    @Configuration
    class TaskPoolConfig {
        @Bean("taskExecutor")
        public Executor taskExecutor() {
            ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
            executor.setCorePoolSize(10);
            executor.setMaxPoolSize(20);
            executor.setQueueCapacity(200);
            executor.setKeepAliveSeconds(60);
            executor.setThreadNamePrefix("taskExecutor-");
            executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
            return executor;
        }
    }
}

上面我們通過使用ThreadPoolTaskExecutor創建瞭一個線程池,同時設置瞭以下這些參數:

  • 核心線程數10:線程池創建時候初始化的線程數
  • 最大線程數20:線程池最大的線程數,隻有在緩沖隊列滿瞭之後才會申請超過核心線程數的線程
  • 緩沖隊列200:用來緩沖執行任務的隊列
  • 允許線程的空閑時間60秒:當超過瞭核心線程出之外的線程在空閑時間到達之後會被銷毀
  • 線程池名的前綴:設置好瞭之後可以方便我們定位處理任務所在的線程池
  • 線程池對拒絕任務的處理策略:這裡采用瞭CallerRunsPolicy策略,當線程池沒有處理能力的時候,該策略會直接在 execute 方法的調用線程中運行被拒絕的任務;如果執行程序已關閉,則會丟棄該任務

使用線程池

在定義瞭線程池之後,我們如何讓異步調用的執行任務使用這個線程池中的資源來運行呢?方法非常簡單,我們隻需要在@Async註解中指定線程池名即可,比如:

@Slf4j
@Component
public class Task {
    public static Random random = new Random();
    @Async("taskExecutor")
    public void doTaskOne() throws Exception {
        log.info("開始做任務一");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        log.info("完成任務一,耗時:" + (end - start) + "毫秒");
    }
    @Async("taskExecutor")
    public void doTaskTwo() throws Exception {
        log.info("開始做任務二");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        log.info("完成任務二,耗時:" + (end - start) + "毫秒");
    }
    @Async("taskExecutor")
    public void doTaskThree() throws Exception {
        log.info("開始做任務三");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        log.info("完成任務三,耗時:" + (end - start) + "毫秒");
    }
}

單元測試

最後,我們來寫個單元測試來驗證一下

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class ApplicationTests {
    @Autowired
    private Task task;
    @Test
    public void test() throws Exception {
        task.doTaskOne();
        task.doTaskTwo();
        task.doTaskThree();
        Thread.currentThread().join();
    }
}

執行上面的單元測試,我們可以在控制臺中看到所有輸出的線程名前都是之前我們定義的線程池前綴名開始的,說明我們使用線程池來執行異步任務的試驗成功瞭!

2018-03-27 22:01:15.620 INFO 73703 — [ taskExecutor-1] com.didispace.async.Task : 開始做任務一
2018-03-27 22:01:15.620 INFO 73703 — [ taskExecutor-2] com.didispace.async.Task : 開始做任務二
2018-03-27 22:01:15.620 INFO 73703 — [ taskExecutor-3] com.didispace.async.Task : 開始做任務三
2018-03-27 22:01:18.165 INFO 73703 — [ taskExecutor-2] com.didispace.async.Task : 完成任務二,耗時:2545毫秒
2018-03-27 22:01:22.149 INFO 73703 — [ taskExecutor-3] com.didispace.async.Task : 完成任務三,耗時:6529毫秒
2018-03-27 22:01:23.912 INFO 73703 — [ taskExecutor-1] com.didispace.async.Task : 完成任務一,耗時:8292毫秒

以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。

推薦閱讀: