Semaphore e Barreira
Em programas multithread, muitas vezes é necessário controlar o acesso a recursos ou sincronizar a execução de threads. Semaphore e Barrier são mecanismos de sincronização de alto nível que auxiliam na resolução desses desafios.
Hoje, vamos explorar cada um desses mecanismos em sequência e compreender suas diferenças. Vamos começar com o Semaphore.
Semaphore em Java são implementados por meio da classe java.util.concurrent.Semaphore.
Construtores
Semaphore(int permits): Construtor que cria um semaphore com um determinado número de permissões. As permissões representam o número de acessos ao recurso compartilhado.
Main.java
1Semaphore semaphore = new Semaphore(20);
Semaphore(int permits, boolean fair): Construtor que fornece resolução ordem de chegada, primeiro a entrar, primeiro a ser atendido.
Main.java
1Semaphore semaphore = new Semaphore(20, true);
Se fair estiver definido como true, o semaphore concederá permissões na ordem primeiro a entrar, primeiro a sair (FIFO), o que pode ajudar a evitar a inanição. Padrão - false.
Métodos Principais
O método acquire() solicita uma única permissão. Se uma permissão estiver disponível, ela é concedida imediatamente; caso contrário, a thread é bloqueada até que uma permissão esteja disponível. Após a conclusão de uma tarefa, o método release() é utilizado para liberar a permissão, devolvendo-a ao semaphore. Se outras threads estavam aguardando uma permissão, uma delas será desbloqueada.
Imagine um estacionamento com um número limitado de vagas. O semaphore funciona como um controlador, monitorando as vagas disponíveis e negando o acesso quando o estacionamento está cheio.
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(); } } }
Também é possível verificar quantas permissões estão atualmente disponíveis em Semaphore utilizando o método int availablePermits(). É possível tentar obter uma permissão utilizando o método boolean tryAcquire(), que retorna true se uma permissão foi obtida e false caso contrário.
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()); } } } }
Em outras palavras, um Semaphore é útil quando é necessário fornecer acesso limitado e simultâneo a um segmento específico de código. A única desvantagem é o potencial para um deadlock caso as threads sejam bloqueadas em ordem incorreta.
Agora, prossiga para o próximo mecanismo de sincronização, que é ainda mais simples de utilizar e será 100 por cento valioso para suas necessidades.
CyclicBarrier
Barrier em Java é representado pela classe java.util.concurrent.CyclicBarrier. Os principais métodos de CyclicBarrier incluem:
Construtores CyclicBarrier
CyclicBarrier(int parties): Construtor que cria uma barreira que bloqueia threads até que um determinado número de threads (parties) chegue.
Main.java
1CyclicBarrier cyclicBarrier = new CyclicBarrier(10);
CyclicBarrier(int parties, Runnable barrierAction): Construtor que cria uma barreira com um número definido de participantes e uma ação (barrierAction) que é executada quando todos os participantes chegam à barreira.
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);
Métodos CyclicBarrier
O método principal await() é utilizado como uma barreira e não permite que a thread prossiga até que todas as threads alcancem este método. Retorna um número de sequência indicando a ordem de chegada dos participantes.
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(); } } } }
Pode ocorrer que nem todas as threads alcancem a barreira e o programa fique travado. Para esse propósito, utiliza-se o método int await(long timeout, TimeUnit unit), que é semelhante ao await(), mas com tempo limite. Se o tempo limite expirar antes que todos os participantes cheguem, o método gera uma exceção TimeoutException.
Também é possível verificar o número de participantes necessários para completar a barreira com int getParties() e seu método semelhante int getNumberWaiting(), que retorna o número de participantes atualmente aguardando na barreira.
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()); } } }
Também é possível verificar se a barreira foi destruída caso uma das threads seja interrompida ou o tempo limite de espera expire, utilizando o método boolean isBroken(). Se ela tiver sido quebrada, pode-se utilizar o método void reset(), que simplesmente restaura a barreira.
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(); }
Deve-se levar em consideração que algum fluxo pode não alcançar a barreira devido a um erro ou outro motivo, e então fica claro que a barreira não permitirá que os fluxos que estão aguardando na barreira prossigam.
Obrigado pelo seu feedback!
Pergunte à IA
Pergunte à IA
Pergunte o que quiser ou experimente uma das perguntas sugeridas para iniciar nosso bate-papo
Awesome!
Completion rate improved to 3.33
Semaphore e Barreira
Deslize para mostrar o menu
Em programas multithread, muitas vezes é necessário controlar o acesso a recursos ou sincronizar a execução de threads. Semaphore e Barrier são mecanismos de sincronização de alto nível que auxiliam na resolução desses desafios.
Hoje, vamos explorar cada um desses mecanismos em sequência e compreender suas diferenças. Vamos começar com o Semaphore.
Semaphore em Java são implementados por meio da classe java.util.concurrent.Semaphore.
Construtores
Semaphore(int permits): Construtor que cria um semaphore com um determinado número de permissões. As permissões representam o número de acessos ao recurso compartilhado.
Main.java
1Semaphore semaphore = new Semaphore(20);
Semaphore(int permits, boolean fair): Construtor que fornece resolução ordem de chegada, primeiro a entrar, primeiro a ser atendido.
Main.java
1Semaphore semaphore = new Semaphore(20, true);
Se fair estiver definido como true, o semaphore concederá permissões na ordem primeiro a entrar, primeiro a sair (FIFO), o que pode ajudar a evitar a inanição. Padrão - false.
Métodos Principais
O método acquire() solicita uma única permissão. Se uma permissão estiver disponível, ela é concedida imediatamente; caso contrário, a thread é bloqueada até que uma permissão esteja disponível. Após a conclusão de uma tarefa, o método release() é utilizado para liberar a permissão, devolvendo-a ao semaphore. Se outras threads estavam aguardando uma permissão, uma delas será desbloqueada.
Imagine um estacionamento com um número limitado de vagas. O semaphore funciona como um controlador, monitorando as vagas disponíveis e negando o acesso quando o estacionamento está cheio.
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(); } } }
Também é possível verificar quantas permissões estão atualmente disponíveis em Semaphore utilizando o método int availablePermits(). É possível tentar obter uma permissão utilizando o método boolean tryAcquire(), que retorna true se uma permissão foi obtida e false caso contrário.
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()); } } } }
Em outras palavras, um Semaphore é útil quando é necessário fornecer acesso limitado e simultâneo a um segmento específico de código. A única desvantagem é o potencial para um deadlock caso as threads sejam bloqueadas em ordem incorreta.
Agora, prossiga para o próximo mecanismo de sincronização, que é ainda mais simples de utilizar e será 100 por cento valioso para suas necessidades.
CyclicBarrier
Barrier em Java é representado pela classe java.util.concurrent.CyclicBarrier. Os principais métodos de CyclicBarrier incluem:
Construtores CyclicBarrier
CyclicBarrier(int parties): Construtor que cria uma barreira que bloqueia threads até que um determinado número de threads (parties) chegue.
Main.java
1CyclicBarrier cyclicBarrier = new CyclicBarrier(10);
CyclicBarrier(int parties, Runnable barrierAction): Construtor que cria uma barreira com um número definido de participantes e uma ação (barrierAction) que é executada quando todos os participantes chegam à barreira.
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);
Métodos CyclicBarrier
O método principal await() é utilizado como uma barreira e não permite que a thread prossiga até que todas as threads alcancem este método. Retorna um número de sequência indicando a ordem de chegada dos participantes.
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(); } } } }
Pode ocorrer que nem todas as threads alcancem a barreira e o programa fique travado. Para esse propósito, utiliza-se o método int await(long timeout, TimeUnit unit), que é semelhante ao await(), mas com tempo limite. Se o tempo limite expirar antes que todos os participantes cheguem, o método gera uma exceção TimeoutException.
Também é possível verificar o número de participantes necessários para completar a barreira com int getParties() e seu método semelhante int getNumberWaiting(), que retorna o número de participantes atualmente aguardando na barreira.
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()); } } }
Também é possível verificar se a barreira foi destruída caso uma das threads seja interrompida ou o tempo limite de espera expire, utilizando o método boolean isBroken(). Se ela tiver sido quebrada, pode-se utilizar o método void reset(), que simplesmente restaura a barreira.
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(); }
Deve-se levar em consideração que algum fluxo pode não alcançar a barreira devido a um erro ou outro motivo, e então fica claro que a barreira não permitirá que os fluxos que estão aguardando na barreira prossigam.
Obrigado pelo seu feedback!