Вы знаете, что такое безопасность потоков, верно? Если нет, ниже приведен простой пример. Все классы должны быть потокобезопасными, верно? На самом деле, нет. Некоторые из них должны быть потокобезопасными? Опять не так. Я думаю, что ни один из них не должен быть потокобезопасным, в то время как все они должны предоставлять синхронизированные декораторы.
Начнем с примера (кстати, он изменчив ):
1
2
3
4
5
6
7
8
9
|
class Position { private int number = 0 ; @Override public void increment() { int before = this .number; int after = before + 1 ; this .number = after; } } |
Как вы думаете — это потокобезопасно ? Этот термин относится к тому, будет ли объект этого класса работать без ошибок при одновременном использовании несколькими потоками. Допустим, у нас есть два потока, работающие с одним и тем же объектом, position
и вызывающие его метод increment()
в один и тот же момент времени.
Мы ожидаем, что целое число будет равно 2, когда оба потока завершатся, потому что каждый из них будет увеличивать его один раз, верно? Однако, скорее всего, этого не произойдет.
Посмотрим что будет. В обоих потоках before
будет равен 0
при запуске. Затем after
будет установлен в 1
. Затем оба потока сделают this.number = 1
и мы получим number
1
вместо ожидаемых 2
. Видишь проблему? Классы с таким недостатком в дизайне не являются поточно-ориентированными .
Самое простое и очевидное решение — synchronized
наш метод. Это гарантирует, что независимо от того, сколько потоков вызовет его одновременно, все они будут идти последовательно, а не параллельно: один поток за другим. Конечно, это займет больше времени, но предотвратит эту ошибку:
1
2
3
4
5
6
7
8
9
|
class Position { private int number = 0 ; @Override public synchronized void increment() { int before = this .number; int after = before + 1 ; this .number = after; } } |
Класс, который гарантирует, что он не сломается, независимо от того, сколько потоков работает с ним, называется потокобезопасным .
Теперь возникает вопрос: должны ли мы сделать все классы потокобезопасными или только некоторые из них? Казалось бы, лучше, чтобы все классы были безошибочными, верно? Зачем кому-то нужен объект, который может сломаться в какой-то момент? Ну, не совсем так. Помните, что это связано с проблемой производительности; у нас не часто бывает несколько потоков, и мы всегда хотим, чтобы наши объекты работали как можно быстрее. Механизм синхронизации между потоками определенно замедлит нас.
Я думаю, что правильный подход — это два класса. Первый не является потокобезопасным, а другой является синхронизированным декоратором , который будет выглядеть следующим образом:
01
02
03
04
05
06
07
08
09
10
|
class SyncPosition implements Position { private final Position origin; SyncPosition(Position pos) { this .origin = pos; } @Override public synchronized void increment() { this .origin.increment(); } } |
Теперь, когда нам нужно, чтобы наш объект position
был потокобезопасным, мы украшаем его с помощью SyncPosition
:
1
2
3
|
Position position = new SyncPosition( new SimplePosition() ); |
Когда нам нужна простая простая позиция без какой-либо безопасности потока, мы делаем это:
1
|
Position position = new SimplePosition(); |
Создание функциональности класса как богатой, так и поточно-ориентированной, на мой взгляд, является нарушением этого знаменитого принципа единой ответственности .
Кстати, эта проблема очень близка к проблеме защитного программирования и валидаторов.
Вы можете также найти эти посты интересными: что вы делаете с InterruptedException? ; Почему дизайн InputStream неправильный ; Ограничить время выполнения Java-метода ; Как реализовать итерирующий адаптер ; Защитное программирование через валидацию декораторов ;
Ссылка: | Синхронизированные декораторы для замены многопоточных классов от нашего партнера JCG Егора Бугаенко в блоге About Programming . |