Взаимная блокировка
Особый тип ошибок, которого следует избегать, имеющий отношение к многозадачности это взаимная блокировка (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 на первом счёте, а затем на втором, то может произойти так, что:
- Берётся блокировка this на методе synchronized у счёта 1.
- Происходит переключение потоков.
- Берётся блокировка this на методе synchronized у счёта 2.
- Происходит переключение потоков.
- Поток, уже имеющий блокировку на счёте 1, пытается взять блокировку на счёте 2 в синхронизированном блоке, но блокировка this у счёта 2 уже занята, поэтому поток останавливается до тех пор, пока блокировка счёта 2 не освободиться.
- Поток, уже имеющий блокировку на счёте 2, пытается взять блокировку на счёте 1 в синхронизированном блоке, но блокировка this у счёта 1 уже занята, поэтому поток останавливается до тех пор, пока блокировка счёта 1 не освободится.
- Оба потока ждут освобождения блокировок друг друга, чего никогда не произойдёт.
Как избавиться от подобного? В данном случае можно обратить внимание на идентификатор счёта 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?
Вот одна из моих версий:
