Анализ взаимоблокировок параллельного программирования

Java задняя часть Linux Windows

предисловие

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

Шаги, которые мы предпринимаем, чтобы понять что-либо: что, как, почему, почему нет.

1. Что такое тупик?

Давайте просто напишем кусок кода, чтобы увидеть:

package hello;

public class DeadLock {

  public static void main(String[] args) {

    new Thread(() -> {
      try {
         new DeadLock().resource1();
      } catch (InterruptedException e) {
      }
    }
    ).start();

    new Thread(() -> {
      try {
         new DeadLock().resource2();
      } catch (InterruptedException e) {
      }
    }
    ).start();
  }

  void resource1() throws InterruptedException {

    synchronized ("resource1") {
      System.out.println("获取资源1");
       // 等待 1 秒让另一个线程拿到锁
      Thread.sleep(1000);
      resource2();
    }


  }

  void resource2() throws InterruptedException {
    synchronized ("resource2") {
      System.out.println("获取资源2");
      // 等待 1 秒让另一个线程拿到锁
      Thread.sleep(1000);
      resource1();
    }
  }
}


В приведенном выше коде мы разрешили двум потокам вытеснять два ресурса соответственно, но эти два ресурса заблокированы разными объектами (строками). Когда первый поток вызывает метод resource1, входит в блок синхронизации, получает блокировку и ждет 1 секунду, пока другой поток не войдет в блок синхронизации resource2, когда второй поток входит в блок синхронизации, обратите внимание: в это время удерживайте resourcec1 lock Поток пытается получить блокировку ресурса2, но на этот раз поток, удерживающий ресурс2, также хочет получить блокировку ресурса1. Таким образом, друг с другом возникла патовая ситуация, никто не мог получить замок другого, и вся система застряла.

Эта ситуация является тупиковой.

Например, код, который мы пишем сейчас, представляет собой тупик, намеренно созданный нами самими. Мы можем выяснить, что делать, если это онлайн-среда. Если наша система застряла, как мы узнаем, в каком фрагменте кода возникла проблема? любая проблема — возможные проблемы взаимоблокировки. То есть, как обнаружить взаимоблокировки.

2. Как обнаружить взаимоблокировку?

Поскольку взаимные блокировки крайне сложно обнаружить вручную, JDK предоставляет команды для определения ситуации в центральном потоке процесса Java и проверки на наличие взаимоблокировок. Как насчет приведенной выше команды? jps, используется для просмотра номера процесса java-программы, конечно же, его можно получить и другими способами в Linux, jstack进程号Команда может ответить на информацию о стеке соответствующего процесса и найти взаимоблокировку.

У нас просто есть программа, и мы используем эту команду в Windows.

C:\Users\stateis0>jps
11060
2084 Launcher
10712 RemoteMavenServer
18040 Jps
11820 DeadLock





C:\Users\stateis0>jstack 11820
2017-12-29 18:52:38
Full thread dump Java HotSpot(TM) Client VM (25.131-b11 mixed mode):

"DestroyJavaVM" #11 prio=5 os_prio=0 tid=0x051fe800 nid=0x1e0c waiting on condition [0x00000000]
   java.lang.Thread.State: RUNNABLE

"Thread-1" #10 prio=5 os_prio=0 tid=0x18777800 nid=0x5664 waiting for monitor entry [0x18e0f000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at hello.DeadLock.resource1(DeadLock.java:31)
        - waiting to lock <0x07415a50> (a java.lang.String)
        at hello.DeadLock.resource2(DeadLock.java:43)
        - locked <0x0742bd18> (a java.lang.String)
        at hello.DeadLock.lambda$main$1(DeadLock.java:20)
        at hello.DeadLock?Lambda$2/4983748.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:748)

"Thread-0" #9 prio=5 os_prio=0 tid=0x18776c00 nid=0x4dc4 waiting for monitor entry [0x18d7f000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at hello.DeadLock.resource2(DeadLock.java:41)
        - waiting to lock <0x0742bd18> (a java.lang.String)
        at hello.DeadLock.resource1(DeadLock.java:33)
        - locked <0x07415a50> (a java.lang.String)
        at hello.DeadLock.lambda$main$0(DeadLock.java:11)
        at hello.DeadLock?Lambda$1/5592464.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:748)

"Service Thread" #8 daemon prio=9 os_prio=0 tid=0x186e4c00 nid=0x172c runnable [0x00000000]
   java.lang.Thread.State: RUNNABLE

"C1 CompilerThread0" #7 daemon prio=9 os_prio=2 tid=0x186af000 nid=0x53f8 waiting on condition [0x00000000]
   java.lang.Thread.State: RUNNABLE

"Monitor Ctrl-Break" #6 daemon prio=5 os_prio=0 tid=0x1861e800 nid=0x3928 runnable [0x18b3f000]
   java.lang.Thread.State: RUNNABLE
        at java.net.SocketInputStream.socketRead0(Native Method)
        at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
        at java.net.SocketInputStream.read(SocketInputStream.java:171)
        at java.net.SocketInputStream.read(SocketInputStream.java:141)
        at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
        at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
        at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
        - locked <0x07861da0> (a java.io.InputStreamReader)
        at java.io.InputStreamReader.read(InputStreamReader.java:184)
        at java.io.BufferedReader.fill(BufferedReader.java:161)
        at java.io.BufferedReader.readLine(BufferedReader.java:324)
        - locked <0x07861da0> (a java.io.InputStreamReader)
        at java.io.BufferedReader.readLine(BufferedReader.java:389)
        at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:64)

"Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x179c0800 nid=0x40a0 waiting on condition [0x00000000]
   java.lang.Thread.State: RUNNABLE

"Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x17985c00 nid=0x5004 runnable [0x00000000]
   java.lang.Thread.State: RUNNABLE

"Finalizer" #3 daemon prio=8 os_prio=1 tid=0x17972400 nid=0x41a8 in Object.wait() [0x17cff000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x0ca1b830> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143)
        - locked <0x0ca1b830> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:164)
        at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:209)

"Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x17960000 nid=0x4ef0 in Object.wait() [0x17c6f000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x0ca1b9d0> (a java.lang.ref.Reference$Lock)
        at java.lang.Object.wait(Object.java:502)
        at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
        - locked <0x0ca1b9d0> (a java.lang.ref.Reference$Lock)
        at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)

"VM Thread" os_prio=2 tid=0x1795a800 nid=0x3f54 runnable

"VM Periodic Task Thread" os_prio=2 tid=0x18739400 nid=0x4a14 waiting on condition

JNI global references: 229

// 找到一个死锁
Found one Java-level deadlock:
=============================
"Thread-1":
  waiting to lock monitor 0x17978de4 (object 0x07415a50, a java.lang.String),
  which is held by "Thread-0"
"Thread-0":
  waiting to lock monitor 0x1797a974 (object 0x0742bd18, a java.lang.String),
  which is held by "Thread-1"

Java stack information for the threads listed above:
===================================================
"Thread-1":
        at hello.DeadLock.resource1(DeadLock.java:31)
         // 等待 0x07415a50 锁
        - waiting to lock <0x07415a50> (a java.lang.String)
        at hello.DeadLock.resource2(DeadLock.java:43)
        // 持有 0x0742bd18
        - locked <0x0742bd18> (a java.lang.String)
        at hello.DeadLock.lambda$main$1(DeadLock.java:20)
        at hello.DeadLock?Lambda$2/4983748.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:748)
"Thread-0":
        at hello.DeadLock.resource2(DeadLock.java:41)
        // 等待 0x0742bd18 锁
        - waiting to lock <0x0742bd18> (a java.lang.String)
        at hello.DeadLock.resource1(DeadLock.java:33)
        // 持有 0x07415a50
        - locked <0x07415a50> (a java.lang.String)
        at hello.DeadLock.lambda$main$0(DeadLock.java:11)
        at hello.DeadLock?Lambda$1/5592464.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:748)

// 发现了一个死锁
Found 1 deadlock.


C:\Users\stateis0>

Thread-1 waiting to lock <0x07415a50> locked <0x0742bd18> Thread-0 waiting to lock <0x0742bd18> locked <0x07415a50>

Сначала мы используем команду jps, чтобы найти номер процесса java, затем используем jstack进程号Вывести информацию о стеке процесса, в котором в прошлой части jstack сообщает нам, что он нашел взаимоблокировку, в которой есть подробная информация: Thread-1 thread (здесь мы не даем треду его собственное имя, если он онлайн , дать Подходящее имя для потока будет более способствовать поиску и устранению неполадок) Удерживает блокировку с номером 0x07415a50 типа String и ждет блокировки с номером 0x07415a50, но эта блокировка удерживается Thread-0. Thread-1 вместо этого. Поток Thread-0 удерживает блокировку по адресу 0x07415a50, ожидая блокировки по адресу 0x07415a50. Это тоже написано в наших заметках.

Тогда возникает тупик, что мне делать? Самый простой способ - перезапустить.После перезапуска изменить код в информации о стеке, напечатанной в jstack. Переиздать. Конечно, есть некоторые продвинутые стратегии, такие как откат процессов к состоянию, в котором они находились до взаимоблокировки, а затем их последовательный вход в синхронизированный блок.

3. Каковы причины взаимоблокировок

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

  1. Условие взаимного исключения: ресурс может использоваться только одним потоком одновременно.

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

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

  4. Условие циклического ожидания: между несколькими процессами формируются отношения ресурсов циклического ожидания.

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

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

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

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

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

4 Резюме

В параллельном программировании есть много ям, особенно взаимоблокировок.Вызванные проблемы могут быть решены только путем перезапуска.Если вы столкнетесь с данными, хранящимися в памяти, но не постоянными, то перезапуск вызовет большие проблемы. Поэтому мы должны быть осторожны при использовании замков. Чтобы избежать взаимоблокировки, если она возникла, вы можете использовать команду jstack, чтобы проверить, есть ли взаимоблокировка в потоке. для устранения неполадок.

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

удачи! ! ! !