You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java类私有实例变量items值异常变更原因排查

问题分析:ProductSpec类的并发数据异常

原Java代码

class ProductSpec{
    private List<Item> items;

    public ProductSpec(){}

    List<Item> getItems(Long id){
        return itemRepository.getAllByProductId(id);
    }

    void getProductDetails(Parameters parameters){
        //LOGIC...
        //....
        Long productId = //从API请求参数中获取
        items = getItems(productId); //LINE A
        items.forEach(i->System.out.println(i.getId() + " " + i.getProductId())); //LINE B

        doStuff(items);
    }

    private void doStuff(List<Item> items){
        items.forEach(
            i->{
                System.out.println(i.getId() + " "+ i.getProductId()); //LINE C
            }
        );
    }
}

数据库中的Item数据

产品ID=1的Item

items idproduct id
1011
1021

产品ID=2的Item

items idproduct id
1032
1042

异常现象

当传入产品ID=1时,LINE B正常打印101 1、102 1,但部分场景下LINE C却打印103 1、104 1。且LINE A之后未对items实例变量重新赋值,尝试返回Collections.unmodifiableList(items)也无法解决问题。

异常原因

1. 实例变量的多线程共享问题

如果ProductSpec是单例(比如Spring容器默认的单例Bean),所有请求会共享同一个ProductSpec实例,其成员变量items会被多线程并发修改:

  • 线程1处理产品ID=1的请求:执行LINE A后,items指向产品1的Item列表,LINE B打印数据正常。
  • 线程切换后,线程2处理产品ID=2的请求:执行LINE A将items覆盖为产品2的Item列表。
  • 线程切回线程1执行doStuff时,使用的是线程2修改后的items引用,因此LINE C打印产品2的Item ID。

2. Item对象的可变与复用问题

即使items改为局部变量,若数据访问层(如ORM框架)复用Item对象实例(比如用对象池、一级缓存复用实例,仅修改属性),也会触发异常:

  • 线程1查询得到的Item对象,在LINE B打印后,被线程2的查询覆盖了id属性(改为103、104),但productId可能因线程切换或框架缓存逻辑未被完全覆盖,最终LINE C打印出103 1这类混合数据。
  • Collections.unmodifiableList仅限制列表的结构修改(增删元素),无法阻止列表内可变对象的属性被修改,因此无法解决此问题。

代码本身的隐患

原代码中items作为实例变量完全没必要,应改为局部变量避免多线程共享;同时方法缺少返回值和访问修饰符,属于语法不规范问题。


内容的提问来源于stack exchange,提问作者ashvini

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 09:14:53