Семафор і Бар'єр
У багатопотокових програмах часто виникає потреба контролювати доступ до ресурсів або синхронізувати виконання потоків. Semaphore та Barrier — це високорівневі механізми синхронізації, які допомагають вирішувати ці завдання.
Сьогодні розглянемо кожен із цих механізмів по черзі та зрозуміємо їх відмінності. Почнемо з Semaphore.
Semaphore у Java реалізовані за допомогою класу java.util.concurrent.Semaphore.
Конструктори
Semaphore(int permits): Конструктор, який створює semaphore з певною кількістю дозволів. Дозволи визначають кількість доступів до спільного ресурсу.
Main.java
1Semaphore semaphore = new Semaphore(20);
Semaphore(int permits, boolean fair): Конструктор, що забезпечує вирішення за принципом перший прийшов — перший обслужений.
Main.java
1Semaphore semaphore = new Semaphore(20, true);
Якщо параметр fair встановлено у true, semaphore надає дозволи у порядку першим прийшов — першим отримав (FIFO), що допомагає уникнути голодування потоків. За замовчуванням — false.
Основні методи
Метод acquire() запитує один дозвіл. Якщо дозвіл доступний, він надається негайно; інакше потік блокується до появи дозволу. Після завершення завдання використовується метод release() для звільнення дозволу, повертаючи його до semaphore. Якщо інші потоки очікували на дозвіл, один із них буде розблоковано.
Уявіть собі паркінг з обмеженою кількістю місць. semaphore виконує роль контролера, відстежуючи доступні місця та забороняючи в’їзд, коли паркінг заповнений.
Main.java
1234567891011121314151617181920212223242526272829303132package com.example; import java.util.concurrent.Semaphore; public class Main { private final Semaphore semaphore; public Main(int slots) { semaphore = new Semaphore(slots); } public void parkCar() { try { semaphore.acquire(); // Request a parking spot System.out.println("Car parked. Available slots: " + semaphore.availablePermits()); Thread.sleep(2000); // Simulate parking time } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { semaphore.release(); // Release the parking spot System.out.println("Car left. Available slots: " + semaphore.availablePermits()); } } public static void main(String[] args) { Main parking = new Main(3); // Parking lot with 3 spots for (int i = 0; i < 5; i++) { new Thread(parking::parkCar).start(); } } }
Можна також дізнатися, скільки дозволів наразі доступно в Semaphore, використовуючи метод int availablePermits(). Можна також спробувати отримати дозвіл за допомогою методу boolean tryAcquire(), який повертає true, якщо дозвіл отримано, і false, якщо ні.
Main.java
1234567891011121314151617181920212223242526272829303132333435363738394041package com.example; import java.util.concurrent.Semaphore; public class Main { // Define the maximum number of permits available private static final int MAX_PERMITS = 3; private static Semaphore semaphore = new Semaphore(MAX_PERMITS); public static void main(String[] args) { // Create and start 5 worker threads for (int i = 1; i <= 5; i++) { new Thread(new Worker(), "Worker-" + i).start(); } } static class Worker implements Runnable { @Override public void run() { String name = Thread.currentThread().getName(); System.out.println(name + " trying to acquire a permit..."); // Try to acquire a permit if (semaphore.tryAcquire()) { try { System.out.println(name + " acquired a permit! Available permits: " + semaphore.availablePermits()); Thread.sleep(1000); // Simulate work } catch (InterruptedException e) { e.printStackTrace(); } finally { // Release the permit after the work is done semaphore.release(); System.out.println(name + " released a permit. Available permits: " + semaphore.availablePermits()); } } else { // Inform if the permit could not be acquired System.out.println(name + " could not acquire a permit. Available permits: " + semaphore.availablePermits()); } } } }
Іншими словами, Semaphore корисний, коли потрібно надати обмежений одночасний доступ до певного сегмента коду. Єдиний недолік — це можливість виникнення взаємного блокування (deadlock), якщо потоки блокуються у неправильному порядку.
Тепер перейдемо до наступного механізму синхронізації, який ще простіший у використанні, але буде на 100 відсотків корисним для ваших потреб.
CyclicBarrier
Barrier у Java представлені класом java.util.concurrent.CyclicBarrier. Основні методи CyclicBarrier включають:
Конструктори CyclicBarrier
CyclicBarrier(int parties): Конструктор, який створює бар'єр, що блокує потоки до тих пір, поки не прибуде певна кількість потоків (parties).
Main.java
1CyclicBarrier cyclicBarrier = new CyclicBarrier(10);
CyclicBarrier(int parties, Runnable barrierAction): Конструктор, який створює бар'єр із заданою кількістю учасників та дією (barrierAction), що виконується, коли всі учасники досягають бар'єра.
Main.java
1234567Runnable task = () -> { // This task will be executed when all parties have reached the barrier System.out.println("Hello))"); }; // Create a `CyclicBarrier` for 10 parties with a barrier action CyclicBarrier cyclicBarrier = new CyclicBarrier(10, task);
Методи CyclicBarrier
Основний метод await(), який використовується як бар'єр і не дозволяє потоку рухатися далі, доки всі потоки не досягнуть цього методу. Повертає порядковий номер, що вказує на порядок прибуття учасників.
Main.java
1234567891011121314151617181920212223242526272829303132333435363738394041424344package com.example; import java.util.concurrent.CyclicBarrier; public class Main { public static void main(String[] args) { // Create a `CyclicBarrier` for 3 parties with a barrier action CyclicBarrier barrier = new CyclicBarrier(3, () -> { System.out.println("All parties have reached the barrier. Barrier action executed."); }); // Create and start 3 worker threads for (int i = 1; i <= 3; i++) { new Thread(new Worker(barrier), "Worker-" + i).start(); } } static class Worker implements Runnable { private CyclicBarrier barrier; Worker(CyclicBarrier barrier) { this.barrier = barrier; } @Override public void run() { String name = Thread.currentThread().getName(); System.out.println(name + " is working..."); try { // Simulate work Thread.sleep((int) (Math.random() * 1000)); System.out.println(name + " is waiting at the barrier."); barrier.await(); // Wait at the barrier // This code will execute after all parties have reached the barrier System.out.println(name + " has crossed the barrier."); } catch (Exception e) { e.printStackTrace(); } } } }
Може статися так, що не всі потоки досягнуть бар'єра, і програма зависне. Для цього використовується метод int await(long timeout, TimeUnit unit), який подібний до await(), але з тайм-аутом. Якщо тайм-аут спливає до того, як усі учасники прибудуть, метод генерує виняток TimeoutException.
Також можна дізнатися кількість учасників, необхідних для завершення бар'єра — int getParties(), а також подібний метод int getNumberWaiting(), який повертає кількість учасників, що наразі очікують на бар'єрі.
Main.java
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849package com.example; import java.util.concurrent.CyclicBarrier; public class Main { public static void main(String[] args) { // Create a `CyclicBarrier` for 3 parties with a barrier action CyclicBarrier barrier = new CyclicBarrier(3, () -> { System.out.println("All parties have reached the barrier. Barrier action executed."); }); System.out.println("Total number of parties required to complete the barrier: " + barrier.getParties()); // Create and start 3 worker threads for (int i = 1; i <= 3; i++) { new Thread(new Worker(barrier), "Worker-" + i).start(); } } static class Worker implements Runnable { private CyclicBarrier barrier; Worker(CyclicBarrier barrier) { this.barrier = barrier; } @Override public void run() { String name = Thread.currentThread().getName(); System.out.println(name + " is working..."); try { // Simulate work Thread.sleep((int) (Math.random() * 1000)); System.out.println(name + " is waiting at the barrier."); barrier.await(); // Wait at the barrier // This code will execute after all parties have reached the barrier System.out.println(name + " has crossed the barrier."); } catch (Exception e) { e.printStackTrace(); } // Print the number of participants currently waiting at the barrier System.out.println("Number of participants currently waiting at the barrier: " + barrier.getNumberWaiting()); } } }
Також можливо перевірити, чи бар'єр було зруйновано у випадку, якщо один із потоків був перерваний або закінчився час очікування, використовуючи метод boolean isBroken(). Якщо бар'єр зламано, можна скористатися методом void reset(), який просто відновить бар'єр.
Main.java
12345// Check if the barrier is broken and reset it if necessary if (barrier.isBroken()) { System.out.println("Barrier is broken. Resetting the barrier."); barrier.reset(); }
Слід враховувати, що деякий потік може не досягти бар'єра через помилку або інші причини, і тоді стає очевидно, що бар'єр не пропустить ті потоки, які наразі очікують на бар'єрі.
Дякуємо за ваш відгук!
Запитати АІ
Запитати АІ
Запитайте про що завгодно або спробуйте одне із запропонованих запитань, щоб почати наш чат
Awesome!
Completion rate improved to 3.33
Семафор і Бар'єр
Свайпніть щоб показати меню
У багатопотокових програмах часто виникає потреба контролювати доступ до ресурсів або синхронізувати виконання потоків. Semaphore та Barrier — це високорівневі механізми синхронізації, які допомагають вирішувати ці завдання.
Сьогодні розглянемо кожен із цих механізмів по черзі та зрозуміємо їх відмінності. Почнемо з Semaphore.
Semaphore у Java реалізовані за допомогою класу java.util.concurrent.Semaphore.
Конструктори
Semaphore(int permits): Конструктор, який створює semaphore з певною кількістю дозволів. Дозволи визначають кількість доступів до спільного ресурсу.
Main.java
1Semaphore semaphore = new Semaphore(20);
Semaphore(int permits, boolean fair): Конструктор, що забезпечує вирішення за принципом перший прийшов — перший обслужений.
Main.java
1Semaphore semaphore = new Semaphore(20, true);
Якщо параметр fair встановлено у true, semaphore надає дозволи у порядку першим прийшов — першим отримав (FIFO), що допомагає уникнути голодування потоків. За замовчуванням — false.
Основні методи
Метод acquire() запитує один дозвіл. Якщо дозвіл доступний, він надається негайно; інакше потік блокується до появи дозволу. Після завершення завдання використовується метод release() для звільнення дозволу, повертаючи його до semaphore. Якщо інші потоки очікували на дозвіл, один із них буде розблоковано.
Уявіть собі паркінг з обмеженою кількістю місць. semaphore виконує роль контролера, відстежуючи доступні місця та забороняючи в’їзд, коли паркінг заповнений.
Main.java
1234567891011121314151617181920212223242526272829303132package com.example; import java.util.concurrent.Semaphore; public class Main { private final Semaphore semaphore; public Main(int slots) { semaphore = new Semaphore(slots); } public void parkCar() { try { semaphore.acquire(); // Request a parking spot System.out.println("Car parked. Available slots: " + semaphore.availablePermits()); Thread.sleep(2000); // Simulate parking time } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { semaphore.release(); // Release the parking spot System.out.println("Car left. Available slots: " + semaphore.availablePermits()); } } public static void main(String[] args) { Main parking = new Main(3); // Parking lot with 3 spots for (int i = 0; i < 5; i++) { new Thread(parking::parkCar).start(); } } }
Можна також дізнатися, скільки дозволів наразі доступно в Semaphore, використовуючи метод int availablePermits(). Можна також спробувати отримати дозвіл за допомогою методу boolean tryAcquire(), який повертає true, якщо дозвіл отримано, і false, якщо ні.
Main.java
1234567891011121314151617181920212223242526272829303132333435363738394041package com.example; import java.util.concurrent.Semaphore; public class Main { // Define the maximum number of permits available private static final int MAX_PERMITS = 3; private static Semaphore semaphore = new Semaphore(MAX_PERMITS); public static void main(String[] args) { // Create and start 5 worker threads for (int i = 1; i <= 5; i++) { new Thread(new Worker(), "Worker-" + i).start(); } } static class Worker implements Runnable { @Override public void run() { String name = Thread.currentThread().getName(); System.out.println(name + " trying to acquire a permit..."); // Try to acquire a permit if (semaphore.tryAcquire()) { try { System.out.println(name + " acquired a permit! Available permits: " + semaphore.availablePermits()); Thread.sleep(1000); // Simulate work } catch (InterruptedException e) { e.printStackTrace(); } finally { // Release the permit after the work is done semaphore.release(); System.out.println(name + " released a permit. Available permits: " + semaphore.availablePermits()); } } else { // Inform if the permit could not be acquired System.out.println(name + " could not acquire a permit. Available permits: " + semaphore.availablePermits()); } } } }
Іншими словами, Semaphore корисний, коли потрібно надати обмежений одночасний доступ до певного сегмента коду. Єдиний недолік — це можливість виникнення взаємного блокування (deadlock), якщо потоки блокуються у неправильному порядку.
Тепер перейдемо до наступного механізму синхронізації, який ще простіший у використанні, але буде на 100 відсотків корисним для ваших потреб.
CyclicBarrier
Barrier у Java представлені класом java.util.concurrent.CyclicBarrier. Основні методи CyclicBarrier включають:
Конструктори CyclicBarrier
CyclicBarrier(int parties): Конструктор, який створює бар'єр, що блокує потоки до тих пір, поки не прибуде певна кількість потоків (parties).
Main.java
1CyclicBarrier cyclicBarrier = new CyclicBarrier(10);
CyclicBarrier(int parties, Runnable barrierAction): Конструктор, який створює бар'єр із заданою кількістю учасників та дією (barrierAction), що виконується, коли всі учасники досягають бар'єра.
Main.java
1234567Runnable task = () -> { // This task will be executed when all parties have reached the barrier System.out.println("Hello))"); }; // Create a `CyclicBarrier` for 10 parties with a barrier action CyclicBarrier cyclicBarrier = new CyclicBarrier(10, task);
Методи CyclicBarrier
Основний метод await(), який використовується як бар'єр і не дозволяє потоку рухатися далі, доки всі потоки не досягнуть цього методу. Повертає порядковий номер, що вказує на порядок прибуття учасників.
Main.java
1234567891011121314151617181920212223242526272829303132333435363738394041424344package com.example; import java.util.concurrent.CyclicBarrier; public class Main { public static void main(String[] args) { // Create a `CyclicBarrier` for 3 parties with a barrier action CyclicBarrier barrier = new CyclicBarrier(3, () -> { System.out.println("All parties have reached the barrier. Barrier action executed."); }); // Create and start 3 worker threads for (int i = 1; i <= 3; i++) { new Thread(new Worker(barrier), "Worker-" + i).start(); } } static class Worker implements Runnable { private CyclicBarrier barrier; Worker(CyclicBarrier barrier) { this.barrier = barrier; } @Override public void run() { String name = Thread.currentThread().getName(); System.out.println(name + " is working..."); try { // Simulate work Thread.sleep((int) (Math.random() * 1000)); System.out.println(name + " is waiting at the barrier."); barrier.await(); // Wait at the barrier // This code will execute after all parties have reached the barrier System.out.println(name + " has crossed the barrier."); } catch (Exception e) { e.printStackTrace(); } } } }
Може статися так, що не всі потоки досягнуть бар'єра, і програма зависне. Для цього використовується метод int await(long timeout, TimeUnit unit), який подібний до await(), але з тайм-аутом. Якщо тайм-аут спливає до того, як усі учасники прибудуть, метод генерує виняток TimeoutException.
Також можна дізнатися кількість учасників, необхідних для завершення бар'єра — int getParties(), а також подібний метод int getNumberWaiting(), який повертає кількість учасників, що наразі очікують на бар'єрі.
Main.java
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849package com.example; import java.util.concurrent.CyclicBarrier; public class Main { public static void main(String[] args) { // Create a `CyclicBarrier` for 3 parties with a barrier action CyclicBarrier barrier = new CyclicBarrier(3, () -> { System.out.println("All parties have reached the barrier. Barrier action executed."); }); System.out.println("Total number of parties required to complete the barrier: " + barrier.getParties()); // Create and start 3 worker threads for (int i = 1; i <= 3; i++) { new Thread(new Worker(barrier), "Worker-" + i).start(); } } static class Worker implements Runnable { private CyclicBarrier barrier; Worker(CyclicBarrier barrier) { this.barrier = barrier; } @Override public void run() { String name = Thread.currentThread().getName(); System.out.println(name + " is working..."); try { // Simulate work Thread.sleep((int) (Math.random() * 1000)); System.out.println(name + " is waiting at the barrier."); barrier.await(); // Wait at the barrier // This code will execute after all parties have reached the barrier System.out.println(name + " has crossed the barrier."); } catch (Exception e) { e.printStackTrace(); } // Print the number of participants currently waiting at the barrier System.out.println("Number of participants currently waiting at the barrier: " + barrier.getNumberWaiting()); } } }
Також можливо перевірити, чи бар'єр було зруйновано у випадку, якщо один із потоків був перерваний або закінчився час очікування, використовуючи метод boolean isBroken(). Якщо бар'єр зламано, можна скористатися методом void reset(), який просто відновить бар'єр.
Main.java
12345// Check if the barrier is broken and reset it if necessary if (barrier.isBroken()) { System.out.println("Barrier is broken. Resetting the barrier."); barrier.reset(); }
Слід враховувати, що деякий потік може не досягти бар'єра через помилку або інші причини, і тоді стає очевидно, що бар'єр не пропустить ті потоки, які наразі очікують на бар'єрі.
Дякуємо за ваш відгук!