6. O que é Cancarenzi?
Simultaneidade é uma biblioteca de classes em Java que contém classes especiais otimizadas para trabalhar em vários threads. Essas classes são coletadas em um pacotejava.util.concurrent
. Eles podem ser divididos esquematicamente de acordo com a funcionalidade da seguinte forma: Coleções Simultâneas - um conjunto de coleções que funcionam de forma mais eficiente em um ambiente multithread do que as coleções universais padrão do java.util
pacote. Em vez de um wrapper básico Collections.synchronizedList
com bloqueio de acesso a toda a coleção, são usados bloqueios em segmentos de dados ou o trabalho é otimizado para leitura paralela de dados usando algoritmos sem espera. Filas - filas sem bloqueio e bloqueio com suporte multi-threading. As filas sem bloqueio são projetadas para velocidade e operação sem bloquear threads. As filas de bloqueio são usadas quando você precisa “desacelerar” os threads “Produtor” ou “Consumidor” se algumas condições não forem atendidas, por exemplo, a fila está vazia ou transbordada, ou não há “Consumidor” livre. Sincronizadores são utilitários auxiliares para sincronização de threads. Eles são uma arma poderosa na computação “paralela”. Executores – contém excelentes frameworks para criação de pools de threads, agendamento de tarefas assíncronas e obtenção de resultados. Locks - representa mecanismos de sincronização de threads alternativos e mais flexíveis em comparação aos synchronized
básicos . Atomics - classes com suporte para operações atômicas em primitivas e referências. Fonte:wait
notify
notifyAll
7. Quais aulas de Kankarensi você conhece?
A resposta a esta pergunta está perfeitamente exposta neste artigo . Não vejo sentido em reimprimir tudo aqui, então darei descrições apenas das classes com as quais tive a honra de me familiarizar brevemente. ConcurrentHashMap<K, V> - Ao contrárioHashtable
dos blocos e synhronized
em HashMap
, os dados são apresentados na forma de segmentos, divididos em hashes de chaves. Como resultado, os dados são acessados por segmentos e não por um único objeto. Além disso, os iteradores representam dados de um período de tempo específico e não lançam ConcurrentModificationException
. AtomicBoolean, AtomicInteger, AtomicLong, AtomicIntegerArray, AtomicLongArray - E se em uma classe você precisar sincronizar o acesso a uma variável simples do tipo int
? Você pode usar construções com synchronized
e, ao usar operações atômicas set/get
, volatile
. Mas você pode fazer ainda melhor usando novas classes Atomic*
. Devido ao uso do CAS, as operações com essas classes são mais rápidas do que se sincronizadas via synchronized/volatile
. Além disso, existem métodos para adição atômica por um determinado valor, bem como incremento/decremento.
8. Como funciona a classe ConcurrentHashMap?
No momento de sua introdução,ConcurrentHashMap
os desenvolvedores Java precisavam da seguinte implementação de mapa hash:
- Segurança do fio
- Sem bloqueios em toda a tabela ao acessá-la
- É desejável que não haja bloqueios de tabela ao realizar uma operação de leitura
ConcurrentHashMap
são as seguintes:
-
Elementos do mapa
Ao contrário dos elementos
HashMap
,Entry
inConcurrentHashMap
são declarados comovolatile
. Esta é uma característica importante, também devido às alterações no JMM .static final class HashEntry<K, V> { final K key; final int hash; volatile V value; final HashEntry<K, V> next; HashEntry(K key, int hash, HashEntry<K, V> next, V value) { this .key = key; this .hash = hash; this .next = next; this .value = value; } @SuppressWarnings("unchecked") static final <K, V> HashEntry<K, V>[] newArray(int i) { return new HashEntry[i]; } }
-
Função hash
ConcurrentHashMap
uma função de hash aprimorada também é usada.Deixe-me lembrá-lo de como era no
HashMap
JDK 1.2:static int hash(int h) { h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); }
Versão do ConcurrentHashMap JDK 1.5:
private static int hash(int h) { h += (h << 15) ^ 0xffffcd7d; h ^= (h >>> 10); h += (h << 3); h ^= (h >>> 6); h += (h << 2) + (h << 14); return h ^ (h >>> 16); }
Por que é necessário tornar a função hash mais complexa? As tabelas em um mapa hash têm comprimento determinado por uma potência de dois. Para códigos hash cujas representações binárias não diferem em posição baixa e alta, teremos colisões. Aumentar a complexidade da função hash apenas resolve esse problema, reduzindo a probabilidade de colisões no mapa.
-
Segmentos
O mapa é dividido em N segmentos diferentes (16 por padrão, o valor máximo pode ser 16 bits e é uma potência de dois). Cada segmento é uma tabela thread-safe de elementos do mapa. Aumentar o número de segmentos incentivará as operações de modificação a abranger vários segmentos, reduzindo a probabilidade de bloqueio em tempo de execução.
-
Nível de simultaneidade
Este parâmetro afeta o uso do cartão de memória e o número de segmentos no cartão.
O número de segmentos será escolhido como a potência mais próxima de dois maior que concurrencyLevel. A redução do concurrencyLevel aumenta a probabilidade de os threads bloquearem os segmentos do mapa durante a gravação. A superestimação do indicador leva ao uso ineficiente da memória. Se apenas um thread modificar o mapa e o restante for lido, é recomendado usar o valor 1.
-
Total
Então, as principais vantagens e recursos de implementação
ConcurrentHashMap
:hashmap
O mapa possui uma interface de interação semelhante a- As operações de leitura não requerem bloqueios e são executadas em paralelo
- Muitas vezes, as operações de gravação também podem ser executadas em paralelo sem bloqueio
- Ao criar, é indicado o necessário
concurrencyLevel
, determinado pela leitura e gravação de estatísticas - Os elementos do mapa têm um valor
value
declarado comovolatile
9. O que é a classe Lock?
Para controlar o acesso a um recurso compartilhado, podemos usar bloqueios como alternativa ao operador sincronizado. A funcionalidade de bloqueio é empacotada emjava.util.concurrent.locks
. Primeiro, o thread tenta acessar o recurso compartilhado. Se estiver livre, um cadeado será colocado no fio. Assim que o trabalho for concluído, o bloqueio do recurso compartilhado é liberado. Se o recurso não estiver livre e um bloqueio já estiver colocado nele, o thread aguardará até que esse bloqueio seja liberado. As classes Lock implementam uma interface Lock
que define os seguintes métodos:
void lock():
espera até que o bloqueio seja adquiridoboolean tryLock():
tenta adquirir um bloqueio; se o bloqueio for obtido, ele retorna true . Se o bloqueio não for adquirido, ele retornará false . Ao contrário do método,lock()
ele não espera para adquirir um bloqueio se este não estiver disponívelvoid unlock():
remove o bloqueioCondition newCondition():
retorna o objetoCondition
que está associado ao bloqueio atual
lock()
, e após terminar de trabalhar com os recursos compartilhados chama-se o método unlock()
, que libera o bloqueio. O objeto Condition
permite gerenciar o bloqueio. Via de regra, para trabalhar com bloqueios, é utilizada uma classe ReentrantLock
do pacote , java.util.concurrent.locks.
que implementa a interface Lock
. Vejamos como usar a API Java Lock usando um pequeno programa como exemplo: Então, digamos que temos uma classe Resource
com alguns métodos thread-safe e métodos onde a segurança de thread não é necessária.
public class Resource {
public void doSomething(){
// пусть здесь происходит работа с базой данных
}
public void doLogging(){
// потокобезопасность для логгирования нам не требуется
}
}
Agora vamos pegar uma classe que implementa a interface Runnable
e usa métodos de classe Resource
.
public class SynchronizedLockExample implements Runnable{
// экземпляр класса Resource для работы с методами
private Resource resource;
public SynchronizedLockExample(Resource r){
this.resource = r;
}
@Override
public void run() {
synchronized (resource) {
resource.doSomething();
}
resource.doLogging();
}
}
Agora vamos reescrever o programa acima usando a API Lock em vez do synchronized
.
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
// класс для работы с Lock API. Переписан с приведенной выше программы,
// но уже без использования ключевого слова synchronized
public class ConcurrencyLockExample implements Runnable{
private Resource resource;
private Lock lock;
public ConcurrencyLockExample(Resource r){
this.resource = r;
this.lock = new ReentrantLock();
}
@Override
public void run() {
try {
// лочим на 10 секунд
if(lock.tryLock(10, TimeUnit.SECONDS)){
resource.doSomething();
}
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
//убираем лок
lock.unlock();
}
// Для логгирования не требуется потокобезопасность
resource.doLogging();
}
}
Como você pode ver no programa, usamos o método tryLock()
para garantir que o thread espere apenas um determinado período de tempo. Se não obtiver um bloqueio no objeto, ele simplesmente efetua login e sai. Outro ponto importante. Você deve usar um bloco try-finally
para garantir que o bloqueio será liberado mesmo se o método doSomething()
lançar uma exceção. Fontes:
11. O que é um mutex?
Um mutex é um objeto especial para sincronizar threads/processos. Pode levar dois estados - ocupado e livre. Para simplificar, um mutex é uma variável booleana que assume dois valores: ocupado (verdadeiro) e livre (falso). Quando um thread deseja propriedade exclusiva de um objeto, ele marca seu mutex como ocupado e, quando termina de trabalhar com ele, marca seu mutex como livre. Um mutex é anexado a cada objeto em Java. Somente a máquina Java tem acesso direto ao mutex. Está escondido do programador.12. O que é um monitor?
Um monitor é um mecanismo especial (um pedaço de código) - um complemento sobre o mutex, que garante o correto funcionamento dele. Afinal, não basta marcar que o objeto está ocupado; devemos também garantir que outras threads não tentem utilizar o objeto ocupado. Em Java, o monitor é implementado usando a palavra-chavesynchronized
. Quando escrevemos um bloco sincronizado, o compilador Java o substitui por três trechos de código:
- No início do bloco
synchronized
, é adicionado um código que marca o mutex como ocupado. - No final do bloco,
synchronized
é adicionado um código que marca o mutex como livre. - Antes do bloco
synchronized
é adicionado um código que verifica se o mutex está ocupado, então a thread deve aguardar sua liberação.
GO TO FULL VERSION