Перейти к содержимому

Как избежать deadlock java

  • автор:

Взаимная блокировка

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

Например, две девочки Маша и Даша в детском саду делают аппликацию. Для работы каждой нужны ножницы и цветная бумага. Предположим Маша взяла ножницы (поток Маша вошла в монитор объекта ножницы), а Даша бумагу (поток Даша вошла в монитор объекта бумага). Каждая из них ждет другой предмет и не хочет делиться тем, что взяла. Они не могут продолжить свою работу и будут ждать вечно (пока воспитательница не поможет им).

Взаимная блокировка фото

Пример 1. Взаимная блокировка
public class DeadlockRisk implements Runnable < private static class Resource < >private final Resource scissors = new Resource(); private final Resource paper = new Resource(); public void doSun() < synchronized (scissors) < // May deadlock here System.out.println(Thread.currentThread().getName() + " взяла ножницы для вырезания солнышка"); synchronized (paper) < System.out.println(Thread.currentThread().getName() + " взяла бумагу для вырезания солнышка"); System.out.println(Thread.currentThread().getName() + " вырезает солнышко"); >> > public void doCloud() < synchronized (paper) < // May deadlock here System.out.println(Thread.currentThread().getName() + " взяла бумагу для вырезания облачка"); synchronized (scissors) < System.out.println(Thread.currentThread().getName() + " взяла ножницы для вырезания облачка"); System.out.println(Thread.currentThread().getName() + " вырезает облачко"); >> > public void run() < doSun(); doCloud(); >public static void main(String[] args) < DeadlockRisk job = new DeadlockRisk(); Thread masha = new Thread(job, "Маша"); Thread dasha = new Thread(job, "Даша"); masha.start(); dasha.start(); >>
  • Многозадачность
  • Класс Thread
  • Состояния потоков
  • Переключение между потоками
  • Приоритеты потоков
  • Метод Thread.sleep()
  • Метод Thread.yield()
  • Метод Thread.join()
  • Метод Thread.isAlive()
  • Потокобезопасные классы
  • Синхронизация потоков
  • Межпотоковые коммуникации
  • Задания

Борьба с взаимными блокировками в Java

В жизни обычного программиста редко возникает возможность писать что-то действительно крутое и интересное. Большая часть нашей работы связана лишь со скрупулёзностью, усидчивостью, вниманием и монотонностью. Лишь на собеседованиях можно применить что-либо действительно интересное. Или странное. В этой статье я опишу способ решения проблемы смертельных блокировок (deadlock-ов), который может вам пригодиться при прохождении некоторых собеседований. Предположим, что у нас есть система, работающая со счетами пользователей. Счета пользователей представлены классом Account:

public class Account < private final int id ; private int amount ; public Account ( int id ) < this . id = id ; public int getId ( ) < return this . id ; public int getAmount ( ) < return this . amount ; public synchronized void transfer ( Account fromAccount , int transferSum ) < synchronized ( fromAccount ) < fromAccount . amount -= transferSum ; this . amount += transferSum ;

На первый взгляд может показаться, что всё в порядке. Но на самом деле в этом коде спрятана взаимная блокировка. Если вызвать transfer на первом счёте, а затем на втором, то может произойти так, что:

  1. Берётся блокировка this на методе synchronized у счёта 1.
  2. Происходит переключение потоков.
  3. Берётся блокировка this на методе synchronized у счёта 2.
  4. Происходит переключение потоков.
  5. Поток, уже имеющий блокировку на счёте 1, пытается взять блокировку на счёте 2 в синхронизированном блоке, но блокировка this у счёта 2 уже занята, поэтому поток останавливается до тех пор, пока блокировка счёта 2 не освободиться.
  6. Поток, уже имеющий блокировку на счёте 2, пытается взять блокировку на счёте 1 в синхронизированном блоке, но блокировка this у счёта 1 уже занята, поэтому поток останавливается до тех пор, пока блокировка счёта 1 не освободится.
  7. Оба потока ждут освобождения блокировок друг друга, чего никогда не произойдёт.

Как избавиться от подобного? В данном случае можно обратить внимание на идентификатор счёта id. Понятное дело, что каждый счёт имеет уникальный идентификатор. Чтобы избежать deadlock-ов мы можем всегда брать блокировки строго в порядке сортировки их идентификаторов, то есть сначала меньший, а затем больший, чем решим проблему взаимных блокировок для этого случая:

Руководство по Java Core. Многопоточность. Взаимная блокировка.

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

Для понимания того, как это работает на практике рассмотрим пример простого приложения.

 public class DeadLockDemo < public static final Object Lock1 = new Object(); public static final Object Lock2 = new Object(); public static void main(String[] args) < DeadThreadOne threadOne = new DeadThreadOne(); DeadThreadTwo threadTwo = new DeadThreadTwo(); threadOne.start(); threadTwo.start(); >private static class DeadThreadOne extends Thread < public void run() < synchronized (Lock1) < System.out.println("DeadThreadOne is holding LOCK 1. "); try < Thread.sleep(1000); >catch (InterruptedException e) < e.printStackTrace(); >System.out.println("DeadThreadOne is waiting for Lock 2. "); synchronized (Lock2) < System.out.println("DeadThreadOne is holding Lock 1 and Lock 2. "); >> > > private static class DeadThreadTwo extends Thread < public void run() < synchronized (Lock2) < System.out.println("DeadThreadTwo is holding LOCK 2. "); try < Thread.sleep(1000); >catch (InterruptedException e) < e.printStackTrace(); >System.out.println("DeadThreadOne is waiting for Lock 1. "); synchronized (Lock1) < System.out.println("DeadThreadOne is holding Lock 1 and Lock 2. "); >> > > > 

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

 DeadThreadOne is holding LOCK 1. DeadThreadTwo is holding LOCK 2. DeadThreadOne is waiting for Lock 2. DeadThreadOne is waiting for Lock 1. 

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

Рассмотрим пример приложения, в котором показано, как избежать такой ситуации.
Пример:

 public class SolveDeadLock < public static final Object Lock1 = new Object(); public static final Object Lock2 = new Object(); public static void main(String args[]) < ThreadOne threadOne = new ThreadOne(); ThreadTwo threadTwo = new ThreadTwo(); threadOne.start(); threadTwo.start(); >private static class ThreadOne extends Thread < public void run() < synchronized (Lock1) < System.out.println("ThreadOne is holding lock 1. "); try < Thread.sleep(10); >catch (InterruptedException e) < e.printStackTrace(); >System.out.println("ThreadOne is waiting for lock 2. "); synchronized (Lock2) < System.out.println("ThreadOne is holding lock 1 & 2. "); >> > > private static class ThreadTwo extends Thread < public void run() < synchronized (Lock1) < System.out.println("ThreadTwo is holding lock 1. "); try < Thread.sleep(10); >catch (InterruptedException e) < e.printStackTrace(); >System.out.println("ThreadOne is waiting for lock 2. "); synchronized (Lock2) < System.out.println("ThreadTwo is holding lock 1 & 2. "); >> > > > 

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

 /*Some System Messages*/ ThreadOne is holding lock 1. ThreadOne is waiting for lock 2. ThreadOne is holding lock 1 & 2. ThreadTwo is holding lock 1. ThreadOne is waiting for lock 2. ThreadTwo is holding lock 1 & 2. 

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

Полезности

Туториалы
Системный дизайн
Собеседования
Студенты
Задачи

Немного о себе

Приветствую! Меня зовут Евгений. На этом сайте я пишу о разработке программного обеспечения. Связаться со мной вы можете по email: proselytear@yahoo.com Имеет смысл, предварительно ознакомиться вот с этим FAQ разделом.

Недавние публикации
  • Механизмы CAS и FAA глазами Java разработчика
  • ExecutorService в Java и примеры его применения.
  • Особенности работы PreparedStatement в JDBC
  • Основы кэширования в Hibernate
  • Феномены чтения глазами разработчика

Copyright © 2023 PROSELYTE.

Omega WordPress Theme by ThemeHall

Как избежать тупика в потоках Java?

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

Вопросы интервью начинаются с «Что такое тупик?»

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

Как вы обнаруживаете тупик в Java?

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

Другой способ – найти его, когда вы фактически заблокированы во время работы приложения, попробуйте сделать дамп потока, в Linux вы можете сделать это командой «kill -3», это выведет на экран статус всех потоков в файле журнала приложения. и вы можете увидеть, какой поток заблокирован на каком объекте.

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

Другой способ – использовать jConsole / VisualVM , он покажет вам, какие потоки заблокированы и на каком объекте.

Написать программу на Java, которая приведет к тупику?

Как только вы ответите на предыдущий вопрос, они могут попросить вас написать код, который приведет к тупику в Java?

Вот одна из моих версий:

Добавить комментарий

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