Что такое использование коллекций.synchronizedList() метод? Похоже, он не синхронизирует список

Я пытаюсь добавить String значения ArrayList использование двух потоков. Я хочу, чтобы в то время как один поток добавляет значения, другой поток не должен вмешиваться, поэтому я использовал Collections.synchronizedList метод. Но похоже, что если я явно не синхронизирую объект, добавление выполняется несинхронизированным способом.

без явного synchronized-блок:

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...

с явным синхронизированным блоком uncommented я останавливаю интерференцию из другого потока при добавлении. Как только поток приобрел блокировку, он выполняется до тех пор, пока он не будет завершен.

пример вывода после разблокировки синхронизированного блока:

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

почему Collections.synchronizedList() не делать синхронизацию?

3 ответов


синхронизированный список синхронизирует только методы этого списка.

это означает, что поток не сможет изменить список, пока другой поток в настоящее время выполняет метод из этого списка. Объект блокируется при обработке методом.

в качестве примера, скажем, два потока run addAllв вашем списке, с 2 различными списками (A=A1,A2,A3, B=B1,B2,B3) в качестве параметра.

  • как метод synchronized, вы можете быть уверены, что эти списки не будут объединены случайным образом, как A1,B1,A2,A3,B2,B3

  • вы не решаете, когда поток передает процесс другому потоку, чтобы вы могли либо получить A1,A2,A3,B1,B2,B3 или B1,B2,B3,A1,A2,A3.

в вашем первом фрагменте кода оба потока выполняются одновременно. И оба пытаются add элемент списка. У вас нет никакого способа заблокировать один поток, кроме синхронизации на add метод, поэтому ничто не мешает потоку 1 запускать несколько add операции перед передачей процесса в поток 2. Таким образом, ваш выход совершенно нормальный.

во втором фрагменте кода (незафиксированном) вы четко указываете, что поток полностью блокирует список из другого потока перед запуском цикла. Следовательно, вы убедитесь, что один из ваших потоков выполнит полный цикл, прежде чем другой сможет получить доступ к списку.


Collections.synchronizedList() синхронизирует все обращения к резервному списку, за исключением итерации, которая все еще должна быть выполнена в синхронизированном блоке с экземпляром синхронизированного списка в качестве монитора объекта.

так, например, вот код add метод

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

это гарантирует последовательный доступ к резервному списку, поэтому, если ваши 2 потока вызывают add в то же время, один поток приобретет замок, добавит свой элемент и выпустит замок после этого второй поток сможет получить блокировку и добавить ее элемент, поэтому вы получите альтернативно one и two в свой выходной.

когда вы раскомментируете синхронизированный блок, код будет тогда

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

в этом случае поток может получить блокировку на o первый выполнит весь for цикл перед освобождением блокировки (за исключением случаев, когда возникает исключение), позволяя другому потоку выполнить содержимое его синхронизированный блок, поэтому вы получаете 100 раз подряд one или two затем 100 последовательные времена другое значение.


наблюдаемое поведение абсолютно правильно -synchronized подход, который вы демонстрируете в примере кода, не совпадает с synchronizedList. В первом случае вы синхронизируете весь оператор for -, поэтому только один поток будет выполнять его одновременно. Во втором случае вы synchromize методов сбора, сама - то чего synchronizedList стенды для. Так что будьте уверены, что add метод synchronized, но не for способ!