Collections.synchronizedList() 方法有什么用?它似乎无法同步列表

我正在尝试使用两个线程向添加值。我想要的是,当一个线程添加值时,另一个线程不应该干扰,所以我使用了该方法。但是,如果我不显式同步对象,则添加似乎是以不同步的方式完成的。StringArrayListCollections.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()


答案 1

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

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

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

  • 由于该方法已同步,因此您可以确保这些列表不会像随机合并一样A1,B1,A2,A3,B2,B3

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

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

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


答案 2

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

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

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

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

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

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

在这种情况下,可以首先获取锁的线程将在释放锁之前执行整个循环(除非引发异常),允许另一个线程执行其同步块的内容,这就是为什么您可以连续多次或连续乘以另一个值的原因。ofor100onetwo100