Uma introdução às coleções Java sincronizadas
1. Visão geral
Ocollections framework é um componente-chave do Java. Ele fornece um grande número de interfaces e implementações, o que nos permite criar e manipular diferentes tipos de coleções de maneira direta.
Embora o uso geral de coleções não sincronizadas seja simples, ele também pode se tornar um processo assustador e propenso a erros ao trabalhar em ambientes com vários threads (também conhecido como programação simultânea).
Portanto, a plataforma Java fornece forte suporte para este cenário por meio de diferenteswrappers de sincronização implementados na classeCollections.
Esses wrappers facilitam a criação de visualizações sincronizadas das coleções fornecidas por meio de vários métodos estáticos de fábrica.
Neste tutorial,we’ll take a deep dive into these *static synchronization wrappers. Also, we’ll highlight the difference between synchronized collections and concurrent collections. *
2. O MétodosynchronizedCollection()
O primeiro invólucro de sincronização que abordaremos neste resumo é o métodosynchronizedCollection(). Como o nome sugere,it returns a thread-safe collection backed up by the specified Collection.
Agora, para entender mais claramente como usar esse método, vamos criar um teste de unidade básico:
Collection syncCollection = Collections.synchronizedCollection(new ArrayList<>());
Runnable listOperations = () -> {
syncCollection.addAll(Arrays.asList(1, 2, 3, 4, 5, 6));
};
Thread thread1 = new Thread(listOperations);
Thread thread2 = new Thread(listOperations);
thread1.start();
thread2.start();
thread1.join();
thread2.join();
assertThat(syncCollection.size()).isEqualTo(12);
}
Como mostrado acima, a criação de uma exibição sincronizada da coleção fornecida com esse método é muito simples.
Para demonstrar que o método realmente retorna uma coleção segura para threads, primeiro criamos alguns threads.
Depois disso, injetamos uma instânciaRunnable em seus construtores, na forma de uma expressão lambda. Vamos ter em mente queRunnable é uma interface funcional, então podemos substituí-la por uma expressão lambda.
Por fim, apenas verificamos que cada encadeamento efetivamente adiciona seis elementos à coleção sincronizada, para que seu tamanho final seja doze.
3. O MétodosynchronizedList()
Da mesma forma, semelhante ao métodosynchronizedCollection(), podemos usar o wrappersynchronizedList() para criar umList sincronizado.
Como podemos esperar,the method returns a thread-safe view of the specified List:
List syncList = Collections.synchronizedList(new ArrayList<>());
Sem surpresa, o uso do métodosynchronizedList() parece quase idêntico a sua contraparte de nível superior,synchronizedCollection().
Portanto, como acabamos de fazer no teste de unidade anterior, uma vez que criamos umList sincronizado, podemos gerar vários threads. Depois de fazer isso, vamos usá-los para acessar / manipular oList de destino de maneira thread-safe.
Além disso, se quisermos iterar sobre uma coleção sincronizada e evitar resultados inesperados, devemos fornecer explicitamente nossa própria implementação do loop com segurança para threads. Portanto, poderíamos conseguir isso usando um blocosynchronized:
List syncCollection = Collections.synchronizedList(Arrays.asList("a", "b", "c"));
List uppercasedCollection = new ArrayList<>();
Runnable listOperations = () -> {
synchronized (syncCollection) {
syncCollection.forEach((e) -> {
uppercasedCollection.add(e.toUpperCase());
});
}
};
Em todos os casos em que precisamos iterar sobre uma coleção sincronizada, devemos implementar esse idioma. Isso ocorre porque a iteração em uma coleção sincronizada é realizada através de várias chamadas na coleção. Portanto, eles precisam ser executados como uma única operação atômica.
The use of the synchronized block ensures the atomicity of the operation.
4. O MétodosynchronizedMap()
A classeCollections implementa outro wrapper de sincronização bacana, chamadosynchronizedMap().. Podemos usá-lo para criar facilmente umMap sincronizado.
The method returns a thread-safe view of the supplied Map implementation:
Map syncMap = Collections.synchronizedMap(new HashMap<>());
5. O MétodosynchronizedSortedMap()
Há também uma implementação de contrapartida do métodosynchronizedMap(). É chamado desynchronizedSortedMap(), que podemos usar para criar uma instânciaSortedMap sincronizada:
Map syncSortedMap = Collections.synchronizedSortedMap(new TreeMap<>());
6. O MétodosynchronizedSet()
A seguir, avançando nesta revisão, temos o métodosynchronizedSet(). Como o próprio nome indica, ele nos permite criarSets sincronizados com o mínimo de confusão.
The wrapper returns a thread-safe collection backed by the specified Set:
Set syncSet = Collections.synchronizedSet(new HashSet<>());
7. O MétodosynchronizedSortedSet()
Finalmente, o último wrapper de sincronização que mostraremos aqui ésynchronizedSortedSet().
Semelhante a outras implementações de wrapper que revisamos até agora,the method returns a thread-safe version of the given SortedSet:
SortedSet syncSortedSet = Collections.synchronizedSortedSet(new TreeSet<>());
8. Coleções sincronizadas versus coleções simultâneas
Até este ponto, demos uma olhada mais de perto nos wrappers de sincronização do framework de coleções.
Agora, vamos nos concentrar nas implementações de the differences between synchronized collections and concurrent collections, comoConcurrentHashMapeBlockingQueue.
8.1. Coleções Sincronizadas
Synchronized collections achieve thread-safety through intrinsihttps://docs.oracle.com/javase/tutorial/essential/concurrency/locksync.html[c locking], and the entire collections are locked. O bloqueio intrínseco é implementado por meio de blocos sincronizados dentro dos métodos da coleção agrupada.
Como seria de esperar, as coleções sincronizadas garantem a consistência / integridade dos dados em ambientes multithread. No entanto, eles podem ter uma penalidade no desempenho, pois apenas um único encadeamento pode acessar a coleção por vez (também conhecido como acesso sincronizado).
Para obter um guia detalhado sobre como usar os métodos e blocossynchronized, verifiqueour article no tópico.
8.2. Coleções simultâneas
Concurrent collections (e.g. ConcurrentHashMap), achieve thread-safety by dividing their data into segments. Em umConcurrentHashMap, por exemplo, diferentes threads podem adquirir bloqueios em cada segmento, portanto, vários threads podem acessarMap ao mesmo tempo (a.k.a. acesso simultâneo).
As coleções simultâneas sãomuch more performant than synchronized collections, devido às vantagens inerentes do acesso ao thread simultâneo.
Portanto, a escolha de qual tipo de coleção thread-safe usar depende dos requisitos de cada caso de uso e deve ser avaliada de acordo.
9. Conclusão
In this article, we took an in-depth look at the set of synchronization wrappers implemented within the Collections class.
Além disso, destacamos as diferenças entre coleções sincronizadas e simultâneas e também analisamos as abordagens implementadas para garantir a segurança do encadeamento.
Como de costume, todos os exemplos de código mostrados neste artigo estão disponíveis emGitHub.