当前位置:   article > 正文

springboot创建及使用多线程的几种方式_springboot创建线程

springboot创建线程

在数据处理中,多线程用到的场景很多,在满足计算机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线程池数量冲突了。

 

引用地址:https://www.jianshu.com/p/67052c477dbf

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号