Como uma ferramenta para compartilhar dados simultaneamente e garantir consistência, os bloqueios têm múltiplas implementações na plataforma JAVA (como sincronizado e ReentrantLock, etc.). Esses bloqueios já escritos proporcionam comodidade para o nosso desenvolvimento, mas a natureza específica e o tipo dos bloqueios raramente são mencionados. Esta série de artigos analisará nomes e características comuns de bloqueios em JAVA para responder às suas perguntas.
1. Bloqueio giratório
Os bloqueios de rotação são implementados permitindo que o thread atual seja executado continuamente dentro do corpo do loop. Somente quando as condições do loop são alteradas por outros threads a seção crítica pode ser inserida. Copie o código da seguinte maneira:
classe pública SpinLock {
private AtomicReference<Thread> sign =new AtomicReference<>();
bloqueio de vazio público(){
Tópico atual = Thread.currentThread();
while(!sign .compareAndSet(null, atual)){
}
}
desbloqueio público vazio (){
Tópico atual = Thread.currentThread();
assinar .compareAndSet(atual, nulo);
}
}
Usando operações atômicas CAS, a função lock define o proprietário para o thread atual e prevê que o valor original está vazio. A função de desbloqueio define o proprietário como nulo e o valor previsto é o thread atual.
Quando um segundo thread chama a operação de bloqueio, porque o valor do proprietário não está vazio, o loop é executado até que o primeiro thread chame a função de desbloqueio para definir o proprietário como nulo e o segundo thread possa entrar na seção crítica.
Como o spin lock apenas mantém o thread atual executando o corpo do loop sem alterar o estado do thread, a velocidade de resposta é mais rápida. Mas quando o número de threads continua a aumentar, o desempenho cai significativamente porque cada thread precisa ser executado e ocupa tempo de CPU. Se a competição de threads não for intensa e o bloqueio for mantido por um período de tempo. Adequado para uso com bloqueios giratórios.
Nota: Este exemplo é um bloqueio injusto. A ordem de obtenção do bloqueio não será baseada na ordem de entrada do bloqueio.
2. Outros tipos de bloqueios giratórios
Falamos sobre spin locks acima. Existem três formas de bloqueio comuns em spin locks: TicketLock, CLHlock e MCSlock.
O bloqueio de tickets resolve principalmente o problema da sequência de acesso. O principal problema está na CPU multi-core:
Copie o código do código da seguinte forma:
pacote com.alipay.titan.dcc.dal.entity;
importar java.util.concurrent.atomic.AtomicInteger;
classe pública TicketLock {
private AtomicInteger serviceNum = new AtomicInteger();
private AtomicInteger ticketNum = new AtomicInteger();
private static final ThreadLocal<Integer> LOCAL = new ThreadLocal<Integer>();
bloqueio de vazio público() {
int meuticket = ticketNum.getAndIncrement();
LOCAL.set(meuticket);
while (meuticket! = serviceNum.get()) {
}
}
desbloqueio vazio público() {
int meuticket = LOCAL.get();
serviceNum.compareAndSet(meuticket, meuticket + 1);
}
}
Um número de serviço serviceNum deve ser consultado sempre, o que afeta o desempenho (ele deve ser lido da memória principal e outras CPUs devem ser impedidas de modificá-lo).
CLHLock e MCSLock são dois tipos semelhantes de fair locks, classificados na forma de uma lista vinculada.
Copie o código do código da seguinte forma:
importar java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
classe pública CLHLock {
classe estática pública CLHNode {
privado volátil booleano isLocked = verdadeiro;
}
@SuppressWarnings("não utilizado")
cauda CLHNode volátil privada;
private static final ThreadLocal<CLHNode> LOCAL = new ThreadLocal<CLHNode>();
final estático privado AtomicReferenceFieldUpdater<CLHLock, CLHNode> UPDATER = AtomicReferenceFieldUpdater.newUpdater(CLHLock.class,
CLHNode.class, "cauda");
bloqueio de vazio público() {
Nó CLHNode = new CLHNode();
LOCAL.set(nó);
CLHNode preNode = UPDATER.getAndSet(este, nó);
if (préNode! = null) {
enquanto (preNode.isLocked) {
}
préNode = null;
LOCAL.set(nó);
}
}
desbloqueio vazio público() {
CLHNode node = LOCAL.get();
if (!UPDATER.compareAndSet(this, node, null)) {
node.isLocked = falso;
}
nó = nulo;
}
}
CLHlock consulta continuamente variáveis precursoras, tornando-o inadequado para uso na arquitetura NUMA (nesta arquitetura, cada thread é distribuído em uma área de memória física diferente)
MCSLock percorre os nós de variáveis locais. Não há problema com CLHlock.
Copie o código do código da seguinte forma:
importar java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
classe pública MCSLock {
classe estática pública MCSNode {
MCSNode volátil a seguir;
volátil booleano isLocked = verdadeiro;
}
private static final ThreadLocal<MCSNode> NODE = new ThreadLocal<MCSNode>();
@SuppressWarnings("não utilizado")
fila MCSNode volátil privada;
final estático privado AtomicReferenceFieldUpdater<MCSLock, MCSNode> UPDATER = AtomicReferenceFieldUpdater.newUpdater(MCSLock.class,
MCSNode.class, "fila");
bloqueio de vazio público() {
MCSNode currentNode = new MCSNode();
NODE.set(nó atual);
MCSNode preNode = UPDATER.getAndSet(this, currentNode);
if (préNode! = null) {
preNode.next = currentNode;
enquanto (currentNode.isLocked) {
}
}
}
desbloqueio vazio público() {
MCSNode currentNode = NODE.get();
if (currentNode.next == nulo) {
if (UPDATER.compareAndSet(this, currentNode, null)) {
} outro {
while (currentNode.next == nulo) {
}
}
} outro {
currentNode.next.isLocked = falso;
currentNode.next = null;
}
}
}
Do ponto de vista do código, o CLH é mais simples que o MCS.
A fila CLH é uma fila implícita e não possui atributos de nó sucessor real.
A fila MCS é uma fila explícita com atributos reais do nó sucessor.
O bloqueio padrão usado internamente pelo JUC ReentrantLock é o bloqueio CLH (há muitas melhorias, como a substituição dos bloqueios de rotação por bloqueios de bloqueio, etc.).
(O texto completo termina)