Доказательство следующего кода не является потокобезопасным - PullRequest
12 голосов
/ 09 марта 2010

Как я могу быстро доказать, что следующий класс не является потокобезопасным (так как он использует Lazy Initialization и не использует синхронизацию), написав некоторый код? Другими словами, если я тестирую следующий класс на предмет безопасности потоков, как я могу его потерпеть?

public class LazyInitRace {
  private ExpensiveObject instance = null;

  public ExpensiveObject getInstance() {
     if (instance == null)
        instance = new ExpensiveObject();
    return instance;
  }
}

Ответы [ 8 ]

15 голосов
/ 09 марта 2010

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

Кстати, все это на самом деле не является «доказательством». Формальная проверка будет, но очень, очень трудно сделать, даже для относительно небольшого количества кода.

13 голосов
/ 09 марта 2010

Можете ли вы заставить ExpensiveObject занять много времени, чтобы построить в вашем тесте? Если это так, просто вызовите getInstance() дважды из двух разных потоков, за достаточно короткое время, чтобы первый конструктор не завершил работу до того, как будет сделан второй вызов. В итоге вы получите два разных экземпляра, в которых вы должны потерпеть неудачу.

Сделать наивную двойную проверку блокировки не удастся, заметьте ... (даже если без указания volatile для переменной небезопасно).

5 голосов
/ 09 марта 2010

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

| Thread 1              | Thread 2              |
|-----------------------|-----------------------|
| **start**             |                       |
| getInstance()         |                       |
| if(instance == null)  |                       |
| new ExpensiveObject() |                       |
| **context switch ->** | **start**             |
|                       | getInstance()         |
|                       | if(instance == null)  | //instance hasn't been assigned, so this check doesn't do what you want
|                       | new ExpensiveObject() |
| **start**             | **<- context switch** |
| instance = result     |                       |
| **context switch ->** | **start**             |
|                       | instance = result     |
|                       | return instance       |
| **start**             | **<- context switch** |
| return instance       |                       |
3 голосов
/ 09 марта 2010

Поскольку это Java, вы можете использовать библиотеку thread-weaver для вставки пауз или разрывов в ваш код и управления несколькими потоками выполнения. Таким образом, вы можете получить медленный конструктор ExpensiveObject, не изменяя код конструктора, как другие (правильно) предложили.

2 голосов
/ 09 марта 2010

Хорошо ... Результат этого кода будет ложным, если вы ожидаете истинное значение.

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class LazyInitRace {

    public class ExpensiveObject {
        public ExpensiveObject() {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
            }
        }
    }

    private ExpensiveObject instance = null;

    public ExpensiveObject getInstance() {
        if (instance == null)
            instance = new ExpensiveObject();
        return instance;
    }

    public static void main(String[] args) {
        final LazyInitRace lazyInitRace = new LazyInitRace();

        FutureTask<ExpensiveObject> target1 = new FutureTask<ExpensiveObject>(
                new Callable<ExpensiveObject>() {

                    @Override
                    public ExpensiveObject call() throws Exception {
                        return lazyInitRace.getInstance();
                    }
                });
        new Thread(target1).start();

        FutureTask<ExpensiveObject> target2 = new FutureTask<ExpensiveObject>(
                new Callable<ExpensiveObject>() {

                    @Override
                    public ExpensiveObject call() throws Exception {
                        return lazyInitRace.getInstance();
                    }
                });
        new Thread(target2).start();

        try {
            System.out.println(target1.get() == target2.get());
        } catch (InterruptedException e) {
        } catch (ExecutionException e) {
        }
    }
}
0 голосов
/ 09 марта 2010

Вы можете легко доказать это с помощью отладчика.

  1. Напишите программу, которая вызывает getInstance () на двух отдельных резьб.
  2. Установить точку останова на строительстве из дорогого объекта. Удостовериться отладчик только приостановит нить, а не ВМ.
  3. Тогда, когда первый поток останавливается на точка останова, оставьте ее приостановленной.
  4. Когда второй поток останавливается, вы просто продолжаете.
  5. Если вы проверите результат вызов getInstance () для обоих потоков, они будут ссылаться на разные экземпляры.

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

0 голосов
/ 09 марта 2010

Поместите действительно длинный расчет в конструктор:

public ExpensiveObject()
{
    for(double i = 0.0; i < Double.MAX_VALUE; ++i)
    {
        Math.pow(2.0,i);
    }
}

Возможно, вы захотите уменьшить условие завершения до Double.MAX_VALUE/2.0 или поделить на большее число, если MAX_VALUE занимает слишком много времени для вашего вкуса.

0 голосов
/ 09 марта 2010

Ну, это не потокобезопасно. Проверка безопасности потока случайна, но довольно проста:

  1. Сделать конструктор ExорогоObject полностью безопасным:

    Синхронизированный ExорогоObject () {...

  2. Поместить в код конструктора, который проверяет, существует ли другая копия объекта, - затем вызвать исключение.

  3. Создать потокобезопасный метод для очистки переменной 'instance'

  4. Поместить последовательный код getInstance / clearInstance в цикл для выполнения несколькими потоками и ожидания исключения из (2)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...