Оригинальная статья, краткое изложение опыта и жизненные перипетии на всем пути от набора в школу до фабрики А
Нажмите, чтобы узнать подробностиwww.codercc.com
1. Уровень структуры параллельного пакета
В области параллельного программирования мастер Дуг Ли предоставил нам большое количество практичных и высокопроизводительных инструментов.Исследование этих кодов поможет нашей команде лучше понять параллельное программирование и значительно усилит любовь нашей команды к технологии параллельного программирования. Эти коды находятся в пакете java.util.concurrent. На следующем рисунке показана структура каталогов параллельного пакета.
Он содержит два подпакета: атомарный и блокирующий, а также блокирующие очереди и исполнителей в параллельном режиме.Это суть параллельного пакета, и они будут изучены один за другим позже. Реализация этих классов в основном зависит от volatile и CAS (см. volatile дляэта статья, про CAS можно посмотретьРаздел 3.1 этой статьи), в целом общая схема реализации concurrent-пакета представлена на следующем рисунке:
2. Введение в замок
Давайте взглянем на подпакет блокировки под пакетом концентрата. Блокировки используются для управления тем, как несколько потоков получают доступ к общим ресурсам.Вообще говоря, блокировка может предотвратить одновременный доступ нескольких потоков к общим ресурсам. До появления интерфейса Lock java-программы в основном реализовывали функцию блокировки с помощью ключевого слова synchronized, после Java SE5 интерфейс блокировки был добавлен в пакет concurrent, который обеспечивает ту же функцию блокировки, что и synchronized. **Хотя он утрачивает удобство неявной блокировки и разблокировки, подобно ключевому слову синхронизации, он обладает функциональностью захвата и снятия блокировки, захвата блокировок с прерыванием и захвата блокировки по тайм-ауту и других ключевых слов синхронизации. **Обычно используйте форму блокировки использования дисплея следующим образом:
Lock lock = new ReentrantLock();
lock.lock();
try{
.......
}finally{
lock.unlock();
}
должен быть в курсеКогда синхронизированный блок выполняется или возникает исключение, блокировка будет автоматически снята, и блокировка должна вызвать метод unlock(), чтобы снять блокировку, поэтому блокировка снимается в блоке finally..
2.1 API интерфейса блокировки
Давайте теперь посмотрим, какие методы определяет интерфейс блокировки:
void lock(); //Получить блокировку void lockInterruptably() throws InterruptedException;//Процесс получения блокировки может реагировать на прерывание boolean tryLock();//Прерывание неблокирующего ответа может вернуться немедленно, получить блокировку и вернуть ее в значение true, в противном случае вернуть fasle boolean tryLock(long time, TimeUnit unit) throws InterruptedException;//Блокировка устанавливается с течением времени, и блокировка может быть получена в течение тайм-аута или без прерывания Condition newCondition();//Получите компонент уведомления об ожидании, связанный с блокировкой. Текущий поток должен получить блокировку для ожидания. При ожидании блокировка будет снята первой, и блокировка может быть возвращена из ожидания, когда блокировка будет получена снова.
Вышеупомянутые пять методов под интерфейсом блокировки, и они только переведены из китайско-английского перевода исходного кода, Если вам интересно, вы можете взглянуть на него самостоятельно. Итак, какие классы в пакете locks реализуют этот интерфейс? Начнем с самого знакомого ReentrantLock.
public class ReentrantLock implements Lock, java.io.Serializable
Очевидно, что ReentrantLock реализует интерфейс блокировки, давайте подробнее рассмотрим, как он реализован. Когда вы посмотрите на исходный код, вы будете удивлены, обнаружив, что в ReentrantLock не так много кода.Еще одна очевидная особенность:По сути, реализация всех методов фактически вызывает класс статической памяти.Sync
метод в , а класс Sync наследуетAbstractQueuedSynchronizer(AQS)
. Можно видеть, что ключ к пониманию ReentrantLock лежит в понимании синхронизатора очереди AbstractQueuedSynchronizer (называемого синхронизатором).
2.2 Первое знакомство с AQS
В исходном коде есть очень конкретное объяснение AQS:
Provides a framework for implementing blocking locks and related synchronizers (semaphores, events, etc) that rely on first-in-first-out (FIFO) wait queues. This class is designed to be a useful basis for most kinds of synchronizers that rely on a single atomic {@code int} value to represent state. Subclasses must define the protected methods that change this state, and which define what that state means in terms of this object being acquired or released. Given these, the other methods in this class carry out all queuing and blocking mechanics. Subclasses can maintain other state fields, but only the atomically updated {@code int} value manipulated using methods {@link #getState}, {@link #setState} and {@link #compareAndSetState} is tracked with respect to synchronization.
<p>Subclasses should be defined as non-public internal helper classes that are used to implement the synchronization properties of their enclosing class. Class {@code AbstractQueuedSynchronizer} does not implement any synchronization interface. Instead it defines methods such as {@link #acquireInterruptibly} that can be invoked as appropriate by concrete locks and related synchronizers to implement their public methods.
Синхронизатор является базовой структурой для создания блокировок и других компонентов синхронизации.Его реализация в основном опирается на переменную-член int для представления состояния синхронизации и представляет собой очередь ожидания через очередь FIFO. этоМетод, используемый для изменения нескольких защищенных измененных подклассов состояния синхронизации AQS, должен переопределять, и другие методы в основном реализуют механизмы очередей и блокировки.Обновление состояния использует три метода getState, setState и compareAndSetState..
подклассыРекомендуется определить статический внутренний класс в качестве пользовательского компонента синхронизации., сам синхронизатор не реализует какой-либо интерфейс синхронизации, он только определяет ряд методов получения и освобождения состояния синхронизации для использования пользовательских компонентов синхронизации, синхронизатор поддерживает как монопольное получение состояния синхронизации, так и совместное получение состояния синхронизации, так что вы можете легко реализовать различные типы компонентов синхронизации.
Синхронизаторы являются ключом к реализации блокировок (или любого компонента синхронизации).Синхронизаторы объединяются при реализации блокировок, а синхронизаторы используются для реализации семантики блокировок. Отношения между ними можно понять следующим образом:Замок ориентирован на пользователя, он определяет интерфейс для взаимодействия пользователя с замком и скрывает детали реализации; синхронизатор является реализатором блокировки, что упрощает реализацию блокировки, экранирует управление состоянием синхронизации и постановка потоков в очередь.Операции низкого уровня, такие как ожидание и пробуждение. Блокировки и синхронизаторы прекрасно изолируют области, представляющие интерес для пользователей и разработчиков.
2.3 Шаблон проектирования метода шаблона AQS
AQS предназначен для использования шаблонного подхода к проектированию, которыйНекоторые методы открыты для подклассов для переопределения, и метод шаблона, предоставленный синхронизатором для компонента синхронизации, снова вызовет метод, переопределенный подклассом.. Например, в AQS необходимо переопределить метод tryAcquire:
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
NonfairSync (наследующий AQS) в ReentrantLock переопределит этот метод следующим образом:
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
И метод шаблона Acquis() в AQS:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
Будет вызван метод tryAcquire, а когда NonfairSync, наследующий AQS, вызовет метод получения шаблона, будет вызван метод tryAcquire, который был переопределен NonfairSync. Это способ использования AQS.После понимания этого понимание реализации блокировки будет значительно улучшено. Его можно резюмировать в следующих пунктах:
- Реализация компонентов синхронизации (не только блокировок значений, но и CountDownLatch и т. д.) зависит от AQS синхронизатора.При реализации компонентов синхронизации рекомендуется использовать AQS для определения классов статической памяти, наследующих AQS;
- AQS разработан с использованием метода шаблона.Защищенный метод модификации AQS должен быть переписан подклассом, который наследует AQS.При вызове метода подкласса AQS будет вызываться переопределенный метод;
- AQS отвечает за управление состоянием синхронизации, очередями потоков, ожиданием и пробуждением этих базовых операций, в то время как компоненты синхронизации, такие как Lock, в основном сосредоточены на реализации семантики синхронизации;
- При переопределении метода AQS используйте предоставляемый AQS
getState(),setState(),compareAndSetState()
способ изменить состояние синхронизации
Переписываемые методы AQS следующие (из книги "The Art of Java Concurrent Programming"):
Метод шаблона, предоставляемый AQS при реализации компонента синхронизации, выглядит следующим образом:
Шаблонные методы, предоставляемые AQS, можно разделить на 3 категории:
- Эксклюзивное получение и освобождение состояния синхронизации;
- Общее состояние синхронизации приобретения и выпуска;
- Запрос состояния ожидающих потоков в очереди синхронизации;
Компоненты синхронизации реализуют собственную семантику синхронизации с помощью шаблонных методов, предоставляемых AQS.
3. Пример
Далее используется пример, чтобы лучше понять использование AQS. Этот пример также получен из примера в исходном коде AQS.
class Mutex implements Lock, java.io.Serializable { // Our internal helper class // 继承AQS的静态内存类 // 重写方法 private static class Sync extends AbstractQueuedSynchronizer { // Reports whether in locked state protected boolean isHeldExclusively() { return getState() == 1; }
скопировать код// Acquires the lock if state is zero public boolean tryAcquire(int acquires) { assert acquires == 1; // Otherwise unused if (compareAndSetState(0, 1)) { setExclusiveOwnerThread(Thread.currentThread()); return true; } return false; } // Releases the lock by setting state to zero protected boolean tryRelease(int releases) { assert releases == 1; // Otherwise unused if (getState() == 0) throw new IllegalMonitorStateException(); setExclusiveOwnerThread(null); setState(0); return true; } // Provides a Condition Condition newCondition() { return new ConditionObject(); } // Deserializes properly private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException { s.defaultReadObject(); setState(0); // reset to unlocked state } } // The sync object does all the hard work. We just forward to it. private final Sync sync = new Sync(); //使用同步器的模板方法实现自己的同步语义 public void lock() { sync.acquire(1); } public boolean tryLock() { return sync.tryAcquire(1); } public void unlock() { sync.release(1); } public Condition newCondition() { return sync.newCondition(); } public boolean isLocked() { return sync.isHeldExclusively(); } public boolean hasQueuedThreads() { return sync.hasQueuedThreads(); } public void lockInterruptibly() throws InterruptedException { sync.acquireInterruptibly(1); } public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException { return sync.tryAcquireNanos(1, unit.toNanos(timeout)); }
}
MutexDemo:
public class MutextDemo { private static Mutex mutex = new Mutex();
скопировать кодpublic static void main(String[] args) { for (int i = 0; i < 10; i++) { Thread thread = new Thread(() -> { mutex.lock(); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } finally { mutex.unlock(); } }); thread.start(); } }
}
Выполнение:
В приведенном выше примере реализована семантика эксклюзивной блокировки, когда только один поток может одновременно удерживать блокировку. MutexDemo создал 10 новых потоков и заснул на 3 секунды соответственно. Из выполнения также видно, что текущий Thread-6 выполняет блокировку, в то время как другие потоки, такие как Thread-7 и Thread-8, находятся в состоянии WAIT. Рекомендуемым способом Mutex определяетСтатический внутренний класс Sync, наследующий AQS, а также переписали tryAcquire и другие методы AQS, а для обновления состояния также используются три метода setState(), getState(), compareAndSetState(). Метод в реализации интерфейса блокировки также просто вызывает метод шаблона, предоставляемый AQS (поскольку Sync наследует AQS). Из этого примера ясно видно, что AQS в основном используется для реализации компонентов синхронизации, а AQS «защищает» изменение состояния синхронизации, организации очереди потоков и других базовых реализаций. синхронный компонент делает вызов. Пользователям достаточно вызвать метод, предоставляемый компонентом синхронизации, для реализации параллельного программирования. В то же время при создании нового компонента синхронизации необходимо уяснить два ключевых момента:
- При реализации компонента синхронизации рекомендуется определить класс статической памяти, наследующий AQS, и переопределить требуемые защищенные измененные методы;
- Реализация семантики синхронных компонентов основана на методах шаблона AQS, которые, в свою очередь, полагаются на методы, переопределяемые подклассами AQS.
С точки зрения непрофессионала, поскольку общая идея дизайна AQS использует режим проектирования шаблонного метода, функции компонентов синхронизации и AQS фактически разделены на две части:
Перспектива реализации синхронного компонента:
С помощью переопределяемого метода:Эксклюзивный: tryAcquire() (исключительно получает состояние синхронизации), tryRelease() (эксклюзивно освобождает состояние синхронизации);общий: tryAcquireShared() (состояние синхронизации общего приобретения), tryReleaseShared() (состояние синхронизации общего выпуска);Сообщите AQS, как определить, успешно ли получено или освобождено текущее состояние синхронизации.. Компонент синхронизации фокусируется на логической оценке текущего состояния синхронизации, чтобы реализовать собственную семантику синхронизации. Это предложение относительно абстрактно. Например, приведенный выше пример Mutex реализует собственную семантику синхронизации с помощью метода tryAcquire. В этом методе, если текущее состояние синхронизации равно 0 (то есть компонент синхронизации не получен ни одним потоком), текущий поток может одновременно получить, изменить состояние на 1 и вернуть true, в противном случае компонент был занят потоком и вернуть false. Очевидно, что компонент синхронизации может быть одновременно занят только потоком, и Mutex сосредотачивается на логике получения и освобождения для достижения семантики синхронизации, которую он хочет выразить.
Точка зрения AQS
Для AQS вам нужны только true и false, возвращаемые компонентом синхронизации, потому что AQS будет иметь разные операции над true и false.Если true, он будет думать, что текущий поток получает компонент синхронизации и возвращает его напрямую, а если false , AQS также будет использовать ряд методов, таких как вставка текущего потока в очередь синхронизации.
В общем, компонент синхронизации реализует семантику синхронизации, которую он хочет выразить, переписывая метод AQS, и AQS нужно только синхронизировать истинные и ложные выражения компонента.AQS будет выполнять различную обработку для разных ситуаций истинности и ложности. базовую реализацию, вы можетесм. эту статью.
использованная литература
Искусство параллельного программирования на Java