Quais são os bloqueios em java
Não pude responder a essa pergunta depois de ler <Java Concorrente Programação>, que mostra que não entendo o suficiente sobre o conceito de bloqueios. Então, olhei pelo conteúdo do livro novamente e de repente senti como se eu abri minha testa. Parece que a melhor maneira de aprender é aprender com problemas e resolvê -los.
Em Java, existem dois tipos principais de bloqueios: bloqueio interno sincronizado e exibir bloqueio java.util.concurrent.locks.lock. Mas se você pensar com cuidado, parece que o resumo não está correto. Deve ser uma série de bloqueios implementados por bloqueios internos de Java e simultâneos.
Por que isso diz, porque em Java tudo é um objeto, e Java tem uma trava embutida em cada objeto, que também pode ser chamado de bloqueio de objeto/bloqueio interno. A operação de bloqueio relevante é concluída por meio de sincronizado.
Devido às falhas na implementação de sincronizados e na complexidade de cenários simultâneos, alguém desenvolveu uma trava explícita e essas bloqueios são derivadas de java.util.concurrent.locks.locks. Obviamente, ele foi incorporado às versões JDK1.5 e posteriores.
sincronizado
Primeiro, vamos dar uma olhada no sincronizado que é usado com mais frequência. Também é usado no meu trabalho diário. O sincronizado é usado para fornecer um mecanismo de bloqueio para um determinado bloco de código. Ele terá implicitamente um bloqueio nos objetos Java. Este bloqueio é chamado de bloqueios intrínsecos ou de monitor. O thread adquire automaticamente esse bloqueio antes de inserir o bloco protegido por sincronizado, até que o bloqueio seja liberado automaticamente após a conclusão do código (ou também pode ser uma exceção). Os bloqueios embutidos são mutuamente exclusivos. Uma fechadura só pode ser mantida por um fio ao mesmo tempo, o que também levará a vários threads, e os fios atrás da fechadura serão bloqueados após serem mantidos. Isso permite a segurança da linha do código para garantir a atomicidade.
Reentro
Como os bloqueios embutidos do Java são mutuamente exclusivos e os threads subsequentes causarão bloqueio, o que acontece se a rosca que mantém a trava entrar novamente ao tentar obter a trava? Por exemplo, uma das seguintes situações:
public class Baseclass {public sincronizado void do () {System.out.println ("é base"); }} classe pública SONClass estende a base de Baseclass {public sincronizada void Do () {System.out.println ("Is Son"); super.do (); }} SONCLASS SON = new SONCLASS (); SON.DO ();Neste momento, o método DO da classe derivada primeiro manterá o bloqueio uma vez e depois entrará no bloqueio novamente e o segurará ao chamar super.do (). Se o bloqueio for mutuamente exclusivo, deve ser um impasse neste momento.
Mas o resultado não é o caso, porque o bloqueio interno tem a característica do reentrante, ou seja, o bloqueio implementa um mecanismo reentrante, gerenciamento de contagem de referência. Quando a rosca 1 mantém a trava do objeto A, a referência ao bloqueio A será calculada adicionando 1. Em seguida, quando o Thread 1 adquire o bloqueio de A novamente, o encadeamento 1 ainda mantém o bloqueio A, o cálculo adicionará 1. É claro que, toda vez que você sair do bloco de sincronização, será reduzido em 1 até 0.
Algumas características de sincronizado
Como modificar o código
Método de modificação
public class Baseclass {public sincronizado void do () {System.out.println ("é base"); }}Isso significa travar diretamente um método e você precisa obter um bloqueio ao inserir esse bloco de método.
Modificar blocos de código
classe pública Baseclass {Private Static Object Lock = new Object (); public void Do () {Synchronized (Lock) {System.out.println ("Is Base"); }}}Aqui, o alcance do bloqueio é reduzido a alguns blocos de código no método, o que melhora a flexibilidade do bloqueio. Afinal, o controle de granularidade da trava também é um problema fundamental para o bloqueio.
Tipo de trava de objeto
Muitas vezes vejo que alguns códigos usam sincronizados em termos especiais e observe o seguinte código:
classe pública Baseclass {Private Static Object Lock = new Object (); public void Do () {Synchronized (Lock) {}} public sincronizado void Dovoid () {} public sincronizado estático void DostaticVoid () {} public static void DostaticVoid () {Synchronized (Baseclass.class) {}}}}}}}}}}}}Existem quatro situações aqui: modificando o bloco de código, modificando o método, modificando o método estático e modificando o objeto de classe da base de base. Então, quais são as diferenças nessas situações?
Modificar blocos de código
Nesse caso, criamos um bloqueio de objeto, usando (bloqueio) sincronizado no código, o que significa usar o bloqueio interno do objeto. Nesse caso, o controle de bloqueio é entregue a um objeto. Claro que há outra maneira de fazer isso:
public void Do () {sincronizado (this) {System.out.println ("é base"); }}Usando isso significa o bloqueio do objeto atual. A chave do bloqueio embutida também é mencionada aqui. Eu forneço um bloqueio para proteger este código. Não importa qual fio venha, ele enfrentará a mesma fechadura.
Método para modificar objetos
Qual é a situação com essa modificação direta? De fato, é semelhante à modificação dos blocos de código, exceto que esse é o bloqueio do objeto atual por padrão. Dessa forma, é relativamente simples e claro escrever o código. Como mencionado anteriormente, a diferença entre os blocos de código de modificação é principalmente a diferença entre o controle da granularidade.
Modificar métodos estáticos
Existe algo diferente sobre os métodos estáticos? É realmente diferente. O bloqueio adquirido neste momento não é mais isso, e a classe apontada por esse objeto é o bloqueio da classe. Como as informações da classe no Java serão carregadas na área constante do método, o global é único. Na verdade, isso fornece um bloqueio global.
Objeto de classe da classe modificada
Essa situação é realmente bastante semelhante ao modificar métodos estáticos, mas ainda é o mesmo motivo. Este método pode fornecer granularidade de controle mais flexível.
resumo
Através da análise e compreensão dessas situações, podemos realmente ver que o principal conceito central de bloqueio interno é fornecer uma peça de código com um bloqueio que pode ser usado para mutuamente exclusivo e desempenha uma função semelhante a um switch.
O Java também fornece algumas implementações para bloqueios internos. O principal recurso é que o Java são todos objetos e cada objeto tem um bloqueio, para que você possa escolher qual bloqueio usar de acordo com a situação.
java.util.concurrent.locks.lock
Eu olhei sincronizada anteriormente. Na maioria dos casos, é quase o suficiente. No entanto, o sistema está se tornando cada vez mais complexo na programação simultânea; portanto, sempre existem muitos cenários em que o processamento sincronizado é mais difícil. Ou, conforme declarado em <Java Concorrente Programação>, os bloqueios em simultâneos são um complemento aos bloqueios internos, fornecendo recursos mais avançados.
Análise simples de java.util.concurrent.locks.lock
Essa interface abstrai a operação principal da trava e, portanto, permite bloqueios derivados da trava para ter essas características básicas: incondicional, ciclável, cronometrável, interrompível. Além disso, as operações de bloqueio e desbloqueio são executadas explicitamente. Aqui está o seu código:
interface pública Lock {void Lock (); Void LOCKInterruptível () lança interruptedException; Boolean Trylock (); BOOLEAN TRYLOCK (Long Time, Timeunit) lança interruptedException; vazio desbloqueio (); Condição newcondition ();} Reentrantlock
Reentrantlock é uma trava reentrante, mesmo o nome é tão explícito. O ReentrantLock fornece semântica semelhante à sincronizada, mas o ReentrantLock deve ser chamado explicitamente, como:
classe pública Baseclass {private Lock = new ReentrantLock (); public void Do () {Lock.lock (); tente {// ..} finalmente {Lock.unlock (); }}}Esse método é bastante claro para a leitura de código, mas há um problema, ou seja, se você esquecer de adicionar, tente finalmente ou esquecer de escrever Lock.unlock (), fará com que o bloqueio não seja liberado, o que pode levar a alguns deadlocks. Não há risco de sincronizado.
Trylock
O ReentrantLock implementa a interface de bloqueio, por isso naturalmente possui seus recursos, incluindo o TryLock. Trylock é tentar adquirir a fechadura. Se o bloqueio tiver sido ocupado por outros threads, ele retornará imediatamente false. Caso contrário, deve ser ocupado e retornar verdadeiro, o que significa que a trava foi obtida.
Outro método Trylock contém parâmetros. A função desse método é especificar um tempo, o que significa que você continua tentando obter a trava durante esse período e desistir se o tempo não tiver sido obtido.
Como o Trylock nem sempre bloqueia e aguarda bloqueios, pode evitar mais a ocorrência de impasse.
LOCK INTERRUPBILIÁRIO
O Lock é interruptivelmente responde às interrupções quando os threads adquirem bloqueios. Se uma interrupção for detectada, uma exceção de interrupção será lançada pelo código da camada superior. Nesse caso, é fornecido um mecanismo de saída para uma trava redonda. Para entender melhor a operação de bloqueio interrompível, uma demonstração foi escrita para entendê -la.
pacote com.test; importar java.util.date; importar java.util.concurrent.locks.reentrantlock; classe pública testlock interruptivelmente {estática reentrantlock bloqueio = new reentrantlock (); public static void main (string [] args) {thread Thread1 = new Thread (new Runnable () {@Override public void run () {try {DOPRINT ("Thread 1 Get. Thread Thread2 = new Thread (new Runnable () {@Override public void run () {try {DOPRINT ("Thread 2 Get Lock."); DO123 (); DOPRINT ("Thread 2 end.");} Catch (interruptException e) {DOPRINT ("2 2 Is interrompido."); Thread1.setName ("Thread1"); Thread2.SetName ("Thread2"); Thread1.start (); tente {thread.sleep (100); // Aguarde um pouco para fazer o thread1 executar na frente do thread2} catch (interruptedException e) {e.printStacktrace (); } thread2.start (); } private estático void do123 () lança interruptedException {Lock.lockInterruptível (); DOPRINT (Thread.currentThread (). getName () + "está bloqueado."); tente {doprint (thread.currentThread (). getName () + "Dosoming1 ...."); Thread.sleep (5000); // usa alguns segundos para facilitar a visualização da ordem dos threads doprint (thread.currentThread (). GetName () + "doSoming2 ...."); DOPRINT (Thread.currentThread (). getName () + "está concluído."); } finalmente {Lock.unlock (); }} DOPRINT PRIVADA ESTATIC STATIC (texto da String) {System.out.println ((new Date ()). tolocalestring () + ":" + text); }}Existem dois threads no código acima. Thread1 inicia antes do thread2. Para ver o processo de bloqueio, o código bloqueado fica dormido por 5 segundos, para que você possa sentir o processo do primeiro e do segundo threads que entram no processo de aquisição de bloqueio. O resultado final do código acima é o seguinte:
2016-9-28 15:12:56: Tópico 1 Obtenha bloqueio.
2016-9-28 15:12:56: Thread1 está bloqueado.
2016-9-28 15:12:56: Thread1 Dosoming1 ....
2016-9-28 15:12:56: Tópico 2 Obtenha bloqueio.
2016-9-28 15:13:01: Thread1 Dosoming2 ....
2016-9-28 15:13:01: Thread1 está concluído.
2016-9-28 15:13:01: Thread1 é descarregado.
2016-9-28 15:13:01: Thread2 está bloqueado.
2016-9-28 15:13:01: Thread2 Dosoming1 ....
2016-9-28 15:13:01: Tópico 1 Fim.
2016-9-28 15:13:06: Thread2 Dosoming2 ....
2016-9-28 15:13:06: Thread2 está concluído.
2016-9-28 15:13:06: Thread2 é descarregado.
2016-9-28 15:13:06: Thread 2 final.
Pode -se observar que o Thread1 obtém o bloqueio primeiro e o Thread2 também receberá o bloqueio mais tarde, mas o Thread1 o ocupou neste momento, para que o Thread2 não obtenha o bloqueio até que o Thread1 libere o bloqueio.
** Este código mostra que o encadeamento por trás do LockInterrupt para adquirir o bloqueio precisa esperar que o bloqueio anterior seja liberado antes de obter o bloqueio. ** Mas ainda não há recurso interrompível, então algum código é adicionado a isso:
thread2.start (); tente {thread.sleep (1000); } catch (interruptEdException e) {e.printStackTrace ();} // interrompa o thread2 em 1 segundo thread2.Interrup ();Após o início do Thread2, ligue para o método de interrupção do Thread2. OK, execute o código primeiro e veja os resultados:
2016-9-28 15:16:46: Tópico 1 Obtenha bloqueio.
2016-9-28 15:16:46: Thread1 está bloqueado.
2016-9-28 15:16:46: Thread1 Dosoming1 ....
2016-9-28 15:16:46: Thread 2 Obtenha bloqueio.
2016-9-28 15:16:47: O tópico 2 é interrompido. <-responda diretamente à interrupção do thread
2016-9-28 15:16:51: Thread1 Dosoming2 ....
2016-9-28 15:16:51: Thread1 está concluído.
2016-9-28 15:16:51: Thread1 é descarregado.
2016-9-28 15:16:51: Tópico 1 Fim.
Comparado com o código anterior, pode -se descobrir que o Thread2 está aguardando o Thread1 para liberar o bloqueio, mas o próprio Thread2 interrompe e o código por trás do Thread2 não continuará sendo executado.
ReadWritelock
Como o nome sugere, é um bloqueio de leitura. Esse tipo de cenário de aplicação de bloqueio de leitura e gravação pode ser entendido dessa maneira. Por exemplo, uma onda de dados é fornecida principalmente para leitura e há apenas um número relativamente pequeno de operações de gravação. Se um bloqueio mutex for usado, ele levará à concorrência bloqueada entre os threads. Se todos puderem ler ao ler, bloqueie um recurso quando for escrito. Tais alterações resolvem bem esse problema, permitindo que a operação de leitura melhore o desempenho de leitura sem afetar a operação de gravação.
Um recurso pode ser acessado por vários leitores, ou acessado por um escritor, e ambos não podem ser executados simultaneamente.
Esta é a interface abstrata para bloqueios de leitura e gravação, definindo um bloqueio de leitura e um bloqueio de gravação.
Public Interface ReadWritelock { /*** Retorna o bloqueio usado para leitura. * * @return the bloqueio usado para leitura */ readlock de bloqueio (); /*** Retorna o bloqueio usado para escrever. * * @RETURN O bloqueio usado para escrever */ Lock Writelock ();}Há uma implementação de reentrantreadwritelock no JDK, que é um bloqueio de leitura reentrante. O reentrantreadwritelock pode ser construído em dois tipos: justo ou injusto. Se não for especificado explicitamente durante a construção, um bloqueio não fair será criado por padrão. No modo de bloqueio não fair, a ordem do acesso a roscas é incerta, ou seja, pode ser dividida; Pode ser rebaixado do escritor para o leitor, mas o leitor não pode ser atualizado para o escritor.
Se for um modo de bloqueio justo, a opção será entregue ao thread com o tempo de espera mais longo. Se um thread de leitura obtiver o bloqueio e um thread de gravação solicita o bloqueio de gravação, a aquisição de bloqueio de leitura não será mais recebida até que a operação de gravação seja concluída.
A análise simples de código mantém um bloqueio de sincronização no reentrantreadwritelock, mas parece semanticamente um bloqueio de leitura e um bloqueio de gravação. Dê uma olhada em seu construtor:
public reentrantreadWriteLock (Boolean Fair) {Sync = Fair? New FairSync (): New NonFairSync (); leitorlock = new readlock (this); writerlock = new writeLock (this);} // o construtor da leitura de leitura protegida (reentrantreadwritelock bloqueio) {sync = Lock.sync;} // o construtor de bloqueio de gravação Writelock (reentrantreadWritelock Lock) {Sync = LockyNC;Você pode ver que o objeto de bloqueio de sincronização do reentrantreadWritelock é realmente referenciado quando construído. E essa classe de sincronização é uma classe interna de reentrantreadwritelock. Em resumo, os bloqueios de leitura/gravação são feitos por meio de sincronização. Como ele colabora no relacionamento entre os dois?
// Método de travamento para ler Lock public void Lock () {sync.acquireshared (1);} // Método de travamento para escrever bloqueio public void Lock () {sync.acquire (1);}A principal diferença é que o bloqueio de leitura obtém um bloqueio compartilhado, enquanto o bloqueio de gravação adquire um bloqueio exclusivo. Há um ponto aqui que pode ser mencionado, isto é, para garantir que o ReentrantreadWritelock, bloqueios compartilhados e bloqueios exclusivos, devem apoiar a contagem de mantimentos e os reentrantes. O ReentrantLock é armazenado usando o estado e o estado só pode armazenar um valor de modelagem. Para ser compatível com o problema de dois bloqueios, ele é dividido no número de threads que mantêm a trava compartilhada ou o número de threads que mantêm o bloqueio exclusivo ou a contagem de reentrada, respectivamente.
outro
Escrevi um grande artigo que senti que demoraria muito para escrever, e há algumas bloqueios mais úteis:
Countdownlatch
É definir um contador que é mantido simultaneamente. Quando o chamador chama o método Aguardar Countdownlatch, ele bloqueará se o contador atual não for 0. Chamando o método de liberação do CountdownLatch pode reduzir a contagem até que o chamadas que chama aguarde desbloqueará.
Semafone
O semáforo é uma forma de autorização e licença, como a configuração de 100 licenças, para que 100 threads possam manter bloqueios ao mesmo tempo e, se esse valor exceder, retornará à falha.
Obrigado por ler este artigo, espero que possa ajudá -lo. Obrigado pelo seu apoio a este site!