【问题标题】:Why does synchronizedList doesn't work as expected?为什么 synchronizedList 不能按预期工作?
【发布时间】:2020-03-05 10:38:49
【问题描述】:

我编写了以下程序,该程序将创建三个线程并启动它们。每个线程的任务是迭代字符串列表。这个列表是一个同步列表。当我运行程序时,我仍然看到线程输出不同步。即,在第一个线程遍历列表中的所有内容之前,第二个线程中断,第三个线程以此类推

import java.util.*;
public class Program implements Runnable{
    List<String> stars = Collections.synchronizedList(new ArrayList<String>());
    public static void main(String[] ars){
        System.out.println(Thread.currentThread().getName());   
        Program p = new Program();
        p.init();
    }
    public void init(){
        stars.add("Tom Hanks");
        stars.add("Bruce Lee");
        stars.add("Matthew");
        stars.add("fasbender");
        stars.add("pitt");
        Thread one = new Thread(this);
        one.setName("First");
        Thread two = new Thread(this);
        two.setName("Two");
        Thread three = new Thread(this);
        three.setName("Three");
        one.start();
        two.start();
        three.start();

    }
    public void run(){
        for(int i=0;i<stars.size();i++){
            System.out.println(stars.get(i)+" "+Thread.currentThread().getName());
        }
    }
}

我期望输出为: 主要的 汤姆汉克斯第一 李小龙第一 马修第一 法斯宾德第一 皮特第一 汤姆汉克斯第二 李小龙第二 马修第二 法斯宾德第二 皮特第二 汤姆汉克斯第三 李小龙第三 马修三 法斯宾德第三 三等奖

但是当我运行程序时,实际的输出是这样的:

main
Tom Hanks First
Bruce Lee First
Matthew First
fasbender First
pitt First
Tom Hanks Three
Tom Hanks Two
Bruce Lee Three
Bruce Lee Two
Matthew Three
Matthew Two
fasbender Three
fasbender Two
pitt Three
pitt Two

【问题讨论】:

  • 对我来说看起来是正确的。为什么您希望第一个线程在第二个线程开始之前完全完成?
  • 检查this的答案,也许它会为你清除它。

标签: java arraylist


【解决方案1】:

对同步列表的迭代不是原子的。客户端必须提供锁定。

如果您查看Collections.synchronizedList 的源文档,它使用返回的列表作为锁(互斥锁)。因此,客户端代码可以通过使用返回的列表应用同步块来使其迭代原子化。

来自SynchronizedCollection 的 Java 代码

SynchronizedCollection(Collection<E> c) {
    this.c = Objects.requireNonNull(c);
    mutex = this;
}

SynchronizedList 扩展 SynchronizedCollection 锁定互斥体。

static class SynchronizedList<E>
        extends SynchronizedCollection<E>
        implements List<E> {
...
...
    public void add(int index, E element) {
        synchronized (mutex) {list.add(index, element);}
    }
}

在这个例子中,即使在客户端代码上同步之后,线程操作的顺序仍然是不确定的,并且取决于线程调度程序。线程 3 可以在线程 2 之前调用。

public void run(){

        synchronized (stars) {
            for(int i=0;i<stars.size();i++){
                System.out.println(stars.get(i)+" "+Thread.currentThread().getName());
            }
        }
    } 

输出可能是。

main
Tom Hanks First
Bruce Lee First
Matthew First
fasbender First
pitt First
Tom Hanks Three
Bruce Lee Three
Matthew Three
fasbender Three
pitt Three
Tom Hanks Two
Bruce Lee Two
Matthew Two
fasbender Two
pitt Two

【讨论】:

    【解决方案2】:

    List 是同步的,但从线程的角度来看,访问顺序是不可预测的,并且完全取决于线程调度程序。因此,如果您想要一致的行为,那么您必须在列表本身上添加synchronize(将其包含在synchronized 块中,列表作为参数。
    查看docs了解更多详情。
    另外,请查看this 答案以获得更多解释。

    【讨论】:

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