Подтвердить что ты не робот

Что такое метод 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() не выполняет синхронизацию?

4b9b3361

Ответ 1

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

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

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

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

  • Вы не решаете, когда поток передает процесс другому потоку. Каждый вызов метода должен быть полностью запущен и возвращен до того, как другой сможет работать. Таким образом, вы можете получить A1,A2,A3,B1,B2,B3 или B1,B2,B3,A1,A2,A3 (поскольку мы не знаем, какой вызов потока будет выполняться первым).

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

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

Ответ 2

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

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

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

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

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

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

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

Ответ 3

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

Ответ 4

Согласно предыдущим ответам вам необходимо синхронизировать synList из потока доступа tOne и tTwo. В этом случае вы можете использовать шаблон монитора для обеспечения безопасного доступа - для потоков.

Ниже я адаптировал ваш код, чтобы поделиться им с теми же проблемами. В этом коде я использовал только synList для контроля доступа синхронизированным способом. Обратите внимание, что нет необходимости создавать другой объект, чтобы обеспечить порядок доступа из synList. Чтобы дополнить этот вопрос, см. книгу "Параллельность Java на практике" jcip, глава 4, в которой рассказывается о шаблонах проектирования мониторов, вдохновленных работой Хоара

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

    Thread tOne=new Thread(() -> {
        synchronized (synList) {
            for (int i = 0; i < 100; i++) {
                System.out.println(synList.add("add one" + i) + " one");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    });

    Thread tTwo=new Thread(()->{
        synchronized (synList) {
            for(int i=0;i<100;i++){
                System.out.println(synList.add("add two"+i)+" two");
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            }

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

}

Ответ 5

Это небольшой классный пример, основанный на оригинальном примере и принятом ответе, чтобы показать, для чего служит synchronizedList.

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class SynTest {
    public static void main(String []args) throws InterruptedException
    {
        final List<String> list = new ArrayList<>();
        final List<String> synList = Collections.synchronizedList(new ArrayList<>());

        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                list.addAll(Arrays.asList("one", "one", "one"));
                synList.addAll(Arrays.asList("one", "one", "one"));
            }
        });

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                list.addAll(Arrays.asList("two", "two", "two"));
                synList.addAll(Arrays.asList("two", "two", "two"));
            }
        });

        t1.start();
        t2.start();

        Thread.sleep(1000);
        System.out.println(list);
        System.out.println(synList);
    }
}

Оригинальный list имеет неопределенное поведение с такими результатами, как:

[one, one, one] // wrong!
[one, one, one, null, null, null] // wrong!
[two, two, two] // wrong!
[one, one, one, two, two, two] // correct

Хотя синхронизированный synList имеет синхронизированный метод addAll и всегда дает один из двух правильных результатов:

[one, one, one, two, two, two] // correct
[two, two, two, one, one, one] // correct