Разница между локальными блокировками и распределенными блокировками

Java

Понимание локальных и распределенных блокировок

1. Разница между локальными блокировками и распределенными блокировками.

1.1 Значение локальной блокировки

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

1.2 Значение распределенных блокировок

Если это одна машина (одна JVM), память распределяется между потоками, и проблема параллелизма может быть решена с помощью блокировок потоков. Но если это распределенная ситуация (несколько JVM), поток A и поток B, скорее всего, не находятся в одной и той же JVM, поэтому блокировки потоков не будут работать, и для решения этой проблемы необходимо использовать распределенные блокировки.

Распределенные блокировки — это способ управления синхронным доступом к общим ресурсам в распределенных системах.

2. Локальная блокировка

2.1 Какие наиболее часто используемые локальные блокировки?

синхронизированы и заблокированы

2.2 Что такое локальная блокировка?

В нестатическом методе блокируется объект, а в нестатическом методе блокируется байт-код класса

2.3 Принцип блокировки замка?

​ Просмотрев исходный код честной блокировки, мы знаем, что в абстрактном классе java.util.concurrent.locks AbstractQueuedSynchronizer есть значение состояния типа int, и затем судим о состоянии блокировки.

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

2.4 Что означает изменчивость? Для чего это?

2.41 Определение летучих

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

джмм

2.42 Видимость летучих

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

  1. Сначала скопируйте переменную из основной памяти в ее собственное пространство рабочей памяти.
  2. Затем оперируйте переменной.
  3. После завершения операции переменные записываются обратно в основную память, и с переменными в основной памяти нельзя напрямую манипулировать.

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

img

2.43 volatile не гарантирует атомарности

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

image-20200309174220675

2.44 Как сделать volatile гарантией атомарности

  1. Самый простой способ — добавить блокировку синхронизации.
  2. Можно использовать атомарные классы-оболочки ниже JUC.

2.45 volatile запрещает перестановку инструкций

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

При переупорядочивании процессор должен учитывать数据依赖性

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

public void mySort() {
	int x = 11;
	int y = 12;
	x = x + 5;
	y = x * x;
}

В обычной однопоточной среде порядок выполнения следующий: 1 2 3 4

Но в многопоточной среде может возникнуть следующая последовательность:

  • 2 1 3 4
  • 1 3 2 4

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

Однако есть и ограничение на перестановку инструкций, то есть следующий порядок не появится

  • 4 3 2 1

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

Поскольку шаг 4: должен зависеть от объявления y и объявления x, он не может быть выполнен первым из-за зависимостей данных.

2.5 Принцип синхронного

Часто используемые методы:


макет объекта Java?

​ Объект интеграции имеет в сумме 16Б, из них 12Б в заголовке объекта, а 4Б это выровненные байты (размер объекта на 64-битной виртуальной машине должен быть кратен 8)

Что хранится в заголовке объекта?

Заголовок объекта является общей частью в начале всех объектов.

Справочный сайт:откройте JDK.java.net/groups/hots…

2.5 В чем разница между битом и байтом?

бит означает «бит» или «бит», что является основой компьютерных операций;

байт означает «байт» и является основной единицей расчета размера компьютерного файла;

byte=byte равен 1byte=8bits, а преобразование между ними представляет собой соотношение 1:8.

3. Типы замков:

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

По справедливости замок можно разделить наЧестные замки и нечестные замки.

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

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

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

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

Пессимистическая блокировка — это пессимистическая идея. Он всегда думает, что может возникнуть наихудшая ситуация. Он думает, что данные могут быть изменены другими, поэтому пессимистическая блокировка всегда будет блокировать ресурсы или данные при хранении данных. Таким образом, другие потоки будут блокировать, когда они хотят запросить этот ресурс, пока пессимистическая блокировка не освободит ресурс. Многие такие механизмы блокировки используются в традиционных реляционных базах данных, например блокировки строк, таблиц и т. д., блокировки чтения, записи и т. д., которые блокируются перед выполнением операций. ** Реализация пессимистичных блокировок часто зависит от функции блокировки самой базы данных.

Эксклюзивные блокировки (эксклюзивные блокировки), такие как Synchronized и ReentrantLock в Java, также являются реализацией пессимистического мышления о блокировках, потому что Synchronzied и ReetrantLock будут пытаться заблокировать независимо от того, держат ли они ресурсы или нет, опасаясь, что их любимое детище будет отнято другими. .

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

Идея оптимистической блокировки противоположна идее пессимистической блокировки, она всегда считает, что ресурсы и данные не будут изменены другими, поэтому чтение не будет заблокировано, но оптимистическая блокировка будет определять, были ли текущие данные Модифицированный (конкретно как судить об этом мы поговорим позже). Обычно существует две схемы реализации оптимистической блокировки: механизм номера версии и реализация CAS. Оптимистическая блокировка в основном подходит для типов приложений с множественным чтением, которые могут повысить пропускную способность. Например, в MyBaits-Plus поддерживается механизм оптимистичной блокировки.

Реализация оптимистичной блокировки: механизм номера версии, справочный код

3.2. Честные замки и нечестные замки.

3.21 Определение справедливой блокировки

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

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

img

3.22 Использование справедливой блокировки

В Java мы обычно используем ReetrantLock для обеспечения справедливости блокировки.

public class MyFairLock extends Thread {
    //创建公平锁
    private ReentrantLock lock = new ReentrantLock(true);

    public void fairLock() {
        try {
            lock.lock();
            System.out.println(Thread.currentThread().getName() + "正在持有锁");
        } finally {
            System.out.println(Thread.currentThread().getName() + "释放了锁");
            lock.unlock();
        }
    }
    public static void main(String[] args) {
        MyFairLock myFairLock = new MyFairLock();
        Runnable runnable = () -> {
            System.out.println(Thread.currentThread().getName() + "启动");
            myFairLock.fairLock();
        };
        Thread[] thread = new Thread[10];
        for (int i = 0; i < 10; i++) {
            thread[i] = new Thread(runnable);
        }
        for (int i = 0; i < 10; i++) {
            thread[i].start();
        }
    }
}

Глядя на исходный код ReentrantLock, вы можете видеть, что если это правда, будет создана справедливая блокировка ReentrantLock, а затем будет создан FairSync.FairSync на самом деле является внутренним классом Sync. Его основная функция заключается в синхронизации объектов. для получения чистых замков.

Глядя на сравнение исходного кода между FairSync и NonfairSync, мы ясно видим, что единственная разница между методами lock() справедливых и нечестных блокировок заключается в том, что честные блокировки имеют дополнительное ограничение при получении состояния синхронизации: hasQueuedPredecessors().

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

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

3.22 Определение недобросовестной блокировки

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

img

3.4 Эксклюзивные блокировки и общие блокировки

3.4.1 Определение эксклюзивной блокировки

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

3.4.2 Определение общей блокировки

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

3.4.3 Примеры

		ReadWriteLock lock=new ReentrantReadWriteLock();
        Lock readLock = lock.readLock();
        Lock writeLock = lock.writeLock();

ReentrantReadWriteLock имеет две блокировки: ReadLock и WriteLock, то есть блокировку чтения и блокировку записи, которые вместе называются блокировками чтения-записи. Среди них блокировка чтения является общей блокировкой, а блокировка записи — монопольной блокировкой, поскольку блокировка чтения и блокировка записи разделены. Поэтому параллелизм ReentrantReadWriteLock был значительно улучшен по сравнению с обычным мьютексом.

3.5 блокировка спина.

3.5.1 Определение спин-блокировки

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

3.5.2 Преимущества спин-блокировок

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

3.5.3 Недостатки спин-блокировок

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

Хороший способ решить описанную выше ситуацию — установить время вращения для блокировки вращения и снять блокировку вращения, как только время истечет. Назначение спин-блокировок состоит в том, чтобы занимать ресурсы ЦП, не освобождая их, и обрабатывать их сразу после получения блокировок. Но как выбрать время отжима? Если время выполнения вращения слишком велико, большое количество потоков будет находиться в состоянии вращения и занимать ресурсы ЦП, что повлияет на производительность всей системы. Поэтому выбор периода вращения имеет особое значение! В JDK 1.6 введены адаптивные спин-блокировки. Адаптивные спин-блокировки означают, что время вращения не фиксировано, а определяется предыдущим временем вращения на той же блокировке и состоянием блокировки. В основном считается, что время для контекста потока Переключение — оптимальное время.

3.5.4 Демонстрация спин-блокировки

/**
 * 手写自旋锁
 * 循环比较获取直到成功为止,没有类似于wait的阻塞
 * 通过CAS操作完成自旋锁,A线程先进来调用myLock方法自己持有锁5秒,B随后进来发现当前有线程持有锁,不是null,所以只能通过自旋等待,直到A释放锁后B随后抢到
 */
public class SpinLock {
    // 现在的泛型装的是Thread,原子引用线程
    AtomicReference<Thread> atomicReference = new AtomicReference<>();

    public void myLock() {
        // 获取当前进来的线程
        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName() + "\t come in ");

        // 开始自旋,期望值是null,更新值是当前线程,如果是null,则更新为当前线程,否者自旋
        while(!atomicReference.compareAndSet(null, thread)) {

        }
    }

    /**
     * 解锁
     */
    public void myUnLock() {

        // 获取当前进来的线程
        Thread thread = Thread.currentThread();

        // 自己用完了后,把atomicReference变成null
        atomicReference.compareAndSet(thread, null);

        System.out.println(Thread.currentThread().getName() + "\t invoked myUnlock()");
    }

    public static void main(String[] args) {

        SpinLock spinLockDemo = new SpinLock();

        // 启动t1线程,开始操作
        new Thread(() -> {
            // 开始占有锁
            spinLockDemo.myLock();
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 开始释放锁
            spinLockDemo.myUnLock();
        }, "t1").start();


        // 让main线程暂停1秒,使得t1线程,先执行
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 1秒后,启动t2线程,开始占用这个锁
        new Thread(() -> {
            // 开始占有锁
            spinLockDemo.myLock();
            // 开始释放锁
            spinLockDemo.myUnLock();
        }, "t2").start();

    }
}

4. Распределенная блокировка

4.1 Какие обычно используются распределенные блокировки?

  1. Распределенная блокировка на основе базы данных
  2. Распределенная блокировка на основе кеша (Redis и т. д.)
  3. Распределенная блокировка на основе Zookeeper

4.2 Условия распределенных блокировок

  1. Эксклюзивность: одновременно только один клиент может получить блокировку, а другие клиенты не могут получить блокировку коллегами.
  2. Избегайте взаимоблокировки: эту блокировку необходимо снять через определенный период времени, в противном случае возникнет взаимоблокировка, включая блокировку нормального освобождения и блокировку аварийного освобождения, например, даже если клиент выйдет из строя во время удержания блокировки и не снимет блокировку. Убедитесь, что последующие клиенты могут быть скованы.
  3. Разблокировать самостоятельно: и блокировка, и разблокировка должны выполняться одним и тем же клиентом и не могут быть разблокированы другими.
  4. Высокая доступность: получение и освобождение блокировок должно быть высокодоступным и отличным.

4.3 Распределенная блокировка базы данных?

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

4.2 Распределенная блокировка Zookeeper?

4.2.1 Тип узла Zookeeper и механизм наблюдения

Тип узла зоопарка:

PERSISTENT 持久化节点
PERSISTENT_SEQUENTIAL 顺序自动编号持久化节点,这种节点会根据当前已存在的节点数自动加 1
EPHEMERAL 临时节点, 客户端session超时这类节点就会被自动删除
EPHEMERAL_SEQUENTIAL 临时自动编号节点

Механизм часов зоозащитника:

Znode发生变化(Znode本身的增加,删除,修改,以及子Znode的变化)可以通过Watch机制通知到客户端。那么要实现Watch,就必须实现org.apache.zookeeper.Watcher接口,并且将实现类的对象传入到可以Watch的方法中。Zookeeper中所有读操作(getData(),getChildren(),exists())都可以设置Watch选项。Watch事件具有one-time trigger(一次性触发)的特性,如果Watch监视的Znode有变化,那么就会通知设置该Watch的客户端。

4.2.2 Идеи реализации

Определить блокировку:

В обычном параллельном программировании на Java существует два распространенных способа определения блокировок, а именно синхронизированный механизм и ReetrantLock, предоставляемый JDK5. Однако в zookeeper нет подобного API, который можно было бы использовать напрямую, но блокировка представлена ​​узлом данных в Zookeeper, например, узел /exclusive_lock/lock может быть определен как блокировка.

получить замок

Когда необходимо получить блокировку, все клиенты попытаются создать временный дочерний узел /exclusive_lock/lock под узлом /exclusive_lock, вызвав интерфейс create(). zookeeper гарантирует, что среди всех клиентов только один клиент может быть успешно создан, тогда можно считать, что клиент получил блокировку. В то же время всем клиентам, которые не получили блокировку, необходимо зарегистрировать Наблюдателя, отслеживающего изменения дочернего узла на узле /exclusive_lock, чтобы отслеживать изменения узла блокировки в режиме реального времени.

разблокировать замок

В разделе «Определение блокировок» мы уже упоминали, что /exclusive_lock/lock является временным узлом, поэтому в следующих двух случаях можно снять блокировку.

1. Если клиент, который в данный момент получает блокировку, не работает, временный узел на Zookeeper будет удален.

2. После нормального выполнения бизнес-логики клиент будет активно удалять временный узел, созданный им самим.

4.2.3 Код эталонного примера

Ссылаясь на идею дизайна ReentrantLock, используется шаблон проектирования метода шаблона.

4.3 Повторно распространять блокировку?

4.31 Причины использования Redis

  1. Redis обладает высокой производительностью;
  2. Команды Redis хорошо это поддерживают и более удобны в реализации.

4.32 Идеи реализации

  1. При получении блокировки используйте setnx для блокировки и используйте команду expire, чтобы добавить к блокировке время ожидания. Если время превышает это время, блокировка автоматически снимается. Значением блокировки является случайно сгенерированный UUID, который используется для определения момента снятия блокировки.
  2. При получении блокировки также устанавливается тайм-аут получения, если это время превышено, получение блокировки прекращается.
  3. При снятии блокировки UUID используется для определения того, является ли блокировка блокировкой.Если блокировка является блокировкой, выполняется удаление, чтобы снять блокировку.

4.33 Пример кода

Вы также можете использовать распределенную структуру блокировки Redis — пример кода Redission