0 Предисловие
Проблемы с безопасностью потока не проявляются в одном потоке, а в многопоточном программировании может проявляться одновременный доступ к одному и тому же потоку.Общие переменные ресурсыВ случае с этим ресурсом этот ресурс может быть:Переменная, объект, файл и т.д.. Обратите особое внимание на два момента:
- общий: означает, что к ресурсу могут обращаться несколько потоков одновременно;
- Переменная: означает, что ресурс может быть изменен во время его жизни;
Проще говоря,Ваш код является потокобезопасным, если он всегда дает те же результаты при выполнении в одном потоке, что и при выполнении в нескольких потоках.. Итак, с какими требованиями безопасности потоков мы сталкиваемся при многопоточном программировании? Как это решить?
1 Функции безопасности резьбы
1.1 Атомарность
С концепцией атомасности транзакции базы данных почти,То есть операция (которая может содержать несколько подопераций) либо выполняется полностью (действует), либо ни одна из них не выполняется (ни не вступают в силу)..
Очень классическим примером атомарности является проблема банковского перевода:
Например: A и B переводят 100 000 юаней C одновременно. Если операция перевода не является атомарной, когда A переводит деньги C, он считывает баланс C как 200 000, а затем добавляет 100 000 перевода и вычисляет, что в это время должно быть 300 000, но это все еще в будущем. и пишет 300 000 Обратно на счет С, когда пришел запрос на перевод от Б, Б обнаружил, что баланс С 200 000, затем добавил к нему 100 000 и отписал обратно. Затем операция перевода А продолжается - 300 000 записываются обратно на баланс С. Окончательный баланс C в этом случае составляет 300 000 вместо ожидаемых 400 000.
1.2 Видимость
Видимость означает,Когда несколько потоков одновременно обращаются к общей переменной, изменение общей переменной одним потоком может быть немедленно замечено другими потоками.. Проблема видимости — это то, что многие люди упускают из виду или неправильно понимают.
Эффективность процессора при чтении данных из основной памяти относительно невысока.В обычных компьютерах имеется несколько уровней кэш-памяти.Когда каждый поток читает общую переменную, он загружает переменную в соответствующий кеш ЦП.После изменения переменной ЦП немедленно обновляет кеш, но не обязательно немедленно записывает ее обратно в основную память (фактически непредсказуемое время для обратной записи в основную память). объем памяти). В это время, когда другие потоки (особенно потоки, не выполняющиеся на том же ЦП) обращаются к переменной, старые данные считываются из основной памяти, а не обновленные данные первого потока.
Это операционная система или механизм на аппаратном уровне.Многие разработчики приложений часто остаются без внимания.
1.3 Порядок
Упорядоченный означает, чтоПорядок выполнения программы выполняется в порядке выполнения кода. В качестве примера возьмем следующий код:
boolean started = false; // 语句1
long counter = 0L; // 语句2
counter = 1; // 语句3
started = true; // 语句4
С точки зрения порядка кода, вышеприведенные четыре оператора должны выполняться последовательно, но на самом деле, когда JVM фактически выполняет этот код, это не гарантирует, что они должны выполняться именно в этом порядке.
Чтобы повысить общую эффективность выполнения программы, процессор может оптимизировать код.Один из методов оптимизации состоит в корректировке порядка кода и выполнении кода в более эффективном порядке..
Говоря об этом, кто-то должен беспокоиться — что, ЦП не выполняет код в порядке моего кода, так как мы можем гарантировать желаемый эффект? На самом деле, вы можете быть уверены, чтоХотя ЦП не гарантирует, что она будет выполняться полностью в порядке кода, он гарантирует, что окончательный результат выполнения программы будет таким же, как и результат выполнения в порядке кода..
2 проблемы безопасности потоков
2.1 Условия гонки и критические участки
Пространство кучи распределяется между потоками, и необходимо соблюдать особую осторожность, чтобы избежать состояния гонки при программировании. Опасность заключается в том, что несколько потоков одновременно обращаются к одному и тому же ресурсу и выполняют операции чтения и записи.Перед одним из потоков необходимо выполнить операцию в соответствии с состоянием переменной, переменная, вероятно, была изменена другими потоками..
То есть, когда два потока конкурируют за один и тот же ресурс, если порядок доступа к ресурсу чувствителен, говорят, что он существует.состояние гонки. Код, вызывающий состояние гонки, называетсякритическая секция.
/**
* 以下这段代码就存在竞态条件,其中return ++count就是临界区。
*/
public class Obj
{
private int count;
public int incr()
{
return ++count;
}
}
2.2 Тупик
Мертвый замок:Относится к явлению, при котором два или более процессов (или потоков) ждут друг друга из-за конкуренции за ресурсы во время процесса выполнения.Если нет внешней силы, они не смогут продвигаться вперед.. В это время говорят, что система находится в состоянии взаимоблокировки или в системе существует взаимоблокировка, и эти процессы, которые всегда ждут друг друга, называются процессами взаимоблокировки.
Относительно условий возникновения взаимоблокировки:
- взаимоисключающее условие: доступ потока к ресурсам является эксклюзивным.Если поток занимает ресурс, другие потоки должны ждать, пока ресурс не будет освобожден.
- Условия запроса и удержания: Поток T1 удержал хотя бы один ресурс R1 занятым, но сделал запрос на другой ресурс R2.В это время ресурс R2 занят другим потоком T2, поэтому поток T1 также должен ждать, но он также запрашивает ресурс R1, который он поддерживает. Не выпускайте.
- отсутствие депривации: Ресурс, полученный потоком, не может быть лишен другими потоками до того, как он будет израсходован, и может быть освобожден сам по себе только после того, как он будет израсходован.
- условие ожидания цикла: при возникновении взаимоблокировки должна быть «кольцевая цепочка процесс-ресурс», то есть:
{p0,p1,p2,...pn}
, процесс p0 (или поток) ожидает ресурсов, занятых p1, p1 ожидает ресурсов, занятых p2, а pn ожидает ресурсов, занятых p0.(Наиболее интуитивно понятно, что p0 ожидает ресурсов, занятых p1, а p1 ожидает ресурсов, занятых p0, поэтому два процесса ждут друг друга).
2.3 Живая блокировка
Livelock: Это означает, что поток 1 может использовать ресурсы, но это вежливо, пусть другие потоки используют ресурсы первыми, а поток 2 также может использовать ресурсы, но это бережно, а также позволяет другим потокам использовать ресурсы первыми.Таким образом, вы позволяете мне, я разрешаю вам, последние два потока не могут использовать ресурс.
Метафора «тупик и тупик»:
тупик: Встречные машины A и B пересекают дорогу, а машина A получает ресурсы на полпути (Условие возникновения тупика 1 выполнено: доступ к ресурсам эксклюзивный, вы не можете подъехать, если я занимаю дорогу, если только вы не лезете мне на голову вверх) , автомобиль B занимает ресурсы другой половины дороги автомобиля A, и A считает, что в прошлом он должен запросить другую половину дороги, занятую B (условие возникновения тупика 2: пространство всего тела можно проехать, и я уже занял ее Половину, другую половину дороги Нимы занимает Б), если Б хочет проехать, он должен ждать, пока А уступит дорогу, А - Lamborghini, B - диаоси с Chery QQ качество у A относительно низкое, и он открывает окно и ругает B: Дай скорее я ухожу с дороги, B очень зол, твоя мать заставила меня, я не позволю (тупиковое состояние 3: его не могут лишить другие потоки до того, как ресурсы будут израсходованы), поэтому два потока заблокированы друг с другом, и ни один из них не может уйти (возникает условие тупика 4: состояние ожидания цикла), и последующие транспортные средства на всей дороге не могут двигаться.
живой замок: Посередине дороги есть небольшой мост, по которому может проехать только одна машина. По обоим концам моста едут две машины А и В. А более вежливо показывает, что В проедет первым, а Б также более вежлив, показывая, что А пройдет первым, смиренно не пропуская никого.
2.4 Голод
Голодание: это означает, что если поток T1 занимает ресурс R, поток T2 снова запрашивает блокировку R, поэтому T2 ждет. Т3 также запрашивает ресурс R. Когда Т1 снимает блокировку с R, система сначала одобряет запрос Т3, а Т2 все еще ждет. Затем T4 снова запросил блокировку R. Когда T3 снял блокировку с R, система снова одобрила запрос T4...,T2 всегда может подождать.
То есть, если поток не получает время выполнения ЦП, потому что все время ЦП занято другими потоками,Это состояние называется «голодание». И поток «умирает от голода» именно потому, что у него нет возможности запустить процессор..
Притчи «Голод»:
Однажды в «первом пробке» в Пекине погода была мрачной, а воздух был полон смога и запаха оттупочного масла. Трудолюбивый временный трафик полицейский был иметь дело с пробкой. Два полоса A и B Человеки были заклинены транспортными средствами., Среди них дорога A заблокирована в течение самого длинного времени, и Road B относительно заблокирован для относительно короткого времени. В это время дорога впереди. Согласно принципу оптимального распределения, ГИБД ГИБ-ДВИГАТЕЛ сообщила, что транспортные средства на дороге B должны пройти первым, а транспортные средства на дороге B проходили один за другим., самое длинное время очереди на дороге не может пройти, вы можете только дождаться, когда полиция трафика выпускает приказ, чтобы позволить Дорожный путь в свою очередь, когда на B дороге B, не проезжают автомобиль.Это несправедливый механизм блокировки, предусмотренный в блокировке дисплея ReentrantLock.(Конечно, ReentrantLock также предоставляет механизм справедливой блокировки, и пользователь решает, какую стратегию блокировки использовать в соответствии с конкретным сценарием использования).Несправедливая блокировка может улучшить пропускную способность, но неизбежно приведет к голоданию некоторых потоков..
В Java есть три распространенные причины голодания потока:
-
Потоки с высоким приоритетом потребляют все процессорное время потоков с низким приоритетом.
Вы можете установить отдельный приоритет потока для каждого потока, чем выше приоритет потока, тем больше значение приоритета потока установлено между 1 и 10, а точная интерпретация этих значений приоритета зависит от платформы, на которой работает ваше приложение.Для большинства приложений лучше не менять значение приоритета..
-
Поток навсегда заблокирован в состоянии ожидания входа в синхронизированный блок, потому что другие потоки всегда могут продолжить доступ к синхронизированному блоку до него.
Синхронизированная область кода Java также является фактором голодания. Синхронизированная область кода Java не дает никаких гарантий в отношении порядка, в котором разрешен вход потоков.Это означает, что теоретически существует риск того, что поток, пытающийся войти в область синхронизации, будет навсегда заблокирован., потому что другие потоки могут последовательно получить доступ до него, что является проблемой «голода»,В то время как поток "голоден до смерти" именно потому, что это не возможность процессорного времени.
-
Поток ожидает объект, который сам (вызывая функцию wait()) также постоянно ожидает завершения, потому что другие потоки всегда бодрствуют.
Если несколько потоков выполняют метод wait(),И вызов notify() не гарантирует, какой поток будет разбужен, любой поток может находиться в состоянии ожидания.. Так что есть такой риск:Ожидаемый поток никогда не просыпается, потому что всегда можно разбудить другие ожидающие потоки..
2.5 Справедливость
Решение проблемы голодания называется «справедливостью», т. е. все потоки получают равные шансы на выполнение.. Для реализации схемы справедливости в Java требуется:
- использовать блокировки, а не синхронизированные блоки;
- использовать честный замок;
- Обратите внимание на производительность;
Справедливость в Яве, хотя Java невозможна достичь 100% справедливости,Структура синхронизации все еще может достичь большего капитала между нитями.
Во-первых, давайте изучим простой код синхронного состояния:
public class Synchronizer{
public synchronized void doSynchronized () {
// do a lot of work which takes a long time
}
}
Если несколько потоков вызывают метод doSynchronized(), другие потоки будут заблокированы до тех пор, пока первый поток, получивший доступ, не будет завершен, и в этом многопоточном заблокированном сценарииНе гарантируется, какой поток получит доступ следующим.
изменить наИспользуйте блокировки вместо синхронизированных блоков, чтобы повысить справедливость ожидающих потоков, мы используем блокировки вместо синхронизированных блоков:
public class Synchronizer{
Lock lock = new Lock();
public void doSynchronized() throws InterruptedException{
this.lock.lock();
//critical section, do a lot of work which takes a long time
this.lock.unlock();
}
}
Обратите внимание, что doSynchronized() больше не объявляется как синхронизированный,Вместо этого используйте lock.lock() и lock.unlock(). Вот реализация с использованием класса Lock:
public class Lock{
private boolean isLocked = false;
private Thread lockingThread = null;
public synchronized void lock() throws InterruptedException{
while(isLocked){
wait();
}
isLocked = true;
lockingThread = Thread.currentThread();
}
public synchronized void unlock(){
if(this.lockingThread != Thread.currentThread()){
throw new IllegalMonitorStateException("Calling thread has not locked this lock");
}
isLocked = false;
lockingThread = null;
notify();
}
}
Обратите внимание на приведенную выше реализацию Lock,Если есть несколько потоков одновременно доступа (), нить будет блокировать доступ к способу блокировки (). Кроме того, если блокировка уже заблокирована (примечание по корректуре: это относится к случаю, когда isLocked равно true),Эти потоки будут блокироваться вызовом wait() цикла while(isLocked). Следует помнить, что,Когда поток ожидает ввода блокировки(), он может вызвать функцию ожидания(), чтобы снять блокировку синхронизации, соответствующую его экземпляру блокировки, чтобы другие потоки могли войти в метод блокировки() и вызвать метод ожидания()..
Глядя на дозинхронизированные () на этот раз, вы заметите примечание между замком () и разблокировкой (): код между этими двумя вызовами будет работать в течение длительного времени. Далее представьте, что этот код будет работать в течение длительного времени, по сравнению с вводом блокировки () и вызова ожидания ().Это означает, что большую часть времени в Wait() используется ожидание в Wait() в процессе ожидания входа в блокировку и входа в критическую область, а не в блокировке при попытке входа методом LOCK()..
Как упоминалось ранее, синхронизированные блоки не дают никаких гарантий относительно того, кто может получить доступ к нескольким потокам, ожидающим входа.Точно так же, когда вызывается notify(), функция wait() не гарантирует, что она разбудит поток.. Следовательно, нет никакой разницы между этой версией класса Lock и версией doSynchronized() с точки зрения обеспечения справедливости.
Но мы можем изменить эту ситуацию следующим образом:
Текущие версии класса блокировки, чтобы вызвать их метод ждать (), если каждая нить вызовы ожидают () на разных объектах, то только один нить вызывает ожидание () на объекте, класс блокировки может решить, какие объекты могут вызывать уведомление ), и поэтому можно эффективно выбирать, какие потоки просыпаются.
Превратим приведенный выше класс Lock в честный замок FairLock.. Вы заметите, что синхронизация и wait()/notify() в новой реализации немного отличаются от предыдущего класса Lock. Дело в том,Каждый поток, который вызывает lock(), войдет в очередь, при разблокировке только первый поток в очереди может заблокировать экземпляр FairLock, все остальные потоки будут ждать, пока они не окажутся во главе очереди.. следующее:
public class FairLock {
private boolean isLocked = false;
private Thread lockingThread = null;
private List<QueueObject> waitingThreads = new ArrayList<QueueObject>();
public void lock() throws InterruptedException{
// 当前线程创建“令牌”
QueueObject queueObject = new QueueObject();
boolean isLockedForThisThread = true;
synchronized(this){
// 所有线程的queueObject令牌,入队
waitingThreads.add(queueObject);
}
while(isLockedForThisThread){
synchronized(this){
// 1. 判断是否已被锁住:是否已有线程获得锁,正在执行同步代码块
// 2. 判断头部令牌与当前线程令牌是否一致:也就是只锁住头部令牌对应的线程;
isLockedForThisThread = isLocked || waitingThreads.get(0) != queueObject;
if(!isLockedForThisThread){
isLocked = true;
// 移除头部令牌
waitingThreads.remove(queueObject);
lockingThread = Thread.currentThread();
return;
}
}
try{
// 其他线程执行doWait(),进行等待
queueObject.doWait();
}catch(InterruptedException e){
synchronized(this) { waitingThreads.remove(queueObject); }
throw e;
}
}
}
public synchronized void unlock(){
if(this.lockingThread != Thread.currentThread()){
throw new IllegalMonitorStateException("Calling thread has not locked this lock");
}
isLocked = false;
lockingThread = null;
if(waitingThreads.size() > 0) {
// 唤醒头部令牌对应的线程,可以执行
waitingThreads.get(0).doNotify();
}
}
}
public class QueueObject {
private boolean isNotified = false;
public synchronized void doWait() throws InterruptedException {
while(!isNotified){
this.wait();
}
this.isNotified = false;
}
public synchronized void doNotify() {
this.isNotified = true;
this.notify();
}
public boolean equals(Object o) {
return this == o;
}
}
Прежде всего обратите внимание, что метод lock() больше не объявляется как синхронизированный.Вместо этого код, который должен быть синхронизирован, вложен в синхронизированный.
FairLock создает новый экземпляр QueueObject и ставит в очередь каждый поток, который вызывает lock(). Поток, вызывающий unlock(), получит объект QueueObject из головы очереди и вызовет для него doNotify(), чтобы разбудить потоки, ожидающие этого объекта.Таким образом, одновременно просыпается только один ожидающий поток, а не все ожидающие потоки.. Это также является основой достижения честности FairLock.
Также обратите внимание, что QueueObject на самом деле является семафором. Методы doWait() и doNotify() хранят сигналы в объекте QueueObject.Это делается для того, чтобы избежать повторного входа одного потока другим потоком, вызывающим unlock() перед вызовом queueObject.doWait() и последующим вызовом queueObject.doNotify(), что приводит к потере сигнала.. Вызов queueObject.doWait () помещается вне синхронизированного (этого) блока, чтобы избежать блокировки вложенного монитора, так что другой поток может быть разблокирован, пока не может быть выполнен ни один поток, синхронизированный в методе блокировки (этом) блоке.
Наконец, обратите внимание, как вызывается QueueObject.dowait() в блоке try-catch.В случае сгенерированного InterruptedException поток может выйти из блокировки() и его необходимо удалить из очереди..
3 Как обеспечить функции безопасности потоков
3.1 Как обеспечить атомарность
3.1.1 Блокировки и синхронизация
Операционный инструмент Java, обычно используемый для обеспечения атомарностиБлокировки и синхронизированные методы (или синхронизированные блоки кода). Использование блокировок может гарантировать, что только один поток может одновременно получить блокировку, что также гарантирует, что только один поток может выполнять код между подачей заявки на блокировку и ее освобождением в одно и то же время.
public void testLock () {
lock.lock();
try{
int j = i;
i = j + 1;
} finally {
lock.unlock();
}
}
Подобными блокировкам являются синхронизированные методы или синхронизированные блоки кода.При использовании нестатического метода синхронизации текущий экземпляр блокируется, при использовании статического метода синхронизации блокируется объект Class класса, при использовании статического блока кода блокируется объект в круглых скобках после синхронизированного ключевого слова. Вот пример синхронизированного блока кода:
public void testLock () {
synchronized (anyObject){
int j = i;
i = j + 1;
}
}
Используете ли вы блокировки или синхронизируете, суть одна и та же,Эксклюзивность ресурсов достигается за счет блокировок или синхронизации, так что фактический целевой сегмент кода будет выполняться только одним потоком за раз., Тем самым обеспечив атомарность целевого сегмента кода.Это подход в ущерб производительности.
3.1.2 CAS (сравнить и поменять местами)
Автоинкремент переменной базового типа (i++) — это операция, которую новички часто ошибочно принимают за атомарную операцию, но на самом деле это не так.. Соответствующий класс атомарной операции предоставляется в Java для реализации этой операции и обеспечения атомарности.Его суть заключается в использовании CAS-инструкций на уровне ЦП.. Поскольку это инструкции по эксплуатации CPU, которые стоят меньше, чем требуемая операционная система блокировки накладных накладных расходов. AtomicInteger используется следующим образом:
AtomicInteger atomicInteger = new AtomicInteger();
for(int b = 0; b < numThreads; b++) {
new Thread(() -> {
for(int a = 0; a < iteration; a++) {
atomicInteger.incrementAndGet();
}
}).start();
}
3.2 Как обеспечить видимость
Java предоставляет ключевое слово volatile, чтобы гарантировать видимость.При использовании volatile для изменения переменной это гарантирует, что модификация переменной будет немедленно обновлена в памяти, а кеш переменной в других кешах потоков будет аннулирован., поэтому, когда другим потокам нужно прочитать значение, они должны прочитать из основной памяти, чтобы получить самое последнее значение.
сценарии приложений volatile: volatile подходит для сценариев, которые не должны гарантировать атомарность, но должны гарантировать видимость.一种典型的使用场景是用它修饰用于停止线程的状态标记。 Следующее:
boolean isRunning = false;
public void start () {
new Thread( () -> {
while(isRunning) {
someOperation();
}
}).start();
}
public void stop () {
isRunning = false;
}
В этой реализации, даже если другие потоки устанавливают Isrunning до false, вызывая метод stop (), цикл не обязательно заканчивается немедленно.Ключевое слово volatile можно использовать, чтобы убедиться, что цикл while получает самое последнее состояние isRunning вовремя, чтобы вовремя остановить цикл и завершить поток..
3.3 Как обеспечить порядок
Как упоминалось выше, когда компилятор и процессор переупорядочивают инструкции, они гарантируют, что переупорядоченный результат выполнения согласуется с результатом выполнения последовательности кода.Поэтому процесс переупорядочивания не повлияет на выполнение однопоточных программ, но может повлиять на корректность одновременного выполнения многопоточных программ..
В Java volatile может использоваться для обеспечения последовательности в определенных программах, а синхронизация и блокировки также могут использоваться для обеспечения последовательности.
Принцип последовательности, гарантирующей синхронизацию и блокировку, такой же, как и принцип гарантии атомарности, которая достигается за счет того, что только один поток одновременно выполняет целевой сегмент кода.
Помимо обеспечения последовательного выполнения целевых сегментов кода с уровня приложения,JVM также неявно гарантирует упорядочение с помощью так называемого принципа «происходит до».. До тех пор, пока порядок выполнения двух операций может быть выведен через «происходит-прежде», JVM будет гарантировать свой порядок, в противном случае JVM не дает никаких гарантий относительно своего порядка и может изменить их порядок по мере необходимости для достижения высокой эффективности.
происходит-прежде чем принцип (принцип происходит первым), следующим образом:
- Прохождение правил: Если операция 1 предшествует операции 2, а операция 2 предшествует операции 3, то операция 1 должна произойти до операции 3.Это правило гласит, что принцип «происходит до» является транзитивным..
- правило блокировки: Операция разблокировки должна выполняться перед последующей операцией блокировки того же замка.Это хорошо понятно, блокировка не будет получена снова, пока она не будет снята..
- правила изменяемой переменной: операция записи, измененная volatile, выполняется перед последующей операцией чтения переменной.
- правила порядка программы: внутри потока выполнять в порядке кода.
- правило начала потока: метод start() объекта Thread выполняется перед другими действиями этого потока.
- Нить конец принципа: Все остальные операции происходили в потоке после обнаружения завершения потока.
- Правила прерывания потока: вызов метода прерывания() потока происходит до получения исключения прерывания.
- Правила завершения объекта: Построение объекта происходит до его финализации.
4 несколько хлопьев о безопасности потоков
-
Максимально используем блокировки и синхронизируем в нашем обычном проекте, и редко используем Volatile, неужели нет видимости?
Блокировки и синхронизация могут гарантировать атомарность и видимость. Это достигается за счет того, что только один поток одновременно выполняет целевой сегмент кода.
-
Почему блокировки и синхронизация обеспечивают видимость?
в соответствии сДокумент Java для JDK 7средняя пара
concurrent
описание пакета,Результаты записи потока гарантированно видны операциям чтения других потоков., пока операция записи может бытьhappen-before
Принцип предполагает, что это происходит до операции чтения. -
Поскольку блокировки и синхронизация могут гарантировать атомарность и видимость, зачем нам volatile?
Синхронизированные и замки должны арбитрат, которые получает блокировку через операционную систему, накладные расходы относительно высоки, а летучий накладной головки намного меньше. Поэтому в условиях, которые необходимо гарантировать для обеспечения видимости, производительность Volatile намного выше, чем использование блокировки и SYNCHRONIZED.
-
Поскольку блокировки и синхронизация могут гарантировать атомарность, зачем вам нужны такие классы, как AtomicInteger, для обеспечения атомарных операций?
Блокировка и синхронизация должны определять, кто получает блокировку через операционную систему, и накладные расходы относительно высоки.AtomicInteger обеспечивает атомарность посредством операций CAS на уровне ЦП с относительно низкими накладными расходами.. Таким образом, цель использования AtomicInteger — повысить производительность.
-
Есть ли другой способ обеспечить безопасность потоков?
имеют.Используйте условия, вызывающие непотоковую безопасность — общие переменные. Если можно избежать использования общих переменных по замыслу, то можно избежать возникновения непоточной безопасности.Нет необходимости решать проблемы атомарности, видимости и упорядочения через блокировки или синхронизированные и изменчивые.
-
Synchronized может изменять нестатические методы, статические методы и блоки кода. В чем разница?
Когда синхронизировано украшает нестатический синхронизированный метод,блокирует текущий экземпляр; Статический синхронизированный метод синхронизированной модификации,Что заблокировано, так это объект класса класса;синхронизируется при оформлении статического блока кода,Что заблокировано, так это объект в круглых скобках после синхронизированного ключевого слова.