Guia para a palavra-chave sincronizada em Java
*1. Visão geral *
Este artigo rápido será uma introdução ao uso do bloco synchronized em Java.
Simplificando, em um ambiente com vários threads, uma condição de corrida ocorre quando dois ou mais threads tentam atualizar dados compartilhados mutáveis ao mesmo tempo. Java oferece um mecanismo para evitar condições de corrida, sincronizando o acesso do encadeamento aos dados compartilhados.
Uma parte da lógica marcada com synchronized se torna um bloco sincronizado,* permitindo que apenas um thread seja executado a qualquer momento *.
2. Por que sincronização?
Vamos considerar uma condição típica de corrida em que calculamos a soma e vários threads executam o método _calculate () _:
public class BaeldungSynchronizedMethods {
private int sum = 0;
public void calculate() {
setSum(getSum() + 1);
}
//standard setters and getters
}
E vamos escrever um teste simples:
@Test
public void givenMultiThread_whenNonSyncMethod() {
ExecutorService service = Executors.newFixedThreadPool(3);
BaeldungSynchronizedMethods summation = new BaeldungSynchronizedMethods();
IntStream.range(0, 1000)
.forEach(count -> service.submit(summation::calculate));
service.awaitTermination(1000, TimeUnit.MILLISECONDS);
assertEquals(1000, summation.getSum());
}
Estamos simplesmente usando um ExecutorService com um pool de 3 threads para executar o _calculate () _ 1000 vezes.
Se nós executássemos isso em série, a saída esperada seria 1000, mas nossa execução multithread falha quase todas as vezes com uma saída real inconsistente, por exemplo:
java.lang.AssertionError: expected:<1000> but was:(965)
at org.junit.Assert.fail(Assert.java:88)
at org.junit.Assert.failNotEquals(Assert.java:834)
...
Obviamente, este resultado não é inesperado.
Uma maneira simples de evitar a condição de corrida é tornar a operação segura para threads usando a palavra-chave synchronized.
*3. A palavra-chave Synchronized *
A palavra-chave synchronized pode ser usada em diferentes níveis:
-
Métodos de instância
-
Métodos estáticos *Blocos de código
Quando usamos um bloco synchronized, internamente o Java usa um monitor também conhecido como bloqueio de monitor ou bloqueio intrínseco, para fornecer sincronização. Esses monitores estão ligados a um objeto, portanto, todos os blocos sincronizados do mesmo objeto podem ter apenas um encadeamento executando-os ao mesmo tempo.
====* 3.1 Métodos de instância Synchronized *
Basta adicionar a palavra-chave synchronized na declaração do método para tornar o método sincronizado:
public synchronized void synchronisedCalculate() {
setSum(getSum() + 1);
}
Observe que quando sincronizamos o método, o caso de teste passa, com saída real como 1000:
@Test
public void givenMultiThread_whenMethodSync() {
ExecutorService service = Executors.newFixedThreadPool(3);
SynchronizedMethods method = new SynchronizedMethods();
IntStream.range(0, 1000)
.forEach(count -> service.submit(method::synchronisedCalculate));
service.awaitTermination(1000, TimeUnit.MILLISECONDS);
assertEquals(1000, method.getSum());
}
Os métodos de instância são sincronizados sobre a instância da classe que possui o método. O que significa que apenas um encadeamento por instância da classe pode executar esse método.
====* 3.2 Métodos Synchronized Static *
Os métodos estáticos são sincronizados, assim como os métodos da instância:
public static synchronized void syncStaticCalculate() {
staticSum = staticSum + 1;
}
Esses métodos são synchronized no objeto Class associado à classe e como existe apenas um objeto Class por JVM por classe, apenas um encadeamento pode ser executado dentro de um método static synchronized por classe, independentemente do número de instâncias que ele possui.
Vamos testá-lo:
@Test
public void givenMultiThread_whenStaticSyncMethod() {
ExecutorService service = Executors.newCachedThreadPool();
IntStream.range(0, 1000)
.forEach(count ->
service.submit(BaeldungSynchronizedMethods::syncStaticCalculate));
service.awaitTermination(100, TimeUnit.MILLISECONDS);
assertEquals(1000, BaeldungSynchronizedMethods.staticSum);
}
====* 3.3 Blocos Synchronized Dentro de Métodos *
Às vezes, não queremos sincronizar o método inteiro, mas apenas algumas instruções nele. Isso pode ser alcançado applying sincronizado com um bloco:
public void performSynchrinisedTask() {
synchronized (this) {
setCount(getCount()+1);
}
}
vamos testar a mudança:
@Test
public void givenMultiThread_whenBlockSync() {
ExecutorService service = Executors.newFixedThreadPool(3);
BaeldungSynchronizedBlocks synchronizedBlocks = new BaeldungSynchronizedBlocks();
IntStream.range(0, 1000)
.forEach(count ->
service.submit(synchronizedBlocks::performSynchronisedTask));
service.awaitTermination(100, TimeUnit.MILLISECONDS);
assertEquals(1000, synchronizedBlocks.getCount());
}
Observe que passamos um parâmetro this para o bloco synchronized. Este é o objeto monitor, o código dentro do bloco é sincronizado no objeto monitor. Simplificando, apenas um encadeamento por objeto de monitor pode ser executado dentro desse bloco de código.
Caso o método seja _estatic, _ passaríamos o nome da classe no lugar da referência do objeto. E a classe seria um monitor para sincronização do bloco:
public static void performStaticSyncTask(){
synchronized (SynchronisedBlocks.class) {
setStaticCount(getStaticCount() + 1);
}
}
Vamos testar o bloco dentro do método static:
@Test
public void givenMultiThread_whenStaticSyncBlock() {
ExecutorService service = Executors.newCachedThreadPool();
IntStream.range(0, 1000)
.forEach(count ->
service.submit(BaeldungSynchronizedBlocks::performStaticSyncTask));
service.awaitTermination(100, TimeUnit.MILLISECONDS);
assertEquals(1000, BaeldungSynchronizedBlocks.getStaticCount());
}
===* 5. Conclusão*
Neste artigo rápido, vimos maneiras diferentes de usar a palavra-chave synchronized para obter a sincronização de threads.
Também exploramos como uma condição de corrida pode afetar nosso aplicativo e como a sincronização nos ajuda a evitar isso. Para obter mais informações sobre segurança de encadeamentos usando bloqueios em Java, consulte o link java.util.concurrent.Locks:/java-concurrent-locks [artigo].
O código completo deste tutorial está disponível over no GitHub.