Когда несколько потоков необходимо получить доступ к общему ресурсу, мы знаем, что нам нужно заблокировать, чтобы убедиться, что доступ к ресурсу не пойдет не так. 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 в основном сохраняет объект потока, который получает текущую блокировку, и код больше не будет расширяться. Наконец, мы можем увидеть отношения наследования нескольких основных классов.
Разница между 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: с помощью вращения определите, может ли текущий узел очереди получить блокировку.
Видно, что CAS гарантирует, что текущий поток может быть добавлен к хвосту связанного списка в условиях безопасности потоков. ENQ - это спин + вышеуказанная логика, вы можете просматривать исходный код, если вы заинтересованы.
acquireQueued
Видно, что когда текущий поток достигает головы, попробуйте CAS обновить состояние блокировки.Если обновление прошло успешно, это означает, что ожидающий поток успешно получен. Снять с головы.
Наконец, краткий обзор процесса получения замков
public void unlock() { sync.release(1);}скопировать код
Вы можете видеть, что это вызов NonfairSync.release().
Наконец, вызывается NonfairSync.tryRelease().
В основном можно подтвердить, что снятие блокировки означает изменение значения состояния State в AQS. В то же время узел ожидания потока в следующем связанном списке обновляется.
-
Структура хранения блокировки: значение состояния типа int (используется для изменения состояния блокировки), двусвязный список (используется для хранения ожидающих потоков)
-
Процесс получения блокировки: по сути, изменение значения состояния осуществляется через CAS.Если это не будет получено на месте, поток будет помещен в список ожидания потока.
-
Заблокируйте процесс выпуска блокировки: изменение значения состояния для настройки списка ожидания.
-
Можно увидеть на протяжении всего процесса реализации, блокировка широкого использования CAS + спин. Следовательно, согласно характеристикам CAS, блокировка рекомендуется в случае конфликтов низкой блокировки. После текущей версии java1.6 официальная синхронизация сделала много оптимизаций блокировки (смещение блокировки, вращение, облегченная блокировка). Поэтому в случае несущественного рекомендуется выполнять синхронную синхронную операцию.
Наконец, я надеюсь, что мой анализ поможет вам понять реализацию блокировок.
PS: Эта статья написана автором, автором "Linwan Village Totoro".
Столкнувшись с проблемой Java 125: можно ли передать List
Путь к тому, чтобы стать богом №013: Класс сбора Java — Карта.
- ЕЩЕ | Другие интересные статьи -
-
Продвинутые разработчики должны понимать механизм SPI в Java.
-
Случайный разговор: как познакомить вашу девушку с тем, что такое тупики
-
Принцип реализации atomic в пакете параллельного программирования Java
Если вы видите это, значит, вам понравилась эта статья.
Затем нажмите и удерживайте QR-код и следуйте за Холлисом.
Пересылка круга друзей - самая большая поддержка для меня.