【问题标题】:What will be the output accessing a list on different threads?访问不同线程上的列表的输出是什么?
【发布时间】:2015-10-16 09:39:47
【问题描述】:

我有一个Strings 的ArrayList,两个线程同时访问该列表。 以下 sn-p 的输出是什么,为什么?

public static void main(String[] args) {
    final ArrayList<String> list = new ArrayList<String>();

    for (int i = 0; i < 100; i++) {
        list.add("Number" + i);
    }

    new Thread() {

        public void run() {
            for (String s : list) {
                System.out.println(s);
            }
        }
    }.start();

    new Thread() {
        public void run() {
            list.remove("Number5");
        }
    }.start();

}

我尝试使用相同的代码使ArraylistCollections.synchronizedList(list) 同步。它还在抛出java.util.ConcurrentModificationException

【问题讨论】:

  • 你的意思是问“这个函数线程安全吗”?

标签: java multithreading synchronized


【解决方案1】:

synchronizedList 的 Javadoc 明确指出:

用户在迭代返回的列表时必须手动同步它:

List list = Collections.synchronizedList(new ArrayList());
 ...
synchronized (list) {
    Iterator i = list.iterator(); // Must be in synchronized block
    while (i.hasNext())
        foo(i.next());
}

不遵循此建议可能会导致不确定的行为。

由于在此代码中,您在迭代 List 时将其从 List 中删除(在不同的线程中,但它是 List 的同一实例),因此将抛出 ConcurrentModificationException

使用Collections.synchronizedList,下面的代码就可以正常运行了。

final List<String> list = Collections.synchronizedList(new ArrayList<String>());
for (int i = 0; i < 100; i++) {
    list.add("Number" + i);
}

new Thread() {
    public void run() {
        synchronized (list) {
            for (String s : list) {
                System.out.println(s);
            }
        }
    }
}.start();

new Thread() {
    public void run() {
        synchronized (list) {
            list.remove("Number5");
        }
    }
}.start();

【讨论】:

    【解决方案2】:

    您可以使用syncrhonized 关键字锁定list 对象。

    同步关键字
    它的总体目的是只允许一个线程 一次进入特定的代码部分,从而使我们能够 保护,例如,变量或数据不被破坏 来自不同线程的同时修改。

    所以这应该适合你:

    public static void main(String[] args) {
        ...
    
        new Thread() {
    
            public void run() {
            synchronized(list){
                for (String s : list) {
                    System.out.println(s);
                }
            }
            }
        }.start();
    
        new Thread() {
            public void run() {
            synchronized(list){
                        list.remove("Number5");
            }
            }
        }.start();
    
    }
    

    注意:这取决于你想要什么逻辑。 您想在迭代时从列表中删除还是?你想让这两个任务顺序发生吗?

    【讨论】:

    • 我也是:正在寻找目的。如果需要,我想我们将不得不在两个线程之间交错任务。此外,添加和删除类似于生产者-消费者问题,(打印整个列表需要锁定整个列表对象,这取决于开发人员的意图。)
    • 是的,这完全取决于开发人员的意图。但是开发人员没有这样评论.. :-)
    【解决方案3】:

    你是iterating(在第一个线程中)和updating(在第二个线程中)两个不同线程中的同一个list对象,所以总是(有可能)它会抛出java.util.ConcurrentModificationException

    在 java 中的迭代器本质上是快速失败的,所以一旦他们意识到下划线结构已经改变,他们就会失败并出现 ConcurrentModificationException 异常。

    如果您想根据需要使用相同的列表对象,请考虑在两个线程中使用synchronized(list) 来同步它run 方法。

        public void run() {
            synchronized(list) {
              for (String s : list) {
                  System.out.println(s);
              }
            }
        }
    
        public void run() {
            synchronized(list) {
              list.remove("Number5");
            }
        }
    

    【讨论】:

      【解决方案4】:

      我认为您应该使用Vector 而不是ArrayList 来成为ThreadSafe 加上使用Iterator 来浏览您的列表。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多