Подробно объясните безопасность потоков Java.

Java

1. Модель памяти

тайник

Поскольку скорость выполнения ЦП и скорость чтения и записи данных в памяти сильно различаются, ЦП часто содержит高速缓存структура.此处输入图片的描述Когда программа запущена, она скопирует данные, необходимые для операции, из основной памяти в кеш ЦП, после чего ЦП может напрямую считывать данные из своего кеша и записывать в него данные при выполнении вычислений.После завершения операции , данные в кеше сбрасываются в основную память.

несогласованность кеша

Выполните следующий код:

int i = 0;
i = i + 1;

Когда поток выполняет этот оператор, он сначала считывает значение i из основной памяти.i = 0, затем сделайте копию в高速缓存который, затем ЦП выполняет пару инструкцийiДобавьте 1 операцию, затем запишите данные в кеш и, наконец, поместите их в кешiПоследнее значение обновляется до主存среди.

Возможна ситуация: сначала два потока читают значение i и сохраняют его в кэше ЦП, где они находятся, а затем线程1Добавьте 1, а затем запишите последнее значение i, 1, в память. В это время значение i в кеше потока 2 по-прежнему равно 0. После добавления 1 значение i равно 1, а затем线程2Запишите значение i в память.

То есть, если переменная кэшируется в нескольких процессорах (многопоточный случай), то может бытьнесогласованность кешаПроблема.

Разрешение несогласованности кэша

Обычно есть два решения:

  • автобусный замок

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

  • Протокол когерентности кэша

Это неэффективно, потому что другие процессоры не могут получить доступ к памяти, пока шина заблокирована. Итак, существует протокол когерентности кеша. Самый известный из них — Intel.MESI协议,MESI协议Копии общих переменных, используемых в каждом кэше, гарантированно непротиворечивы.MESI协议Основная идея заключается в следующем: когда ЦП записывает данные, если он обнаруживает, что рабочая переменная является общей переменной, то есть существует копия переменной в других ЦП, он посылает сигнал, чтобы уведомить другие ЦП, чтобы установить значение переменной. строку кэша в недопустимое состояние, поэтому, когда другим процессорам необходимо прочитать эту переменную и обнаружить, что строка кэша, которая кэширует переменную в их собственном кэше, недействительна, она будет повторно считана из памяти.

2. Вопросы безопасности потоков

причина

Исходя из предыдущего анализа, при параллельном программировании (многопоточном программировании) могут возникнуть проблемы безопасности потоков:

  • Несколько потоков манипулируют общими данными.

  • Существует несколько потоков кода, которые работают с общими данными.

  • Когда поток выполняет несколько фрагментов кода, работающих с общими данными, в операции участвуют другие потоки.

Основные концепции параллелизма

Три основные концепции: атомарность, видимость и последовательность.

  • Атомарность: это похоже на концепцию атомарности транзакций базы данных, то есть операция (которая может содержать несколько подопераций) либо выполняется полностью (эффективна), либо ни одна из них не выполняется (неэффективна).

锁和同步(синхронизированные методы и синхронизированные блоки кода),CAS(Инструкция CAS уровня ЦПcmpxchg).

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

volatileключевые слова для обеспечения видимости.

  • Последовательный: порядок выполнения программы соответствует порядку выполнения кода. Поскольку процессор может оптимизировать входной код для повышения эффективности программы, он не гарантирует, что последовательность выполнения каждого оператора в программе будет такой же, как последовательность в коде, но гарантирует, что окончательное выполнение результат программы и результат выполнения кодовой последовательности непротиворечивы, т.е.指令重排序.

volatileГарантируют порядок в определенной процедуре, а также могут проходитьsynchronizedидля обеспечения заказа.

В-третьих, структура заголовка объекта Java.

Объекты Java могут действовать как блокировки в параллельном программировании. Блокировка фактически существует в заголовке объекта Java. Если объект является типом массива, виртуальная машина использует 3 слова (ширина слова) для хранения заголовка объекта, а если объект не является типом массива, она использует 2 слова для хранения заголовка объекта. В 64-битной виртуальной машине одно слово равно восьми байтам, т.е.64bit.

Заголовок объекта JavaMark WordHashCode, возраст поколения и бит флага блокировки объекта сохраняются по умолчанию. Структура хранения Mark Word по умолчанию для 32-разрядной JVM выглядит следующим образом:

|-|25-битный|4-битный|смещенный флаг блокировки (1 бит)|флаг блокировки (2 бита)| |::|::|::|::|::| |Статус блокировки|Хеш-код объекта|Возраст генерации объекта| |01|

Структура хранения 64-битной JVM выглядит следующим образом:

статус блокировки

25bit

31bit

1bit

4bit

1bit

2bit

</td>
<td>

</td>
<td>
<p><span lang="EN-US">cms_free</span></p>
</td>
<td>
<p><span>分代年龄<span lang="EN-US"></span></span></p>
</td>
<td colspan="2">
<p><span>偏向锁<span lang="EN-US"></span></span></p>
</td>
<td>
<p><span>锁标志位<span lang="EN-US"></span></span></p>
</td>

нет замка

unused

hashCode

</td>
<td>

</td>
<td>

</td>
<td colspan="2">
<p><span lang="EN-US">01</span></p>
</td>

Блокировка смещения

ThreadID(54bit) Epoch(2bit)

</td>
<td>

</td>
<td>
<p><span lang="EN-US">1</span></p>
</td>
<td colspan="2">
<p><span lang="EN-US">01</span></p>
</td>

во время операцииMark WordХранящиеся в нем данные будут изменяться при изменении бита флага блокировки.


После понимания соответствующих концепций давайте представим, как Java гарантирует безопасность при параллельном программировании.


Четыре, синхронизированные

использование

  • Украсьте синхронизированные блоки кода

Инкапсулируйте коды нескольких потоков, которые работают с общими данными. Когда поток выполняет эти коды, другие потоки не могут участвовать в операции. Текущий поток должен выполнить все эти коды, прежде чем другие потоки смогут участвовать в операции.

synchronized(对象)
{
需要被同步的代码 ;
}
  • Украсьте функции синхронизации (методы)
修饰符 synchronized 返回值 方法名(){
}

  • Изменить статический метод, его областью действия является весь статический метод, а объектом действия являются все объекты этого класса;

  • Изменяет класс, область действия которогоsynchronizedВ части, заключенной в скобки, основным объектом являются все объекты этого класса.

synchronizedЕсть три основные функции:

(1) Доступ к коду синхронизации для обеспечения взаимного исключения потоков (2) Убедитесь, что изменение общих переменных можно увидеть вовремя (3) Эффективно решить проблему переупорядочивания.

заблокировать объект

  • Для синхронизированных методов блокировка является текущей.实例对象.
  • Для статических синхронизированных методов блокировкой является текущий объект.Class 对象.
  • Для блоков синхронизированных методов блокировкаsynchonizedОбъекты, указанные в скобках.

Принцип реализации

К скомпилированному байт-коду добавляются две инструкции для синхронизации кода.

монитор введите:

Каждый объект имеет监视器锁(monitor). когдаmonitorКогда он занят, он будет в заблокированном состоянии, и поток будет выполнятьсяmonitorenterПопытка получить команду whenmonitorправа собственности, процесс выглядит следующим образом:

  • еслиmonitorНомер входа равен 0, затем поток входитmonitor, а затем установите количество записей равным 1, потокmonitorвладелец .
  • Если поток уже владеетmonitor, просто введите еще раз, затем введитеmonitorДобавьте 1 к номеру записи.
  • Если другие темы уже занятыmonitor, поток переходит в состояние блокировки до тех пор, покаmonitorКоличество записей равно 0, а затем повторите попытку полученияmonitorправо собственности.

Выход монитора:

воплощать в жизньmonitorexitНить должна бытьobjectrefсоответствующийmonitorвладелец . Когда инструкция выполняется,monitorКоличество записей уменьшается на 1. Если количество записей равно 0 после уменьшения на 1, поток завершается.monitor, уже не этоmonitorвладелец . другое этимmonitorЗаблокированный поток может попытаться получить этоmonitorправо собственности.

synchronizedСемантическая основа лежит черезmonitorобъект для завершения, на самом делеwait/notifyи т.д. методы также зависят отmonitorобъекта, поэтому его можно вызывать только в синхронизированном блоке или методеwait/notifyи другие методы, иначе выкинетjava.lang.IllegalMonitorStateExceptionпричина исключения.

Преимущества и недостатки

выгода: проблема с безопасностью потоков решена.

недостатки: Относительно снижает эффективность, поскольку потоки вне синхронизации будут судить о блокировке синхронизации. Получение и освобождение блокировок увеличивает производительность.

Компилятор оптимизирует синхронизированный

Чтобы снизить потребление производительности, вызванное получением и снятием блокировок, в Java6 введены «предвзятые блокировки» и «облегченные блокировки», поэтому в Java6 существует четыре состояния блокировок:Нет состояния блокировки, смещенное состояние блокировки, упрощенное состояние блокировки и тяжелое состояние блокировки, он будет постепенно обостряться с конкуренцией. Замки можно улучшать, но нельзя понижать.

  • Блокировка смещения: в большинстве случаев блокировки не только не являются многопоточными, но и всегда запрашиваются несколько раз одним и тем же потоком. Назначение предвзятой блокировки состоит в том, что после того, как поток получит блокировку (идентификатор потока будет записан вMark Wod), устраняя накладные расходы, связанные с повторным входом в блокировку потока (CAS), по-видимому, позволяет отдавать предпочтение этому потоку.

  • Легкий замок (CAS): Облегченные блокировки обновляются с предвзятых блокировок. Когда предвзятые блокировки запускаются, когда поток входит в синхронизированный блок, когда второй поток присоединяется к состязанию за блокировку, предвзятые блокировки будут обновлены до облегченных блокировок; Целью тяжеловесной блокировки является попытка обновления MarkWord к указателю на LockRecord через операцию CAS без многопоточной конкуренции, что снижает потребление производительности, вызванное системным мьютексом, использующим тяжеловесную блокировку.

  • тяжелый замок: виртуальная машина использует операцию CAS, чтобы попытаться обновить MarkWord до указателя на LockRecord. Если обновление прошло успешно, это означает, что поток владеет блокировкой объекта, в случае неудачи он проверит, указывает ли MarkWord на стек кадр текущего потока. Если да, то это означает, что текущий поток уже владеет блокировкой. Эта блокировка, если нет, то это означает, что эта блокировка вытесняется другими потоками, и в это время она расширяется в тяжеловесную блокировку.

Пометить слово, соответствующее состоянию блокировки

Возьмем в качестве примера 32-битную JVM:

статус блокировки

25 bit

4bit

1bit

2bit

23bit

2bit

Это нестандартный замок?

замок флаг

Легкий замок

указатель на запись блокировки в стеке

00

тяжелый замок

указатель на мьютекс (тяжеловесная блокировка)

10

GCотметка

нулевой

11

Блокировка смещения

нитьID

Epoch

возраст генерации объекта

1

01

Пять, летучий

volatileэто ключевое слово в Java, используемое для изменения общих переменных (переменных-членов класса, статических переменных-членов класса).

Модифицируемые переменные содержат два уровня семантики:

  • Гарантированная видимость

Когда поток записывает переменную, он не записывает переменную в кеш, а напрямую сбрасывает значение обратно в основную память. В то же время, когда другие потоки читают общую переменную, они будут повторно извлекать значение из основной памяти вместо использования значения в текущем кеше. (и, следовательно, некоторое снижение производительности).Примечание. Не гарантируется, что запись в основную память будет атомарной.

  • Отключить перестановку инструкций

Отключение переупорядочивания инструкций имеет два значения:

1) Когда программа выполняется доvolatileКогда переменная читается или записывается, все изменения предыдущих операций должны быть выполнены, а результаты должны быть видны последующим операциям, а следующие за ней операции еще не должны быть выполнены; 2) При оптимизации инструкций невозможноvolatileОператор доступа к переменной выполняется после него, и его нельзя поставитьvolatileОператор, следующий за переменной, выполняется перед ней.

**Низкоуровневая реализация: **Добавлено наблюдениеvolatileключевые слова и никаких объединенийvolatileключевое слово генерируется, когда код сборки найден, добавляяvolatileключевое слово, будет еще одноlock前缀指令.

6. Блокировка

Сценарии применения

Если кодовый блокsynchronizedИзмененный, когда поток получает соответствующую блокировку и выполняет блок кода, другие потоки могут только ждать, пока поток, получивший блокировку, освободит блокировку, а поток, получивший блокировку здесь, освобождает блокировку только путемдва случая:

  • Поток, получивший блокировку, завершает выполнение блока кода, а затем освобождает блокировку;
  • Когда при выполнении потока возникает исключение, JVM автоматически снимает блокировку с потока.

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

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

Анализ исходного кода

Интерфейсы и классы, связанные с Lock, расположены по адресуJ.U.Cизjava.util.concurrent.locksпод пакет.此处输入图片的描述

(1)Блокировка интерфейса

public interface Lock {
    void lock();
    void lockInterruptibly() throws InterruptedException;
    boolean tryLock();
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    void unlock();
    Condition newCondition();
}
  • получить замок

lock(): Получите блокировку и подождите, если блокировка временно используется.tryLock(): получить блокировку с возвращаемым значением. Обратите внимание, что возвращаемый типboolean, если блокировка занята, когда блокировка получена, вернутьfalse, иначе возвратtrue.tryLock(long time, TimeUnit unit): по сравнению с tryLock(), он дает ограничение по времени для обеспечения времени ожидания параметра.lockInterruptibly(): при получении блокировки с помощью этого метода, если поток ожидает получения блокировки, поток может ответить на прерывание, то есть на состояние ожидания потока прерывания. То есть, когда два потока проходят одновременноlock.lockInterruptibly()Когда вы хотите получить блокировку, если поток A получает блокировку, а поток B только ждет, тогда вызовите поток BthreadB.interrupt()Метод может прервать ожидающий процесс потока B.

Уведомление: когда поток получает блокировку, он неinterrupt()метод прерывается. Потому что в предыдущей статье сказано, что он называется отдельноinterrupt()Метод не может прервать поток в запущенном процессе, он может только прервать поток в заблокированном процессе. Поэтому при прохожденииlockInterruptibly()Когда метод получает блокировку, если она не может быть получена, он может реагировать на прерывание только в том случае, если ожидает. использоватьsynchronizedЕсли он украшен, когда поток находится в состоянии ожидания блокировки, его нельзя прервать, и он может только ждать вечно.

  • разблокировать замок

unlock(): Разблокируйте замок.

(2)Класс ReentrantLock

ReentrantLock, что означает "повторно входимая блокировка".ReentrantLockединственныйLockкласс интерфейса иReentrantLockпредоставляет больше методов, основанных наAQS(AbstractQueuedSynchronizer)быть реализованным.

и,ConcurrentHashMapне использовалsynchronizedконтролировать, но использоватьReentrantLock.

  • Метод строительства

ReentrantLockразделен начестный замокинесправедливый замок, вы можете указать конкретный тип через конструктор:

public ReentrantLock() {
    sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}
  • получить замок
public void lock() {
    sync.lock();
}

иsyncЯвляетсяabstractвнутренний класс:

abstract static class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = -5179523762034025860L;
    abstract void lock();

Тотlock()метод построен с использованиемFairSyncобъект, то естьsyncкласс реализации.

public ReentrantLock() {
    sync = new NonfairSync();
}
//删去一些方法
static final class NonfairSync extends Sync {
    final void lock() {
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
    }
    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
}

иcompareAndSetStateдаAQSметод, основанный наCASработать.

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

Попытка получить блокировку дальше (вызов, унаследованный от родительского классаsyncизfinalметод):

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

первый судьяAQSсерединаstateЕсли он равен 0, 0 означает, что ни один другой поток в настоящее время не получает блокировку, и текущий поток может попытаться получить блокировку. еслиstateКогда он больше 0, это означает, что блокировка была получена, и необходимо определить, является ли поток, получивший блокировку, текущим потоком (ReentrantLockПоддержка реентерабельности), если да, то нужно добавить state+1 и обновить значение.

еслиtryAcquire(arg)Если получение блокировки не удается, вам нужно использоватьaddWaiter(Node.EXCLUSIVE)Записать текущий поток в очередь. Текущий поток должен быть обернут какNodeобъект(addWaiter(Node.EXCLUSIVE)).

то есть вернуться к:

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
  • разблокировать замок
公平锁和非公平锁的释放流程都是一样的:
public void unlock() {
    sync.release(1);
}

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
        	   //唤醒被挂起的线程
            unparkSuccessor(h);
        return true;
    }
    return false;
}

//尝试释放锁
protected final boolean tryRelease(int releases) {
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}        

(3)Интерфейс ReadWriteLockиКласс ReentrantReadWriteLock

  • определение
public interface ReadWriteLock {
    Lock readLock();
    Lock writeLock();
}

существуетReentrantLockВ , синхронизация между потоками является взаимоисключающей, будь то операция чтения или операция записи, но в некоторых сценариях операция чтения может выполняться параллельно, а взаимоисключающей является только операция записи, хотя такая ситуация также может быть использовалReentrantLockрешить, но это также потеряет производительность,ReadWriteLockиспользуется для решения этой проблемы.

  • Реализация — класс ReentrantReadWriteLock

существуетReentrantReadWriteLockБлокировки чтения и блокировки записи определены вReentrantLockТочно так же функции блокировок чтения и блокировок записи такжеSyncосуществленный,Syncсуществуетсправедливый и несправедливыйДве реализации, разница в том, что состояние блокировки представленоstateопределение, вReentrantReadWriteLockКонкретные определения следующие:

static final int SHARED_SHIFT     = 16;
  static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
  static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;
  static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;

  //获取读锁的占有次数
  static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
  //获取写锁的占有次数
  static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
  
  //线程的id和对应线程获取的读锁的数量
  static final class HoldCounter {
    int count = 0;
    // Use id, not reference, to avoid garbage retention
    final long tid = Thread.currentThread().getId();
  }
  
  //线程变量保存线程和线程中获取的读写的数量
  static final class ThreadLocalHoldCounter extends ThreadLocal<HoldCounter> {
    public HoldCounter initialValue() {
      return new HoldCounter();
    }
  }
  
  private transient ThreadLocalHoldCounter readHolds;
  //缓存最后一个获取读锁的线程
  private transient HoldCounter cachedHoldCounter;
  //保存第一个获取读锁的线程
  private transient Thread firstReader = null;  
  private transient int firstReaderHoldCount; 

Среди них есть два статических внутренних класса:ReadLock()иWriteLock(), все понялLock接口.

получить блокировку чтения:

  • Если ни один поток не удерживает блокировку записи, блокировка чтения получена успешно.
  • Получение блокировки чтения завершается ошибкой, если другой поток удерживает блокировку записи.
  • Если поток удерживает блокировку записи и нет других потоков, ожидающих блокировки записи, блокировка чтения получена успешно.
  • Если поток удерживает блокировку записи и есть другие потоки, ожидающие блокировки записи, если поток уже удерживает блокировку чтения, блокировка чтения получена успешно.Если блокировки чтения нет, на этот раз получение блокировки чтения завершается неудачно.

получить блокировку записи:

  • Определяем, есть ли потоки, удерживающие блокировки, в том числе блокировки чтения и записи, если да, то переходим к шагу 2, иначе к шагу 3
  • Если блокировка записи пуста (в это время поток, удерживающий блокировку чтения, существует из-за существования блокировки на шаге 1) или поток, удерживающий блокировку записи, не является потоком, он возвращает отказ напрямую. блокировок записи больше, чем MAX_COUNT, возвращается ошибка, в противном случае обновляется состояние и возвращается true
  • Если требуется решение о блокировке блокировки записи или сбой CAS, верните false напрямую, в противном случае установите поток, удерживающий блокировку записи, в качестве потока и верните true.
  • Судя по писателюShouldBlock блокировка записи
final boolean writerShouldBlock() {
    return hasQueuedPredecessors();
  }
//判断是否堵塞
public final boolean hasQueuedPredecessors() {
    Node t = tail;
    Node h = head;
    Node s;
    return h != t &&
      ((s = h.next) == null || s.thread != Thread.currentThread());
  }

7. Сравнение

Блокировка и синхронизация

synchronizedОн реализован на уровне JVM, а Lock реализован на уровне JDK.Lockнеобходимостьlockиrelease,Сравниватьsynchronizedсложный, ноLockМожет выполнять более мелкие блокировки, поддерживать тайм-аут получения, прерывание получения, чтоsynchronizedне приходит с. Реализация Lock в основном включаетReentrantLock,ReadLockиWriteLock, совместное чтение-чтение, взаимное исключение записи-записи и взаимное исключение чтения-записи.

  • Замок - этоинтерфейс, а синхронизировано на Javaключевые слова, synchronized — это встроенная языковая реализация;

  • Когда возникает исключение, синхронизированный будетавтоматический выпускБлокировка занята потоком, поэтому она не вызовет взаимоблокировки; и когда возникает исключение, если блокировка не освобождает активно блокировку через unLock(), это, вероятно, вызоветявление тупика, поэтому вам нужно снять блокировку в блоке finally при использовании Lock;

  • Блокировка позволяет потокам ожидать блокировкиОтвечать на прерывания, но синхронизированный не работает.При использовании синхронизированного ожидающий поток будет ждать вечно и не сможет отвечать на прерывания;

  • Вы можете узнать, есть ли они через Lockуспешно получил замок, в то время как синхронизированный не может этого сделать.

  • Блокировка может повысить производительность нескольких потоков для операций чтения.эффективный.

  • Реализация блокировки отличается от синхронизированной, котораяпессимистический замок, очень пугливая, боится, что ее кто-нибудь схватит и съест, поэтому каждый раз перед едой запирается. Нижний уровень блокировки на самом деле CASоптимистическая блокировкаНе имеет значения, если кто-то другой украл его, это нормально, получить его снова, так что это оптимистично. Дно в основном зависит отvolatileиCASреализуется операция.

синхронизированный и изменчивый

  • Суть volatile в том, чтобы сообщить jvm, что значение текущей переменной в регистре (рабочей памяти) неопределенно и его нужно считать из основной памяти;

  • Synchronized блокирует текущую переменную, только текущий поток может получить доступ к переменной, а другие потоки заблокированы.

  • volatile можно использовать только на уровне переменной; synchronized можно использовать на уровне переменной, метода и класса.

  • Volatile может обеспечить только видимость модификации переменных и не может гарантировать атомарность; в то время как синхронизация может гарантировать видимость модификации и атомарность переменных

  • Volatile не приведет к блокировке потока, а synchronized может привести к блокировке потока.

  • Переменные, помеченные как volatile, не будут оптимизированы компилятором; переменные, помеченные как synchronized, могут быть оптимизированы компилятором.

Семь, проблема взаимоблокировки

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

Четыре необходимых условия:

  • взаимоисключающее условие

Ресурс может использоваться только одним процессом одновременно.

  • просить и держать

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

  • отсутствие депривации

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

  • циклическое состояние ожидания

Несколько потоков образуют прямую циклическую взаимосвязь ожидающих ресурсов.

пример тупика

При синхронизации вложения два потока блокируют друг друга и не освобождаются, что приводит к взаимоблокировке.Пример:Создайте две строки a и b, затем создайте два потока A и B, и пусть каждый поток блокирует строку с помощью synchronized (A сначала блокирует a, затем блокирует b; B сначала блокирует b, затем блокирует a), если A блокирует a, B блокирует b, A не может заблокировать b, а B не может заблокировать a, то он попадает в тупик.

public class DeadLock {
    public static String obj1 = "obj1";
    public static String obj2 = "obj2";
    public static void main(String[] args){
        Thread a = new Thread(new Lock1());
        Thread b = new Thread(new Lock2());
        a.start();
        b.start();
    }    
}
class Lock1 implements Runnable{
    @Override
    public void run(){
        try{
            System.out.println("Lock1 running");
            while(true){
                synchronized(DeadLock.obj1){
                    System.out.println("Lock1 lock obj1");
                    Thread.sleep(3000);//获取obj1后先等一会儿,让Lock2有足够的时间锁住obj2
                    synchronized(DeadLock.obj2){
                        System.out.println("Lock1 lock obj2");
                    }
                }
            }
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}
class Lock2 implements Runnable{
    @Override
    public void run(){
        try{
            System.out.println("Lock2 running");
            while(true){
                synchronized(DeadLock.obj2){
                    System.out.println("Lock2 lock obj2");
                    Thread.sleep(3000);
                    synchronized(DeadLock.obj1){
                        System.out.println("Lock2 lock obj1");
                    }
                }
            }
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

Восемь, концепция блокировки

В Java есть два основных типа реализации блокировок: внутренние блокировкиsynchronized(встроенная блокировка монитора объекта) и блокировка дисплеяjava.util.concurrent.locks.Lock.

  • повторная блокировка

Это означает, что после того, как внешняя функция того же потока получает блокировку, внутренняя рекурсивная функция все еще имеет код для получения блокировки, но это не влияет на нее, и всем синхронизированным методам в объекте выполнения не нужно снова получать блокировку. .synchronizedиLockВсе реентерабельны.

  • прерываемый замок

synchronizedЭто не прерываемая блокировка, а Lock — прерываемая блокировка.

  • честный замок

Получено согласно времени ожидания потока, ожидающего получения блокировки,долгое время ожиданияимеет приоритет для получения блокировки.synchronizedявляется несправедливым замком; дляReentrantLockиReentrantReadWriteLock, что по умолчанию является несправедливой блокировкой, но может быть установлено справедливой блокировкой.

  • Блокировка чтения-записи

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

  • блокировка спина

Пусть поток выполняет бессмысленный цикл, а затем снова конкурирует за блокировку после завершения цикла.Если соревнование не может продолжить цикл, поток всегда будет находиться в цикле во время цикла.runningсостояние, но на основе планирования потоков JVM даются временные интервалы, поэтому другие потоки по-прежнему имеют возможность применять блокировки и снимать блокировки. Спин-блокировки экономят время и место (обслуживание очередей и т. д.) накладных расходов блокирующих блокировок, но длительные циклы становятся «ожиданием занятости», а ожидание занятости, очевидно, не так хорошо, как блокирующие блокировки. Таким образом, количество вращений обычно контролируется в диапазоне, таком как 10, 100 и т. д. После превышения этого диапазона блокировка вращения будет обновлена ​​до блокировки блокировки.

  • эксклюзивный замок

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

  • оптимистическая блокировка

Каждый раз, когда он не заблокирован, предполагается, что для завершения операции нет конфликта, и если из-за конфликта произойдет сбой, он будет повторять попытку, пока не добьется успеха.

  • пессимистический замок

Заставляет все другие потоки, которым нужна блокировка, зависать, ожидая, пока поток, удерживающий блокировку, освободит блокировку.

О ЮК

Содержит два подпакета: atomic и lock, а также блокирующие очереди и исполнителей под concurrent.Давайте рассмотрим их подробно позже.Следующая картинка очень классическая:此处输入图片的描述


Ссылка на ссылку