Рубрики

Тупик в Java Многопоточность

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

   
// Java-программа для иллюстрации тупика
// в многопоточности.

class Util

{

    // Используем класс для спящего потока

    static void sleep(long millis)

    {

        try

        {

            Thread.sleep(millis);

        }

        catch (InterruptedException e)

        {

            e.printStackTrace();

        }

    }

}

  
// Этот класс является общим для обоих потоков

class Shared

{

    // первый синхронизированный метод

    synchronized void test1(Shared s2)

    {

        System.out.println("test1-begin");

        Util.sleep(1000);

  

        // снимаем блокировку объекта s2

        // в метод test2

        s2.test2(this);

        System.out.println("test1-end");

    }

  

    // второй синхронизированный метод

    synchronized void test2(Shared s1)

    {

        System.out.println("test2-begin");

        Util.sleep(1000);

  

        // снимаем блокировку объекта s1

        // в метод test1

        s1.test1(this);

        System.out.println("test2-end");

    }

}

  

  

class Thread1 extends Thread

{

    private Shared s1;

    private Shared s2;

  

    // конструктор для инициализации полей

    public Thread1(Shared s1, Shared s2)

    {

        this.s1 = s1;

        this.s2 = s2;

    }

  

    // запустить метод для запуска потока

    @Override

    public void run()

    {

        // снимаем блокировку объекта s1

        // в метод test1

        s1.test1(s2);

    }

}

  

  

class Thread2 extends Thread

{

    private Shared s1;

    private Shared s2;

  

    // конструктор для инициализации полей

    public Thread2(Shared s1, Shared s2)

    {

        this.s1 = s1;

        this.s2 = s2;

    }

  

    // запустить метод для запуска потока

    @Override

    public void run()

    {

        // берём объектную блокировку s2

        // входит в метод test2

        s2.test2(s1);

    }

}

  

  

public class GFG

{

    public static void main(String[] args)

    {

        // создаем один объект

        Shared s1 = new Shared();

  

        // создаем второй объект

        Shared s2 = new Shared();

  

        // создаем первый поток и запускаем его

        Thread1 t1 = new Thread1(s1, s2);

        t1.start();

  

        // создаем второй поток и запускаем его

        Thread2 t2 = new Thread2(s1, s2);

        t2.start();

  

        // спящий основной поток

        Util.sleep(2000);

    }

}

Output : test1-begin
test2-begin

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

  1. Поток t1 запускается и вызывает метод test1, принимая объектную блокировку s1.
  2. Поток t2 запускается и вызывает метод test2, принимая объектную блокировку s2.
  3. t1 печатает test1-begin и t2 печатает test-2 begin и оба ждут 1 секунду, так что оба потока могут быть запущены, если ни один из них не запущен.
  4. t1 пытается взять объектную блокировку s2 и вызвать метод test2, но так как он уже получен t2, он ждет, пока не освободится. Он не снимет блокировку s1, пока не получит блокировку s2.
  5. То же самое происходит с T2. Он пытается получить объектную блокировку s1 и вызвать метод test1, но он уже получен t1, поэтому он должен ждать, пока t1 не снимет блокировку. t2 также не снимет блокировку s2, пока не получит блокировку s1.
  6. Теперь оба потока находятся в состоянии ожидания, ожидая друг друга, чтобы снять блокировки. Сейчас идет гонка вокруг условия того, кто первым откроет замок.
  7. Поскольку ни один из них не готов снять блокировку, это условие Dead Lock.
  8. Когда вы запустите эту программу, будет похоже, что выполнение приостановлено.

Определить состояние мертвого замка

Мы также можем обнаружить взаимоблокировку, запустив эту программу на cmd. Мы должны собрать дамп темы. Команда для сбора зависит от типа ОС. Если мы используем Windows и Java 8, команда jcmd $ PID Thread.print
Мы можем получить PID, выполнив команду jps. Дамп потока для вышеуказанной программы ниже:

5524:
2017-04-21 09:57:39
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.25-b02 mixed mode):

"DestroyJavaVM" #12 prio=5 os_prio=0 tid=0x0000000002690800 nid=0xba8 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Thread-1" #11 prio=5 os_prio=0 tid=0x0000000018bbf800 nid=0x12bc waiting for monitor entry [0x000000001937f000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at Shared.test1(GFG.java:15)
        - waiting to lock  (a Shared)
        at Shared.test2(GFG.java:29)
        - locked  (a Shared)
        at Thread2.run(GFG.java:68)

"Thread-0" #10 prio=5 os_prio=0 tid=0x0000000018bbc000 nid=0x1d8 waiting for monitor entry [0x000000001927f000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at Shared.test2(GFG.java:25)
        - waiting to lock  (a Shared)
        at Shared.test1(GFG.java:19)
        - locked  (a Shared)
        at Thread1.run(GFG.java:49)

"Service Thread" #9 daemon prio=9 os_prio=0 tid=0x000000001737d800 nid=0x1680 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C1 CompilerThread2" #8 daemon prio=9 os_prio=2 tid=0x000000001732b800 nid=0x17b0 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread1" #7 daemon prio=9 os_prio=2 tid=0x0000000017320800 nid=0x7b4 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread0" #6 daemon prio=9 os_prio=2 tid=0x000000001731b000 nid=0x21b0 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

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

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

"Finalizer" #3 daemon prio=8 os_prio=1 tid=0x0000000002781800 nid=0x5a0 in Object.wait() [0x000000001867f000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on  (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(Unknown Source)
        - locked  (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(Unknown Source)
        at java.lang.ref.Finalizer$FinalizerThread.run(Unknown Source)

"Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x000000000277a800 nid=0x15b4 in Object.wait() [0x000000001857f000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on  (a java.lang.ref.Reference$Lock)
        at java.lang.Object.wait(Unknown Source)
        at java.lang.ref.Reference$ReferenceHandler.run(Unknown Source)
        - locked  (a java.lang.ref.Reference$Lock)

"VM Thread" os_prio=2 tid=0x00000000172e6000 nid=0x1fec runnable

"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x00000000026a6000 nid=0x21fc runnable

"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x00000000026a7800 nid=0x2110 runnable

"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x00000000026a9000 nid=0xc54 runnable

"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x00000000026ab800 nid=0x704 runnable

"VM Periodic Task Thread" os_prio=2 tid=0x0000000018ba0800 nid=0x610 waiting on condition

JNI global references: 6


Found one Java-level deadlock:
=============================
"Thread-1":
  waiting to lock monitor 0x0000000018bc1e88 (object 0x00000000d5d645a0, a Shared),
  which is held by "Thread-0"
"Thread-0":
  waiting to lock monitor 0x0000000002780e88 (object 0x00000000d5d645b0, a Shared),
  which is held by "Thread-1"

Java stack information for the threads listed above:
===================================================
"Thread-1":
        at Shared.test1(GFG.java:15)
        - waiting to lock  (a Shared)
        at Shared.test2(GFG.java:29)
        - locked  (a Shared)
        at Thread2.run(GFG.java:68)
"Thread-0":
        at Shared.test2(GFG.java:25)
        - waiting to lock  (a Shared)
        at Shared.test1(GFG.java:19)
        - locked  (a Shared)
        at Thread1.run(GFG.java:49)

Found 1 deadlock.

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

Избегайте мертвой блокировки

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

  • Избегайте вложенных блокировок : это основная причина мертвой блокировки. Dead Lock в основном происходит, когда мы даем блокировки нескольким потокам. Избегайте блокировки нескольких потоков, если мы уже дали один.
  • Избегайте ненужных блокировок : у нас должна быть блокировка только тех членов, которые необходимы. Излишняя блокировка может привести к мертвой блокировке.
  • Использование объединения потоков: состояние мертвой блокировки появляется, когда один поток ожидает завершения другого. Если это условие возникает, мы можем использовать Thread.join с максимальным временем, которое, по вашему мнению, займет выполнение.

Важные моменты :

  • Если потоки ждут завершения друг друга, то это состояние называется тупиком.
  • Условие тупика — это сложное состояние, которое возникает только в случае нескольких потоков.
  • Условие тупика может нарушить наш код во время выполнения и может разрушить бизнес-логику.
  • Мы должны избегать этого состояния как можно больше.

Эта статья предоставлена Вишалом Гаргом . Если вы как GeeksforGeeks и хотели бы внести свой вклад, вы также можете написать статью с помощью contribute.geeksforgeeks.org или по почте статьи contribute@geeksforgeeks.org. Смотрите свою статью, появляющуюся на главной странице GeeksforGeeks, и помогите другим вундеркиндам.

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

Рекомендуемые посты:

Тупик в Java Многопоточность

0.00 (0%) 0 votes