Один из вас понимает принцип реализации LOCK в Java

Java

Когда несколько потоков необходимо получить доступ к общему ресурсу, мы знаем, что нам нужно заблокировать, чтобы убедиться, что доступ к ресурсу не пойдет не так. Java предоставляет два способа заблокировать, одно ключевое слово: синхронизировано, а другой - замок блокировки под параллельным пакетом. Синхронизирован поддерживается нижний слой Java, а параллельный пакет реализуется JDK. Принцип синхронизированного можно прочитатьЕсли кто-то спросит вас, что такое synchronized, пришлите ему эту статью.

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

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

Я сравниваю этот процесс с процессом приготовления пищи: какие бывают блюда и как они готовятся?

Позвольте мне перечислить несколько ключевых слов в процессе реализации блокировки: значение счетчика, двусвязный список, CAS+spin.

Пример использования
import java.util.concurrent.locks.ReentrantLock;public class App {    public static void main(String[] args) throws Exception {        final int[] counter = {0};        ReentrantLock lock = new ReentrantLock();        for (int i= 0; i < 50; i++){            new Thread(new Runnable() {                @Override                public void run() {                    lock.lock();                    try {                        int a = counter[0];                        counter[0] = a + 1;                    }finally {                        lock.unlock();                    }                }            }).start();        }        // 主线程休眠,等待结果        Thread.sleep(5000);        System.out.println(counter[0]);    }}скопировать код

В этом примере одновременно выполняется 50 потоков для обновления счетчика. Разделите его на три части, чтобы увидеть исходный код (инициализация, получение блокировки, снятие блокировки)

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

Что делает ReentrantLock()

 /**     * Creates an instance of {@code ReentrantLock}.     * This is equivalent to using {@code ReentrantLock(false)}.     */    public ReentrantLock() {        sync = new NonfairSync();    }скопировать код

В конструкторе блокировки определен NonFairSync,

static final class NonfairSync extends Sync скопировать код

Nonfairsync унаследован в синхронизации

abstract static class Sync extends AbstractQueuedSynchronizerскопировать код

Просматривая шаг за шагом, я нашел этот призрак AbstractQueuedSynchronizer (сокращенно AQS), и, наконец, этот призрак унаследован от AbstractOwnableSynchronizer (AOS), AOS в основном сохраняет объект потока, который получает текущую блокировку, и код больше не будет расширяться. Наконец, мы можем увидеть отношения наследования нескольких основных классов.

Отношения наследования классов блокировки.jpg

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

Кода в других классах немного.Последний основной код находится в AQS.Для начала рассмотрим основную структуру этого класса.

Что такое AbstractQueuedSynchronizer

Давайте посмотрим, что такое Node?

Увидев здесь одноклассников, у меня слезы на глазах, у этой Нимы, не так ли?Двусвязный списокКакие? Я до сих пор помню, когда впервые написал эту структуру данных, я обнаружил, что существует такая волшебная вещь.

Наконец, мы можем обнаружить, что структура хранения блокировки состоит из двух вещей: «двухсвязный список» + «состояние типа int».Следует отметить, что все их переменные определяются как "transientиvolatileретушь.

Значение INT, двусторонний связанный список - это то, как приготовить это блюдо, Даг Лей Бог - большой бог, давайте посмотрим, как получить замок?

Как lock.lock() получает блокировку?
/** * Acquires the lock. */public void lock() {    sync.lock();}скопировать код

Видно, что звонок есть,NonfairSync.lock()

Видя это у нас в принципе есть общее понимание.Я еще помню значение состояния типа int в AQS раньше.Вот модифицировать значение состояния через CAS(оптимистическая блокировка).Основная операция блокировки по-прежнему реализуется через оптимистическую блокировку..

Если блокировка получена через CAS, то блокировка не получена, как ждать достижения блокировки? Мы можем посмотреть на логику ветки else, метода accept:

public final void acquire(int arg) {    if (!tryAcquire(arg) &&        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))        selfInterrupt();}скопировать код

Здесь делаются три вещи:

  • tryAcquire: снова попытается получить блокировку через CAS.

  • addWaiter: добавить текущий поток в двусвязный список (очередь ожидания) блокировки выше

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

addWaiter добавляет текущий поток в список ожидания

Видно, что CAS гарантирует, что текущий поток может быть добавлен к хвосту связанного списка в условиях безопасности потоков. ENQ - это спин + вышеуказанная логика, вы можете просматривать исходный код, если вы заинтересованы.

acquireQueued

Spin + попытка CAS получить блокировку

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

Каждый поток крутится + CAS

Наконец, краткий обзор процесса получения замков

Получить блокировку process.jpg
lock.unlock() снимает блокировку
public void unlock() {    sync.release(1);}скопировать код

Вы можете видеть, что это вызов NonfairSync.release().

image.png

Наконец, вызывается NonfairSync.tryRelease().

image.png

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

Суммировать
  • Структура хранения блокировки: значение состояния типа int (используется для изменения состояния блокировки), двусвязный список (используется для хранения ожидающих потоков)

  • Процесс получения блокировки: по сути, изменение значения состояния осуществляется через CAS.Если это не будет получено на месте, поток будет помещен в список ожидания потока.

  • Заблокируйте процесс выпуска блокировки: изменение значения состояния для настройки списка ожидания.

  • Можно увидеть на протяжении всего процесса реализации, блокировка широкого использования CAS + спин. Следовательно, согласно характеристикам CAS, блокировка рекомендуется в случае конфликтов низкой блокировки. После текущей версии java1.6 официальная синхронизация сделала много оптимизаций блокировки (смещение блокировки, вращение, облегченная блокировка). Поэтому в случае несущественного рекомендуется выполнять синхронную синхронную операцию.

Наконец, я надеюсь, что мой анализ поможет вам понять реализацию блокировок.

PS: Эта статья написана автором, автором "Linwan Village Totoro".

 

Столкнувшись с проблемой Java 125: можно ли передать List методу, который получает параметр List?

Путь к тому, чтобы стать богом №013: Класс сбора Java — Карта.

- ЕЩЕ | Другие интересные статьи -

Если вы видите это, значит, вам понравилась эта статья.

Затем нажмите и удерживайте QR-код и следуйте за Холлисом.

Пересылка круга друзей - самая большая поддержка для меня.