`
找不着北
  • 浏览: 315246 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

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

阅读更多

这是我们的天才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评论

 

:)

 

分享到:
评论
2 楼 DanielYWoo 2010-08-19  
不知道我观点对不对,请抛砖.

toArray和ArrayList的构造函数一样, 都不是thread safe的. 这是JDK6 ArrayList.toArray()的代码

    
    public Object[] toArray() {
	Object[] result = new Object[size]; //时间点1
	System.arraycopy(elementData, 0, result, 0, size); //时间点2
	return result;
    }


当原始list shrink的时候, T1和T2对size会有自己的缓存里的local copy, T1在时间点1记录的size, T2如果减小了list, T1因此仍然会在时间点2数组越界. 我觉得你的解决方案也不会根治这个问题,只是大大的降低了概率.

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


理论上这里toArray可能会有问题, 除非你保证所有对collection的更改都是collection被发布之后,也就是说不更改原来的collection, 否则即便在循环上加锁也没有用.

1 楼 找不着北 2010-08-18  
抱歉,布局是在是搞不好

相关推荐

    struts-logic iterate标签学习指南

    Struts-Logic Iterate标签是Apache Struts框架中的一个重要组件,用于在JSP页面中迭代集合对象,如数组、列表或Map。这个标签提供了一种简洁的方式来遍历数据,并且可以与Struts的其他标签(如`bean:write`)配合...

    day37 04-Hibernate二级缓存:list和iterate方法比较

    在启用二级缓存的情况下,`list`和`iterate`方法的行为也会有所不同。对于`list`,如果查询结果已经在二级缓存中,那么可以直接从缓存读取,避免了数据库查询。而对于`iterate`,由于它是逐条加载,二级缓存主要作用...

    Java 9 - 在线API中文手册中文版 - 函数查询助手

    3. 新的集合工厂方法:Java 9的集合框架添加了新的工厂方法,如`List.of()`, `Set.of()`, `Map.of()`等,这些方法提供了不可变的集合,简化了代码并减少了潜在的错误。 4. 多版本兼容JAR(MVCJ):Java 9允许开发...

    ibatis中iterate的例子

    下面我们将详细探讨`&lt;iterate&gt;`标签的用法及其示例。 `&lt;iterate&gt;`标签的主要用途是将集合中的元素插入到SQL语句中,形成IN或OR条件。这在处理大量ID或条件时非常有用,避免了手动拼接SQL字符串的繁琐工作。其基本...

    NX二次开发UF-CLONE-iterate 函数介绍

    NX二次开发UF_CLONE_iterate 函数介绍,Ufun提供了一系列丰富的 API 函数,可以帮助用户实现自动化、定制化和扩展 NX 软件的功能。无论您是从事机械设计、制造、模具设计、逆向工程、CAE 分析等领域的专业人士,还是...

    random-iterate:以随机顺序迭代列表中的值

    用法 var iterate = require ( 'random-iterate' ) var ite = iterate ( [ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 ] ) console . log ( ite ( ) ) // maybe 4 console . log ( ite ( ) ) // maybe 9 . . . 7 more time...

    batis 配置文件

    在Java Web开发中,MyBatis是一个非常流行的持久层框架,它简化了数据库操作,将SQL语句与Java代码解耦。"Batis配置文件"是MyBatis框架的核心组成部分,用于定义数据源、事务管理、映射文件等关键设置。在本篇中,...

    logic:iterate 是什么,怎么用

    ### logic:iterate 标签详解 #### 一、概述 `logic:iterate` 是 Struts 标签库中的一个重要组成部分,主要用于在 JSP 页面中循环遍历集合对象(如 List、Array 或 Map 等)。它能够有效地帮助开发者在前端展示动态...

    java-web-tld.rar_java tld

    本资源包“java-web-tld.rar”显然专注于Java Web编程中的TLD使用,特别是针对Struts1.x和2.x这两个流行的MVC框架。 Struts1.x是一个基于MVC设计模式的Java Web框架,它的标签库(TLDs)使得开发者能够更方便地处理...

    struts 标签 logic:iterate使用 logic:iterate

    `struts`是一个著名的Java Web开发框架,它提供了一套基于MVC(Model-View-Controller)设计模式的标签库,使得开发者可以更方便地构建动态网页应用。在`struts`的标签库中,`logic:iterate`是一个非常重要的标签,...

    ibatis标签

    iBATIS是一个优秀的持久层框架,它允许将SQL语句直接嵌入到Java代码中,简化了数据库操作。而`&lt;iterate&gt;`标签是iBATIS提供的一个便利的循环结构,用于处理数组、集合或Map等数据结构,方便地生成动态SQL。 在描述中...

    初级java笔试题-iterate-clojure-workshop12:迭代clojure-workshop12

    初级java笔试题迭代Clojure介绍工作坊 Clojure 编程的实用、动手介绍,有史以来最好的 JVM 语言 :-) 是一种面向 JVM 和浏览器的现代 Lisp 语言 (),专为并发性、简单性和生产力而设计。 Clojure 我们在交互式 Web ...

    pop-iterate:JavaScript 对象的多态迭代器运算符

    iterate 运算符接受一个数组或实现 iterate 的对象,并返回一个迭代器,如,带有一些扩展。 迭代具有索引属性,索引与值对应。 var iterator = iterate ( [ 1 , 2 , 3 ] ) ; expect ( iterator . next ( ) ) . to...

    How to Iterate Over a Map in Java

    注意,对于并发访问的Map,如`ConcurrentHashMap`,应该使用`forEach()`方法,这是Java 8引入的流(Stream)API的一部分,具有更好的并发性能: ```java map.forEach((key, value) -&gt; System.out.println("Key: " +...

    oop-java-create-streams-SinyiMark:oop-java-create-streams-SinyiMark由GitHub Classroom创建

    Java 8 引入了流(Stream)的概念,这是一个革命性的特性,极大地改善了处理集合数据的方式。流提供了一种声明式编程风格,允许我们以更简洁、更易读的代码来处理数据。在这个名为“oop-java-create-streams-...

    oop-java-create-streams-KovacsAliz:oop-java-create-streams-KovacsAliz由GitHub Classroom创建

    在Java中,你可以通过调用`stream()`方法从集合或数组创建流,或者使用`IntStream.range()`等静态工厂方法创建数值流。 1. **创建流** - **从集合创建流**:你可以使用`Collection.stream()`或`Iterable.stream()`...

    matlab代码做游戏-OGDA-last-iterate:OGDA-最后一次

    这在恒定步长,任何初始点且不假设平衡是唯一的前提下成立(这是以前的工作所要求的)。 本文还将这一结果扩展到有限水平的马尔可夫博弈中。 测试环境 MATLAB.2018b或更高版本。 操作说明 运行Exp_Ai.m ,其中i = 1...

    hayk-assessment-iterate-over-collections

    使用app.rb文件可以: 生成具有名称和大小的字符串列表(例如“ Alpha-30”) 结合所有尺寸与减少 筛选列表以仅显示100个以上的公司 查找名为“ Beta”的公司 寻找最大的公司 从大到小对公司进行排序

    jsp脚本和标签:实现循环和分支逻辑

    jsp脚本和&lt;logic:iterate&gt;标签:实现循环和分支逻辑 jsp脚本和&lt;logic:iterate&gt;标签:实现循环和分支逻辑 jsp脚本和&lt;logic:iterate&gt;标签:实现循环和分支逻辑

    怎么将利用DWR调用JAVA类方法所返回的ArrayList数组   赋值给   logiciterate 迭代器

    ### 如何将通过DWR调用Java类方法返回的ArrayList赋值给逻辑迭代器 在Web开发中,经常需要从前端向后端发起请求并获取数据。Dynamic Web Remoting(DWR)是一种简化Ajax开发的技术,它使得JavaScript可以像调用本地...

Global site tag (gtag.js) - Google Analytics