Spring多线程的使用以及问题详解

 更新时间:2022年05月18日 16:05:52   作者:李明  
在我们开发系统过程中,经常会处理一些费时间的任务(如:向数据库中插入大量数据),这个时候就就需要使用多线程,下面这篇文章主要给大家介绍了关于Spring多线程的使用以及问题的相关资料,需要的朋友可以参考下

前言

由于本周大部分时间都在写原型,主要遇到的问题就是对实际功能理解不准确导致多次修改原型浪费了很多时间,这也就告诉我们一定要明确实际要求再去下手。

因为之前会议中也多次提到了线程,而我本人对线程没有什么理解于是便有了以下文章。

为什么使用多线程

在我们开发系统过程中,经常会处理一些费时间的任务(如:向数据库中插入大量数据),这个时候就就需要使用多线程。

Springboot中是否对多线程方法进行了封装

是,Spring中可直接由@Async实现多线程操作

如何控制线程运行中的各项参数

通过配置线程池。

线程池ThreadPoolExecutor执行规则如下

然后我们来认为构造一个线程池来试一下:

@Configuration
@EnableAsync
public class ThreadPoolConfig implements AsyncConfigurer {
  /**
   * 核心线程池大小
   */
  private static final int CORE_POOL_SIZE = 3;

  /**
   * 最大可创建的线程数
   */
  private static final int MAX_POOL_SIZE = 10;

  /**
   * 队列最大长度
   */
  private static final int QUEUE_CAPACITY = 10;

  /**
   * 线程池维护线程所允许的空闲时间
   */
  private static final int KEEP_ALIVE_SECONDS = 300;

  /**
   * 异步执行方法线程池
   *
   * @return
   */
  @Override
  @Bean
  public Executor getAsyncExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setMaxPoolSize(MAX_POOL_SIZE);
    executor.setCorePoolSize(CORE_POOL_SIZE);
    executor.setQueueCapacity(QUEUE_CAPACITY);
    executor.setKeepAliveSeconds(KEEP_ALIVE_SECONDS);
    executor.setThreadNamePrefix("LiMingTest");
    // 线程池对拒绝任务(无线程可用)的处理策略
    executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
    executor.initialize();
    return executor;
  }
}

ThreadPoolExecutor是JDK中的线程池实现,这个类实现了一个线程池需要的各个方法,它提供了任务提交、线程管理、监控等方法。

corePoolSize:核心线程数

线程池维护的最小线程数量,默认情况下核心线程创建后不会被回收(注意:设置allowCoreThreadTimeout=true后,空闲的核心线程超过存活时间也会被回收)。

大于核心线程数的线程,在空闲时间超过keepAliveTime后会被回收。

maximumPoolSize:最大线程数

线程池允许创建的最大线程数量。

当添加一个任务时,核心线程数已满,线程池还没达到最大线程数,并且没有空闲线程,工作队列已满的情况下,创建一个新线程,然后从工作队列的头部取出一个任务交由新线程来处理,而将刚提交的任务放入工作队列尾部。

keepAliveTime:空闲线程存活时间

当一个可被回收的线程的空闲时间大于keepAliveTime,就会被回收。

被回收的线程:

设置allowCoreThreadTimeout=true的核心线程。
大于核心线程数的线程(非核心线程)。

workQueue:工作队列

新任务被提交后,如果核心线程数已满则会先添加到工作队列,任务调度时再从队列中取出任务。工作队列实现了BlockingQueue接口。

handler:拒绝策略

当线程池线程数已满,并且工作队列达到限制,新提交的任务使用拒绝策略处理。可以自定义拒绝策略,拒绝策略需要实现RejectedExecutionHandler接口。

JDK默认的拒绝策略有四种:

AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
DiscardPolicy:丢弃任务,但是不抛出异常。可能导致无法发现系统的异常状态。
DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务。
CallerRunsPolicy:由调用线程处理该任务。

我们在非测试文件中直接使用new Thread创建新线程时编译器会发出警告:

不要显式创建线程,请使用线程池。
说明:使用线程池的好处是减少在创建和销毁线程上所花的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题

public class TestServiceImpl implements TestService {
  private final static Logger logger = LoggerFactory.getLogger(TestServiceImpl.class);
  @Override
  public void task(int i) {
      logger.info("任务: "+i);
  }
}
@Autowired
  TestService testService;
  @Test
  public void test() {
    for (int i = 0; i < 50; i++) {
      testService.task(i);
    }

我们可以看到一切执行正常;

之后我有对线程进行了一些测试:

class TestServiceImplTest {
  @Test
  public void test() {
    Thread add = new AddThread();
    Thread dec = new DecThread();
    add.start();
    dec.start();
    add.join();
    dec.join();
    System.out.println(Counter.count);
  }

  static class Counter {
    public static int count = 0;
  }

  class AddThread extends Thread {
    public void run() {
      for (int i=0; i<10000; i++) { Counter.count += 1; }
    }
  }

  class DecThread extends Thread {
    public void run() {
      for (int i=0; i<10000; i++) { Counter.count -= 1; }
    }
  }

一个自增线程,一个自减线程,对0进行同样次数的操作,理应结果仍然为零,但是执行结果却每次都不同。

经过搜索之后发现对变量进行读取和写入时,结果要正确,必须保证是原子操作。原子操作是指不能被中断的一个或一系列操作。

例如,对于语句: n +=1; 看似只有一行语句却包括了3条指令:

读取n, n+1, 存储n;

比如有以下两个进程同时对10进行加1操作

这说明多线程模型下,要保证逻辑正确,对共享变量进行读写时,必须保证一组指令以原子方式执行:即某一个线程执行时,其他线程必须等待。

static class Counter {
    public static final Object lock = new Object();//每个线程都需获得锁才能执行
    public static int count = 0;
  }

  class AddThread extends Thread {
    public void run() {
      for (int i=0; i<10000; i++) {
        synchronized(Counter.lock) { static class Counter {
    public static final Object lock = new Object();
    public static int count = 0;
  }

  class DecThread extends Thread {
    public void run() {
      for (int i=0; i<10000; i++) {
        synchronized(Counter.lock) {
          Counter.count -= 1;
        }
      }
    }
  }

值得注意的是每个类可以设置多个锁,如果线程获取的不是同一个锁则无法起到上述功能;

springBoot中也定义了很多类型的锁,在此就不一一说明了,我们目前能做到的就是注意项目中的异步操作,观察操作所使用的线程,做到在以后项目中遇到此类问题时能及时发现问题,解决问题。

总结

到此这篇关于Spring多线程的使用及问题的文章就介绍到这了,更多相关Spring多线程使用内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • logback 自定义Pattern模板教程

    logback 自定义Pattern模板教程

    这篇文章主要介绍了logback 自定义Pattern模板教程,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-07-07
  • JVM Tomcat性能实战(推荐)

    JVM Tomcat性能实战(推荐)

    下面小编就为大家带来一篇JVM Tomcat性能实战(推荐)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2016-05-05
  • SpringCloud中NacosNamingService的作用详解

    SpringCloud中NacosNamingService的作用详解

    这篇文章主要介绍了SpringCloud中NacosNamingService的作用详解,NacosNamingService类完成服务实例注册,撤销与获取服务实例操作,NacosNamingService初始化采用单例模式,使用反射生成,需要的朋友可以参考下
    2023-11-11
  • 基于Java内存溢出的解决方法详解

    基于Java内存溢出的解决方法详解

    本篇文章是对Java内存溢出的解决方法进行了详细的分析介绍,需要的朋友参考下
    2013-05-05
  • 基于SpringBoot的SSMP的整合案例

    基于SpringBoot的SSMP的整合案例

    这篇文章主要介绍了SpringBoot整合SSMP的详细教程,文中通过代码示例介绍的非常详细,需要的朋友可以参考下
    2023-05-05
  • java中response对象用法实例分析

    java中response对象用法实例分析

    这篇文章主要介绍了java中response对象用法,结合实例形式分析了Java中response对象的功能及具体使用技巧,需要的朋友可以参考下
    2015-12-12
  • SpringBoot 下在 yml 中的 logging 日志配置方法

    SpringBoot 下在 yml 中的 logging 日志配置方法

    logging 配置主要用于控制应用程序的日志输出行为,可以通过配置定制日志的格式、级别、输出位置等,这篇文章主要介绍了SpringBoot 下在 yml 中的 logging 日志配置,需要的朋友可以参考下
    2024-06-06
  • Java的JSON转换类库GSON的基础使用教程

    Java的JSON转换类库GSON的基础使用教程

    GSON是谷歌开源的一款Java对象与JSON对象互相转换的类库,Java的JSON转换类库GSON的基础使用教程,需要的朋友可以参考下
    2016-06-06
  • JVM常见垃圾收集器学习指南

    JVM常见垃圾收集器学习指南

    这篇文章主要为大家介绍了JVM常见垃圾收集器学习指南,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-06-06
  • dependencies导致的Maven依赖出错包红问题解决方法

    dependencies导致的Maven依赖出错包红问题解决方法

    多模块和分布式开发一般都是有专门的的dependencies来进行jar包的版本依赖问题,本文主要介绍了dependencies导致的Maven依赖出错包红问题解决方法,具有一定的参考价值,感兴趣的可以了解一下
    2022-05-05

最新评论