前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >issues | SpringBoot 修复低版本中线程安全问题

issues | SpringBoot 修复低版本中线程安全问题

作者头像
码农架构
发布2022-06-28 16:17:26
3460
发布2022-06-28 16:17:26
举报
文章被收录于专栏:码农架构码农架构

导读:本文从业务为背景,结合实例给出最佳使用实践,来分析ArrayList线程安全问题,以便帮助各开发人员构建出稳定、高效的Java应用服务。

背景

由于目前当前产品用的SpringBoot 版本为2.1.17.RELEASE。

在JobExecutionExitCodeGenerator 监听事件中读取事件ordinal 时候发现多线程压测中偶发出现数据错乱问题。当时还以为是元数据扫描脏数据导致,没有留意;但是切换新环境后依然出现类似问题。

代码语言:javascript
复制
public class JobExecutionExitCodeGenerator 
    implements ApplicationListener<JobExecutionEvent>, ExitCodeGenerator {

    private final List<JobExecution> executions = new ArrayList();

    public JobExecutionExitCodeGenerator() {
    }

    public void onApplicationEvent(JobExecutionEvent event) {
      this.executions.add(event.getJobExecution());
    }

    public int getExitCode() {
      Iterator var1 = this.executions.iterator();

      JobExecution execution;
      do {
        if (!var1.hasNext()) {
          return 0;
        }
        execution = (JobExecution)var1.next();
      } while(execution.getStatus().ordinal() <= 0);

      return execution.getStatus().ordinal();
    }
}

就引起了我们的关注,最后定位到监听器可以被多个线程并发调用,线程是不安全的。

代码语言:javascript
复制
private final List<JobExecution> executions = new ArrayList();

因此这里应该调整为使用CopyOnWriteArrayList。

代码语言:javascript
复制
public class JobExecutionExitCodeGenerator implements 
  ApplicationListener<JobExecutionEvent>, ExitCodeGenerator {
  
  // CopyOnWriteArrayList 保证线程安全
  private final List<JobExecution> executions = new CopyOnWriteArrayList();

  public JobExecutionExitCodeGenerator() {
  }

  public void onApplicationEvent(JobExecutionEvent event) {
    this.executions.add(event.getJobExecution());
  }

  public int getExitCode() {
    Iterator var1 = this.executions.iterator();

    JobExecution execution;
    do {
      if (!var1.hasNext()) {
        return 0;
      }
      execution = (JobExecution)var1.next();
    } while(execution.getStatus().ordinal() <= 0);

    return execution.getStatus().ordinal();
  }
}
v2..7.0 R版本后已经修复

CopyOnWriteArrayList类图结构

原理

CopyOnWriteArrayList采用了一种读写分离的并发策略。CopyOnWriteArrayList容器允许并发读,读操作是无锁的,性能较高。至于写操作,比如向容器中添加一个元素,则首先将当前容器复制一份,然后在新副本上执行写操作,结束之后再将原容器的引用指向新容器。示意图如下:

▐ 注意事项
  • 由于写操作的时候,需要拷贝数组,会消耗内存,如果原数组的内容比较多的情况下,可能导致young gc或者full gc。
    • 年轻代(Young Generation):对象被创建时,内存的分配首先发生在年轻代(大对象可以直接被创建在年老代),大部分的对象在创建后很快就不再使用,因此很快变得不可达,于是被年轻代的GC机制清理掉(IBM的研究表明,98%的对象都是很快消亡的),这个GC机制被称为Minor GC或叫Young GC。
    • 年老代(Old Generation):对象如果在年轻代存活了足够长的时间而没有被清理掉(即在几次Young GC后存活了下来),则会被复制到年老代,年老代的空间一般比年轻代大,能存放更多的对象,在年老代上发生的GC次数也比年轻代少。当年老代内存不足时,将执行Major GC,也叫 Full GC
  • 不能用于实时读的场景,像拷贝数组、新增元素都需要时间,所以调用一个set操作后,读取到数据可能还是旧的,虽然CopyOnWriteArrayList 能做到最终一致性,但是还是没法满足实时性要求;

总结

CopyOnWriteArrayList这是一个ArrayList的线程安全的变体,其原理大概可以通俗的理解为:初始化的时候只有一个容器,很长一段时间,这个容器数据、数量等没有发生变化的时候,大家(多个线程),都是读取(假设这段时间里只发生读取的操作)同一个容器中的数据,所以这样大家读到的数据都是唯一、一致、安全的,但是后来有人往里面增加了一个数据,这个时候CopyOnWriteArrayList 底层实现添加的原理是先copy出一个容器(可以简称副本),再往新的容器里添加这个新的数据,最后把新的容器的引用地址赋值给了之前那个旧的的容器地址,但是在添加这个数据的期间,其他线程如果要去读取数据,仍然是读取到旧的容器里的数据。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2022-05-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 码农架构 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • ▐ v2..7.0 R版本后已经修复
  • ▐ 注意事项
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档