Синхронизация потоков и блокировки | Многопоточность Java (5)

задняя часть
Синхронизация потоков и блокировки | Многопоточность Java (5)

Это 29-й день моего участия в Gengwen Challenge.Подробности о мероприятии:Обновить вызов


Статьи по Теме

Резюме многопоточности Java:Многопоточность Java


предисловие

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

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

1. Метод синхронизации

Сначала позвольте мне представитьsynchronizedключевые слова.

public synchronized void method(int args){}
  • синхронизировано:

    • Блокировка по умолчанию является собственным объектом.
    • Для охвата объектов обычно используется синхронизированный блок.
    • То есть объект, в котором заблокирован общий ресурс.
  • Пример кода:

/**
 * 手机抢购案例
 */
public class PhoneSnapUp implements Runnable {

    private Integer inventory = 10;//手机库存
    private boolean flag = true;

    public static void main(String[] args) {
        PhoneSnapUp phoneSnapUp = new PhoneSnapUp();
        //模拟5人同时抢购,即同时开启5个线程
        new Thread(phoneSnapUp, "丁大大1号").start();
        new Thread(phoneSnapUp, "丁大大2号").start();
        new Thread(phoneSnapUp, "丁大大3号").start();
        new Thread(phoneSnapUp, "丁大大4号").start();
        new Thread(phoneSnapUp, "丁大大5号").start();
    }

    @Override
    public synchronized void run() {
        while (flag){
            try {
//                synchronized (this) {
                    buy();
//                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    //购票方法提出来
    private void buy() throws Exception {
        //当库存为0时,抢购结束
        if (inventory <= 0) {
            flag = false;
            return;
        }
        //模拟延迟,否则结果不容易看出来
        Thread.sleep(500);
        //每次抢购完,库存减1
        System.out.println("恭喜!!" + Thread.currentThread().getName() + "--抢到了一台小米12!库存还剩:" + --inventory + "台");
    }
}

  • Результат выполнения следующий:

在这里插入图片描述

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

2. Блок синхронизированного кода

synchronized(Obj){}
  • Случай кода: остальные коды совпадают с методом синхронизации, они не повторяются и не вставляются.Здесь вводится только код метода запуска.
@Override
    public void run() {
        while (flag){
            try {
                synchronized (this) {
                    buy();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
  • Результаты:

在这里插入图片描述

  • Суммировать:

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

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

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

  • будь осторожен:

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

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

3. Тупик

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

  • Простое и грубое понимание:

    • Два ресурса A и B
    • Дин И удерживает блокировку ресурса A и хочет продолжить блокировку ресурса B после выполнения
    • Дин Эр удерживает блокировку ресурса B и хочет продолжить блокировку ресурса A после выполнения
    • Если ни один из них не закончит выполнение, то есть они не снимут блокировку друг с друга, они будут сражаться, и ни один из них не сможет выполнить.
    • привести в тупик
  • Текст всегда непонятен, и его точно можно понять, если следовать коду:

/**
 * 模拟多线程死锁的情况
 */
public class Demo {
    public static void main(String[] args) {
        DeadLockThread deadLockThread = new DeadLockThread();
        new Thread(deadLockThread,"丁一+").start();
        new Thread(deadLockThread,"丁二+").start();
    }
}

class DeadLockThread implements Runnable {
    A a = new A();
    B b = new B();

    @SneakyThrows
    @Override
    public void run() {
        if (Thread.currentThread().getName().equals("丁一")){
            synchronized (this){
                System.out.println(Thread.currentThread().getName());
                a.soutA();
                Thread.sleep(2000);
                b.soutB();
            }
        }else {
            synchronized (this) {
                System.out.println(Thread.currentThread().getName());
                b.soutB();
                Thread.sleep(2000);
                a.soutA();
            }
        }
    }
}


class A {
    public void soutA(){
        System.out.println("我是A");
    }
}

class B {
    public void soutB(){
        System.out.println("我是B");
    }
}

  • Результат выполнения следующий:

在这里插入图片描述

  • Резюме: Четыре необходимых условия взаимоблокировки:

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

4. Блокировка замка

  • Синхронизация путем явного определения объекта блокировки синхронизации (Lock)

  • Класс ReentrantLock реализует интерфейс Lock, который имеет ту же семантику параллелизма и памяти, что и синхронизированный.В управлении потокобезопасностью чаще используется ReentrantLock, который может явно блокировать и снимать блокировки.

  • Случай кода:

/**
 * 使用Lock锁
 */
public class TestLock {

    public static void main(String[] args) {
        TestLock2 testLock = new TestLock2();

        new Thread(testLock, "A").start();
        new Thread(testLock, "B").start();
        new Thread(testLock, "C").start();
    }

}

class TestLock2 implements Runnable{
    int count = 1000;

    //定义lock锁
    private final ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        while(true)
        {
            try {
                //进入加锁状态
                lock.lock();
                if(count > 0)
                    System.out.println(Thread.currentThread().getName() + "---" +count--);
                else
                    break;
            }
            finally {
                //解锁
                lock.unlock();
            }
        }
    }
}
  • Результаты:

在这里插入图片描述

  • Суммировать:
    • В случае отсутствия блокировки ABC может одновременно выполнять подсчет, что приводит к беспорядку данных.
    • После добавления блокировки ABC ставит операцию в очередь

Впереди долгий путь, и я обязательно буду его искать вдоль и поперёк~

Если вы думаете, что я блогеры хорошо пишу! Писать нелегко, пожалуйста, ставьте лайки, подписывайтесь и комментируйте, чтобы поощрять блоггеров ~ хахах