Collections.synchronizedList() 方法有什么用?好像没有同步列表

新手上路,请多包涵

我正在尝试使用两个线程将 String 值添加到 ArrayList 。我想要的是,当一个线程正在添加值时,另一个线程不应干扰,所以我使用了 Collections.synchronizedList 方法。但是看起来,如果我没有明确地同步一个对象,那么添加就会以一种不同步的方式完成。

没有显式同步块:

 public class SynTest {
    public static void main(String []args){
        final List<String> list=new ArrayList<String>();
        final List<String> synList=Collections.synchronizedList(list);
        final Object o=new Object();
        Thread tOne=new Thread(new Runnable(){

            @Override
            public void run() {
                //synchronized(o){
                for(int i=0;i<100;i++){
                    System.out.println(synList.add("add one"+i)+ " one");
                }
                //}
            }

        });

        Thread tTwo=new Thread(new Runnable(){

            @Override
            public void run() {
                //synchronized(o){
                for(int i=0;i<100;i++){
                    System.out.println(synList.add("add two"+i)+" two");
                }
                //}
            }

        });
        tOne.start();
        tTwo.start();
    }
}

我得到的输出是:

 true one
true two
true one
true two
true one
true two
true two
true one
true one
true one...

在显式同步块未注释的情况下,我在添加时停止了来自其他线程的干扰。一旦线程获得了锁,它就会执行直到完成。

取消注释同步块后的示例输出:

 true one
true one
true one
true one
true one
true one
true one
true one...

那么为什么 Collections.synchronizedList() 没有进行同步?

原文由 Fullstack Guy 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 681
2 个回答

同步列表仅同步此列表的方法。

这意味着当另一个线程当前正在运行此列表中的方法时,线程将无法修改列表。对象在处理方法时被锁定。

例如,假设两个线程运行 addAll 在您的列表中,有 2 个不同的列表( A=A1,A2,A3B=B1,B2,B3 作为参数)

  • 由于该方法是同步的,您可以确定这些列表不会像 A1,B1,A2,A3,B2,B3 那样随机合并

  • 您不决定线程何时将进程移交给另一个线程。每个方法调用都必须完全运行并返回,然后另一个方法才能运行。因此,您可以获得 A1,A2,A3,B1,B2,B3B1,B2,B3,A1,A2,A3 (因为我们不知道哪个线程调用将首先运行)。

在您的第一段代码中,两个线程同时运行。两者都尝试 add 列表中的一个元素。除了 add 方法上的同步之外,您没有任何方法可以阻止一个线程,因此没有什么可以阻止线程 1 在将进程移交给线程 2 之前运行多个 add 操作。所以你的输出是完全正常的。

在您的第二段代码(未注释的代码)中,您清楚地声明一个线程在开始循环之前完全锁定了另一个线程的列表。因此,您要确保您的一个线程在另一个线程可以访问列表之前运行完整的循环。

原文由 jhamon 发布,翻译遵循 CC BY-SA 4.0 许可协议

Collections.synchronizedList() 将同步对支持列表的所有访问,但迭代时除外,迭代仍需要在同步块内完成,同步列表实例作为对象的监视器。

因此,例如这里是 add 方法的代码

public boolean add(E e) {
    synchronized (mutex) {return c.add(e);}
}

这保证了对支持列表的串行访问,因此如果您的 2 个线程同时调用 add ,一个线程将获取锁,添加其元素并释放锁,然后第二个线程将能够获取锁定并添加它的元素,这就是为什么您在输出中交替获得 onetwo 的原因。

当您取消注释同步块时,代码就是

synchronized(o) {
    for(int i=0;i<100;i++){
        ...
    }
}

在这种情况下,可以在 o 上获取锁的线程首先将执行 整个 for 释放锁之前的循环(除非抛出异常),允许其他线程执行the content of its synchronized block, which is why you get 100 consecutive times one or two then 100 consecutive times the other value.

原文由 Nicolas Filotto 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题