美文网首页Java
假ArrayList导致的线上事故......

假ArrayList导致的线上事故......

作者: 前端架构巴拉啦 | 来源:发表于2022-11-06 15:36 被阅读0次

线上事故回顾

晚饭时,当我正沉迷于排骨煲肉质鲜嫩,汤汁浓郁时,产研沟通群内发出一条消息,显示用户存在可用劵,但进去劵列表却什么也没有,并附含了一个视频。于是我一边吃了排骨,一边查看消息点开了视频,en~,视频跟描述一样。但没有系统告警,用户界面也没有明显的报错提示,怀疑是小部分特殊情况导致的,查看消息后几秒,我直接被@来处理问题,擦,只好把外卖盒重新盖好,先去处理问题。


处理经过

通过群内产品发的用户邮箱查到了用户id,再根据接口的相关日志结合uid在日志平台进行关联查询,查到日志后,再拿到traceId进行链路查询,果不其然,发现了异常日志,如下部分日志所示

java.lang.UnsupportedOperationException: null
    at java.util.AbstractList.add(AbstractList.java:148) ~[na:1.8.0_151]
    at java.util.AbstractList.add(AbstractList.java:108) ~[na:1.8.0_151]
复制代码

乍一看,这不是空指针嘛,so easy啊

image.png

仔细一瞧,这
UnsupportedOperationException是个什么玩意

于是,根据日志找到代码中报错的那一行,下面给大家简单模拟下

@Slf4j
@SpringBootTest
public class Demo {

    public void test(Context context) { 
        context.getList().add("Code皮皮虾");
    }

}

@Data
class Context {

    private List<String> list;

}
复制代码

基本操作就是拿到上下文中的List,然后再add一个元素

image.png

讲道理,add操作是不会有问题的,有问题的还得是List,追根溯源,让我康康这个List是怎么来的

于是我一顿狂点,来到了set这个list的位置

@Slf4j
@SpringBootTest
public class Demo {

    public void test(Context context) {
        context.setList(Arrays.asList("Code皮皮虾"));
    }

}

@Data
class Context {

    private List<String> list;

}
复制代码

context.setList(Arrays.asList("Code皮皮虾")); 这行看起来好像没问题啊

Arrays.asList(T... a)我们平时也会用,传入一个数组,返回出一个List没啥问题呀

image.png

那我再试试add方法

image.png

擦,问题复现了,还真是Arrays.asList(T... a)生成的List的add方法报错

由于线上存在问题,则先修改为以下代码上线,也就是修改为我们平时正常的写法

image.png

上线后,观察了下日志,群里回复已解决问题,也让用户重试,发现没问题,自此问题解决。

接下来,咱们来看看为啥Arrays.asList(T... a)的add方法会报错


追根溯源

进入asList方法,发现底层new了一个ArrayList,并将数组传入作为List的元素

@SafeVarargs
@SuppressWarnings("varargs")
public static <T> List<T> asList(T... a) {
    return new ArrayList<>(a);
}
复制代码

emm,看起来很简单啊,没问题啊,咋会报错呢

别着急,咱们在点开这个ArrayList瞅瞅

private static class ArrayList<E> extends AbstractList<E>
    implements RandomAccess, java.io.Serializable
{
    private static final long serialVersionUID = -2764017481108945198L;
    private final E[] a;

    ArrayList(E[] array) {
        a = Objects.requireNonNull(array);
    }
    // ... 省略
}
复制代码

擦,这ArrayList是Arrays类的一个静态内部类,不是我们经常用的java.util.ArrayList

image.png

真是离谱他妈给离谱开门,离谱大家了,还是我源码看得太少了,呜呜呜~

继续看,这个静态内部类ArrayList继承了AbstractList,而且默认是没有实现add方法的

image.png

也就是说调用add方法会直接调用父类,也就是AbstractList的add方法,源码点开一看,真相大白了

AbstractList的add方法直接抛出
UnsupportedOperationException异常,跟线上报错一模一样!!!

public boolean add(E e) {
    add(size(), e);
    return true;
}

public void add(int index, E element) {
    throw new UnsupportedOperationException();
}
复制代码

至此,排查结束,继续吃饭去排骨去咯~~~

image.png

小彩蛋

在使用Arrays.asList(T... a)方法时,如果只是单个元素的话,Idea会提示我们更建议Collections.singletonList

image.png

别用!!!真的别用!!!

因为Collections.singletonList底层跟Arrays.asList(T... a)差不多

SingletonList也是继承了AbstractList的一个内部类,调用add一样会报
UnsupportedOperationException异常

public static <T> List<T> singletonList(T o) {
    return new SingletonList<>(o);
}

private static class SingletonList<E>
    extends AbstractList<E>
    implements RandomAccess, Serializable {

    private static final long serialVersionUID = 3093736618740652951L;

    private final E element;

    SingletonList(E obj) {
        element = obj;
    }
}
复制代码

结尾

当然咯,也不是禁止使用Collections.singletonList和Arrays.asList(T... a),只是我们在使用的时候一定要区分一下场景,如果创建的是一个不会再添加元素的List,那么则可以使用

但我们平时不想写那么麻烦,想要在创建的时候就把元素塞到List中,那咋办呢?

我们其实能使用google的工具类

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>23.0</version>
</dependency>
复制代码

如下写法

@Slf4j
@SpringBootTest
public class Demo {

    public static void main(String[] args) {
        List<String> strings = Lists.newArrayList("Code皮皮虾", "哈哈哈");
        strings.add("憨憨熊");
        System.out.println(strings);
    }
}
复制代码

其内部已经为我们封装好了,拿来即用即可,哈哈

@SafeVarargs
@CanIgnoreReturnValue // TODO(kak): Remove this
@GwtCompatible(serializable = true)
public static <E> ArrayList<E> newArrayList(E... elements) {
  checkNotNull(elements); // for GWT
  // Avoid integer overflow when a large array is passed in
  int capacity = computeArrayListCapacity(elements.length);
  ArrayList<E> list = new ArrayList<>(capacity);
  Collections.addAll(list, elements);
  return list;
}

作者:Code皮皮虾
链接:
https://juejin.cn/post/7159839509868183583

相关文章

  • 假ArrayList导致的线上事故......

    线上事故回顾 晚饭时,当我正沉迷于排骨煲肉质鲜嫩,汤汁浓郁时,产研沟通群内发出一条消息,显示用户存在可用劵,但进去...

  • 并发下的缓存设计

    之前线上发生过缓存设计导致系统宕机的事故,让我很奇怪的是,已经缓存了数据,还是导致数据库cpu过高,让我很是奇怪,...

  • 记录一次HashMap并发导致的线上事故

    发现问题 线上日志平台报错如下 同时,系统监控告警平台也发来了CPU告急的消息,根据排查,是以下代码片段导致的: ...

  • 一次JVM内存问题导致的线上事故

    背景 公司线上有个tomcat服务,里面合并部署了大概8个微服务,之所以没有像其他微服务那样单独部署,其目的是为了...

  • 记一次很久以前的出现的线上事故。

    php.ini request_order 在之前的公司,曾今出现了一次线上运营事故,因这个bug导致项目延迟上线...

  • 一次线上MySQL分页事故,搞了半夜...

    目录 背景 分析 数据模拟 测试 解决方案 小结 今天给大家分享个生产事故,一个由于 MySQL 分页导致的线上事...

  • 【GPG】验证软件来源

    Electrum钱包前段时间出现一次盗币事故,用户被诱使下载假的Electrum,导致币被盗。黑客利用的是Elec...

  • ArrayList源码分析

    问题提出 ArrayList底层采用什么数据结构? ArrayList是如何扩容的? 频繁扩容导致性能下降如何处理...

  • 一个线程罢工的诡异事件

    背景 事情(事故)是这样的,突然收到报警,线上某个应用里业务逻辑没有执行,导致的结果是数据库里的某些数据没有更新。...

  • 11.并发容器类二

    List ArrayList 总结:arraylist并发不安全,底层是数组,遍历同时删除,会导致异常;可通过Co...

网友评论

    本文标题:假ArrayList导致的线上事故......

    本文链接:https://www.haomeiwen.com/subject/ivobtdtx.html