1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/yangdechao_admin-guage-notes

В этом репозитории не указан файл с открытой лицензией (LICENSE). При использовании обратитесь к конкретному описанию проекта и его зависимостям в коде.
Клонировать/Скачать
03JUC并发编程总结.md 220 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
Отправлено 24.06.2025 02:12 0782333

1. Объясните понятие volatile

Volatile — это легковесная синхронизация, предоставляемая Java виртуальной машиной.

Три ключевые характеристики volatile: гарантия видимости, отсутствие гарантий атомарности и запрет на переупорядочивание инструкций.

JVM: видимость, атомарность, упорядоченность (volatile может решить эти проблемы).

JMM (Java Memory Model)

  • JMM сам по себе является абстрактной концепцией и не существует реально. Это набор правил и норм, которые определяют способы доступа к данным в программе.

  • Синхронизационные правила JMM

    • Перед разблокировкой потока необходимо обновить значение общего доступа в основной памяти.
    • Перед блокировкой потока необходимо считать последнее значение из основной памяти в рабочую память потока.- Каждый поток при создании получает свою рабочую память, которая является приватной областью данных для каждого потока. В модели памяти Java все переменные хранятся в основной памяти, которая является общей областью для всех потоков. Все операции над переменными должны выполняться в рабочей памяти потока.
  • Сначала переменную нужно скопировать из основной памяти в рабочую память потока, затем выполнить операцию над ней, после чего записать результат обратно в основную память. Непосредственный доступ к переменным в основной памяти невозможен. Рабочая память содержит копию переменных из основной памяти. Поскольку рабочая память является приватной областью данных для каждого потока, разные потоки не могут получить доступ к рабочей памяти друг друга. Коммуникация между потоками (передача значений) должна осуществляться через основную память.Иллюстрация

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

/**
 * @author yangdechao
 * @date 2022/1/22 20:53
 */
public class VolatileDemo {
    /**
     * Тест видимости
     */
    private static void volatieltest1() {
        Data data = new Data();
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "coming...");
            try {
                Thread.sleep(1000);
            } catch (Exception e) {
                e.printStackTrace();
            }
            data.addOne();
            System.out.println(Thread.currentThread().getName() + "updata...");
            System.out.println(data.a);
        }, "AAA").start();

        while (data.a == 0) {
            // Основной поток будет здесь ждать, пока a не станет ненулевым
        }
        System.out.println(Thread.currentThread().getName() + " работа завершена...");
    }

    /**
     * volatile видимость,
     * в этом программе: если не добавить volatile ключевое слово, поток не выйдет.
     * Добавление volatile переменной, когда она изменяется, сразу становится известно другому потоку, текущее значение устаревает, и новое значение берется из основной памяти. Для других потоков видимо.
     */
    static class Data {
        volatile int a = 0;

        void addOne() {
            this.a += 1;
        }
    }

    public static void main(String[] args) {
        volatieltest1();
    }
}

Результат выполнения: первая картинка показывает результат выполнения программы, где переменная i имеет ключевое слово volatile. Вторая картинка показывает результат выполнения программы, где переменная i не имеет ключевого слова volatile.

```![img](https://cdn.nlark.com/yuque/0/2021/png/1459722/1614167586219-a56f21e5-b03f-4b8b-b754-17562549fe5a.png)![img](https://cdn.nlark.com/yuque/0/2021/png/1459722/1614167618715-a1b98fa9-6f02-4d86-afc5-02dac18205e4.png)

**2. volatile не гарантирует атомарность, для обеспечения атомарности можно использовать synchronized**

**2.1 Что такое атомарность?**

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

Код для проверки того, что volatile не гарантирует атомарность:

**2.2 Как решить проблему атомарности**

Самый простой способ — добавить ключевое слово synchronized, также можно использовать класс AtomicInteger.

```java
package com.interview.volatiletest;

import java.util.Date;
import java.util.concurrent.TimeUnit;

/**
 * @author yangdechao
 * @date 2022/1/22 20:53
 */
public class VolatileDemo {

    /**
     * Для обеспечения атомарности, добавьте ключевое слово synchronized
     */
    static class Data {
        volatile int a = 0;
        void addOne() {
            a++;
        }
    }

    /**
     * Тестирование атомарности
     */
    private static void volatieltest2() {
        Data data = new Data();
        for (int i = 0; i < 20; i++) {
            new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    data.addOne();
                }
            }, String.valueOf(i)).start();
        }

        while (Thread.activeCount() > 2) {
            Thread.yield();
        }
        System.out.println(data.a);
    }
}

Из результатов выполнения вы заметите, что значение является случайным. Это показывает, что volatile не гарантирует атомарность.

Где используется volatile?

Где используется volatile?```java

/** Простейший паттерн одиночки

  • @author yangdechao
  • @date 2022/1/24 9:31 */ public class Singleton01 { private static volatile Singleton01 instance = null; private Singleton01(){ System.out.println(Thread.currentThread().getName() + " construction..."); } public static Singleton01 getInstance(){ if (instance == null){ synchronized (Singleton01.class){ if (instance == null){ instance = new Singleton01(); } } } return instance; } }
       System.out.println(Singleton01.getInstance() == Singleton01.getInstance());
        System.out.println(Singleton01.getInstance() == Singleton01.getInstance());
        System.out.println(Singleton01.getInstance() == Singleton01.getInstance());
        System.out.println(Singleton01.getInstance() == Singleton01.getInstance());
      
    }
}

Результат выполнения:

img

А что если использовать его в многопоточной среде? Как показано ниже, результат выполнения не будет таким, как ожидалось. В этом случае кто-то может предложить добавить ключевое слово synchronized к методу. Это действительно решает проблему, но это слишком тяжелое решение. Поэтому рассмотрим DCL (Double Checked Locking).

public class Singleton01 {
    private static volatile Singleton01 instance = null;
    private Singleton01(){
        System.out.println(Thread.currentThread().getName() + " construction...");
    }
    public static Singleton01 getInstance(){
        if (instance == null){
            synchronized (Singleton01.class){
                if (instance == null){
                    instance = new Singleton01();
                }
            }
        }
        return instance;
    }

    public static void main(String[] args) {
     
      /**ExecutorService executorService = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 10; i++) {
            executorService.execute(() -> Singleton01.getInstance());
        }
        executorService.shutdown();*/
        
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                Singleton01.getInstance();
            }, String.valueOf(i)).start();
        }
        
    }
}

Двойная проверка блокировки ленивого синглтонаЕсли не использовать volatile, то это может привести к небезопасному поведению в многопоточной среде. Причина заключается в возможности переупорядочивания команд, которое может нарушить последовательность выполнения операций. Введение volatile позволяет запретить переупорядочивание команд. Это важно потому, что при отсутствии volatile объект instance может быть считан до завершения его инициализации.Создание объекта instance = new Singleton() можно разделить на три шага:

memory = allocate();  // 1. Выделение памяти для объекта
instance(memory);     // 2. Инициализация объекта
instance = memory;    // 3. Установка instance на выделенную память, instance != null

Шаги 2 и 3 не связаны между собой зависимостью, и переупорядочивание этих шагов не изменяет поведение программы в однопоточной среде. Однако это может вызвать проблемы в многопоточной среде, если объект считается до завершения его инициализации.

Поэтому, когда один поток обращается к instance, который не равен null, объект может ещё не быть полностью инициализирован, что приводит к проблемам с безопасностью потока.

memory = allocate();  // 1. Выделение памяти для объекта
instance = memory;    // 3. instance != null, но объект ещё не инициализирован
instance(memory);     // 2. Инициализация объекта

Поэтому без использования volatile возвращаемый объект не будет пустым, но он может быть неинициализированным.

public class Singleton02 {
    // Без использования volatile объект может быть небезопасным в многопоточной среде.
    private static volatile Singleton02 instance = null;
    private Singleton02() {
        System.out.println(Thread.currentThread().getName() + " инициализация...");
    }
    
    // DCL
    public static Singleton02 getInstance() {
        if (instance == null) {
            synchronized (Singleton01.class) {
                if (instance == null) {
                    instance = new Singleton02();
                }
            }
        }
        return instance;
    }
}
```    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 10; i++) {
            executorService.execute(() -> Singleton02.getInstance());
        }
        executorService.shutdown();
    }
}

2. Оптимистичный и пессимистичный блокировки

Памятка: пессимистическая блокировка выполняется заранее, а оптимистическая — в конце.

2.1 Оптимистичный лок

Памятка: перед получением данных не блокируется, при обновлении данных проверяется, был ли он изменён другими.

Всегда предполагается наилучший сценарий, каждый раз, когда данные берутся, считается, что никто другой не изменяет их, поэтому блокировка не применяется, но при обновлении проверяется, не было ли данных изменено в это время. Это можно реализовать с помощью механизма версий или алгоритма CAS. Оптимистичный лок подходит для приложений с большим количеством чтений, что позволяет увеличить пропускную способность. Например, механизм write_condition в базах данных предоставляет оптимистическую блокировку. В Java классы атомарных переменных из пакета java.util.concurrent.atomic используют реализацию оптимистического лока через алгоритм CAS.**

Два основных способа реализации оптимистического лока

Оптимистический лок обычно реализуется с помощью механизма версий или алгоритма CAS.

2.2 Пессимистичный лок

**Памятка: перед получением данных сразу блокируется.**Всегда предполагается худший сценарий, каждый раз, когда данные берутся, считается, что кто-то другой изменяет их, поэтому блокировка применяется сразу. Это означает, что другой поток, который пытается получить доступ к данным, будет заблокирован до тех пор, пока текущий поток не освободит блокировку. Обычно ресурс используется только одним потоком за раз, остальные потоки заблокированы до тех пор, пока этот поток не освободит ресурс. В традиционных реляционных базах данных используются различные виды блокировок, такие как блокировка строки или таблицы, чтение или запись. В Java синхронизация и ReentrantLock являются примерами реализации пессимистической блокировки.

2.3 Преимущества и недостатки механизма CAS

4.31 Преимущества

CAS является оптимистическим локом и представляет собой лёгкий и неблокирующий оптимистический лок. Что такое неблокирующий? Это значит, что когда поток пытается получить блокировку, другой поток отвечает, может ли он её получить. При низкой конкуренции ресурсами производительность высока, в отличие от тяжёлого синхронизированного лока, который требует более сложной процедуры блокировки, разблокировки и пробуждения.

4.32 Недостатки

  1. Долгое время цикла и большие затраты на использование процессора.2) Может гарантировать атомарность только одного общего переменного

  2. Проблема ABA

4.33 Проблема ABA

Алгоритм CAS предполагает важное условие — извлечение данных из памяти в определённый момент времени, а затем сравнение и замена в следующий момент времени. Временной промежуток между этими моментами может привести к изменениям данных. Например, поток one берёт значение A из памяти по адресу V. В этот момент другой поток two также берёт значение A из памяти и после выполнения некоторых операций превращает его в B, а затем снова записывает A в ту же позицию V. Когда поток one выполняет операцию CAS, он обнаруживает, что значение в памяти по-прежнему A, и операция успешно завершается. Однако успешное завершение операции CAS не гарантирует отсутствие проблем. Если голова связанного списка изменилась дважды и вернулась к исходному значению, это не означает, что сам список не изменился. Поэтому атомарные операции AtomicStampedReference/AtomicMarkableReference оказываются полезными. Они позволяют выполнять атомарные операции над парой изменяемых элементов.

4.34 Решение проблемы ABA

  1. Добавление версий

  2. AtomicStampedReferenceДля решения этой проблемы пакет java.util.concurrent предоставляет атомарный класс с маркером "AtomicStampedReference". Этот класс позволяет контролировать версию значений переменной, что гарантирует корректность операции CAS. Поэтому перед использованием CAS следует убедиться, что проблема ABA не влияет на корректность параллельного выполнения программы. Если требуется решение проблемы ABA, использование традиционного синхронного блокирования может быть более эффективным.#### 4.35 Время использования CAS

  3. При небольшом количестве потоков и коротком времени ожидания можно использовать спящий замок для попытки CAS захвата блока, что будет более эффективным, чем synchronized.

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

3. Процессы и потоки

Процесс:

Это программа с определённой независимой функцией; он является независимой единицей распределения и планирования ресурсов системы. Основное внимание уделяется системному планированию и независимой единице, то есть процесс представляет собой независимую программу, которая может быть запущена самостоятельно.

Поток:

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

4. Состояния потокаНовый - New

Готов к выполнению (вызов метода start потока, помещается в пуле потоков) - Runnable
Выполняется (получает время выполнения от ЦПУ) - Running
Блокировка (ожидание, синхронизация, асинхронизация, другие виды блокировки) - Blocked
(Постоянное) ожидание - Waiting
(Временное) ожидание - Timed-Waiting
Уничтожен (когда поток или main завершают выполнение) - Dead (Terminated)## 5. Различия между wait() и sleep()

5.1 Различие в принципах работы

Метод sleep() является статическим методом класса Thread, он используется потоком для управления собственным процессом. Он приостанавливает выполнение текущего потока на некоторое время, предоставляя другим потокам возможность выполнения. Когда таймер истекает, поток автоматически возобновляется. Например, если поток выполняет функцию отсчёта времени, то каждую секунду он выводит текущее время. В этом случае перед методом вывода времени следует вызвать метод sleep(), чтобы позволить потоку выполнять эту операцию каждую секунду, что аналогично работе будильника.

Метод wait() является методом класса Object, он используется для межпоточного взаимодействия. Этот метод приостанавливает текущий поток, который владеет объектом, до тех пор, пока другой поток не вызовет метод notify() или notifyAll(). Однако разработчик также может указать время, после которого поток автоматически возобновляется.#### 5.2 Различия в механизме работы с блокировкамиОсновная задача метода sleep() — приостановить выполнение потока на определённое количество времени, после чего выполнение автоматически возобновляется. Этот метод не включает в себя взаимодействие между потоками, поэтому вызов метода sleep() не приводит к освобождению блокировки. В отличие от этого, вызов метода wait() приводит к тому, что поток освобождает блокировку, которую он использует, что позволяет другим потокам использовать синхронизированные данные объекта.#### 5.3 Различия в области использования

Метод wait() должен использоваться внутри синхронизированного метода или блока кода, тогда как метод sleep() может использоваться в любом месте. Метод sleep() требует обработки исключения, в то время как методы wait(), notify() и notifyAll() не требуют обработки исключений. Во время выполнения метода sleep() поток может быть прерван другим потоком через метод interrupt(), что приведёт к выбросу исключения InterruptedException. Поскольку метод sleep() не освобождает блокировку, это может привести к проблемам с мёртвыми замками, поэтому рекомендуется использовать метод wait().

6. Параллелизм и параллельность

Параллелизм Когда несколько потоков одновременно используют один и тот же ресурс. Примеры: бронирование билетов на поезд во время праздников, моментальные покупки в интернет-магазинах.

Параллельность Когда несколько задач выполняются одновременно, а затем объединяются. Это можно сделать путём запуска нескольких задач в многопоточном или многопроцессорном режиме.

7. Пользовательские потоки и демон-потоки

В Java существует два типа потоков: пользовательские потоки (User Thread) и демон-потоки (Daemon Thread).

Пользовательские потоки

Определение: Обычные потоки, используемые в повседневной работе, являются пользовательскими потоками. Когда вы создаёте поток в Java-программе, он считается пользовательским потоком.### Демон-потоки

Определение: Демон-потоки — это служебные потоки, которые служат для обслуживания других потоков. В Java есть два типа потоков: пользовательские потоки и демон-потоки.

Различия

Основное различие заключается в том, когда JVM завершает свою работу.

Пользовательские потоки: JVM не завершит работу, пока хотя бы один пользовательский поток не завершит свою работу.

Демон-потоки: Если останутся только демон-потоки, JVM может завершить свою работу.

В Java создание демон-потока очень простое: достаточно использовать метод .setDaemon(true).

8. Ключевое слово synchronized

Это ключевое слово в Java, которое представляет собой синхронизированный блок, который может модифицировать объект, переменную или метод, чтобы контролировать последовательный доступ к этим элементам. Synchronized — это один из самых распространённых методов решения проблем параллелизма в Java и одновременно самый простой. Основные функции Synchronized следующие: (1) обеспечивает взаимоисключение при доступе к синхронизированному коду для разных потоков; (2) гарантирует своевременную видимость изменений общих переменных; (3) эффективно решает проблемы переупорядочивания. С точки зрения синтаксиса, Synchronized имеет три основных применения: (1) модификация обычных методов; (2) модификация статических методов; (3) модификация блока кода.

9. Недостатки synchronized

Ключевое слово synchronized имеет несколько недостатков:

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

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

  3. Проблемы с переупорядочиванием: Хотя synchronized блоки помогают решить проблемы переупорядочивания, они могут сами стать источником таких проблем, если не используются правильно.

  4. Отсутствие возможности использования более сложных блоков управления: Synchronized блоки не позволяют использовать более сложные механизмы блокировки, такие как семафоры или мьютексы, что может ограничивать возможности управления доступом к ресурсам.

  5. Проблемы с распределением нагрузки: Использование synchronized блоков может привести к неравномерному распределению нагрузки между потоками, что может привести к снижению производительности системы.

Эти недостатки следует учитывать при использовании ключевого слова synchronized, чтобы избежать возможных проблем с производительностью и управлением потоками.synchronized — это ключевое слово в Java, то есть это встроенная возможность языка Java. Но почему же появился Lock?

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

  1. Владелец замка завершает выполнение кодового блока и освобождает замок;

  2. Происходит исключение, и JVM автоматически освобождает замок.

Однако если владелец замка был заблокирован из-за операции ввода/вывода или других причин (например, вызова метода sleep), он не сможет освободить замок, и другие потоки будут вынуждены ждать, что негативно влияет на производительность программы.

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

Пример: при чтении и записи файла несколькими потоками могут возникнуть конфликты между операциями чтения и записи, а также между операциями записи и записи. Однако операции чтения и чтения не конфликтуют друг с другом.

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

Поэтому требуется механизм, который позволит нескольким потокам выполнять только операции чтения без конфликтов, и Lock позволяет это сделать.

Кроме того, с помощью Lock можно определить, удалось ли потоку успешно получить замок. Это невозможно сделать с помощью synchronized.

Вот основные различия:

  1. Lock не является встроенной возможностью Java, в то время как synchronized — это ключевое слово языка Java. Lock представляет собой класс, который позволяет реализовать синхронизированный доступ;

  2. Основное отличие между Lock и synchronized заключается в том, что при использовании synchronized нет необходимости вручную освобождать замок; когда метод synchronized или кодовый блок synchronized завершаются, система автоматически освобождает замок для потока; в то время как Lock требует от пользователя вручную освобождать замок, иначе это может привести к появлению ситуации смертельного замка.

10. Различия между интерфейсом Lock и ключевым словом synchronized1. Ключевое слово synchronized является встроенной частью языка, а Lock — это интерфейс.

  1. При возникновении исключений synchronized автоматически освобождает блокировку, что предотвращает возможность возникновения мертвых琐锁,а Lock в случае исключения не освобождает блокировку автоматически, поэтому необходимо освобождать её вручную в блоке finally.
  2. synchronized представляет собой непрерывную блокировку, которая требует ожидания завершения выполнения потока для освобождения блокировки, в то время как Lock позволяет прерывать блокировку.
  3. Интерфейс Lock поддерживает использование чтения блокировки, что повышает производительность при многопоточном чтении.
  4. synchronized не позволяет проверять состояние получения блокировки, в то время как Lock предоставляет такую возможность.
  5. Блокировка Lock подходит для синхронизации большого количества кода, в то время как блокировка synchronized лучше подходит для синхронизации небольших участков кода.
  6. Блокировка synchronized является повторяемой, непрерывной, неконкурентной, в то время как блокировка Lock является повторяемой, проверяемой и конкурентной (оба могут быть).## 11. Методы класса Object в Java

Класс Object является базовым классом для всех классов в Java, представляя вершину всей иерархии классов. Этот класс содержит методы, такие как toString(), equals(), hashCode(), wait(), notify() и getClass().

Класс Object содержит следующие методы: registerNatives(), getClass(), hashCode(), equals(), clone(), toString(), notify(), notifyAll(), wait(long), wait(long,int), wait() и finalize(). Этот порядок соответствует последовательности объявления методов в классе Object.

12. Шаги для многопоточного программирования

  1. Создайте класс ресурса, определив свойства и методы операций.
  2. В методах операций класса ресурса выполните следующие действия:
    • Проверьте условие.
    • Выполните операцию.
    • Уведомите другие потоки.
  3. Создайте несколько потоков, вызывающих методы операций класса ресурса.
  4. Предотвратите проблему ложного пробуждения.

13. Что такое ложное пробуждение в многопоточном программировании?

В разных языках программирования и даже в разных операционных системах условные блокировки могут вызывать ложное пробуждение. Все библиотеки условных блокировок рекомендуют пользователям помещать wait() в цикл, чтобы предотвратить ложное пробуждение.

package cn.com.guage.guc.sync;
``````java
//Шаг 1: Создание класса ресурса, определение свойств и методов операций
class Share {
    //Изначальное значение
    private int number = 0;
    //Метод увеличения значения на 1
    public synchronized void incr() throws InterruptedException {
        //Шаг 2: Проверка, выполнение операции, уведомление
        if(number != 0) { //Проверка значения number, если оно не равно 0, ждать
            this.wait(); //Где спишь, там и проснешься
        }
        //Если значение number равно 0, увеличиваем его на 1
        number++;
        System.out.println(Thread.currentThread().getName()+" :: "+number);
        //Уведомляем другие потоки
        this.notifyAll();
    }
}
    // -1 метода
    public synchronized void decr() throws InterruptedException {
        // проверка
        if (number != 1) {
            this.wait();
        }
        // выполнение
        number--;
        System.out.println(Thread.currentThread().getName() + " :: " + number);
        // уведомление других потоков
        this.notifyAll();
    }
}
    // -1 метода
    public synchronized void decr() throws InterruptedException {
        // проверка
        if (number != 1) {
            this.wait();
        }
        // выполнение
        number--;
        System.out.println(Thread.currentThread().getName() + " :: " + number);
        // уведомление других потоков
        this.notifyAll();
    }
}
``````markdown
public class ThreadDemo1 {
    // третий шаг: создание нескольких потоков, вызывающих методы операций ресурсного класса
    public static void main(String[] args) {
        Share share = new Share();
        // создание потока
        new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                try {
                    share.incr(); // +1
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "AA").start();

        new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                try {
                    share.decr(); // -1
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "BB").start();

        new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                try {
                    share.incr(); // +1
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "CC").start();

        new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                try {
                    share.decr(); // -1
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "DD").start();
    }
}

Запуск программы:

package cn.com.guage.guc.sync;

/**
 * 
 * @author yangdechao
 *
 */

class Share2 {
    private int number = 0;

    // +1
    public synchronized void inc() throws InterruptedException {
        // замена while гарантирует корректность логики, даже если поток был пробудлен.
        while (number != 0) {
            this.wait();
        }
        number++;
        System.out.println(Thread.currentThread().getName() + "::" + number);
        this.notifyAll();
    }

    // -1
    public synchronized void dec() throws InterruptedException {
        while (number != 1) {
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName() + "::" + number);
        this.notifyAll();
    }
}

публичный класс ThreadDemo2 {

    public static void main(String[] args) {
        Share2 share2 = new Share2();
        new Thread(() -> {
            for (int i = 1; i < 10; i++) {
                try {
                    share2.inc();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "AA").start();
        new Thread(() -> {
            for (int i = 1; i < 10; i++) {
                try {
                    share2.dec();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "BB").start();
        new Thread(() -> {
            for (int i = 1; i < 10; i++) {
                try {
                    share2.dec();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "CC").start();
        
        new Thread(() -> {
            for (int i = 1; i < 10; i++) {
                try {
                    share2.dec();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "DD").start();
    }

}
```

Результат выполнения:

![](D:\git-my-flink-workspace\guage-guc\images\1642410836(1).jpg)

## 14. Дедлок

### Что такое дедлок?

Дедлок  это состояние, при котором два или более процесса в процессе выполнения заблокированы из-за конкуренции за ресурсы или из-за взаимной коммуникации. Без внешнего воздействия эти процессы не могут продолжить выполнение. В этом случае говорят, что система находится в состоянии дедлока, а эти всегда заблокированные друг на друга процессы называются процессами дедлока.### Четыре необходимых условия для возникновения дедлока

** Условие монопольного использования**: процесс использует выделенный ему ресурс эксклюзивно, то есть в течение некоторого времени ресурс может использоваться только одним процессом. Если другой процесс просит этот же ресурс, он будет заблокирован до тех пор, пока текущий процесс не освободит его.

** Условие запроса и удержания**: процесс уже удерживает хотя бы один ресурс, но также делает запрос на другой ресурс, который уже занят другим процессом. В этом случае процесс будет заблокирован, но он продолжит удерживать уже полученные им ресурсы.

** Условие неделимости**: процесс не может быть принудительно лишен ресурса до тех пор, пока он не завершит его использование. То есть ресурс может быть освобожден только самим процессом, который его получил (только активное освобождение).

** Условие циклического ожидания**: при возникновении мертвых locks, обязательно существует процесс, который зависит от циклической цепочки ресурсов. То есть, в множестве процессов {P0, P1, P2, ..., Pn}, P0 ожидает ресурс, занятый P1; P1 ожидает ресурс, занятый P2, и так далее, до Pn, который ожидает ресурс, занятый P0.Эти четыре условия являются необходимыми условиями для возникновения deadlocks. Если система столкнулась с deadlockом, эти условия обязательно будут выполнены. Однако, если хотя бы одно из этих условий не выполняется, deadlock не может произойти.## 15. Несколько способов реализации взаимодействия между потоками

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

### Способ 1: Использование ключевого слова volatile

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

```java
package cn.com.guage.guc.communication;

import java.util.ArrayList;
import java.util.List;

public class VolatileCommunicationTest {

    // Определение переменной общего доступа для реализации взаимодействия, она должна быть помечена как volatile, чтобы потоки могли своевременно воспринять изменения
    static volatile boolean notice = false;
```    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        // Реализация потока A
        Thread threadA = new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                list.add("abc");
                System.out.println("Поток A добавляет элемент в список, количество элементов в списке: " + list.size());
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (list.size() == 5)
                    notice = true;
            }
        });
        // Реализация потока B
        Thread threadB = new Thread(() -> {
            while (true) {
                if (notice) {
                    System.out.println("Поток B получил уведомление, начинает выполнять свою бизнес-логику...");
                    break;
                }
            }
        });
        // Сначала запускаем поток B
        threadB.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // Затем запускаем поток A
        threadA.start();
    }
}
```## Вариант 2: Использование методов `wait()` и `notify()` класса `Object````Класс `Object` предоставляет методы для межпоточного взаимодействия: `wait()`, `notify()` и `notifyAll()`. Эти методы являются основой для межпоточного взаимодействия, а идея этого подхода заключается в том, чтобы использовать межпоточное взаимодействие.

```
Примечание: Методы `wait()` и `notify()` должны использоваться вместе с `synchronized`, `wait()` освобождает блокировку, а `notify()` не освобождает блокировку.
```

```java
package cn.com.guage.guc.communication;

import java.util.ArrayList;
import java.util.List;

/**
 *
 * @author yangdechao
 * Класс `Object` предоставляет методы для межпоточного взаимодействия: `wait()`, `notify()` и `notifyAll()`.
 * Эти методы являются основой для межпоточного взаимодействия, а идея этого подхода заключается в том,
 * чтобы использовать межпоточное взаимодействие.
 */
public class WaitCommunicationTest {
```    public static void main(String[] args) {
        // Определяем объект-блокировку
        Object lock = new Object();
        List<String> list = new ArrayList<>();
        // Создаем поток A
        Thread threadA = new Thread(() -> {
            synchronized (lock) {
                for (int i = 1; i <= 10; i++) {
                    list.add("abc");
                    System.out.println("Поток A добавил элемент в список, теперь количество элементов в списке равно: " + list.size());
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    if (list.size() == 5)
                        lock.notify(); // Пробуждает поток B
                }
            }
        });
        // Создаем поток B
        Thread threadB = new Thread(() -> {
            while (true) {
                synchronized (lock) {
                    if (list.size() != 5) {
                        try {
                            lock.wait(); // Ждет сигнала
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println("Поток B получил сигнал и начал выполнение своего бизнес-процесса...");
                }
            }
        });
        // Нужно запустить поток B первым
        threadB.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // Затем запускаем поток A
        threadA.start();
    }
}
```Запущенные результаты:```![](D:\git-my-flink-workspace\guage-guc\images\1642472215(1).jpg)

После того как поток A вызвал notify(), он завершил выполнение своего бизнес-лога, а затем поток B начал выполнение. Это также показывает, что метод notify() не освобождает блокировку, в то время как метод wait() освобождает блокировку.

### Метод три: использование JUC инструментального класса CountDownLatch

С версии JDK 1.5 в пакете `java.util.concurrent` появились много инструментальных классов для параллельного программирования, что упростило написание кода для параллельного программирования. Класс `CountDownLatch` основан на фреймворке AQS и также поддерживает общую переменную состояния `state`, которую используют несколько потоков.

```java
package cn.com.guage.guc.communication;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;

/**
 *
 * @author yangdechao
 * С версии JDK 1.5 в пакете java.util.concurrent появились много инструментальных классов для параллельного программирования,
 * что упростило написание кода для параллельного программирования. Класс CountDownLatch основан на фреймворке AQS и также поддерживает общую переменную состояния state,
 * которую используют несколько потоков.
 */
public class CountDownLatchCommunicationTest {

    public static void main(String[] args) {
        CountDownLatch countDownLatch = new CountDownLatch(1);
    }
}
```        List<String> list = new ArrayList<>();
        // Создаем поток A
        Thread threadA = new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                list.add("abc");
                System.out.println("Поток A добавляет элемент в список, теперь количество элементов в списке равно: " + list.size());
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (list.size() == 5)
                    countDownLatch.countDown();
            }
        });
        // Создаем поток B
        Thread threadB = new Thread(() -> {
            while (true) {
                if (list.size() != 5) {
                    try {
                        countDownLatch.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("Поток B получил уведомление и начал выполнять свой бизнес...");
                break;
            }
        });
        // Нужно запустить поток B первым
        threadB.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // Затем запускаем поток A
        threadA.start();
    }
}
```Запуск программы:

![](D:\git-my-flink-workspace\guage-guc\images\1642472550(1).jpg)



### Метод 4: Использование ReentrantLock вместе с Condition

```java
package cn.com.guage.guc.communication;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/**
 *
 * @author yangdechao
 */
public class ReentrantLockCommunicationTest {
    public static void main(String[] args) {
        ReentrantLock lock = new ReentrantLock();
        Condition condition = lock.newCondition();
```        List<String> list = new ArrayList<>();
        // Реализация потока A
        Thread threadA = new Thread(() -> {
            lock.lock();
            for (int i = 1; i <= 10; i++) {
                list.add("abc");
                System.out.println("Поток A добавляет элемент в список, количество элементов в списке: " + list.size());
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (list.size() == 5)
                    condition.signal();
        });```java
package cn.com.guage.guc.communication;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.LockSupport;

public class Example {

    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        Thread threadA = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                list.add(i);
            }
            System.out.println("Поток A завершил добавление элементов в список...");

            // Пробуждаем поток B
            LockSupport.unpark(Thread.currentThread());
        });

        Thread threadB = new Thread(() -> {
            // Ждем сигнала от потока A
            LockSupport.park();

            System.out.println("Поток B получил сигнал и начал выполнение своей задачи...");
        });

        threadB.start();
        threadA.start();
    }
}
```

Результат запуска программы:

![](D:\git-my-flink-workspace\guage-guc\images\1642473205(1).jpg)

Очевидно, что этот метод не очень удобен в использовании, код сложен для написания, а также поток B не может немедленно выполниться после пробуждения, так как он не имеет доступа к замку. То есть, после сигнала от A, замок не освобождается. Этот подход аналогичен использованию методов `wait()` и `notify()` объекта.
```/**
 *
 * @author yangdechao
 * LockSupport — это очень гибкий инструмент для блокировки и пробуждения потоков,
 * при его использовании нет необходимости беспокоиться о том, какой поток начнет выполнение раньше — ждущий или пробуждающий, но важно знать имя потока.
 */
public class LockSupportCommunicationTest {    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        // Реализация потока B
        final Thread threadB = new Thread(() -> {
            if (list.size() != 5) {
                LockSupport.park();
            }
            System.out.println("Поток B получил уведомление и начал выполнять свою бизнес-логику...");
        });
        // Реализация потока A
        Thread threadA = new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                list.add("abc");
                System.out.println("Поток A добавляет один элемент в список, теперь количество элементов в списке равно: " + list.size());
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (list.size() == 5)
                    LockSupport.unpark(threadB);
            }
        });
        threadA.start();
        threadB.start();
    }
}
```

Результат выполнения:

![](D:\git-my-flink-workspace\guage-guc\images\1642473425(1).jpg)

## 16. Java коллекции и вопросы безопасности в многопоточной среде

В многопоточной среде может возникнуть исключение `java.util.ConcurrentModificationException`.

### 16.1 Пример кода

```java
package cn.com.guage.guc.collection.safe;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

/**
 * @author yangdechao
 * Пример небезопасного использования ArrayList в многопоточной среде
 * В многопоточной среде может возникнуть исключение java.util.ConcurrentModificationException.
 * В многопоточной среде конкурирующие попытки модификации приводят к этому исключению.
 */
public class ArrayListUnsafeTest {
``````java
    /**
     * @param args
     */
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
``````markdown
            for (int i = 0; i < 30; i++) {
                new Thread(() -> {
                    list.add(UUID.randomUUID().toString().substring(0, 8));
                    System.out.println(list);
                }).start();
            }
```        }

    }
```

Выполнение программы приводит к следующему результату:

![](D:\git-my-flink-workspace\guage-guc\images\1642476697(1).jpg)

### 16.2 Причина ошибки

```
В многопоточной среде из-за конкурентного доступа к изменению данных возникает данная ошибка.
```

### 16.3 Решение проблемы

1) Использование поточно-безопасного класса `Vector`: `new Vector();`

2) Использование метода `synchronizedList` из класса `Collections`: `Collections.synchronizedList(new ArrayList<>());`

3) Использование контейнера `CopyOnWriteArrayList` из пакета `java.util.concurrent`: `new CopyOnWriteArrayList<>();`

### 16.4 Концепция write-through (запись при копировании)

Контейнер `CopyOnWrite` работает по принципу записи при копировании. При добавлении элемента в контейнер, вместо добавления элемента непосредственно в текущий массив `Object[]`, создается его копия `Object[] newElements`. Элемент добавляется в новый массив, после чего ссылка на оригинальный массив перенаправляется на новый массив `setArray(newElements);`. Преимуществом такого подхода является возможность параллельного чтения контейнера без необходимости блокировки, так как оригинальный массив не изменяется. Таким образом, контейнер `CopyOnWrite` также использует идею разделения операций чтения и записи.

## 17. Методы обеспечения поточного безопасного использования коллекции `ArrayList`

### 1) Использование поточно-безопасного класса `Vector`

   `new Vector();`   Если количество элементов в коллекции превышает размер текущего массива, то `Vector` увеличивается на 100% от текущего размера, тогда как `ArrayList` увеличивается на 50%.

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

### 2) Использование метода `synchronizedList` из класса `Collections`

   `Collections.synchronizedList(new ArrayList<>());`

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

### 3) Использование контейнера `CopyOnWriteArrayList` из пакета `java.util.concurrent`

   `new CopyOnWriteArrayList<>();`   Чтение не защищено блокировками, запись использует CAS-блокировку с использованием цикла ожидания. Сначала создается копия исходного массива, затем изменяется копия массива, после чего копия массива снова копируется обратно в исходное место (при большом размере массива копирование становится очень медленным). Большое количество конкурентных операций записи и столкновения CAS могут также влиять на производительность. При чтении данные берутся из исходного массива, поэтому если метод `add` ещё не завершил выполнение метода `setArray`, прочитанные данные будут представлять собой старые данные.
   
```## 18. Ф fair lock и non-fair lock```**Ф fair lock:** эффективность ниже
**Non-fair lock:** высокая эффективность, но потоки могут быть заблокированы

Просмотр исходного кода показывает, что ReentrantLock(true) с параметром является fair lock, а ReentrantLock(false)  non-fair lock. Основные вызовы: NonfairSync() и FairSync().

## 19. Возвратяемые (reentrant) замки

### Концепция

**Возвратяемый (или рекурсивный) замок** позволяет одному и тому же потоку получить доступ к замку внутри метода, который уже владеет этим замком. Это значит, что: `поток может войти в любой блок кода, синхронизированный с тем же замком`.

Это работает на уровне потока, когда один поток получает объект замка, он может снова получить этот же объект замка, но другие потоки не могут.

`synchronized` и `lock` являются возвратяемыми замками:

- `synchronized`  это неявный замок, который не требует ручной блокировки и разблокировки, в то время как `lock`  явный замок, который требует ручной блокировки и разблокировки.
- Возвратяемый замок также называется **рекурсивным замком**.

### Значение

**Одним из значений возвратяемого замка является предотвращение мертвыхlocks (deadlocks)**. Когда несколько потоков ожидают друг друга для освобождения ресурсов, они могут создать ситуацию, при которой ни один из них не сможет продолжить выполнение. Возвратяемый замок помогает избежать таких ситуаций, позволяя одному и тому же потоку повторно захватывать тот же замок.

## 20. Разные способы создания потоков

Существует несколько способов создания потоков в Java. Вот некоторые из них:

- **Расширение класса Thread**: Вы можете расширить класс `Thread`, переопределить метод `run()` и затем запустить поток с помощью метода `start()`. Например:

```java
class MyRunnable implements Runnable {
    public void run() {
        // Код, выполняемый потоком
    }
}

public class Main {
    public static void main(String[] args) {
        Thread thread = new Thread(new MyRunnable());
        thread.start();
    }
}
```

- **Использование интерфейса Runnable**: Вы можете использовать интерфейс `Runnable`, реализовать метод `run()` и передать его экземпляр в конструктор класса `Thread`. Например:

```java
class MyRunnable implements Runnable {
    public void run() {
        // Код, выполняемый потоком
    }
}

public class Main {
    public static void main(String[] args) {
        Thread thread = new Thread(new MyRunnable());
        thread.start();
    }
}
```

- **Расширение класса Runnable**: Вы можете расширить класс `Runnable`, переопределить метод `run()` и затем запустить поток с помощью метода `start()`. Например:

```java
class MyRunnable extends Runnable {
    @Override
    public void run() {
        // Код, выполняемый потоком
    }
}

public class Main {
    public static void main(String[] args) {
        Thread thread = new Thread(new MyRunnable());
        thread.start();
    }
}
```

- **Использование ExecutorService**: Вы можете использовать `ExecutorService` для управления потоками. Например:

```java
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Main {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 10; i++) {
            executor.submit(new MyRunnable());
        }
        executor.shutdown();
    }
}
```

Каждый из этих подходов имеет свои преимущества и недостатки, и выбор зависит от конкретной задачи и требований к производительности и управлению потоками.### 1) Наследование от класса Thread

### 2) Реализация интерфейса Runnable

### 3) Интерфейс Callable

### 4) Пул потоков

**Сравнение интерфейсов Runnable и Callable**

В `Callable` метод `call()` вычисляет результат, если результат не может быть вычислен, выбрасывается исключение. В `Runnable` метод `run()` используется для создания потока с использованием объекта, реализующего интерфейс `Runnable`. Запуск этого потока приводит к вызову метода `run()` этого объекта в независимом потоке выполнения. В целом: метод `run()` не имеет возвращаемого значения и не выбрасывает исключения, в то время как метод `call()` имеет возвращаемое значение и может выбрасывать исключения.

**Класс FutureTask**

Конструктор класса `FutureTask` имеет следующие формы:

```
FutureTask(Callable<> callable) создает FutureTask, который будет выполнен при запуске данного Callable
FutureTask(Runnable runnable,V result) создает FutureTask, который будет выполнен при запуске данного Runnable, и устанавливает результат get при успешном завершении
```

Другие часто используемые коды:
`get()`  получение результата
`isDone()`  проверка завершения вычислений

FutureTask может быть реализован двумя способами (в данном случае используются параметры-представители).


```python
package cn.com.guage.guc.callable;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

// Сравнение двух интерфейсов
// Реализация интерфейса Runnable
class MyThread1 implements Runnable {
    @Override
    public void run() {
``````markdown
    }
}

// Реализация интерфейса Callable
class MyThread2 implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        System.out.println(Thread.currentThread().getName() + " come in callable");
        return 200;
    }
}

public class Demo1 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // Использование интерфейса Runnable для создания потока
        new Thread(new MyThread1(), "AA").start();

        // Использование интерфейса Callable, вызывает ошибку
        // new Thread(new MyThread2(), "BB").start();

        // FutureTask
        FutureTask<Integer> futureTask1 = new FutureTask<>(new MyThread2());

        // Лямбда-выражение
        FutureTask<Integer> futureTask2 = new FutureTask<>(() -> {
            System.out.println(Thread.currentThread().getName() + " come in callable");
            return 1024;
        });

        // Создание потока
        new Thread(futureTask2, "lucy").start();
        new Thread(futureTask1, "mary").start();

//        while (!futureTask2.isDone()) {
//            System.out.println("wait.....");
//        }
        // Вызов метода get() FutureTask
        System.out.println(futureTask2.get());

        System.out.println(futureTask1.get());

        System.out.println(Thread.currentThread().getName() + " come over");
        // Принцип работы FutureTask — будущий задачи

    }
}
```

Результат выполнения:

![](D:\git-my-flink-workspace\guage-guc\images\1642505258(1).jpg)

## 21. Принцип работы Callable интерфейса
```Механизм future заключается в том, что при выполнении задачи через поток, если она занимает много времени, можно использовать механизм FutureTask для асинхронного получения результата, продолжая выполнять другие задачи. **При необходимости получить результат выполнения задачи, используется метод future.get(), который блокирует основной поток с помощью LockSupport.park(), пока метод run() не завершится, после чего поток разблокируется с помощью LockSupport.unpark().**``````java
public class FutureTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyThread myThread = new MyThread();
        FutureTask<String> futureTask = new FutureTask<>(myThread);
        /**
         * То же самое futureTask объект может быть вызван несколькими потоками несколько раз,
         * но выполнится только один раз.
         * Если это вычислительная операция, требующая нескольких вычислений, необходимо объявить
         * разные объекты futureTask.
         */
        new Thread(futureTask, "A").start();
        new Thread(futureTask, "B").start();
        System.out.println(Thread.currentThread().getName() + " тест, до вызова future.get() метода, можно выполнять другую логику ");
        System.out.println(futureTask.get());
        System.out.println("тест futureTask get метода блокировки");
    }
}

class MyThread implements Callable<String> {

    @Override
    public String call() throws Exception {
        System.out.println(Thread.currentThread().getName() + " тест callable");
        TimeUnit.SECONDS.sleep(4);
        return "успех";
    }
}
```

Принцип работы
Давайте сначала поговорим о том, как future.get() блокирует выполнение, то есть как основной поток блокируется, пока соответствующий метод потока не завершится.

В исходном коде есть важное свойство:
``````markdown
private volatile int state;
/**
 * В конструкторе устанавливается значение NEW
 */
private static final int NEW          = 0;
/**
 * При успешном завершении выполнения потока через CAS состояние state изменяется на COMPLETING
 */
private static final int COMPLETING   = 1;
/**
 * При успешном завершении выполнения потока через CAS состояние state изменяется на NORMAL
 */
private static final int NORMAL       = 2;
/**
 * Если при выполнении потока возникает исключение, через CAS состояние state изменяется на EXCEPTIONAL
 */
private static final int EXCEPTIONAL  = 3;
/**
 * Если вызывается метод cancel(boolean mayInterruptIfRunning),
 * и параметр mayInterruptIfRunning равен true, через CAS состояние state изменяется на INTERRUPTING
 * Если параметр равен false, через CAS состояние state изменяется на CANCELLED
 */
private static final int CANCELLED    = 4;
private static final int INTERRUPTING = 5;
/**
 * Если вызывается метод cancel(), и параметр равен true, и если прерывание успешно,
 * через CAS состояние state изменяется с INTERRUPTING на INTERRUPTED
 */
private static final int INTERRUPTED  = 6;
```Далее рассмотрим метод `get()`. В методе `get()` сначала проверяется текущее состояние (`state`), если оно меньше или равно `COMPLETING`, то происходит блокировка.

```markdown
/**
 * @throws CancellationException {@inheritDoc}
 * При вызове метода future.get(), если метод run() ещё не завершён, основной поток будет ждать, пока run() завершится корректно.
 * Внутри это делается с помощью метода lockSupport.park().
 * В этом механизме используется состояние потока.
 */
public V get() throws InterruptedException, ExecutionException {
    int s = state;
    if (s <= COMPLETING)
        s = awaitDone(false, 0L);
    return report(s);
}
```

Основная логика метода `get()` реализуется в методе `awaitDone()`, где вызывается `lockSupport.park(this)` для блокировки текущего потока.

Таким образом, можно заключить, что если основной поток вызывает метод `future.get()`, а метод `run()` ещё не завершён, то поток будет заблокирован. После завершения метода `run()`, заблокированный поток будет разблокирован и продолжит выполнение бизнес-логики.

### Заключение

**Таким образом, при вызове метода `future.get()`, если метод `run()` ещё не завершён, этот метод заблокирует поток, а после завершения метода `run()`, разблокирует заблокированный поток и продолжит выполнение соответствующей бизнес-логики.**

## 22. CountDownLatch (уменьшение счетчика)

### Способ использования

CountDownLatch  это синхронизирующий инструмент из пакета `java.util.concurrent`, который позволяет одному или нескольким потокам ждать, пока другие потоки не завершат определенные операции.### Описание работы

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

### Пример кода

```java
package cn.com.guage.guc.juc;

import java.util.concurrent.CountDownLatch;

// Демонстрация использования CountDownLatch
public class CountDownLatchDemo {
    // Шесть учеников по очереди покидают класс, затем классный руководитель запирает дверь
    public static void main(String[] args) throws InterruptedException {

        // Создание объекта CountDownLatch и установка начального значения
        CountDownLatch countDownLatch = new CountDownLatch(6);

        // Шесть учеников последовательно покидают класс
        for (int i = 1; i <= 6; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + " ученик учится");
                System.out.println(Thread.currentThread().getName() + " ученик играет");
                System.out.println(Thread.currentThread().getName() + " ученик покинул класс");

                // Уменьшение счетчика на 1
                countDownLatch.countDown();

            }, String.valueOf(i)).start();
        }

        // Ожидание
        countDownLatch.await();

        System.out.println(Thread.currentThread().getName() + " классный руководитель запирает дверь и уходит"); // Прямой перевод китайского текста, так как нет точного аналога в русском языке
    }
}
```

Результат выполнения:

![](D:\git-my-flink-workspace\guage-guc\images\1642557594(1).jpg)## 23. CyclicBarrier (Циклический барьер)

CyclicBarrier类似于CountDownLatch, также является счетчиком, но отличается тем, что CyclicBarrier считает количество потоков, вызывающих CyclicBarrier.await() и переходящих в состояние ожидания. Когда количество потоков достигает значения, заданного при создании CyclicBarrier, все ожидающие потоки пробуждаются и продолжают выполнение. CyclicBarrier можно рассматривать как препятствие, через которое могут пройти только все потоки одновременно. При создании CyclicBarrier можно передать Runnable задачу, которая будет выполнена после того, как все потоки достигнут состояния ожидания.

Пример:

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

```markdown
package cn.com.guage.guc.juc;

import java.util.concurrent.CyclicBarrier;

// Десять человек должны прибыть в ресторан, чтобы начать обед
public class CyclicBarrierDemo {

    // Создание фиксированного значения 10, например, десять человек идут обедать в ресторан
    private static final int NUMBER = 10;
```    public static void main(String[] args) {
        // Создание CyclicBarrier
        CyclicBarrier cyclicBarrier = new CyclicBarrier(NUMBER, () -> {System.out.println("*****10 человек прибыли, начинается обед");});
``````markdown
//Процесс прибытия 10 человек в ресторан
for (int i = 1; i <= 10; i++) {
    new Thread(() -> {
        try {
            System.out.println(Thread.currentThread().getName() + " прибыл в ресторан, ожидает начала приема пищи");
            //ожидание
            cyclicBarrier.await();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }, String.valueOf(i)).start();
}
}
```
```Результат выполнения:```![](D:\git-my-flink-workspace\guage-guc\images\1642560489(1).jpg)

## 24. Semaphore (семафор)

### Инструкция по использованию

Семафор обычно называют сигналом, который используется для контроля количества одновременно доступных к определённому ресурсу потоков. С помощью координации различных потоков обеспечивается правильное использование ресурсов. Семафор используется для управления сигналами и в синхронизации программирования может контролировать количество потоков, имеющих доступ к синхронному коду. При создании экземпляра семафора передаётся целое число, указывающее количество сигналов. Основные методы  это `acquire()` и `release()`. Метод `acquire()` используется для запроса сигнала; каждый вызов уменьшает количество сигналов на единицу. Метод `release()` используется для освобождения сигнала; каждый вызов увеличивает количество сигналов на единицу. Когда все сигналы будут использованы, последующие потоки, использующие метод `acquire()`, будут добавлены в очередь ожидания.Можно представить его как дисплей, установленный у входа на парковку. Каждый раз, когда автомобиль заезжает на парковку, на дисплее отображается количество свободных мест минус один. Когда автомобиль покидает парковку, количество свободных мест на дисплее увеличивается на единицу. Когда количество свободных мест на дисплее становится нулевым, шлагбаум у входа на парковку не будет открываться, и автомобили не смогут заехать на парковку до тех пор, пока какой-то автомобиль не покинет парковку.### Сценарии использования

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

### Объяснение часто используемых методов Semaphore

```java
acquire()  
Получает токен; поток остаётся в состоянии ожидания до тех пор, пока не получит токен или не будет прерван другим потоком.

acquire(int permits)  
Получает токен; поток остаётся в состоянии ожидания до тех пор, пока не получит токен, не будет прерван другим потоком или не истечёт время ожидания.

acquireUninterruptibly()  
Получает токен; поток остаётся в состоянии ожидания до тех пор, пока не получит токен (игнорирует прерывание).

tryAcquire()  
Пытается получить токен; возвращает успешность операции получения токена без блокировки потока.
```tryAcquire(long timeout, TimeUnit unit)
Пытается получить токен; повторяет попытки получения токена в течение указанного времени ожидания, возвращает успешность операции получения токена без блокировки потока.

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

hasQueuedThreads()
Проверяет наличие ожидающих потоков в очереди.

getQueueLength()
Возвращает количество ожидающих потоков в очереди.

drainPermits()
Очищает все доступные токены, возвращает количество освобожденных токенов.

availablePermits()
Возвращает количество доступных токенов.

```### Пример кода

```java
package cn.com.guage.guc.juc;

import java.util.Random;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

// 10 автомобилей, 5 парковочных мест
public class SemaphoreDemo {
    public static void main(String[] args) {
        // Создание объекта Semaphore с количеством разрешений
        Semaphore semaphore = new Semaphore(3);

        // Моделирование 6 автомобилей
        for (int i = 1; i <= 5; i++) {
            new Thread(() -> {
                try {
                    // Получение разрешения
                    semaphore.acquire();

                    System.out.println(Thread.currentThread().getName() + " забронировал парковочное место");

                    // Установка случайного времени ожидания
                    TimeUnit.SECONDS.sleep(new Random().nextInt(5));

                    System.out.println(Thread.currentThread().getName() + " освободил парковочное место");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    // Освобождение разрешения
                    semaphore.release();
                }
            }, String.valueOf(i)).start();
        }
    }
}
```### Обзор принципов

Контроль семафора осуществляется с помощью AQS (AbstractQueuedSynchronizer). Внутри класса `Semaphore` есть внутренний класс `Sync`, который наследует AQS. Также в `Semaphore` есть два внутренних класса `FairSync` и `NonfairSync`, которые наследуют `Sync`. Это означает, что `Semaphore` имеет как честный замок (fair lock), так и нечестный замок (non-fair lock).

## 25. Обзор принципов AQS (не завершен)

## 26. Читательский замок и писательский замок

### Читательский замок (общий замок S)

Позволяет транзакциям читать запись, предотвращая другие транзакции от получения эксклюзивного замка на ту же самую запись. Если транзакция T добавляет S-замок на запись A, то транзакция T может читать A, но не может изменять A. Другие транзакции могут добавить S-замок на A, но не могут добавить X-замок. Только после того, как T освободит S-замок на A, другие транзакции могут изменять A. InnoDB использует `lock in share mode` для добавления читательского замка, но обратите внимание, что он блокирует только индексируемые данные.

### Писательский замок (эксклюзивный замок)Позволяет транзакциям, имеющим эксклюзивный замок, изменять данные, предотвращая другие транзакции от получения общего чтения или эксклюзивного записи замка на ту же самую запись. Если транзакция T добавляет X-замок на запись A, то транзакция T может читать A и изменять A. Другие транзакции не могут добавить ни S-замок, ни X-замок на A до тех пор, пока T не освободит X-замок на A. Все DML операции InnoDB по умолчанию используют писательский замок. Выборка может использовать for update для добавления писательского замка, и это будет блокировать все индексы, а не только те, которые охватывают данные.

## 27. Создание пула потоков| Метод                             | Функционал                                             |
| :--------------------------------- | :----------------------------------------------------- |
| newFixedThreadPool(int nThreads)  | Создает пул потоков с фиксированным размером          |
| newSingleThreadExecutor()         | Создает пул потоков с одним потоком                   |
| newCachedThreadPool()             | Создает расширяющийся пул потоков, все задачи выполняются немедленно |

В повседневной работе обычно используются пользовательские пулы потоков, а не те, что созданы выше:

Пример пользовательского пула потоков:

```java
package cn.com.guage.guc.pool;

import java.util.concurrent.*;

// Моделирование банковских окон для обслуживания клиентов. Создание пользовательского пула потоков
public class ThreadPoolDemo2 {
    public static void main(String[] args) {
        ExecutorService threadPool = new ThreadPoolExecutor(
                2,
                 Yöntem: 5,
                2L,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.CallerRunsPolicy()
        );

        //10 запросов от клиентов
        try {
            for (int i = 1; i <= 10; i++) {
                int num = i;
                //Выполнение
                threadPool.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + " Обслуживает клиента под номером " + num);
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //Закрытие
            threadPool.shutdown();
        }
    }
}
```

Результат выполнения:

![](D:\git-my-flink-workspace\guage-guc\images\1642646032(1).jpg)

## 28. Семь параметров конструктора пула потоковКонструктор пула потоков выглядит следующим образом:

![](D:\git-my-flink-workspace\guage-guc\images\1642642685(1).jpg)

Конструктор пула потоков имеет семь параметров: corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler. Далее будут объяснены эти параметры.

1) **corePoolSize  количество ядерных потоков в пуле**

Пул потоков поддерживает минимальное количество потоков, даже если они находятся в состоянии ожидания, они не будут уничтожены, за исключением случаев, когда установлено allowCoreThreadTimeOut. Это минимальное количество потоков равно corePoolSize.

2) **maximumPoolSize  максимальное количество потоков в пуле**

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

3) **keepAliveTime  время жизни свободного потока**Если поток находится в состоянии ожидания и текущее количество потоков превышает corePoolSize, то этот свободный поток будет уничтожен через определённое время. Это время задаётся параметром keepAliveTime.4) **unit единица измерения времени жизни свободного потока**

Единица измерения времени, заданного параметром `keepAliveTime`.

5) **workQueue рабочая очередь**

Новая задача после отправки попадает в эту рабочую очередь. При распределении задач они извлекаются из очереди. JDK предоставляет четыре типа рабочих очередей:

 ArrayBlockingQueue

Ограниченная блокирующая очередь на основе массива, которая упорядочивает задачи по принципу FIFO. Новая задача добавляется в конец очереди. Ограниченный массив предотвращает исчерпание ресурсов. Когда количество потоков достигает значения `corePoolSize`, новые задачи добавляются в конец очереди и ожидают распределения. Если очередь заполнена, создается новый поток. Если количество потоков достигает значения `maximumPoolSize`, применяется стратегия отказа.

 LinkedBlockingQueue

Бесконечная блокирующая очередь на основе связного списка (на самом деле максимальная емкость равна `Integer.MAX_VALUE`), которая упорядочивает задачи по принципу FIFO. Из-за своего приближенно бесконечного размера, когда количество потоков достигает значения `corePoolSize`, новые задачи продолжают добавляться в очередь, не создавая новых потоков до достижения значения `maximumPoolSize`. Поэтому при использовании этой рабочей очереди параметр `maximumPoolSize` фактически не используется. SynchronousQueue

Блокирующая очередь без буферизации, которая требует, чтобы производитель ждал, пока потребитель не извлечёт задачу. То есть, когда поступает новая задача, она немедленно распределяется для выполнения, если есть доступные потоки. Если количество потоков достигает значения maximumPoolSize, применяется стратегия отказа.

 PriorityBlockingQueue

Бесконечная блокирующая очередь с приоритетами, которые задаются с помощью Comparator.

**threadFactory Потоковый завод**

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

**handler Политика отказа**

Когда количество задач в очереди выполнения достигает максимального ограничения, а количество потоков в пуле потоков также достигает максимального значения, что делать с новыми задачами, которые пытаются быть добавлены? Политика отказа решает эту проблему. В JDK предоставляется четыре политики отказа.

```
ThreadPoolExecutor.AbortPolicy: Отклоняет задачу и выбрасывает исключение RejectedExecutionException.
ThreadPoolExecutor.DiscardPolicy: Также отклоняет задачу, но не выбрасывает исключение.
ThreadPoolExecutor.DiscardOldestPolicy: Отклоняет задачу, которая находится в очереди на самом длительном времени, затем снова пытается выполнить задачу (повторяет этот процесс).
ThreadPoolExecutor.CallerRunsPolicy: Выполняет задачу вызывающий поток.
```## 29. Политика отказа для пула потоков

### 1) Отклонение задачи и выброс исключения

ThreadPoolExecutor.AbortPolicy: Отклоняет задачу и выбрасывает исключение RejectedExecutionException.

### 2) Отклонение задачи без выброса исключения

ThreadPoolExecutor.DiscardPolicy: Также отклоняет задачу, но не выбрасывает исключение.

### 3) Отклонение задачи, находящейся в очереди на самом длительном времени

ThreadPoolExecutor.DiscardOldestPolicy: Отклоняет задачу, которая находится в очереди на самом длительном времени, затем снова пытается выполнить задачу (повторяет этот процесс).

### 4) Выполнение задачи вызывающим потоком

ThreadPoolExecutor.CallerRunsPolicy: Выполняет задачу вызывающим потоком.

## 30. Классификация блокировок в Java

### 1) Честная блокировка / Нечестная блокировка

**Честная блокировка**

Честная блокировка означает, что несколько потоков получают блокировку в порядке их запроса.

**Нечестная блокировка**

Нечестная блокировка означает, что несколько потоков получают блокировку не обязательно в порядке их запроса. Это может привести к обратному приоритету или эффекту голода.

Для Java `ReentrantLock` можно указать через конструктор, является ли блокировка честной или нет. По умолчанию это нечестная блокировка. Преимущество нечестной блокировки заключается в том, что её пропускная способность выше, чем у честной блокировки.Для `Synchronized`, это также нечестная блокировка. Поскольку она не реализует логику управления потоками через AQS, нет возможности сделать её честной блокировкой.

### 2) Повторно захватываемая блокировка

**Концепция**

Повторно захватываемая блокировка, также известная как рекурсивная блокировка, означает, что если поток уже захватил блокировку, он может повторно захватить ту же блокировку без необходимости освобождения её. Это звучит абстрактно, но ниже будет пример кода.
Для Java `ReentrantLock`, его имя указывает на то, что это повторно захватываемая блокировка, его полное имя  `Reentrant Lock`. Для `Synchronized` это также является рекурсивной блокировкой.

**Одним из преимуществ является то, что рекурсивная блокировка может в некоторой степени предотвратить мёртвые замки.**

```java
synchronized void setA() throws Exception {
	Thread.sleep(1000);
	setB();
}

synchronized void setB() throws Exception {
	Thread.sleep(1000);
}
```

Вышеуказанный код демонстрирует одну из особенностей рекурсивной блокировки. Без рекурсивной блокировки метод `setB()` мог бы не выполниться, что могло бы привести к мёртвому замку.

### 3) Эксклюзивная блокировка/общая блокировка

**Эксклюзивная блокировка**

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

**Общая блокировка**

Общая блокировка означает, что эта блокировка может быть захвачена несколькими потоками одновременно.

**Synchronized и ReentrantLock являются эксклюзивными блокировками**

ReadWriteLock является общей блокировкой, её чтение-блокировка является общей блокировкой, а запись-блокировка является эксклюзивной блокировкой.

Чтение-блокировка обеспечивает высокую эффективность параллельного чтения, а процессы чтения-записи, записи-чтения и записи-записи являются взаимоисключающими.

Эксклюзивная блокировка и общая блокировка реализуются через AbstractQueuedSynchronizer (AQS), путём реализации различных методов для эксклюзивного или общего использования.

### 4) Эксклюзивная блокировка/чтение-запись блокировка

Вышеупомянутые эксклюзивная блокировка/общая блокировка являются обобщёнными терминами, а эксклюзивная блокировка/чтение-запись блокировка являются конкретными способами реализации.
Эксклюзивная блокировка в Java реализуется через `ReentrantLock`.
Чтение-запись блокировка в Java реализуется через `ReadWriteLock`.### 5) Оптимистическая блокировка/Пессимистическая блокировка

Оптимистическая и пессимистическая блокировки не относятся к конкретному типу блокировки, а указывают на различные подходы к **параллельной синхронизации**.

**Пессимистическая блокировка**

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

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

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

Пессимистическая блокировка подходит для сценариев с частыми операциями записи.

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

Пессимистическая блокировка в Java реализуется с помощью различных блокировок.

Оптимистическая блокировка в Java реализуется с помощью блокировки без использования блокировок, обычно используя алгоритм Compare and Swap (CAS). Примером являются атомарные классы, которые используют CAS для выполнения атомарных операций.

### 6) Сегментированная блокировка

Сегментированная блокировка  это способ дизайна блокировки, а не конкретный тип блокировки. Для `ConcurrentHashMap` эта концепция позволяет эффективно выполнять параллельные операции. Мы рассмотрим сегментированную блокировку на примере `ConcurrentHashMap`.

**Сегментированные блокировки в `ConcurrentHashMap` называются Segment. Они имеют структуру, похожую на `HashMap` (реализация `HashMap` в JDK7 и JDK8), то есть внутри них есть массив Entry, каждый элемент которого представляет собой связанный список; одновременно они являются `ReentrantLock` (Segment наследует `ReentrantLock`).**

При необходимости добавления элемента блокировка не применяется ко всему `hashmap`, а происходит через вычисление хэш-кода, чтобы узнать, в каком сегменте он будет размещен, затем блокируется этот сегмент. Поэтому при многопоточной загрузке элементов, если они не помещаются в один и тот же сегмент, достигается истинная параллельная загрузка.Однако, при подсчете размера, когда требуется получить общую информацию о хэш-таблице, необходимо заблокировать все сегменты для выполнения подсчета.

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

### 7) Предпочтительная блокировка / Легковесная блокировка / Тяжелая блокировка

Эти три типа блокировок относятся к состоянию блокировки и предназначены для `synchronized`. В Java 5 была введена система повышения уровня блокировки для повышения эффективности `synchronized`. Эти три состояния блокировки указываются в полях монитора объекта в заголовке объекта.

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

Легковесная блокировка означает, что если блокировка является предпочтительной, а другой поток пытается получить доступ к ней, то предпочтительная блокировка превращается в легковесную блокировку, и другие потоки будут пытаться получить блокировку путем вращения, не блокируясь, что повышает производительность.Тяжёлая блокировка означает, что если блокировка является лёгковесной, а другой поток пытается получить доступ к ней, то после определённого количества вращений, если блокировка не была получена, поток переходит в состояние блокировки, и блокировка становится тяжёлой. Тяжёлая блокировка заставляет другие потоки, пытающиеся получить доступ к блокировке, переходить в состояние блокировки, что снижает производительность.

### 8) Блокировка вращения

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

**Преимущества:** уменьшение затрат на переключение контекста потока.

**Недостатки:** цикл потребляет CPU.

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

## 31. Использование и принцип работы LockSupport

### **Основные понятия****LockSupport  это инструментальный класс, предоставляемый JUC, который позволяет реализовать пробуждение потока от другого потока. Это очень гибкий инструмент для блокировки и пробуждения потоков, являющийся улучшенной версией wait/notify, с более мощными возможностями**.

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

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

Внутри класса LockSupport есть два основных метода: park (блокировка потока) и unpark (пробуждение потока).

LockSupport предоставляет методы park() и unpark(), чтобы реализовать блокировку и разблокировку потока.

LockSupport связывает каждый использующий его поток с разрешением (permit). Разрешение работает как выключатель, имеющий два состояния: 0 и 1, где по умолчанию значение равно 0.

Вызов метода unpark увеличивает значение разрешения до 1.

**Вызов метода park уменьшает значение разрешения, то есть переводит его из состояния 1 в состояние 0, и сразу же возвращает управление.**

**Если вызвать park повторно, когда разрешение уже равно 0, поток будет заблокирован до тех пор, пока разрешение не станет равным 1. Вызов метода unpark снова установит разрешение в 1. Каждый поток имеет только одно разрешение, и повторный вызов unpark не накапливает разрешений**.### Различия между LockSupport и wait/notify

(1) Методы `wait` и `notify` являются методами класса `Object`, и перед их использованием необходимо получить объект-замок. Однако метод `park` не требует получения замка для блокировки потока.

(2) Метод `notify` может случайным образом выбрать один поток для пробуждения, но не может пробудить конкретный поток. Метод `unpark` может пробудить конкретный поток.

## 32. Четыре способа ожидания и пробуждения потоков в Java

### 1) suspend и resume

Причина, по которой Java废弃 suspend() 去挂起线程的原因是因为 `suspend()` в то время как он приводит к приостановке потока, не освобождает захватываемые им локи. Другие потоки не могут получить доступ к этим локам. Только после выполнения метода `resume()` приостановленный поток может продолжить выполнение, и только тогда другие потоки, заблокированные этим локом, могут продолжить выполнение.

Однако, если операция `resume()` выполняется до того, как был вызван `suspend()`, то поток будет постоянно приостановлен, и будет постоянно заблокирован локом, что приводит к мертвому замку. Кроме того, состояние приостановленного потока остается `Runnable`.

### 2) synchronized -> wait и notify, notifyAll**Методы wait и notify являются методами класса Object**. Использование этих методов требует владения замком, иначе они не могут быть вызваны. Метод wait немедленно освобождает замок, а метод notify освобождает замок только после завершения блока синхронизации. При вызове методов `wait`, `notify` или `notifyAll` объекта обязательно должно быть наличие права управления монитором текущим потоком.

**Если вызвать один из этих трех методов в потоке, который не управляет монитором объекта, будет выброшено исключение `java.lang.IllegalMonitorStateException`.**

**После вызова `wait` следует вызов `notify`, чтобы продолжить выполнение.**### 3) ReentrantLock -> await и signal

Методы `await` и `signal` являются методами интерфейса `Condition`. Объект типа `Condition` получается с помощью `new ReentrantLock().newCondition()`. Как и методы `wait` и `notify`, они требуют использования блока `synchronized`.

### 4) LockSupport -> park и unpark

Методы `park` и `unpark` являются методами класса `LockSupport`. Этот класс представляет собой удобный инструмент для блокировки потока в любом месте. В отличие от `Thread.suspend()`, он решает проблему, когда `resume()` вызывается раньше, что приводит к невозможности продолжения выполнения потока. В отличие от `Object.wait()`, он не требует владения объектом и не выбрасывает исключение `InterruptedException`. Он может пробудить указанный поток.

**Методы `LockSupport` используют native код из класса `Unsafe`.**

## 33. Различия между `Object.wait()` и `LockSupport.park()`

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

(1) Метод `Object.wait()` должен выполняться внутри блока `synchronized`;

(2) Метод `LockSupport.park()` может выполняться в любом месте;

(3) Метод `Object.wait()` объявляет выбрасывание исключения прерывания, которое должно быть перехвачено или переброшено вызывающим кодом;

(4) Метод `LockSupport.park()` не требует перехвата исключения прерывания;

(5) Метод `Object.wait()` без задержки требует выполнения метода `notify()` другим потоком для пробуждения, но не обязательно продолжает выполнение последующего кода;(6) Метод `LockSupport.park()` без задержки требует выполнения метода `unpark()` другим потоком для пробуждения, что обязательно продолжает выполнение последующего кода;

(7) Если метод `notify()` был вызван до `wait()`, что произойдет? Будет выброшено исключение `IllegalMonitorStateException`;

(8) Если метод `unpark()` был вызван до `park()`, что произойдет? Поток не будет заблокирован, пропустит `park()` и продолжит выполнение последующего кода;

## Метод 34. Реализация принципа `ReentrantLock`

**Основной механизм реализации `ReentrantLock` заключается в использовании CAS (Compare and Swap) и AQS (AbstractQueuedSynchronizer) очереди. Он поддерживает как честные блокировки (fair locks), так и нечестные блокировки (non-fair locks), и их реализация аналогична.**CAS: Сравнение и обмен, Compare and Swap. У этого оператора есть три операнда: **значение в памяти V, ожидаемое значение A, новое значение B для изменения**. Изменение значения в памяти V на B происходит только тогда, когда ожидаемое значение A совпадает с значением в памяти V; в противном случае никаких действий не выполняется. Эта операция является атомарной и широко используется в нижележащих реализациях Java. В Java **CAS реализуется главным образом через класс `sun.misc.Unsafe` с помощью вызова низкоуровневых команд процессора через JNI**. Аббревиатура AbstractQueuedSynchronizer  AQS

## 35. Отношения между блокировками и синхронизаторами в Java- Блокировка направлена на пользователя и определяет интерфейс взаимодействия пользователя с блокировкой, скрывая детали реализации.
- Синхронизатор предназначен для разработчиков блокировок, упрощает реализацию блокировок, скрывая управление состоянием синхронизации, очередью потоков, ожиданием и пробуждением.
- Блокировки и синхронизаторы хорошо изолируют области, которые должны быть сосредоточены пользователем и разработчиком.

## 36. Подробное объяснение базовых принципов AQS

**Основные моменты запоминания**:

AQS = состояние + CLH очередь

Узел = статус ожидания (текущий статус узла в очереди) + указатели на предыдущий и следующий узлы

**AQS использует volatile int состояние (представляющее общую ресурс) член класса для представления синхронизированного состояния**, использует встроенный [FIFO](https://so.csdn.net/so/search?q=FIFO&spm=1001.2101.3001.7020) очередь для выполнения работы по организации очереди потоков, желающих получить доступ к ресурсу. AQS использует CAS для атомарной операции изменения значения состояния.**AQS использует volatile int тип члена класса состояние для представления синхронизированного состояния, использует встроенную FIFO очередь для выполнения работы по организации очереди потоков, желающих получить доступ к ресурсу. Каждый поток, желающий получить доступ к ресурсу, упаковывается в узел Node для распределения блока, а также используется CAS для изменения значения состояния.**### Основные сведения

**AbstractQueuedSynchronizer (AQS)  абстрактный синхронизатор очереди**

**Основа реализуется через int член класса + двунаправленную односвязную очередь FIFO (First In, First Out) с названием CLH**

AQS является классом пакета locks библиотеки J.U.C (java.util.concurrent). Он реализует FIFO (First In, First Out) очередь. Основная реализация данных происходит через двунаправленную односвязную очередь.

AQS определяет набор фреймворка синхронизатора для многопоточной работы с общими ресурсами. Многие синхронизирующие классы используют его, такие как часто используемые ReentrantLock/Semaphore/CountDownLatch.



![image](D:\git-my-flink-workspace\guage-guc\images\20190203234437159.png)

### Концептуальное понимание

- Используя Node для реализации FIFO очереди, можно создать основной фреймворк для создания блокировок или других синхронизирующих устройств.


- Используя int тип для обозначения состояния. В классе AQS есть член класса, называемый состояние


``` 
/**
 * Синхронизированное состояние.
 */
private volatile int state;
```

- Наследование: Подклассы унаследуют методы управления состоянием (методы `acquire` и `release`) через реализацию этих методов.Можно одновременно реализовать эксклюзивный и совместный режим блокировки (исключительный, совместный). С точки зрения пользователя, **функциональность AQS делится на две категории: исключительный и совместный доступ**. Всех его подклассов либо реализуют исключительный доступ, либо используют совместный блокировочный режим, но не используют одновременно оба режима. Даже самый известный подкласс `ReentrantReadWriteLock` использует два внутренних класса для чтения и записи для реализации обоих режимов.### Реализация

AQS поддерживает CLH-очередь для управления блокировками. Потоки сначала пытаются получить блокировку; если это не удается, текущий поток и состояние ожидания упаковываются в узел и добавляются в синхронизированную очередь. Затем потоки продолжают пытаться получить блокировку до тех пор, пока текущий узел не станет прямым преемником головы очереди. Если попытка неудачна, поток блокируется до тех пор, пока его не разбудят. Когда поток, удерживающий блокировку, освобождает её, он разбудит следующий поток в очереди.

**CLH (Craig, Landin и Hagersten) очередь является виртуальной двунаправленной очередью** (виртуальная двунаправленная очередь означает отсутствие примера очереди, а только наличие связей между узлами). AQS представляет каждый запрос на доступ к общему ресурсу как узел CLH-очереди для распределения блокировок.

## 37. Основные принципы работы synchronized

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

### Основные функции synchronized

Существует три основные:

(1) Обеспечивает взаимоисключение доступа к синхронизированному коду

(2) Гарантирует своевременную видимость изменений общих переменных(3) Эффективно решает проблемы переупорядочивания.

С точки зрения синтаксиса,

### Три способа использования synchronized

(1) Модификатор для обычного метода

(2) Модификатор для статического метода

(3) Модификатор для блока кода

**1. Модификатор для экземплярного метода:** добавляет блокировку к текущему экземпляру

```java
public synchronized void method() {    
    // код
}
```

**2. Модификатор для статического метода:** добавляет блокировку к текущему объекту класса

```java
public static synchronized void method() {
    // код
}
```

**3. Модификатор для блока кода:** указывает объект для блокировки

```java
synchronized(this) {
    // код
}
```

### Основные принципы работы synchronized

Работа `synchronized` полностью зависит от JVM, поэтому рассмотрение основных принципов работы `synchronized` требует понимания хранения данных в памяти JVM: объектные заголовки Java и объекты монитора. **Объектный заголовок Java**

**Хранение объектов в памяти JVM можно разделить на три области:**

- Объектный заголовок (Header)
- Инстанциальные данные (Instance Data)
- Выравнивание (Padding)

**Объектный заголовок Java включает две основные части данных:**

![Основы реализации synchronized (читайте эту статью)](https://static.mikechen.cc/wp-content/uploads/2021/09/6141.png)

**1) Указатель на тип (Klass Pointer)**

Указатель на метаданные типа объекта, с помощью которого виртуальная машина определяет, является ли объект экземпляром определенного класса;**2) Метка (Mark Word)**

Используется для хранения информации о работе объекта, такой как хэш-код (hash code), возраст поколения сборки мусора (generation age), состояние блокировки, владелец блокировки, идентификатор владельца блокировки (biased lock), отметка времени блокировки и т.д. Это ключевой элемент для реализации легковесной блокировки и блокировки с предпочтением.

Поэтому очевидно, что **блокировка, используемая в synchronized, хранится в поле маркировки объектного заголовка**.

**2. Монитор**

Монитор описывается как **объектный монитор**, который можно сравнить со специальной комнатой, содержащей защищенные данные. Монитор гарантирует, что одновременно может быть только один поток, доступный для чтения защищенных данных. Вход в комнату означает получение монитора, выход из комнаты означает освобождение монитора.

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

![Основы реализации synchronized (читайте эту статью)](https://static.mikechen.cc/wp-content/uploads/2021/09/6145.png)

**При выполнении синхронизированного блока кода с использованием блокировки synchronized в движке байт-кода используется монитор объекта для получения (monitorenter) и освобождения (monitorexit).**

## 38. Преимущества и недостатки механизма CAS

### ПреимуществаCAS представляет собой оптимистическую блокировку, которая также является легковесной и неблокирующей. Что такое неблокирующая блокировка? Это когда поток, пытающийся получить блокировку, получает ответ, указывающий, может ли он получить блокировку. При низкой конкуренции за ресурсы производительность высока, в отличие от тяжёлого блокирования synchronized, которое требует более сложных операций по получению и освобождению блокировки.

### Недостатки

1) Длинные циклы и большие затраты, использование процессорных ресурсов

2) Может гарантировать атомарность только одного общего переменной

3) Проблема ABA

### Решение проблемы ABA

1) Добавление версионного номера

2) AtomicStampedReferenceJava конкурирующий пакет для решения этой проблемы предоставляет атомарный класс ссылки с маркером "AtomicStampedReference". Он может обеспечить правильность операции CAS, контролируя версию значения переменной. Поэтому перед использованием CAS следует тщательно рассмотреть вопрос, влияет ли проблема "ABA" на корректность параллельной работы программы. Если требуется решить проблему ABA, использование традиционной мутуальной блокировки может быть более эффективным, чем использование атомарных классов.

### Когда следует использовать CAS

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

## 39. Что такое пессимистическая блокировка и оптимистическая блокировка

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

### Пессимистическая блокировкаПостоянно предполагает худший сценарий, каждый раз, когда берёт данные, считается, что кто-то другой будет их изменять, поэтому каждый раз при получении данных блокируется. Это означает, что если другой поток попытается получить эти данные, он будет заблокирован до тех пор, пока не получит блокировку (общие ресурсы предоставляются только одному потоку за раз, остальные потоки заблокированы, после использования ресурс передаются следующему потоку). Традиционные реляционные базы данных используют множество таких блокировочных механизмов, таких как блокировка строки, блокировка таблицы и другие, блокировка чтения, блокировка записи и другие, все они выполняют блокировку перед выполнением операции. В Java блокировки synchronized и ReentrantLock являются примерами реализации пессимистической блокировки.### Оптимистическая блокировка

Постоянно предполагает лучший сценарий, каждый раз, когда берёт данные, считается, что кто-то другой не будет их изменять, поэтому блокировка не используется. Однако при обновлении проверяется, не изменился ли кто-то другой этот набор данных за это время, что можно сделать с помощью механизма версий или алгоритма CAS. Оптимистическая блокировка подходит для ситуаций с большим количеством чтений (многократные чтения), так как это увеличивает пропускную способность системы. Механизмы, такие как write_condition в базах данных, также предоставляют оптимистическую блокировку. В Java классы атомарных переменных в пакете `java.util.concurrent.atomic` используются для реализации оптимистической блокировки через алгоритм CAS.Сценарии использования двух типов блокировок
Из вышеупомянутых описаний двух типов блокировок мы знаем, что каждая из них имеет свои преимущества и недостатки, и нельзя сказать, что одна лучше другой. Например, оптимистическая блокировка подходит для ситуаций с небольшим количеством записей (многократные чтения), когда конфликты действительно редки, что позволяет избежать затрат на блокировку и увеличить общую пропускную способность системы. Однако при большом количестве записей конфликты будут часто возникать, что приведёт к необходимости повторного выполнения операций, что может уменьшить производительность. Поэтому в ситуациях с большим количеством записей использование пессимистической блокировки будет более подходящим.### Два наиболее распространённых способа реализации оптимистической блокировки

Оптимистическая блокировка обычно реализуется с помощью механизма версий или алгоритма CAS.

#### Механизм версий

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

Приведу простой пример:

Предположим, что в таблице с информацией о счетах в базе данных есть поле version со значением 1, а поле баланса (balance) равно $100. При необходимости обновления информации о счетах, сначала читается поле version.Оператор A считывает это значение (версия = 1) и вычитает из баланса $50 ($100 - $50).
В процессе работы оператора A, оператор B также считывает информацию о счете (версия = 1) и вычитает из баланса $20 ($100 - $20).
Когда оператор A завершает работу над обновлением, он проверяет, совпадает ли текущий номер версии в базе данных с тем, который был прочитан ранее (версия = 1). Если они совпадают, номер версии увеличивается на 1 (версия = 2), вместе с новым значением баланса ($50), и обновление отправляется в базу данных. В этот момент, так как номер версии в обновлении больше текущего номера версии в базе данных, данные обновляются, и поле версии обновляется до значения 2.
Оператор B завершает свою работу и проверяет, совпадает ли текущий номер версии в базе данных с тем, который был прочитан ранее (версия = 1). Однако при сравнении номера версии в базе данных с номером версии, прочитанным ранее, оказывается, что номер версии в обновлении оператора B равен 2, а прочитанный номер версии равен 1. Это не удовлетворяет условию "текущий номер версии равен номеру версии, прочитанному в первый раз" оптимистичной блокировки, поэтому обновление оператора B отклоняется.
Таким образом, исключается возможность того, что обновление оператора B, основанное на старых данных (версия = 1), заменит обновление оператора A.#### Алгоритм CAS

Compare and Swap (Сравнение и замена)  это известный алгоритм безблокировки. Безблокировка представляет собой программирование без использования блокировок для обеспечения синхронизации переменных между многими потоками, то есть выполнение синхронизации переменных без блокировки потока. Поэтому его также называют неблокирующей синхронизацией (Non-blocking Synchronization). Алгоритм CAS включает три операнда:

**Значение памяти, которое нужно прочитать и записать V**
**Значение для сравнения A**
**Новое значение для записи B**

Когда и только когда значение V равно A, CAS атомарно обновляет значение V новым значением B, в противном случае никаких действий не выполняется (сравнение и замена  это атомарная операция). Обычно это является циклическим процессом, то есть процесс повторяется до тех пор, пока не будет выполнен.

### Недостатки оптимистической блокировки

Проблема ABA  это одна из распространенных проблем оптимистической блокировки.#### Проблема ABA

Если переменная V в первый раз имеет значение A, а затем при попытке присвоить ей новое значение проверяется, что она всё ещё имеет значение A, можно ли утверждать, что её значение не было изменено другими потоками? Очевидно, это неверно, так как за это время её значение могло быть изменено на другое и снова возвращено к значению A. В этом случае операция CAS (Compare and Swap) ошибочно будет считать, что значение никогда не изменялось. Эта проблема известна как проблема "ABA" для операции CAS.

Класс `AtomicStampedReference` в JDK 1.5 и выше предоставляет такую возможность, где метод `compareAndSet` сначала проверяет, равен ли текущий объект ожидаемому объекту, а также равен ли текущий маркер ожидаемому маркеру. Если все условия выполняются, то он атомарно устанавливает значение объекта и маркера.#### Долгое время ожидания и высокие затраты

Циклический CAS (то есть продолжение попыток до успеха) может привести к значительным затратам вычислительных ресурсов CPU при длительном неудачном выполнении. Если JVM поддерживает команду `pause`, предоставленную процессором, то это может повысить эффективность. Команда `pause` имеет две функции: первая заключается в том, что она может задержать выполнение команды в потоке (де-пайплайн), что позволяет CPU не использовать слишком много вычислительных ресурсов. Время задержки зависит от конкретной реализации версии; в некоторых процессорах время задержки равно нулю. Вторая функция заключается в том, чтобы избежать очистки потока выполнения CPU при выходе из цикла из-за конфликта последовательностей памяти (memory order violation), что повышает производительность CPU.

#### Обеспечивает атомарность только для одного общего переменного

CAS действует только для одного общего переменного. Когда операция включает несколько общих переменных, то CAS становится недействительным. Однако начиная с JDK 1.5, предоставлен класс `AtomicReference`, который обеспечивает атомарность для ссылочных объектов. Вы можете поместить несколько переменных в один объект для выполнения операции CAS. Поэтому мы можем использовать блокировки или класс `AtomicReference` для объединения нескольких общих переменных в одну общую переменную для выполнения операции.### Сценарии использования CAS и synchronized

Кратко говоря, CAS подходит для ситуаций с низкой частотой записи (многократное чтение, конфликты обычно редки), а synchronized подходит для ситуаций с высокой частотой записи (многократная запись, конфликты обычно часто возникают). Для случаев с низкой конкуренцией за ресурсы (легкие потоковые конфликты), использование синхронизации с помощью synchronized блоков приводит к дополнительной трате ресурсов процессора на блокировку и разблокировку потока, а также на переключение между состояниями пользователя и ядра; в то время как CAS (Compare and Swap) основан на аппаратной реализации, не требует входа в ядро и переключения потока, что позволяет избежать большого количества циклов ожидания, обеспечивая тем самым более высокую производительность.

Для случаев с высокой конкуренцией за ресурсы (тяжелые потоковые конфликты), вероятность того, что CAS будет выполнять циклы ожидания, значительно возрастает, что приводит к большей трате ресурсов процессора и снижению эффективности по сравнению с synchronized.Дополнительно: ключевое слово `synchronized` в области параллельного программирования на Java всегда играло роль старейшины. Долгое время его называли "тяжёлым замком". Однако после Java SE 6 были внесены изменения, включая введение предпочтительного замка и лёгковесного замка для уменьшения затрат на получение и освобождение замка, а также других различных оптимизаций, что сделало его менее "тяжёлым" в некоторых случаях. Под капотом `synchronized` использует основанный на технологии без блокировки (Lock-Free) подход, основанный на очередях, где основная идея заключается в том, чтобы после цикла ожидания перейти к блокировке, а при конкурирующем переключении продолжать конкурировать за блокировку, немного жертвуя справедливостью, но обеспечивая высокую пропускную способность. В случае низкой конкуренции за ресурсы, можно достичь производительности, аналогичной CAS; в случае же высокой конкуренции за ресурсы, производительность значительно выше, чем у CAS.## 40. Принцип работы CAS в Java### 1. Введение в CAS

CAS (Compare and Swap)  это метод оптимистичной блокировки. Когда несколько потоков пытаются одновременно использовать CAS для обновления одного и того же значения, только один из них может успешно обновить это значение, а остальные получат сообщение о неудаче. Успешный поток обновляет значение, а неудачные продолжают попытки без блокировки.

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

текущее значение в памяти V, ожидаемое значение E и новое значение U. Если текущее значение V равно ожидаемому значению E, то значение в памяти обновляется до U, и операция CAS считается успешной. В противном случае обновление не происходит, и операция считается неудачной.

### 2. Пессимистическая и оптимистическая блокировка

Пессимистическая блокировка: всегда предполагает худший сценарий, каждый раз, когда поток берёт данные, он предполагает, что другой поток может изменить эти данные, поэтому он блокирует доступ к данным. Это приводит к тому, что другие потоки, желающие получить доступ к этим данным, будут заблокированы до тех пор, пока не получат блокировку. В традиционных реляционных базах данных используются различные виды блокировок, такие как блокировка строки или таблицы, а также блокировка чтения или записи. В Java блокировки synchronized и ReentrantLock являются примерами пессимистической блокировки.Оптимистическая блокировка: как следует из названия, оптимистично предполагается, что другие потоки не будут менять данные, поэтому блокировка не используется при получении данных. Однако при обновлении данных проверяется, не изменились ли они другими потоками за это время. Для этого могут использоваться механизмы, такие как версионирование. Оптимистическая блокировка подходит для приложений с большим количеством чтений, так как она увеличивает пропускную способность. В Java классы атомарных переменных в пакете java.util.concurrent.atomic используют оптимистическую блокировку, реализованную через CAS.

### 3. Класс Unsafe

Одним из важных отличий между Java и C++ является возможность напрямую управлять областью памяти. В отличие от C++, где можно самостоятельно выделять и освобождать память, в Java это невозможно. Однако класс Unsafe предоставляет возможности управления памятью, аналогичные тем, что доступны в C++.

Класс Unsafe имеет полное имя sun.misc.Unsafe. Из названия следует, что этот класс "опасен" для обычных разработчиков, и его использование обычно ограничено специфическими случаями.

**CAS операции, предоставляемые классом Unsafe:**

CAS широко используется в Java, особенно в пакете JUC и классах атомарных переменных в пакете java.util.concurrent.atomic.Класс Unsafe предоставляет множество низкоуровневых функций в Java, включая реализацию функциональности CAS.```  
публичный конечный нативный булево compareAndSwapObject(Объект var1, длинное var2, Объект var4, Объект var5);```

Исправленный текст:
Класс Unsafe предоставляет множество низкоуровневых функций в Java, включая реализацию функциональности CAS.```  
публичный конечный нативный boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);```публичный конечный нативный булево compareAndSwapInt(Объект var1, длинное var2, целое var4, целое var5);

публичный конечный нативный булево compareAndSwapLong(Объект var1, длинное var2, длинное var4, длинное var6);

Unsafe предоставляет операции CAS для типов Object, int и long. Для других типов необходимо реализовать самостоятельно. Операция CAS является атомарной. Для обеспечения потокобезопасности требуется атомарность, видимость и упорядоченность. Видимость и упорядоченность в Java могут быть обеспечены с помощью ключевого слова volatile.

Пакет `java.util.concurrent.atomic` содержит классы, обеспечивающие потокобезопасность с помощью ключевого слова `volatile` и повторных попыток операции CAS.

Классы в пакете `java.util.concurrent.atomic` можно разделить на четыре типа:

1. Атомарные скаляры: `AtomicBoolean`, `AtomicInteger`, `AtomicLong`, `AtomicReference`
2. Массивы: `AtomicIntegerArray`, `AtomicLongArray`, `AtomicReferenceArray`
3. Обновители полей: `AtomicLongFieldUpdater`, `AtomicIntegerFieldUpdater`, `AtomicReferenceFieldUpdater`
4. Составные переменные: `AtomicMarkableReference`, `AtomicStampedReference

Основное внимание уделяется классу `AtomicInteger` и тому, как он обеспечивает потокобезопасность:

Значение `value` в классе `AtomicInteger` объявлено как `volatile`, что гарантирует видимость и упорядоченность.

```java
публичный класс AtomicInteger расширяет Число implements java.io.Serializable {
    ...
    приватный volatile целое значение; // значение объявлено как volatile, что гарантирует видимость и упорядоченность
    ...
}

публичный финальный целое get() { вернуть значение; }


Как видно, операция `get` в `AtomicInteger` не требует блокировки. Для не-`volatile` типов общего доступа, при параллельных операциях, читающий поток может не сразу получить изменения от другого потока. Однако здесь значение объявлено как `volatile`, поэтому читающий поток сразу получает изменения от другого потока.

Операция `incrementAndGet`

публичный финальный целое incrementAndGet() { вернуть unsafe.getAndAddInt(this, valueOffset, OnClickListener.ICON_CLICK) + 1; }


Операция `incrementAndGet` увеличивает значение на 1, затем возвращает новое значение. Эта операция использует метод `getAndAddInt` из класса `Unsafe`:

публичный финальный целое getAndAddInt(Объект var1, длинное var2, целое var4) { целое var5; делать { var5 = this.getIntVolatile(var1, var2); } пока (!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); // Атомарное обновление через CAS + циклическое повторение }


Можно заметить, что в `Unsafe` внутри цикла сначала читается значение из памяти, затем выполняется операция `CAS` для обновления. Если обновление успешно, то цикл завершается, если нет — повторяется до тех пор, пока обновление не будет выполнено.

В предыдущих статьях мы анализировали три типа объектов, поддерживающих операцию `CAS` в `Unsafe`: `Object`, `int` и `long`. `AtomicLong` реализован с использованием операции `CAS` для типа `long`, предоставляемой `Unsafe`, а `AtomicReference` использует операцию `CAS` для типа `Object`.
```### Четвертая часть: проблема спин-блокировки

Спин-блокировка с использованием CAS может привести к значительной нагрузке на процессор, если обновление не будет выполнено длительное время. Если JVM поддерживает инструкцию pause процессора, то производительность может повыситься. Инструкция pause имеет два эффекта: она может задержать выполнение команд в процессоре (де-пайплайне), что позволяет процессору не использовать слишком много вычислительных ресурсов, и она может предотвратить очистку очереди команд процессора при выходе из цикла, что повышает производительность процессора.

Пакет `java.util.concurrent.atomic` обеспечивает потокобезопасность с помощью оптимистичной блокировки на основе CAS. В ситуациях с большим количеством чтений и малым количеством записей производительность будет выше по сравнению с блокировками `synchronized` и `ReentrantLock`, которые используют пессимистическую блокировку.

### Пятая часть: проблема ABA

При использовании CAS проверяется, не изменилось ли значение во время операции. Если значение изначально было A, затем стало B, а затем снова A, то проверка CAS покажет, что значение не изменилось, хотя фактически оно изменилось. Проблема ABA решается с помощью версионирования. Каждый раз, когда значение изменяется, увеличивается счетчик версий. Таким образом, A-B-A станет 1A-2B-3A. Класс `AtomicStampedReference` поддерживает версионирование.### Шестая часть: Гарантия атомарности для одной общего переменной

Когда выполняется операция над одной общим переменной, можно использовать циклическую операцию CAS для обеспечения атомарности. Однако при работе с несколькими общими переменными циклическая операция CAS не гарантирует атомарность операции. В этом случае можно использовать блокировку или использовать хитрость, объединяющую несколько переменных в одну. Например, если есть две переменные i=2 и j=a, их можно объединить в ij=2a и использовать CAS для работы с ij. С Java 1.5 JDK предоставляет класс AtomicReference для обеспечения атомарности для ссылочных объектов. Вы можете объединить несколько переменных в один объект и использовать CAS для работы с этим объектом.

## 41. Как гарантировать последовательное выполнение нескольких потоков

### 1) Использование метода Thread.join()Метод `Thread.join()` позволяет родительскому потоку ждать завершения работы дочернего потока перед продолжением выполнения. В примере выше метод `main()` представляет собой родительский поток, в котором создаются три дочерних потока A, B и C. Выполнение этих дочерних потоков относительно родительского потока асинхронно, что не гарантирует их последовательного выполнения. Однако использование метода `Thread.join()` позволяет заставить родительский поток ждать завершения работы каждого дочернего потока перед его выполнением, тем самым обеспечивая синхронное выполнение дочерних потоков. Таким образом, метод `Thread.join()` позволяет гарантировать последовательность выполнения потоков.```java
public class FIFOThreadExample {
    
    public static void foo(String name) {
        System.out.print(name);
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(() -> foo("A"));
        Thread thread2 = new Thread(() -> foo("B"));
        Thread thread3 = new Thread(() -> foo("C"));
        thread1.start();
        thread1.join();
        thread2.start();
        thread2.join();
        thread3.start();
    }
}
Результат вывода: ABC

2) Использование однопоточного пула потоков

Еще один способ обеспечить последовательное выполнение потоков — использовать однопоточный пул потоков. В таком пуле потоков существует только один поток, который выполняет задачи в порядке их добавления.

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class FIFOThreadExample {

    public static void foo(String name) {
        System.out.print(name);
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(() -> foo("A"));
        Thread thread2 = new Thread(() -> foo("B"));
        Thread thread3 = new Thread(() -> foo("C"));
        ExecutorService executor = Executors.newSingleThreadExecutor();
        executor.submit(thread1);
        executor.submit(thread2);
        executor.submit(thread3);
        executor.shutdown();
    }
}
Результат вывода: ABC

3) Использование сигнала, помеченного ключевым словом volatile

Для реализации сигнала, помеченного ключевым словом volatile, можно использовать следующий подход:

public class FIFOThreadExample {

    private static volatile boolean signal = false;

    public static void foo(String name) {
        System.out.print(name);
    }
```    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(() -> {
            foo("A");
            signal = true;
        });
        Thread thread2 = new Thread(() -> {
            while (!signal) {
                // Wait for the signal
            }
            foo("B");
            signal = false;
        });
        Thread thread3 = new Thread(() -> {
            while (!signal) {
                // Wait for the signal
            }
            foo("C");
        });```markdown
        thread1.start();
        thread2.start();
        thread3.start();

        thread1.join();
        thread2.join();
        thread3.join();
    }
}
Результат вывода: ABC
```Две вышеупомянутые стратегии направлены на обеспечение последовательности выполнения потоков, чтобы они выполнялись в определенном порядке. В этом разделе мы рассмотрим третью стратегию, которая заключается в том, что потоки могут выполняться в произвольном порядке, но результаты их выполнения должны следовать за друг другом.

Как вы можете догадаться, три потока создаются и запускаются методом `start()`. В этот момент любой из трех потоков может в любой момент времени выполнить метод `run()`. Поэтому, чтобы гарантировать последовательность выполнения метода `run()`, нам необходим семафор, который позволит потокам знать, могут ли они выполнять логический код в данный момент времени.

Кроме того, поскольку три потока являются независимыми, изменения семафора должны быть видимы для других потоков, поэтому ключевое слово `volatile` также обязательно.```
публичный класс TicketExample2 {

    //сигнальная переменная
    static неизменяемый int ticket = 1;
    //время сна потока
    публичное константное static int SLEEP_TIME = 1;

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

Как вы можете догадаться, три потока создаются и запускаются методом `start()`. В этот момент любой из трех потоков может в любой момент времени выполнить метод `run()`. Поэтому, чтобы гарантировать последовательность выполнения метода `run()`, нам необходим семафор, который позволит потокам знать, могут ли они выполнять логический код в данный момент времени.

Кроме того, поскольку три потока являются независимыми, изменения семафора должны быть видимы для других потоков, поэтому ключевое слово `volatile` также обязательно.```
публичный класс TicketExample2 {

    // сигнальная переменная
    static неизменяемый int ticket = 1;
    // время сна потока
    публичное константное static int SLEEP_TIME = 1;

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

Как вы можете догадаться, три потока создаются и запускаются методом `start()`. В этот момент любой из трех потоков может в любой момент времени выполнить метод `run()`. Поэтому, чтобы гарантировать последовательность выполнения метода `run()`, нам необходим семафор, который позволит потокам знать, могут ли они выполнять логический код в данный момент времени.

Кроме того, поскольку три потока являются независимыми, изменения семафора должны быть видимы для других потоков, поэтому ключевое слово `volatile` также обязательно.```
публичный класс TicketExample2 {

    // сигнальная переменная
    static неизменяемый int ticket = 1;
    // время сна потока
    публичное константное static int SLEEP_TIME = 1;

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

Как вы можете догадаться, три потока создаются и запускаются методом `start()`. В этот момент любой из трех потоков может в любой момент времени выполнить метод `run()`. Поэтому, чтобы гарантировать последовательность выполнения метода `run()`, нам необходим семафор, который позволит потокам знать, могут ли они выполнять логический код в данный момент времени.

Кроме того, поскольку три потока являются независимыми, изменения семафора должны быть видимы для других потоков, поэтому ключевое слово `volatile` также обязательно.```
публичный класс TicketExample2 {

    // сигнальная переменная
    static неизменяемый int ticket = 1;
    // время сна потока
    публичное константное static int SLEEP_TIME = 1;

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

Как вы можете догадаться, три потока создаются и запускаются методом `start()`. В этот момент любой из трех потоков может в любой момент времени выполнить метод `run()`. Поэтому, чтобы гарантировать последовательность выполнения метода `run()`, нам необходим семафор, который позволит потокам знать, могут ли они выполнять логический код в данный момент времени.

Кроме того, поскольку три потока являются независимыми, изменения семафора должны быть видимы для других потоков, поэтому ключевое слово `volatile` также обязательно.```
публичный класс TicketExample2 {

    // сигнальная переменная
    static неизменяемый int ticket = 1;
    // время сна потока
    публичное константное static int SLEEP_TIME = 1;

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

Как вы можете догадаться, три потока создаются и запускаются методом `start()`. В этот момент любой из трех потоков может в любой момент времени выполнить метод `run()`. Поэтому, чтобы гарантировать последовательность выполнения метода `run()`, нам необходим семафор, который позволит потокам знать, могут ли они выполнять логический код в данный момент времени.

Кроме того, поскольку три потока являются независимыми, изменения семафора должны быть видимы для других потоков, поэтому ключевое слово `volatile` также обязательно.```
публичный класс TicketExample2 {

    // сигнальная переменная
    static неизменяемый int ticket = 1;
    // время сна потока
    публичное константное static int SLEEP_TIME = 1;

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

Как вы можете догадаться, три потока создаются и запускаются методом `start()`. В этот момент любой из трех потоков может в любой момент времени выполнить метод `run()`. Поэтому, чтобы гарантировать последовательность выполнения метода `run()`, нам необходим семафор, который позволит потокам знать, могут ли они выполнять логический код в данный момент времени.

Кроме того, поскольку три потока являются независимыми, изменения семафора должны быть видимы для других потоков, поэтому ключевое слово `volatile` также обязательно.```
публичный класс TicketExample2 {

    // сигнальная переменная
    static неизменяемый int ticket = 1;
    // время сна потока
    публичное константное static int SLEEP_TIME = 1;

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

Как вы можете догадаться, три потока создаются и запускаются методом `start()`. В этот момент любой из трех потоков может в любой момент времени выполнить метод `run()`. Поэтому, чтобы гарантировать последовательность выполнения метода `run()`, нам необходим семафор, который позволит потокам знать, могут ли они выполнять логический код в данный момент времени.

Кроме того, поскольку три потока являются независимыми, изменения семафора должны быть видимы для других потоков, поэтому ключевое слово `volatile` также обязательно.```
публичный класс TicketExample2 {

    // сигнальная переменная
    static неизменяемый int ticket = 1;
    // время сна потока
    публичное константное static int SLEEP_TIME = 1;

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

Как вы можете догадаться, три потока создаются и запускаются методом `start()`. В этот момент любой из трех потоков может в любой момент времени выполнить метод `run()`. Поэтому, чтобы гарантировать последовательность выполнения метода `run()`, нам необходим семафор, который позволит потокам знать, могут ли они выполнять логический код в данный момент времени.

Кроме того, поскольку три потока являются независимыми, изменения семафора должны быть видимы для других потоков, поэтому ключевое слово `volatile` также обязательно.```
публичный класс Ticket    публичный static void foo(int имя) {
        // из-за того, что порядок выполнения потоков непредсказуем, каждый поток должен выполнять цикл
        while (истина) {
            если (ticket == имя) {
                попытаться {
                    Thread.sleep(SLEEP_TIME);
                    // каждый поток циклически выводит 3 раза
                    для (int i = 0; i < 3; i++) {
                        System.out.println(имя + " " + i);
                    }
                } catch (InterruptedException e) {
                    // обработка исключения
                }
            }
        }
    }```java
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // Изменение сигнальной переменной
                ticket = имя % 3 + 1;
                return;

            }
        }
    }
    public static void main(String[] аргументы) throws InterruptedException {
        Thread поток1 = new Thread(() -> foo(1));
        Thread поток2 = new Thread(() -> foo(2));
        Thread поток3 = new Thread(() -> foo(3));
        поток1.start();
        поток2.start();
        поток3.start();
    }
}
результат выполнения:
1 0
1 1
1 2
2 0
2 1
2 2
3 0
3 1
3 2

4) Использование Lock и сигнальной переменной

Этот метод использует ту же идею, что и третий метод, то есть не учитывает порядок выполнения потоков, а контролирует выполнение бизнес-логики с помощью некоторых методов. Здесь мы также используем атомарную сигнальную переменную ticket, хотя вы можете использовать неатомарную переменную, я использую атомарную для обеспечения безопасности при увеличении. Затем мы используем повторяемый блокировщик ReentrantLock для блокировки метода. Когда поток получает блокировку и метка верна, он начинает выполнять бизнес-логику, завершает её и пробуждает следующий поток. Здесь нам больше не требуется использовать while для выполнения цикла, так как Lock позволяет нам пробуждать конкретный поток, поэтому можно заменить while на if для последовательного выполнения.

публичный класс TicketExample3 {
    // сигнальная переменная
    AtomicInteger ticket = новый AtomicInteger(1);
    публичный Lock lock = новый ReentrantLock();
    приватное Condition condition1 = lock.newCondition();
    приватное Condition condition2 = lock.newCondition();
    приватное Condition condition3 = lock.newCondition();
    приватное Condition[] conditions = {condition1, condition2, condition3};
}
```

```java
публичный void foo(int name) {
    try {
        lock.lock();
        // Поскольку порядок выполнения потоков непредсказуем, каждый поток должен вращаться
        System.out.println("Поток " + name + " начал выполнение");
        if (ticket.get() != name) {
            try {
                System.out.println("Текущее значение счетчика равно " + ticket.get() + ", поток " + name + " начал ожидание");
                // Начало ожидания до пробуждения
                conditions[name - 1].await();
                System.out.println("Поток " + name + " был пробужден");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(name);
        ticket.getAndIncrement();
        if (ticket.get() > 3) {
            ticket.set(1);
        }
        // Выполнение завершено, пробуждаем следующий поток. 1 пробуждает 2, 2 пробуждает 3
        conditions[name % 3].signal();
    } finally {
        // Обязательно освобождаем блокировку
        lock.unlock();
    }
}
```    }

    public static void main(String[] args) throws InterruptedException {
        TicketExample3 example = new TicketExample3();
        Thread t1 = new Thread(() -> {
            example.foo(1);
        });
        Thread t2 = new Thread(() -> {
            example.foo(2);
        });
        Thread t3 = new Thread(() -> {
            example.foo(3);
        });
        t1.start();
        t2.start();
        t3.start();
    }
}
```

```
Поток 1 начал выполнение
1
Поток 2 начал выполнение
2
Поток 3 начал выполнение
3
```

## 42. Разница между notify() и notifyAll()

**wait():** заставляет текущий поток ждать, пока другой поток не вызовет метод `notify()` или `notifyAll()` для этого объекта.

**notify():** пробуждает один из ожидающих потоков, находящихся на этом объекте.

**notifyAll():** пробуждает все ожидающие потоки, находящихся на этом объекте.

**Согласно вышеуказанному, при вызове `wait()`, `notify()` или `notifyAll()` необходимо сначала получить блокировку, а также состояние переменной должно быть защищено этой блокировкой. То есть, чтобы выполнить `wait`, `notify` или `notifyAll` на каком-либо объекте, сначала нужно заблокировать этот объект, а состояние переменной должно быть защищено этой блокировкой.**

## 43. Что такое volatile? Может ли он гарантировать порядок операций?

```Когда одна общая переменная (член класса, статический член класса) помечена ключевым словом `volatile`, то она приобретает два уровня семантики:```1) **Видимость**: При работе с этой переменной различными потоками обеспечивается видимость, то есть если один поток изменяет значение переменной, это новое значение сразу становится доступным для других потоков. Ключевое слово `volatile` заставляет JVM немедленно записывать изменённое значение в основную память.2) **Запрет на переупорядочивание команд**: Временные изменения порядка выполнения команд не допускаются.

`volatile` не является атомарной операцией.
Что такое гарантия частичного порядка выполнения?
При выполнении операций чтения или записи переменной типа `volatile`, все изменения, произведённые до этих операций, гарантированно будут завершены и видимы для последующих операций. Операции, следующие за операциями чтения или записи переменной типа `volatile`, гарантированно ещё не были выполнены.

```
x = 2;     // операция 1
y = 0;     // операция 2
flag = true;  // операция 3
x = 4;     // операция 4
y = -1;    // операция 5
```

Так как переменная `flag` помечена ключевым словом `volatile`, то при переупорядочивании команд операция 3 не будет перемещена перед операциями 1 и 2, также она не будет перемещена после операций 4 и 5. Однако следует отметить, что порядок выполнения операций 1 и 2, а также операций 4 и 5 не гарантируется.

Использование `volatile` обычно применяется для маркеров состояния и в паттерне одиночки с двойной проверкой.

## 44. Какие действия выполняет ключевое слово `volatile`?

Когда одна общая переменная (член класса, статический член класса) помечена ключевым словом `volatile`, то она приобретает два уровня семантики:```
1. Обеспечивает видимость изменений переменной для различных потоков, то есть если один поток изменяет значение переменной, это новое значение сразу становится доступным для других потоков.
2. Запрещает переупорядочивание команд.
3. Суть ключевого слова `volatile` заключается в том, чтобы сообщить JVM, что значение переменной в регистре (рабочей памяти) неопределено и требуется считать его из основной памяти; ключевое слово `synchronized` же блокирует доступ к переменной, позволяя только текущему потоку её использовать, а остальные потоки блокируются.
4. `volatile` может использоваться только на уровне переменной; `synchronized` может использоваться на уровне переменной, метода и класса.
5. `volatile` обеспечивает видимость изменений переменной, но не гарантирует атомарность; `synchronized` обеспечивает как видимость изменений переменной, так и её атомарность.
6. `volatile` не приводит к блокировке потока; `synchronized` может приводить к блокировке потока.
```Переменная, помеченная ключевым словом `volatile`, не будет оптимизирована компилятором; переменная, помеченная ключевым словом `synchronized`, может быть оптимизирована компилятором.

## 45. Расскажите о нижележащих принципах работы ключевого слова `synchronized`

Ключевое слово `synchronized` в Java используется для обеспечения потокобезопасности методов и блоков кода. Его реализация основана на использовании монитора (monitor) объекта.

### 1. Синхронизация блока кода

```java
public class SynchronizedDemo {
    public void method() {
        synchronized (this) {
            System.out.println("синхронизированный блок кода");
        }
    }
}
```

При помощи команды `javap` можно получить информацию о байт-коде класса. В данном случае, синхронизированный блок кода реализуется с помощью инструкций `monitorenter` и `monitorexit`. Инструкция `monitorenter` указывает начало синхронизированного блока кода, а `monitorexit`  его конец. Когда выполняется `monitorenter`, поток пытается получить монитор (monitor) объекта, который является частью объектного заголовка каждого Java объекта. Если счетчик монитора равен нулю, поток успешно получает монитор и увеличивает его счетчик до единицы. После выполнения `monitorexit`, счетчик монитора устанавливается обратно в ноль, что указывает на освобождение монитора.

### 2. Синхронизация метода

```java
public class SynchronizedDemo2 {
    public synchronized void method() {
        System.out.println("синхронизированный метод");
    }
}
```В отличие от блока кода, синхронизированный метод не использует инструкции `monitorenter` и `monitorexit`. Вместо этого, метод помечен специальным флагом `ACC_SYNCHRONIZED`, который указывает, что метод является синхронизированным. JVM использует этот флаг для определения того, является ли метод синхронизированным, и выполняет соответствующий синхронизированный вызов.

## 46. Различия между выполнением метода `execute()` и `submit()`

Методы `execute()` и `submit()` используются для запуска задач в ExecutorService. Основное различие заключается в том, что `execute()` используется для запуска задач без получения результата, тогда как `submit()` позволяет запустить задачу и получить её результат.

```java
ExecutorService executor = Executors.newSingleThreadExecutor();

// execute() не возвращает результат
executor.execute(() -> {
    System.out.println("Задача выполнена");
});

// submit() возвращает Future, который может быть использован для получения результата
Future<String> future = executor.submit(() -> "Результат задачи");

try {
    System.out.println(future.get());
} catch (InterruptedException | ExecutionException e) {
    e.printStackTrace();
}

executor.shutdown();
```Эти методы позволяют эффективно управлять потоками и задачами в многопоточных приложениях. (1) метод `execute()` используется для выполнения задач, которые не требуют возврата значения, поэтому невозможно определить, была ли задача успешно выполнена потоком;
(2) метод `submit()` используется для выполнения задач, которые требуют возврата значения. Пул потоков возвращает объект типа `Future`, с помощью которого можно проверить, была ли задача успешно выполнена, а также использовать метод `get()` объекта `Future` для получения возвращаемого значения. Метод `get()` блокирует текущий поток до тех пор, пока задача не будет завершена, в то время как метод `get(long timeout, TimeUnit unit)` блокирует текущий поток на некоторое время, после чего немедленно возвращает управление, возможно, до завершения задачи.

Опубликовать ( 0 )

Вы можете оставить комментарий после Вход в систему

1
https://api.gitlife.ru/oschina-mirror/yangdechao_admin-guage-notes.git
git@api.gitlife.ru:oschina-mirror/yangdechao_admin-guage-notes.git
oschina-mirror
yangdechao_admin-guage-notes
yangdechao_admin-guage-notes
master