springboot多线程注解Async

在数据处理中,多线程用到的场景很多,在满足计算机CPU处理能力的情况下,使用多线程可以明显提高程序运行效率,缩短大数据处理的能力。作为java程序开发,离不开spring,那么在spring中怎么创建多线程并将注册到spring的类在多线程中使用呢?我自己总结了一下,可以有两种方式,使用线程池和spring自带多线程注解使用。

使用线程池

我一般使用固定线程数量的线程池,假如数据量很大,我会将数据放到一个大集合中,然后按照一定的比例分配数目,同时我自己写了一个分页类,线程的数量可以根据分页类来自动调整。看代码:
分页类

 
  1. package com.yiche.tag.model;

  2. import java.util.List;

  3. public class DataPage {

  4. private int page = 1; // 当前页

  5. public int totalPages = 0; // 总页数

  6. private int pageRecorders;// 每页5条数据

  7. private int totalRows = 0; // 总数据数

  8. private int pageStartRow = 0;// 每页的起始数

  9. private int pageEndRow = 0; // 每页显示数据的终止数

  10. private boolean hasNextPage = false; // 是否有下一页

  11. private boolean hasPreviousPage = false; // 是否有前一页

  12. private List list;

  13. // private Iterator it;

  14. public DataPage(List list, int pageRecorders) {

  15. init(list, pageRecorders);// 通过对象集,记录总数划分

  16. }

  17. /** *//**

  18. * 初始化list,并告之该list每页的记录数

  19. * @param list

  20. * @param pageRecorders

  21. */

  22. public void init(List list, int pageRecorders) {

  23. this.pageRecorders = pageRecorders;

  24. this.list = list;

  25. totalRows = list.size();

  26. // it = list.iterator();

  27. hasPreviousPage = false;

  28. if ((totalRows % pageRecorders) == 0) {

  29. totalPages = totalRows / pageRecorders;

  30. } else {

  31. totalPages = totalRows / pageRecorders + 1;

  32. }

  33. if (page >= totalPages) {

  34. hasNextPage = false;

  35. } else {

  36. hasNextPage = true;

  37. }

  38. if (totalRows < pageRecorders) {

  39. this.pageStartRow = 0;

  40. this.pageEndRow = totalRows;

  41. } else {

  42. this.pageStartRow = 0;

  43. this.pageEndRow = pageRecorders;

  44. }

  45. }

  46. // 判断要不要分页

  47. public boolean isNext() {

  48. return list.size() > 5;

  49. }

  50. public void setHasPreviousPage(boolean hasPreviousPage) {

  51. this.hasPreviousPage = hasPreviousPage;

  52. }

  53. public String toString(int temp) {

  54. String str = Integer.toString(temp);

  55. return str;

  56. }

  57. public void description() {

  58. String description = "共有数据数:" + this.getTotalRows() +

  59. "共有页数: " + this.getTotalPages() +

  60. "当前页数为:" + this.getPage() +

  61. " 是否有前一页: " + this.isHasPreviousPage() +

  62. " 是否有下一页:" + this.isHasNextPage() +

  63. " 开始行数:" + this.getPageStartRow() +

  64. " 终止行数:" + this.getPageEndRow();

  65. System.out.println(description);

  66. }

  67. public List getNextPage() {

  68. page = page + 1;

  69. disposePage();

  70. System.out.println("用户凋用的是第" + page + "页");

  71. this.description();

  72. return getObjects(page);

  73. }

  74. /** *//**

  75. * 处理分页

  76. */

  77. private void disposePage() {

  78. if (page == 0) {

  79. page = 1;

  80. }

  81. if ((page - 1) > 0) {

  82. hasPreviousPage = true;

  83. } else {

  84. hasPreviousPage = false;

  85. }

  86. if (page >= totalPages) {

  87. hasNextPage = false;

  88. } else {

  89. hasNextPage = true;

  90. }

  91. }

  92. public List getPreviousPage() {

  93. page = page - 1;

  94. if ((page - 1) > 0) {

  95. hasPreviousPage = true;

  96. } else {

  97. hasPreviousPage = false;

  98. }

  99. if (page >= totalPages) {

  100. hasNextPage = false;

  101. } else {

  102. hasNextPage = true;

  103. }

  104. this.description();

  105. return getObjects(page);

  106. }

  107. /** *//**

  108. * 获取第几页的内容

  109. *

  110. * @param page 从1开始

  111. * @return

  112. */

  113. public List getObjects(int page) {

  114. if (page == 0)

  115. this.setPage(1);

  116. else

  117. this.setPage(page);

  118. this.disposePage();

  119. if (page * pageRecorders < totalRows) {// 判断是否为最后一页

  120. pageEndRow = page * pageRecorders;

  121. pageStartRow = pageEndRow - pageRecorders;

  122. } else {

  123. pageEndRow = totalRows;

  124. pageStartRow = pageRecorders * (totalPages - 1);

  125. }

  126. List objects = null;

  127. if (!list.isEmpty()) {

  128. objects = list.subList(pageStartRow, pageEndRow);

  129. }

  130. //this.description();

  131. return objects;

  132. }

  133. public List getFistPage() {

  134. if (this.isNext()) {

  135. return list.subList(0, pageRecorders);

  136. } else {

  137. return list;

  138. }

  139. }

  140. public boolean isHasNextPage() {

  141. return hasNextPage;

  142. }

  143. public void setHasNextPage(boolean hasNextPage) {

  144. this.hasNextPage = hasNextPage;

  145. }

  146. public List getList() {

  147. return list;

  148. }

  149. public void setList(List list) {

  150. this.list = list;

  151. }

  152. public int getPage() {

  153. return page;

  154. }

  155. public void setPage(int page) {

  156. this.page = page;

  157. }

  158. public int getPageEndRow() {

  159. return pageEndRow;

  160. }

  161. public void setPageEndRow(int pageEndRow) {

  162. this.pageEndRow = pageEndRow;

  163. }

  164. public int getPageRecorders() {

  165. return pageRecorders;

  166. }

  167. public void setPageRecorders(int pageRecorders) {

  168. this.pageRecorders = pageRecorders;

  169. }

  170. public int getPageStartRow() {

  171. return pageStartRow;

  172. }

  173. public void setPageStartRow(int pageStartRow) {

  174. this.pageStartRow = pageStartRow;

  175. }

  176. public int getTotalPages() {

  177. return totalPages;

  178. }

  179. public void setTotalPages(int totalPages) {

  180. this.totalPages = totalPages;

  181. }

  182. public int getTotalRows() {

  183. return totalRows;

  184. }

  185. public void setTotalRows(int totalRows) {

  186. this.totalRows = totalRows;

  187. }

  188. public boolean isHasPreviousPage() {

  189. return hasPreviousPage;

  190. }

  191. }

这个分页类可以多个项目复用,既可以实现分页,还可以用到线程池中给线程分配数量使用。以下代码为如何使用该分页类给线程池使用的。

 
  1. List<Integer> carModelIds = carsTagService.getAllCarModelIds();

  2. DataPage dataPage = new DataPage(carModelIds, 300);

  3. ExecutorService executorService = Executors.newFixedThreadPool(dataPage.totalPages);

  4. for (int i = 0; i < dataPage.totalPages; i++) {

  5. final int index = i + 1;

  6. executorService.execute(new Runnable() {

  7. List<Integer> temp = dataPage.getObjects(index);

  8. @Override

  9. public void run() {

  10. try {

  11. if (temp != null && temp.size() > 0) {

  12. temp=Collections.synchronizedList(temp);

  13. int sum = 0;

  14. for (Integer carId : temp) {

  15. try {

  16. carsTagService.insertCarTagByCarId(carId);

  17. sum++;

  18. if (sum % 100 == 0) {

  19. System.out.println("线程" + index + "当前执行了" + sum + "条,当前进度是" + sum * 100/ temp.size() + "%");

  20. }

  21. } catch (Exception ex) {

  22. ex.printStackTrace();

  23. }

  24. }

  25. System.out.println("线程" + index + "执行CarTagInitTask任务成功:" + temp.size());

  26. } else {

  27. System.out.println("CarTagInitTask任务执行完成:未读取到数据!");

  28. }

  29. } catch (Exception e) {

  30. e.printStackTrace();

  31. }

  32. }

  33. });

  34. }

  35. executorService.shutdown();

  36. while (true) {

  37. if (executorService.isTerminated()) {

  38. System.out.println("所有的子线程都结束了!");

  39. break;

  40. }

  41. try {

  42. Thread.sleep(2000);

  43. } catch (InterruptedException e) {

  44. e.printStackTrace();

  45. }

  46. }

上述代码中carModelIds就是要处理的数据集合,数目很大,如果直接for循环将会消耗很多的时间,利用线程池可以解决这个问题。但是如果直接创建多线程,线程中使用的对象需要final修饰,这对于spring管理的类不适用。使用线程池可以解决这个问题。

使用springboot自带@Async注解创建异步线程

在springboot中,可以使用@Async注解来将一个方法设置为异步方法,调用该方法的时候,是新开一个线程去调用。代码如下:

 
  1. @Component

  2. public class Task {

  3. @Async

  4. public void doTaskOne() throws Exception {

  5. System.out.println("开始做任务一");

  6. long start = System.currentTimeMillis();

  7. Thread.sleep(random.nextInt(10000));

  8. long end = System.currentTimeMillis();

  9. System.out.println("完成任务一,耗时:" + (end - start) + "毫秒");

  10. }

  11. @Async

  12. public void doTaskTwo() throws Exception {

  13. System.out.println("开始做任务二");

  14. long start = System.currentTimeMillis();

  15. Thread.sleep(random.nextInt(10000));

  16. long end = System.currentTimeMillis();

  17. System.out.println("完成任务二,耗时:" + (end - start) + "毫秒");

  18. }

  19. @Async

  20. public void doTaskThree() throws Exception {

  21. System.out.println("开始做任务三");

  22. long start = System.currentTimeMillis();

  23. Thread.sleep(random.nextInt(10000));

  24. long end = System.currentTimeMillis();

  25. System.out.println("完成任务三,耗时:" + (end - start) + "毫秒");

  26. }

  27. }

为了让@Async注解能够生效,还需要在Spring Boot的主程序中配置@EnableAsync,如下所示:

 
  1. @SpringBootApplication

  2. @EnableAsync

  3. public class Application {

  4. public static void main(String[] args) {

  5. SpringApplication.run(Application.class, args);

  6. }

  7. }

此时可以反复执行单元测试,您可能会遇到各种不同的结果,比如:

  • 没有任何任务相关的输出
  • 有部分任务相关的输出
  • 乱序的任务相关的输出

原因是目前doTaskOne、doTaskTwo、doTaskThree三个函数的时候已经是异步执行了。主程序在异步调用之后,主程序并不会理会这三个函数是否执行完成了,由于没有其他需要执行的内容,所以程序就自动结束了,导致了不完整或是没有输出任务相关内容的情况。

注: @Async所修饰的函数不要定义为static类型,这样异步调用不会生效

异步回调

为了让doTaskOne、doTaskTwo、doTaskThree能正常结束,假设我们需要统计一下三个任务并发执行共耗时多少,这就需要等到上述三个函数都完成调动之后记录时间,并计算结果。

那么我们如何判断上述三个异步调用是否已经执行完成呢?我们需要使用Future<T>来返回异步调用的结果,就像如下方式改造doTaskOne函数:

 
  1. @Async

  2. public Future<String> doTaskOne() throws Exception {

  3. System.out.println("开始做任务一");

  4. long start = System.currentTimeMillis();

  5. Thread.sleep(random.nextInt(10000));

  6. long end = System.currentTimeMillis();

  7. System.out.println("完成任务一,耗时:" + (end - start) + "毫秒");

  8. return new AsyncResult<>("任务一完成");

  9. }

按照如上方式改造一下其他两个异步函数之后,下面我们改造一下测试用例,让测试在等待完成三个异步调用之后来做一些其他事情:

 
  1. @Test

  2. public void test() throws Exception {

  3. long start = System.currentTimeMillis();

  4. Future<String> task1 = task.doTaskOne();

  5. Future<String> task2 = task.doTaskTwo();

  6. Future<String> task3 = task.doTaskThree();

  7. while(true) {

  8. if(task1.isDone() && task2.isDone() && task3.isDone()) {

  9. // 三个任务都调用完成,退出循环等待

  10. break;

  11. }

  12. Thread.sleep(1000);

  13. }

  14. long end = System.currentTimeMillis();

  15. System.out.println("任务全部完成,总耗时:" + (end - start) + "毫秒");

  16. }

看看我们做了哪些改变:

在测试用例一开始记录开始时间
在调用三个异步函数的时候,返回Future<String>类型的结果对象
在调用完三个异步函数之后,开启一个循环,根据返回的Future<String>对象来判断三个异步函数是否都结束了。若都结束,就结束循环;若没有都结束,就等1秒后再判断。
跳出循环之后,根据结束时间 - 开始时间,计算出三个任务并发执行的总耗时。
执行一下上述的单元测试,可以看到如下结果:

开始做任务一
开始做任务二
开始做任务三
完成任务三,耗时:37毫秒
完成任务二,耗时:3661毫秒
完成任务一,耗时:7149毫秒
任务全部完成,总耗时:8025毫秒

需要注意,spring管理的异步线程数量有限,如果是web项目的话,线程数量由tomcat的线程池配置有关系,所以如果可以,最好自己配置线程配置类。

 
  1. /**

  2. * springboot里面创建异步线程配置类

  3. * @author kouyy

  4. */

  5. @Configuration

  6. @EnableAsync

  7. public class ThreadAsyncConfigurer implements AsyncConfigurer {

  8. @Bean

  9. public Executor getAsyncExecutor() {

  10. ThreadPoolTaskExecutor threadPool = new ThreadPoolTaskExecutor();

  11. //设置核心线程数

  12. threadPool.setCorePoolSize(10);

  13. //设置最大线程数

  14. threadPool.setMaxPoolSize(100);

  15. //线程池所使用的缓冲队列

  16. threadPool.setQueueCapacity(10);

  17. //等待任务在关机时完成--表明等待所有线程执行完

  18. threadPool.setWaitForTasksToCompleteOnShutdown(true);

  19. // 等待时间 (默认为0,此时立即停止),并没等待xx秒后强制停止

  20. threadPool.setAwaitTerminationSeconds(60);

  21. // 线程名称前缀

  22. threadPool.setThreadNamePrefix("MyAsync-");

  23. // 初始化线程

  24. threadPool.initialize();

  25. return threadPool;

  26. }

  27. @Override

  28. public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {

  29. return null;

  30. }

  31. }

配置类创建之后,以后再使用@Async创建异步线程就可以按照自己配置来使用了。不用担心和spring线程池数量冲突了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值