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 id | product id |
|---|---|
| 101 | 1 |
| 102 | 1 |
产品ID=2的Item
| items id | product id |
|---|---|
| 103 | 2 |
| 104 | 2 |
异常现象
当传入产品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
相关产品推荐
相关产品推荐

