Что, если мы будем использовать только внешнюю нулевую проверку в шаблоне синглтона двойной проверки?

Вопрос 1: Почему в одноэлементном шаблоне для многопоточности нам нужны две нулевые проверки? Что, если мы воспользуемся только внешним чеком?

    if (instance == null) {
        synchronized (ABC.class) {

            // What if we remove this check?
            if (instance == null) {
                instance = new ABC();
            }
    }

Вопрос 2: В чем разница между следующим:

1: Прямое использование имени класса внутри synchronized ()

    public ABC getInstance() {
        if (instance == null) {
            // Difference here
            synchronized (ABC.class) {
                if (instance == null) {
                    instance = new ABC();
                }
            }
         }
         return instance;
    }

2: Использование статического конечного объекта внутри synchronized ()

    private static final Object LOCK = new Object();
    .
    .
    public ABC getInstance() {
        if (instance == null) {

             // Difference here
             synchronized (LOCK) {
                if (instance == null) {
                    instance = new ABC();
                }
             }
         }
         return instance;
    }

3. Использование new Object () внутри synchronized ()

    if (instance == null) {
    // Difference here
         synchronized (new Object()) {
            if (instance == null) {
                instance = new ABC();
            }
        }
     }

person Xigstan    schedule 05.07.2019    source источник


Ответы (1)


  1. Удаление внутренней нулевой проверки может привести к состоянию гонки. Представьте себе следующий сценарий: два потока пытаются получить экземпляр вашего объекта в одно и то же время, поэтому они оба проверяют, равен ли экземпляр нулю, и получают истинный ответ, поэтому оба потока попытаются создать экземпляр вашего объекта. Поскольку этот код синхронизирован, один из этих потоков войдет в синхронизированный блок кода, а другой ожидает снятия блокировки. Когда первый поток завершит создание и возврат экземпляра вашего объекта, блокировка будет снята, а второй поток выполнит синхронизированный блок, поэтому он создаст новый экземпляр и вернет его, потому что он не знает, что он был ранее создан. пока он ждал своей очереди.
  2. Использование класса в качестве аргумента в synchronized вызовет статическую блокировку. Это означает, что все экземпляры класса будут разделять блокировку.
  3. Использование объекта в качестве аргумента для синхронизации полезно, если вы хотите заблокировать синхронизируемый блок с помощью определенного объекта вместо класса или this. Это позволяет использовать разные блоки кода, используя разные блокировки в одном классе. Например:

    Object o1 = new Object();
    Object o2 = new Object();
    synchronized(o1) {
        //your synchronized block
     }
    
    synchronized(o2){
        //other synchronized block
    }
    

В предыдущем примере кода блоки block1 и block2 могут выполняться одновременно разными потоками, поскольку они используют разные объекты блокировки. Если бы вы использовали одну и ту же блокировку для обоих блоков кода (т. Е. Класса), блок 1 был бы заблокирован до тех пор, пока блок 2 не завершит свое выполнение, и наоборот.

person Kuri    schedule 05.07.2019
comment
Это дает некоторую дезинформацию. Использование .class не то же самое, что отсутствие аргумента (никакой аргумент не действует на this в нестатическом контексте, поэтому синхронизация выполняется для каждого экземпляра, а не для каждого класса). Использование статического объекта - это не то же самое, что класс, поскольку к классу могут получить доступ и другие вещи, объект private устраняет этот риск. И их вариант 3. бессмысленен, поскольку каждая вещь будет синхронизироваться на своем собственном объекте, что равносильно отсутствию синхронизации. - person BeUndead; 06.07.2019