Страницы

Поиск по вопросам

понедельник, 17 декабря 2018 г.

Многопоточность в java, почему порядок вывода результата разнится?

Допустим есть такой код. Его результат:[Синхронизация] [в Java] [ полезная] . Если объект Caller запускать без отдельного потока (т.е без "extends Thread" и без метода "start()"), то результат будет в другом порядке- [Синхронизация] [полезная] [в Java] Почему так происходит? Прошу дать развернутый ответ.
class CallMe{ void call(String msg){ System.out.print("[" + msg ); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("]"); } }
class Caller extends Thread{ String msg; CallMe target;
Caller(CallMe target, String msg){ this.target = target; this.msg = msg; start(); }
public void run(){ synchronized (target) { target.call(msg); } } }
public class Main { public static void main(String[] args) { CallMe callMe = new CallMe(); new Caller(callMe, "Синхронизация"); new Caller(callMe, "в Java"); new Caller(callMe, " полезная"); } }


Ответ

Поставьте задержку на 200 миллисекунд в методе run(), а в методе main() добавьте цикл операций на 100 - получите еще больше вариантов.
Потому что невозможно предсказать какой поток войдет в блок synchronized первым, даже если вы их запускаете последовательно.
Для синхронизации потоков используются классы
Semaphore CountDownLatch CyclicBarrier Lock
у них различные цели и методы. Это зависит от конкретной задачи.

Semaphore - как правило служит для ограничения количества потоков при работе с ресурсами. Доступ ограничивается с помощью счетчика, если его значение больше нуля, то доступ потоку разрешается, а значение счетчика уменьшается. Если счетчик равен нулю, то текущий поток блокируется, пока другой поток не освободит ресурс. Для получения доступа используется метод acquire(), для освобождения – release()
public class SemaphoreDemo { public static void main(String[] args) { Semaphore smp = new Semaphore(2); for (int i = 0; i < 5; i++) { final int w = i; new Thread(() -> { try { System.out.println("Поток" + w + " перед семафором"); smp.acquire(); System.out.println("Поток" + w + " получил доступ к ресурсу"); Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } finally { System.out.println("Поток" + w + " освободил ресурс"); smp.release(); } }).start(); } } }
Результат работы:
Поток 0 перед семафором Поток 0 получил доступ к ресурсу Поток 2 перед семафором Поток 2 получил доступ к ресурсу Поток 1 перед семафором Поток 4 перед семафором Поток 3 перед семафором Поток 2 освободил ресурс Поток 1 получил доступ к ресурсу Поток 0 освободил ресурс Поток 4 получил доступ к ресурсу Поток 4 освободил ресурс Поток 1 освободил ресурс Поток 3 получил доступ к ресурсу Поток 3 освободил ресурс
Одновременно семафор могут захватить(с помощью метода acquire()) только два потока, остальные потоки становятся в очередь, пока один из потоков не освободит семафор методом release()

CountDownLatch - Позволяет потоку ожидать до тех пор, пока не завершится определенное количество операций, выполняющихся в других потоках, в режим ожидания поток заходит с помощью метода await(). Количество требуемых операций задается при создании объекта, после чего уменьшается при вызове метода countDown(). Как только счетчик доходит до 0, ожидающий поток разблокируется.
public class SimpleCDL { public static void main(String[] args) { // задаем кол-во потоков final int THREADS_COUNT = 6; // задаем значение счетчика final CountDownLatch cdl = new CountDownLatch(THREADS_COUNT); System.out.println("Начинаем"); for (int i = 0; i < THREADS_COUNT; i++) { final int w = i; new Thread(() -> { try { // считаем что выполнение задачи занимает ~1 сек Thread.sleep(500 + (int)(500 * Math.random())); // как только задача выполнена, уменьшаем счетчик cdl.countDown(); System.out.println("Поток #" + w + " - готов"); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); } try { // ждем пока счетчик не сбросится в ноль, пока это не // произойдет, будем стоять на этой строке cdl.await(); } catch (InterruptedException e) { e.printStackTrace(); } // как только все потоки выполнили свои задачи - пишем сообщение System.out.println("Работа завершена"); } }
Результат работы:
Начинаем Поток #1 - готов Поток #0 - готов Поток #3 - готов Поток #2 - готов Поток #4 - готов Поток #5 - готов Работа завершена
Основной поток создает 6 потоков и ждет пока каждый из этих потоков закончит приготовление к работе.

CyclicBarrier - используется для синхронизации заданного количества потоков в одной точке. При вызове метода await() поток блокируется. Как только заданное количество потоков заблокировалось, с них одновременно снимается блокировка.
public class BarrierExample { public static void main(String[] args) { CyclicBarrier cb = new CyclicBarrier(3); for (int i = 0; i < 3; i++) { final int w = i; new Thread(() -> { try { System.out.println("Поток " + w + " готовится"); Thread.sleep(100 + (int) (3000 * Math.random())); System.out.println("Поток " + w + " готов"); cb.await(); System.out.println("Поток " + w + " запустился"); } catch (Exception e) { e.printStackTrace(); } }).start(); } } }
Результат работы:
Поток 0 готовится Поток 1 готовится Поток 2 готовится Поток 2 готов Поток 0 готов Поток 1 готов Поток 1 запустился Поток 2 запустился Поток 0 запустился Результат работы: Поток 0 готовится Поток 1 готовится Поток 2 готовится Поток 2 готов Поток 0 готов Поток 1 готов Поток 1 запустился Поток 2 запустился Поток 0 запустился
Несмотря на то, что какие-то потоки закончили подготовку раньше, какие-то позже, стартовали они в одно и то же время, так как блокировка снимается одновременно. Несмотря на то, что какие-то потоки закончили подготовку раньше, какие-то позже, стартовали они в одно и то же время, так как блокировка снимается одновременно.

Lock - Интерфейс. Представляет собой продвинутый механизм синхронизации потоков, который предоставляет большую гибкость чем блоки синхронизации. Поскольку Lock это интерфейс, для работы с ним необходимо создать объект одной из его реализаций.
Lock lock = new ReentrantLock(); lock.lock(); lock.unlock();
В начале создается объект типа Lock, после чего у этого объекта вызывается метод lock() и он захватывается. Попытка другого потока вызвать у этого же объекта метод lock() приведет к блокировке этого потока, пока поток удерживающий объект lock не освободит его с помощью метода unlock(). После вызова метода unlock() объект типа Lock освобождается и другие потоки могут его захватить Основные отличия между Lock и синхронизированными блоками:
Синхронизированные блоки не гарантируют сохранность порядка обращения потоков к критической секции; Выйти из синхронизированного блока по времени ожидания(timeout) не получится; Синхронизированные блоки должны полностью содержаться в одном методе, в то время как Lock может быть захвачен в одном метода, а освобожден в другом.

Комментариев нет:

Отправить комментарий