Java的“Copy-on-Iterate”习惯用法也不安全

本文讨论了Java中ArrayList在多线程环境下的构造缺陷,解释了其可能导致的ArrayIndexOutOfBoundsException异常,并提供了避免此类问题的方法。

这是我们的天才Lauri Tulmin处理的一个有趣的技术支持的故事。问题看起来是Wicket里的JRebel导致的ArrayIndexOutOfBoundsException 异常,很罕见。经过一些分析调查,他发现这个异常最先是由下面的Wicket代码抛出的:

 

private
 final
 Map<
IModifiable, Entry>
 modifiableToEntry = new
 ConcurrentHashMap(
)
;


public
 void
 start(
Duration pollFrequency)
 {

    this
.task
 = new
 Task(
"ModificationWatcher"
)
;

   this
 .task
.run
(
pollFrequency, new
 ICode(
)


 {
  public
 void
 run(
Logger log)
{
       Iterator
 iter = new
ArrayList


(

    ModificationWatcher.this
.modifiableToEntry
.values
(
)
).iterator
(
)
;

     while
 (
iter.hasNext
(
)
)



很明确,异常是由ArrayList 构造器抛出的。但这怎么可能?

让我们先暂停一下,说明一下这段代码为什么要这样写。在使用集合(collections)时有可能会出现一个问题,就是当我们重复迭代这个集合时,如果这个集合不巧被修改了(通常是被另外的线程),程序就会抛出ConcurrentModificationException 异常。这是为了防止Iterator 上的不可预期的操作行为。为了避免这个问题,有一个共识的习惯用法,就是在迭代循环之前要把collection拷贝出来使用,就像下面这样:

for
(
Iterator
 i = new
ArrayList
(
collection)
.iterator
(
)
;
 i.hasNext
(
)
;
)
{
...}



为了使collection能在多线程的环境中使用,必须保证它的可同步性和其它相关的特性。

这种用法非常普遍,只要在Google Code 里简单搜一下 就能证明。事实上我们在JRebel程序里多次的这样使用过,在Wicket里的很多地方也是这样用的。所以这怎么会出现ArrayIndexOutOfBoundsException 异常?

Lauri经过深入的研究发现,这种写法在多线程环境中有天生的缺陷(即使在collection已经被同步锁的情况下!)。原因就在于Java 1.6之前的ArrayList 的构造方式。在我的1.5版Java SDK源代码里它是这样写的:

public
ArrayList
(
Collection<?
extends
E>
collection)

 {
  int
 size = collection.size
(
)
;

firstIndex = 0
;


 array = newElementArray(
size + (
size / 10
)
)
;

 collection.toArray
(
array)
;

 lastIndex = size;

 modCount = 1
;
}



问题就出在size 被记录的时间和collection.toArray(array)被调用 的时间有个竞争关系。就在这个时间差内,理论上(的确是有可能)collection的size被其它线程修改了。当size变大了,用toArray() 拷贝array时就会出错,出现可怕的ArrayIndexOutOfBoundsException 异常。在经过进一步研究后我们发现在Oracle Java 1.6 和之后的版本里就不会出现这个问题了。可是我却没有找到跟这个问题相关的bug声明,看来它只是被意外的被修复了。

那么如何才能避免这个问题?一个办法是在整个循环上加上同步锁,但这就会限制你只能当和其它线程在同一个同步区内才能访问这个集合。有一个简单的解决方案,就是使用像下面这样使用 toArray() 方法:

for
 (
Iterator
i = Arrays
.asList
(
collection.toArray
(
)
)
.iterator
(
)
;
 i.hasNext
(
)
;
)



 

 

 

本文来自:外刊IT评论

 

:)

 

内容概要:本文介绍了一个基于Google Earth Engine(GEE)平台的JavaScript函数库,主要用于时间序列数据的优化与子采样处理。核心函数包括de_optim,采用差分进化算法对时间序列模型进行参数优化,支持自定义目标函数、变量边界及多种变异策略,并可返回最优参数或收敛过程的“陡度图”(scree image);sub_sample函数则用于按时间密度对影像集合进行三种方式的子采样(批量、分段打乱、跳跃式),以减少数据量同时保留时序特征;配套函数ts_image_to_coll可将子采样后的数组图像还原为标准影像集合,apply_model可用于将优化所得模型应用于原始时间序列生成预测结果。整个工具链适用于遥感时间序列建模前的数据预处理与参数调优。; 适合人群:具备Earth Engine基础开发经验、熟悉JavaScript语法并从事遥感数据分析、生态建模等相关领域的科研人员或技术人员;有时间序列建模需求且希望自动化参数优化流程的用户。; 使用场景及目标:①在有限观测条件下优化非线性时间序列拟合模型(如物候模型)的参数;②压缩大规模时间序列数据集以提升计算效率;③实现模型验证与交叉验证所需的时间序列子集抽样;④构建端到端的遥感时间序列分析流水线。; 阅读建议:此资源为功能性代码模块,建议结合具体应用场景在GEE平台上实际调试运行,重点关注各函数的输入格式要求(如band命名、image属性设置)和异常处理机制,确保输入数据符合规范以避免运行错误。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值