1. Основи ReadWriteLock

Основна ідея ReadWriteLock полягає в тому, що він надає два локи:

  • Лок на читання: Дозволяє декільком потокам одночасно читати ресурс, але блокується, якщо будь-який потік має лок на запис. Призначено для ситуацій, коли читання здійснюється частіше, ніж запис.

  • Лок на запис: Цей лок може мати лише один потік одночасно(exclusive). Якщо потік має лок на запис, жоден інший потік (чи це читання, чи запис) не може отримати лок на читання або запис. Це гарантує ексклюзивний доступ до ресурсу для потоку, який записує.

2. Внутрішня структура ReentrantReadWriteLock

a. Стан:

  • Внутрішній стан ReentrantReadWriteLock підтримується як єдина змінна типу int. Це число розділено на дві частини:

    • Верхні 16 бітів для кількості локів на читання (всіми потоками).

    • Нижні 16 бітів для кількості локів на запис (лічильник повторного входження для потоку запису).

b. Лічильник утримань (Holding count):

  • Коли потік отримує лок на запис кілька разів (повторно), нижні 16 бітів (частина запису) збільшуються.

  • Коли потік отримує лок на читання, верхні 16 бітів збільшуються. Але є нюанс: так як декілька потоків можуть мати лок на читання одночасно, ReadWriteLock повинен відстежувати, скільки локів на читання має кожен потік. Для цього використовується ThreadLocalHoldCounter, яка відображає потоки на їхні лічильники. Верхні 16 бітів представляють суму всіх цих лічильників.

Приклад. Потік займає лок на читання:
Read count:  0000 0000 0000 0001
Write count: 0000 0000 0000 0000

c. Взяття і відпускання локу (Acquiring/releasing):

  • Коли потік намагається отримати лок на читання, він перевіряє, чи має інший потік лок на запис (перевіряючи нижні 16 бітів). Якщо ні, він збільшує лічильник локів на читання.

  • Коли потік намагається отримати лок на запис, він перевіряє лічильники локів на читання та запису. Лок на запис можна отримати, якщо жоден потік не має локу на читання (верхні 16 бітів нульові) і якщо лок на запис не має або має викликаючий потік (повторне отримання).

  • При відпусканні відповідні лічильники зменшуються.

d. Справедливість (Fairness):

  • ReentrantReadWriteLock може працювати в двох режимах: справедливому та несправедливому.

    • У справедливому режимі потоки отримують доступ в порядку, в якому вони хотіли отримати лок. Це досягається за допомогою внутрішньої черги очікування. Це зменшує шанси на голодування потоків, але часто має меншу пропускну спроможність, ніж в несправедливому режимі.

    • У несправедливому режимі є так звана поведінка “вривання“. Якщо потік хоче читати, і є інші очікуючі потоки, але жоден з них не є “письмовим“, він може "вриватися" і отримувати лок еа читання. Це може підвищити продуктивність, але може вводити деяку непередбачуваність у планування потоків.

e. Умови (Condition):

  • Лок на запис (і лише на запис) надає об'єкти Condition, які дозволяють потокам "очікувати" на конкретні умови для їх виконання. Це не підтримується для локів на читання через потенційні дедлоки.

3. Для чого використовувати?

Головною перевагою ReadWriteLock є “покращена паралельність” під час інтенсивних запитів на читання. Дозволяючи кільком потокам читати одночасно, він часто забезпечує кращу продуктивність, ніж синхронізований блок, особливо коли операції запису виконуються не часто.


  • Взяття локу на запис

    public class Main {
        public static void main(String[] args) {
            ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
            Thread thread1 = new MyThread(readWriteLock, "One");
            Thread thread2 = new MyThread(readWriteLock, "Two");
        }
    }
    
    class MyThread extends Thread {
        private final ReadWriteLock readWriteLock;
        public MyThread(ReadWriteLock readWriteLock, String name) {
            setName(name);
            this.readWriteLock = readWriteLock;
            start();
        }
    
        @Override
        public void run() {
            try {
                System.out.println(getName() +": About to acquire the lock");
                readWriteLock.writeLock().lock();
                System.out.println(getName() +": Took the lock. Doing some operations with it");
                while (true);
            } finally {
                readWriteLock.writeLock().unlock();
            }
        }
    }
output
VisualVM. Java process profiling
  • Взяття локу на читання (змінився тільки метод run)

    @Override
    public void run() {
        try {
            System.out.println(getName() +": About to acquire the lock");
            readWriteLock.readLock().lock();
            System.out.println(getName() +": Took the lock. Doing some operations with it");
            while (true);
        } finally {
            readWriteLock.readLock().unlock();
        }
    }

    output
VisualVM. Java process profiling
Поділись своїми ідеями в новій публікації.
Ми чекаємо саме на твій довгочит!
Oleksandr Klymenko
Oleksandr Klymenko@overpathz

Java Software Engineer

4.6KПрочитань
1Автори
71Читачі
На Друкарні з 19 квітня

Більше від автора

  • Secure networking. Deep Dive

    Глибоке занурення в протоколи TLS/SSL та інфраструктуру відкритих ключів (PKI). Основні поняття, процес встановлення захищеного з'єднання, роль сертифікатів та ланцюжка довіри

    Теми цього довгочиту:

    Security
  • Поширені помилки у дизайні REST API

    У довгочиті розглядаються поширені помилки при проектуванні REST API та способи їх уникнення: версіонування, використання DTO, підхід CQRS, робота з мікросервісами, та інші практики для підвищення продуктивності, безпеки й зручності API

    Теми цього довгочиту:

    Java
  • Java. Короткий огляд еволюції багатопотоковості

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

    Теми цього довгочиту:

    Java

Вам також сподобається

Коментарі (0)

Підтримайте автора першим.
Напишіть коментар!

Вам також сподобається