Я не понимаю, почему неизменяемые объекты в Java всегда наследуют потокобезопасность

Я читаю книгу "Эффективная Java" и главу, посвященную минимизации изменчивости. Пункт 15. Возможно, у меня возникли проблемы с пониманием концепции безопасности потоков, так как я не очень опытен в параллелизме. Могу ли я получить пример, который иллюстрирует, как неизменный объект всегда потокобезопасен?

Заранее спасибо!

4 ответа

Решение

Неизменяемые объекты являются потокобезопасными, потому что они не могут быть изменены.

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

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

Имейте в виду, темы могут делиться ссылками. Если вы изменяете, на какой объект указывает ссылка (не изменяя сам объект, а переназначая ссылку на другой объект вместе со знаком =), то безопасность потока подвергается опасности.

Вы можете сделать объект неизменным, удалив все сеттеры и любые методы, которые изменяют состояние объекта.

Строка является примером неизменяемого объекта. Независимо от того, сколько потоков имеет доступ к строке, они не могут ее изменить. Каждый раз, когда вы изменяете строку, создается новый объект.

Таким образом, несколько потоков могут читать состояние, но никогда не могут обновлять состояние.

Скажем, у вас есть счетчик:

class Counter {
    private int counter = 0;

    public void increment() {
        counter++;
    }

    public int getCounter() {
        return counter;
    }

}

И скажи, что это твой основной метод:

public static void main(String[] args) {
    final Counter counter = new Counter();
    final CountDownLatch startLatch = new CountDownLatch(1);
    final CountDownLatch endLatch = new CountDownLatch(4);
    final Runnable r = () -> {
        try {
            startLatch.await();
        } catch (final InterruptedException e) {
            e.printStackTrace();
        }
        for (int i = 0; i < 100; i++) {
            counter.increment();
            if (counter.getCounter() % 10 == 0) {
                System.out.println(counter.getCounter());
            }
        }
        endLatch.countDown();
    };
    new Thread(r).start();
    new Thread(r).start();
    new Thread(r).start();
    new Thread(r).start();
    startLatch.countDown();
    try {
        endLatch.await();
    } catch (final InterruptedException e) {
        e.printStackTrace();
    }
}

Это долго, но в основном все, что он делает, это создает единый Counter, а затем создает 4 потока, которые увеличивают счетчик в сто раз каждый, и печатает значение счетчика, если значение кратно 20. Какой вывод вы получаете?

20
40
60
80
100
141 // <-- Huh? Not a multiple of 20?
120 // <-- What's up with the order here?
180
220
240
160 // <-- This is way out of place...
280
300
260
200
320
340
360
380
    // <-- missing 400?

Ну, это сюрприз. Неправильные ценности, ценности неуместны и т. Д.

Дело в том, что совместное использование объектов с изменяемым состоянием вроде Counter представляет много трудностей. Вы должны иметь дело с блокировками, синхронизацией и т. Д., Чтобы заставить изменчивый объект вести себя правильно. В этом случае синхронизация относительно проста, но сделать правильную синхронизацию сложных объектов сложно. Посмотрите на классы в java.util.concurrent если вы хотите пример.

Хорошая вещь о неизменных объектах состоит в том, что они избегают этой проблемы, потому что они не могут быть изменены. Поэтому независимо от того, сколько потоков что-то делает с неизменным объектом, вы можете быть абсолютно уверены, что он не изменится, поэтому вы не будете иметь дело с такими странными результатами, как этот. Неизменный Counter было бы довольно бесполезно, но что-то вроде String это является неизменным и может быть разделено между потоками, не беспокоясь о синхронизации изменений между потоками, очень полезно в параллельном мире.

Другие вопросы по тегам