Что означает "синхронизированный"?
У меня есть несколько вопросов, касающихся использования и значения synchronized
ключевое слово.
- Какое значение имеет
synchronized
ключевое слово? - Когда должны быть методы
synchronized
? - Что это означает программно и логически?
15 ответов
synchronized
Ключевое слово - это все о чтении и записи разных потоков в одни и те же переменные, объекты и ресурсы. Это не тривиальная тема в Java, но вот цитата из Sun:
synchronized
методы обеспечивают простую стратегию предотвращения помех потоков и ошибок согласованности памяти: если объект виден более чем одному потоку, все операции чтения или записи в переменные этого объекта выполняются с помощью синхронизированных методов.
В двух словах: если у вас два потока, которые читают и пишут в один и тот же ресурс, произнесите переменную с именем foo
Вы должны убедиться, что эти потоки обращаются к переменной атомарным способом. Без synchronized
ключевое слово, ваша ветка 1 может не видеть ветку изменения 2, внесенную в foo
или, что еще хуже, он может быть изменен только наполовину. Это не будет то, что вы ожидаете логически.
Опять же, это нетривиальная тема в Java. Чтобы узнать больше, изучите темы здесь, в SO и Interwebs:
Продолжайте изучать эти темы, пока имя "Брайан Гетц" не станет постоянно ассоциироваться с термином "параллелизм" в вашем мозгу.
Ну, я думаю, у нас было достаточно теоретических объяснений, поэтому рассмотрим этот код
public class SOP {
public static void print(String s) {
System.out.println(s+"\n");
}
}
public class TestThread extends Thread {
String name;
TheDemo theDemo;
public TestThread(String name,TheDemo theDemo) {
this.theDemo = theDemo;
this.name = name;
start();
}
@Override
public void run() {
theDemo.test(name);
}
}
public class TheDemo {
public synchronized void test(String name) {
for(int i=0;i<10;i++) {
SOP.print(name + " :: "+i);
try{
Thread.sleep(500);
} catch (Exception e) {
SOP.print(e.getMessage());
}
}
}
public static void main(String[] args) {
TheDemo theDemo = new TheDemo();
new TestThread("THREAD 1",theDemo);
new TestThread("THREAD 2",theDemo);
new TestThread("THREAD 3",theDemo);
}
}
Замечания: synchronized
блокирует вызов следующего потока для метода test() до тех пор, пока выполнение предыдущего потока не завершено. Потоки могут обращаться к этому методу по одному. Без synchronized
все потоки могут получить доступ к этому методу одновременно.
Когда поток вызывает синхронизированный метод 'test' объекта (здесь объект является экземпляром класса 'TheDemo'), он получает блокировку этого объекта, любой новый поток не может вызвать ЛЮБОЙ синхронизированный метод того же объекта, пока предыдущий поток который приобрел замок, не снимает замок.
Подобное происходит, когда вызывается любой статический синхронизированный метод класса. Поток получает блокировку, связанную с классом (в этом случае любой нестатический синхронизированный метод экземпляра этого класса может быть вызван любым потоком, потому что эта блокировка уровня объекта все еще доступна). Любой другой поток не сможет вызвать какой-либо статический синхронизированный метод класса, если блокировка уровня класса не снята потоком, который в данный момент удерживает блокировку.
Выход с синхронизированным
THREAD 1 :: 0
THREAD 1 :: 1
THREAD 1 :: 2
THREAD 1 :: 3
THREAD 1 :: 4
THREAD 1 :: 5
THREAD 1 :: 6
THREAD 1 :: 7
THREAD 1 :: 8
THREAD 1 :: 9
THREAD 3 :: 0
THREAD 3 :: 1
THREAD 3 :: 2
THREAD 3 :: 3
THREAD 3 :: 4
THREAD 3 :: 5
THREAD 3 :: 6
THREAD 3 :: 7
THREAD 3 :: 8
THREAD 3 :: 9
THREAD 2 :: 0
THREAD 2 :: 1
THREAD 2 :: 2
THREAD 2 :: 3
THREAD 2 :: 4
THREAD 2 :: 5
THREAD 2 :: 6
THREAD 2 :: 7
THREAD 2 :: 8
THREAD 2 :: 9
Выход без синхронизации
THREAD 1 :: 0
THREAD 2 :: 0
THREAD 3 :: 0
THREAD 1 :: 1
THREAD 2 :: 1
THREAD 3 :: 1
THREAD 1 :: 2
THREAD 2 :: 2
THREAD 3 :: 2
THREAD 1 :: 3
THREAD 2 :: 3
THREAD 3 :: 3
THREAD 1 :: 4
THREAD 2 :: 4
THREAD 3 :: 4
THREAD 1 :: 5
THREAD 2 :: 5
THREAD 3 :: 5
THREAD 1 :: 6
THREAD 2 :: 6
THREAD 3 :: 6
THREAD 1 :: 7
THREAD 2 :: 7
THREAD 3 :: 7
THREAD 1 :: 8
THREAD 2 :: 8
THREAD 3 :: 8
THREAD 1 :: 9
THREAD 2 :: 9
THREAD 3 :: 9
synchronized
Ключевое слово предотвращает одновременный доступ к блоку кода или объекта несколькими потоками. По умолчанию Hashtable
является synchronized
Таким образом, только один поток может получить доступ к таблице одновременно.
На использование non-synchronized
конструкции как HashMap
Вы должны встроить функции безопасности потоков в свой код, чтобы предотвратить ошибки согласованности памяти.
synchronized
означает, что в многопоточной среде объект, имеющий synchronized
Метод (ы)/ блок (ы) не позволяют двум потокам получить доступ к synchronized
метод (ы)/ блок (ы) кода одновременно. Это означает, что один поток не может читать, а другой обновляет его.
Вместо этого второй поток будет ждать, пока первый поток завершит свое выполнение. Накладные расходы - это скорость, но преимущество - это согласованность данных.
Если ваше приложение однопоточное, хотя, synchronized
блоки не дает преимуществ.
synchronized
Ключевое слово заставляет поток получить блокировку при входе в метод, так что только один поток может выполнить метод одновременно (для данного экземпляра объекта, если это не статический метод).
Это часто называют деланием класса потокобезопасным, но я бы сказал, что это эвфемизм. Хотя верно то, что синхронизация защищает внутреннее состояние вектора от повреждения, это обычно не сильно помогает пользователю вектора.
Учти это:
if (vector.isEmpty()){
vector.add(data);
}
Несмотря на то, что используемые методы синхронизированы, поскольку они блокируются и разблокируются индивидуально, два, к сожалению, синхронизированных потока могут создать вектор с двумя элементами.
По сути, вы должны синхронизировать код приложения.
Поскольку синхронизация на уровне метода является а) дорогой, когда она вам не нужна, и б) недостаточной, когда вам нужна синхронизация, теперь есть несинхронизированные замены (ArrayList в случае Vector).
Совсем недавно был выпущен пакет для параллелизма с несколькими умными утилитами, которые решают проблемы с многопоточностью.
обзор
Синхронизированное ключевое слово в Java связано с безопасностью потоков, то есть когда несколько потоков читают или записывают одну и ту же переменную.
Это может произойти напрямую (путем доступа к той же переменной) или косвенно (при использовании класса, который использует другой класс, который обращается к той же переменной).
Ключевое слово synchronized используется для определения блока кода, в котором несколько потоков могут безопасно обращаться к одной и той же переменной.
Глубже
Синтаксис synchronized
Ключевое слово принимает Object
в качестве его параметра (называемого объектом блокировки), за которым следует { block of code }
,
Когда выполнение встречает это ключевое слово, текущий поток пытается "заблокировать / приобрести / владеть" (выбрать) заблокировать объект и выполнить связанный блок кода после того, как блокировка была получена.
Любые записи в переменные внутри блока синхронизированного кода гарантированно будут видны всем другим потокам, которые аналогичным образом выполняют код внутри блока синхронизированного кода, используя тот же объект блокировки.
Только один поток одновременно может удерживать блокировку, в течение которой все другие потоки, пытающиеся получить тот же самый объект блокировки, будут ожидать (приостановить их выполнение). Блокировка будет снята, когда выполнение выйдет из блока синхронизированного кода.
Синхронизированные методы:
Добавление synchronized
Ключевое слово для определения метода равно всему телу метода, помещенному в синхронизированный кодовый блок с объектом блокировки this
(например, методы) и ClassInQuestion.getClass()
(для методов класса).
- Метод экземпляра - это метод, который не имеет static
ключевое слово.
- Метод класса - это метод, который имеет static
ключевое слово.
технический
Без синхронизации не гарантируется, в каком порядке происходит чтение и запись, возможно, оставляя переменную с мусором.
(Например, переменная может закончиться с половиной битов, записанных одним потоком, и половиной битов, записанных другим потоком, оставляя переменную в состоянии, которое ни один из потоков не пытался записать, но объединенный беспорядок обоих.)
Недостаточно завершить операцию записи в потоке до (настенного времени), когда другой поток его прочитает, потому что аппаратное обеспечение могло бы кэшировать значение переменной, и поток чтения видел бы кэшированное значение вместо того, что было записано в Это.
Заключение
Таким образом, в случае с Java вы должны следовать модели памяти Java, чтобы избежать ошибок при многопоточности.
Другими словами: используйте синхронизацию, атомарные операции или классы, которые используют их для вас.
источники
http://docs.oracle.com/javase/specs/jls/se8/html/index.html
Спецификация языка Java®, 2015-02-13
Думайте об этом как о турникете, который вы можете найти на футбольном поле. Есть параллельные группы людей, желающих войти, но на турникете они "синхронизированы". Только один человек за раз может пройти. Все, кто хочет пройти, сделают, но, возможно, им придется подождать, пока они не смогут пройти.
Что такое синхронизированное ключевое слово?
Потоки взаимодействуют, главным образом, путем совместного доступа к полям и ссылкам на объекты, на которые ссылаются поля. Эта форма связи чрезвычайно эффективна, но допускает два вида ошибок: помехи потоков и ошибки согласованности памяти. Инструмент, необходимый для предотвращения этих ошибок - это синхронизация.
Синхронизированные блоки или методы предотвращают взаимодействие потоков и обеспечивают согласованность данных. В любой момент времени только один поток может получить доступ к синхронизированному блоку или методу (критическая секция), получив блокировку. Другие потоки будут ожидать снятия блокировки для доступа к критическому разделу.
Когда методы синхронизируются?
Методы синхронизируются при добавлении synchronized
к определению метода или объявлению. Вы также можете синхронизировать определенный блок кода с помощью метода.
Что это означает про грамматически и логически?
Это означает, что только один поток может получить доступ к критическому разделу, получив блокировку. Если этот поток не снимет эту блокировку, все остальные потоки должны будут ждать блокировки. У них нет доступа к критическому разделу без блокировки получения.
Это не может быть сделано с помощью магии. Программист обязан определить критические разделы в приложении и соответствующим образом их защитить. Java обеспечивает платформу для защиты вашего приложения, но где и какие все разделы должны охраняться, является обязанностью программиста.
Больше деталей со страницы документации Java
Внутренние блокировки и синхронизация:
Синхронизация строится вокруг внутренней сущности, известной как внутренняя блокировка или блокировка монитора. Внутренние блокировки играют роль в обоих аспектах синхронизации: обеспечение исключительного доступа к состоянию объекта и установление отношений "до того, как произойдет", которые важны для видимости.
С каждым объектом связан внутренний замок. По соглашению поток, которому требуется исключительный и согласованный доступ к полям объекта, должен получить внутреннюю блокировку объекта перед тем, как получить к ним доступ, а затем снять внутреннюю блокировку, когда это будет сделано с ними.
Говорят, что поток владеет внутренней блокировкой между моментом, когда он получил блокировку и снял ее. Пока потоку принадлежит внутренняя блокировка, никакой другой поток не может получить такую же блокировку. Другой поток заблокируется, когда попытается получить блокировку.
Когда поток освобождает внутреннюю блокировку, между этим действием и любым последующим получением той же блокировки устанавливается связь "до того".
Синхронизация методов имеет два эффекта:
Во-первых, невозможно выполнить два вызова синхронизированных методов для одного и того же объекта.
Когда один поток выполняет синхронизированный метод для объекта, все остальные потоки, которые вызывают синхронизированные методы для того же блока объекта (приостанавливают выполнение), пока первый поток не завершится с объектом.
Во-вторых, при выходе из синхронизированного метода он автоматически устанавливает отношение "до и после" с любым последующим вызовом синхронизированного метода для того же объекта.
Это гарантирует, что изменения состояния объекта видны всем потокам.
Ищите другие альтернативы синхронизации в:
Synchronized normal method
эквивалентноSynchronized statement
(использовать этот)
class A {
public synchronized void methodA() {
// all function code
}
equivalent to
public void methodA() {
synchronized(this) {
// all function code
}
}
}
Synchronized static method
эквивалентно Synchronized statement
(использовать класс)
class A {
public static synchronized void methodA() {
// all function code
}
equivalent to
public void methodA() {
synchronized(A.class) {
// all function code
}
}
}
Синхронизированный оператор (с использованием переменной)
class A {
private Object lock1 = new Object();
public void methodA() {
synchronized(lock1 ) {
// all function code
}
}
}
За synchronized
у нас есть оба Synchronized Methods
а также Synchronized Statements
, Тем не мение, Synchronized Methods
похож на Synchronized Statements
так что нам просто нужно понять Synchronized Statements
,
=> В основном мы будем иметь
synchronized(object or class) { // object/class use to provides the intrinsic lock
// code
}
Вот 2 думаю что поможет понимание synchronized
- Каждый объект / класс имеет
intrinsic lock
связано с этим. - Когда поток вызывает
synchronized statement
, он автоматически приобретаетintrinsic lock
для этогоsynchronized statement's
объект и освобождает его, когда метод возвращает. Пока нить владеетintrinsic lock
, НИКАКОЙ другой поток не может получить ЖЕ САМОЙ блокировочный => потокобезопасный.
=> Когда thread A
Запускает synchronized(this){// code 1}
=> весь код блока (внутри класса), где есть synchronized(this)
и все synchronized normal method
(внутри класса) заблокирован, потому что ЖЕ блокировка. Будет выполняться после thread A
разблокировать ("// код 1" закончен).
Это поведение похоже на synchronized(a variable){// code 1}
или же synchronized(class)
,
SAME LOCK => lock (не зависит от того, какой метод? Или какие операторы?)
Использовать синхронизированный метод или синхронизированные операторы?
я предпочитаю synchronized statements
потому что это более расширяемый. Например, в будущем вам нужно синхронизировать только часть метода. Например, у вас есть 2 синхронизированных метода, и они не имеют никакого отношения друг к другу, однако, когда поток запускает метод, он блокирует другой метод (это может предотвратить использование synchronized(a variable)
).
Однако применить синхронизированный метод очень просто, а код выглядит просто. Для некоторого класса существует только 1 синхронизированный метод или все синхронизированные методы в классе, имеющие отношение друг к другу => мы можем использовать synchronized method
сделать код короче и проще для понимания
Заметка
(это не имеет отношения к многим synchronized
, это отличается между объектом и классом или не статические и статические).
- Когда вы используете
synchronized
или нормальный метод илиsynchronized(this)
или жеsynchronized(non-static variable)
это будет синхронизировано основание на каждом экземпляре объекта. - Когда вы используете
synchronized
или статический метод илиsynchronized(class)
или жеsynchronized(static variable)
будет синхронизирована база по классу
Ссылка
https://docs.oracle.com/javase/tutorial/essential/concurrency/syncmeth.html https://docs.oracle.com/javase/tutorial/essential/concurrency/locksync.html
Надеюсь, это поможет
Я знаю, что вы уже получили свой ответ.
Я пишу это только для того, чтобы помочь людям, которые задают тот же вопрос и ищут ответ на этой странице.
Вот объяснение из документации Java:
Рассмотрим следующий код:
public class SynchronizedCounter {
private int c = 0;
public synchronized void increment() {
c++;
}
public synchronized void decrement() {
c--;
}
public synchronized int value() {
return c;
}
}
если count
это пример SynchronizedCounter
затем синхронизация этих методов имеет два эффекта:
- Во-первых, невозможно выполнить два вызова синхронизированных методов для одного и того же объекта. Когда один поток выполняет синхронизированный метод для объекта, все остальные потоки, которые вызывают синхронизированные методы для того же блока объекта (приостанавливают выполнение), пока первый поток не завершится с объектом.
- Во-вторых, при выходе из синхронизированного метода он автоматически устанавливает отношение "до и после" с любым последующим вызовом синхронизированного метода для того же объекта. Это гарантирует, что изменения состояния объекта видны всем потокам.
Насколько я понимаю, синхронизация в основном означает, что компилятор напишет monitor.enter и monitor.exit вокруг вашего метода. Таким образом, он может быть потокобезопасным в зависимости от того, как он используется (я имею в виду, что вы можете написать объект с синхронизированными методами, который не является потокобезопасным, в зависимости от того, что делает ваш класс).
Чего не хватает другим ответам, так это одного важного аспекта: барьеров памяти. Синхронизация потоков в основном состоит из двух частей: сериализации и видимости. Я советую всем использовать Google для "барьера памяти jvm", так как это нетривиальная и чрезвычайно важная тема (если вы изменяете общие данные, к которым обращаются несколько потоков). Сделав это, я советую взглянуть на классы пакета java.util.concurrent, которые помогают избежать использования явной синхронизации, что, в свою очередь, помогает сохранять программы простыми и эффективными, возможно, даже предотвращает взаимные блокировки.
Одним из таких примеров является ConcurrentLinkedDeque. Вместе с шаблоном команд он позволяет создавать высокоэффективные рабочие потоки путем помещения команд в параллельную очередь - не требуется явная синхронизация, невозможны взаимоблокировки, не требуется явный сон (), просто опросите очередь, вызвав take().
Вкратце: "синхронизация памяти" происходит неявно, когда вы запускаете поток, поток заканчивается, вы читаете энергозависимую переменную, разблокируете монитор (оставляете синхронизированный блок / функцию) и т. Д. Эта "синхронизация" влияет (в некотором смысле "сбрасывает" ") все записи сделаны до этого конкретного действия. В случае вышеупомянутого ConcurrentLinkedDeque документация "говорит":
Эффекты согласованности памяти: Как и в случае других параллельных коллекций, действия в потоке перед помещением объекта в ConcurrentLinkedDeque выполняются перед выполнением действий, следующих за доступом или удалением этого элемента из ConcurrentLinkedDeque в другом потоке.
Это неявное поведение является несколько пагубным аспектом, потому что большинство Java-программистов без особого опыта просто примут из-за этого многое. А затем внезапно наткнуться на этот поток после того, как Java не выполняет то, что она "должна" делать в рабочей среде, где существует другая рабочая нагрузка - и довольно сложно протестировать проблемы параллелизма.
В java, чтобы предотвратить манипулирование общей переменной несколькими потоками, мы используем synchronized
ключевое слово. Давайте разберемся с этим на следующем примере:
В этом примере я определил два потока и назвал их инкремент и декремент. Приращение потока увеличивает значение общей переменной (counter
) на ту же величину поток декремента уменьшает его, т.е. в 5000 раз увеличивается (что дает 5000 + 0 = 5000) и в 5000 раз уменьшается (что приводит к 5000 - 5000 = 0).
Программа без synchronized
ключевое слово:
class SynchronizationDemo {
public static void main(String[] args){
Buffer buffer = new Buffer();
MyThread incThread = new MyThread(buffer, "increment");
MyThread decThread = new MyThread(buffer, "decrement");
incThread.start();
decThread.start();
try {
incThread.join();
decThread.join();
}catch(InterruptedException e){ }
System.out.println("Final counter: "+buffer.getCounter());
}
}
class Buffer {
private int counter = 0;
public void inc() { counter++; }
public void dec() { counter--; }
public int getCounter() { return counter; }
}
class MyThread extends Thread {
private String name;
private Buffer buffer;
public MyThread (Buffer aBuffer, String aName) {
buffer = aBuffer;
name = aName;
}
public void run(){
for (int i = 0; i <= 5000; i++){
if (name.equals("increment"))
buffer.inc();
else
buffer.dec();
}
}
}
Если мы запустим вышеуказанную программу, мы ожидаем, что значение буфера будет таким же, поскольку увеличение и уменьшение буфера на одно и то же количество приведет к получению начального значения, с которого мы начали правильно?. Давайте посмотрим на результат:
Как видите, независимо от того, сколько раз мы запускаем программу, мы получаем разные результаты, потому что каждый поток манипулирует counter
в то же время. Если бы нам удалось позволить одному потоку сначала увеличить общую переменную, а затем, во-вторых, уменьшить ее, или наоборот, мы получим правильный результат, который можно сделать сsynchronized
ключевое слово, просто добавив synchronized
ключевое слово перед inc
а также dec
методы Buffer
нравится:
Программа с synchronized
ключевое слово:
// rest of the code
class Buffer {
private int counter = 0;
// added synchronized keyword to let only one thread
// be it inc or dec thread to manipulate data at a time
public synchronized void inc() { counter++; }
public synchronized void dec() { counter--; }
public int getCounter() { return counter; }
}
// rest of the code
и вывод:
независимо от того, сколько раз мы запускаем его, мы получаем тот же результат, что и 0
Синхронизированный означает просто, что несколько потоков, если они связаны с одним объектом, могут предотвратить грязное чтение и запись, если синхронизированный блок используется для определенного объекта. Чтобы дать вам больше ясности, давайте возьмем пример:
class MyRunnable implements Runnable {
int var = 10;
@Override
public void run() {
call();
}
public void call() {
synchronized (this) {
for (int i = 0; i < 4; i++) {
var++;
System.out.println("Current Thread " + Thread.currentThread().getName() + " var value "+var);
}
}
}
}
public class MutlipleThreadsRunnable {
public static void main(String[] args) {
MyRunnable runnable1 = new MyRunnable();
MyRunnable runnable2 = new MyRunnable();
Thread t1 = new Thread(runnable1);
t1.setName("Thread -1");
Thread t2 = new Thread(runnable2);
t2.setName("Thread -2");
Thread t3 = new Thread(runnable1);
t3.setName("Thread -3");
t1.start();
t2.start();
t3.start();
}
}
Мы создали два объекта класса MyRunnable, runnable1 совместно используется с потоком 1, а поток 3 и runnable2 совместно используются только с потоком 2. Теперь, когда t1 и t3 начинаются без использования синхронизации, вывод PFB предполагает, что оба потока 1 и 3 одновременно влияют на значение var, где для потока 2 var имеет свою собственную память.
Without Synchronized keyword
Current Thread Thread -1 var value 11
Current Thread Thread -2 var value 11
Current Thread Thread -2 var value 12
Current Thread Thread -2 var value 13
Current Thread Thread -2 var value 14
Current Thread Thread -1 var value 12
Current Thread Thread -3 var value 13
Current Thread Thread -3 var value 15
Current Thread Thread -1 var value 14
Current Thread Thread -1 var value 17
Current Thread Thread -3 var value 16
Current Thread Thread -3 var value 18
Используя Synchronzied, поток 3 ожидает завершения потока 1 во всех сценариях. Получены две блокировки: одна для runnable1, совместно используемая потоком 1 и потоком 3, а другая для runnable2, совместно используемая потоком 2.
Current Thread Thread -1 var value 11
Current Thread Thread -2 var value 11
Current Thread Thread -1 var value 12
Current Thread Thread -2 var value 12
Current Thread Thread -1 var value 13
Current Thread Thread -2 var value 13
Current Thread Thread -1 var value 14
Current Thread Thread -2 var value 14
Current Thread Thread -3 var value 15
Current Thread Thread -3 var value 16
Current Thread Thread -3 var value 17
Current Thread Thread -3 var value 18
volatile
[О себе] =>synchronized
synchronized
блок в Java - это монитор в многопоточности. synchronized
блок с тем же объектом / классом может быть выполнен только одним потоком, все остальные ждут. Это может помочь сrace condition
ситуация, когда несколько потоков пытаются обновить одну и ту же переменную.
Java 5
расширенный synchronized
поддерживая happens-before
[Около]
Разблокировка (синхронизированный блок или выход метода) монитора происходит перед каждой последующей блокировкой (синхронизированным блоком или входом метода) того же самого монитора.
Следующий шаг java.util.concurrent
Синхронизированный простой означает, что никакие два потока не могут получить доступ к блоку / методу одновременно. Когда мы говорим, что любой блок / метод класса синхронизирован, это означает, что только один поток может получить к ним доступ одновременно. Внутренне поток, который пытается получить к нему доступ, сначала блокирует этот объект, и пока эта блокировка недоступна, никакой другой поток не может получить доступ ни к одному из синхронизированных методов / блоков этого экземпляра класса.
Обратите внимание, что другой поток может получить доступ к методу того же объекта, который не определен для синхронизации. Поток может снять блокировку, вызвав
Object.wait()
Синхронизированный - это ключевое слово в Java, которое используется для того, чтобы происходило до взаимосвязи в многопоточной среде, чтобы избежать несогласованности памяти и ошибки вмешательства потока.