palavra -chave sincronizada
Sincronizado, chamamos bloqueios, são usados principalmente para bloquear métodos e blocos de código. Quando um método ou bloco de código é sincronizado, no máximo um thread está executando o código ao mesmo tempo. Quando vários threads acessam o método de bloqueio/bloco de código do mesmo objeto, apenas um thread está executando ao mesmo tempo e os threads restantes devem aguardar o thread atual executar o segmento de código antes de executar. No entanto, os threads restantes podem acessar os blocos de código não bloqueados no objeto.
O sincronizado inclui principalmente dois métodos: método sincronizado e bloco sincronizado.
Método sincronizado
Declare o método sincronizado adicionando a palavra -chave sincronizada à declaração do método. como:
public sincronizado void getResult ();
O método sincronizado controla o acesso às variáveis dos membros da classe. Como isso evita o controle de acesso das variáveis de membro da classe? Sabemos que o método usa a palavra -chave sincronizada para indicar que o método está bloqueado. Quando qualquer thread acessa o método modificado, é necessário determinar se outros threads são "exclusivos". Cada instância da classe corresponde a um bloqueio. Cada método sincronizado deve chamar o bloqueio da instância da classe do método antes que ele possa ser executado. Caso contrário, o fio ao qual pertence é bloqueado. Depois que o método for executado, o bloqueio será exclusivo. O bloqueio não será liberado até que retorne do método e o encadeamento bloqueado pode obter o bloqueio.
De fato, o método sincronizado tem falhas. Se declararmos um grande método como sincronizado, isso afetará bastante a eficiência. Se vários threads estiverem acessando um método sincronizado, apenas um thread está executando o método ao mesmo tempo, enquanto outros threads devem esperar. No entanto, se o método não usar sincronizado, todos os threads poderão executá -lo ao mesmo tempo, reduzindo o tempo total de execução. Portanto, se soubermos que um método não será executado por vários threads ou não há problema em compartilhamento de recursos, não precisamos usar a palavra -chave sincronizada. Mas se precisarmos usar a palavra -chave sincronizada, podemos substituir o método sincronizado pelo bloco de código sincronizado.
bloco sincronizado
O bloco de código sincronizado desempenha a mesma função que o método sincronizado, exceto que torna a área crítica o mais curta possível. Em outras palavras, protege apenas os dados compartilhados necessários, deixando esta operação para os restantes blocos de código longo. A sintaxe é a seguinte:
Código sincronizado (objeto) {// que permite o controle de acesso} Se precisarmos usar a palavra -chave sincronizada dessa maneira, devemos usar uma referência de objeto como um parâmetro. Geralmente, costumamos usar este parâmetro como este.synchronized (this) {// código que permite o controle de acesso}Existe o seguinte entendimento de sincronizado (isso):
1. Quando dois threads simultâneos acessam este bloco de código sincronizado (this) sincronizado no mesmo objeto, apenas um thread pode ser executado dentro de um tempo. Outro thread deve esperar que o encadeamento atual execute este bloco de código antes de poder executar o bloco de código.
2. No entanto, quando um thread acessa um bloco de código de sincronização sincronizado (this) de um objeto, outro thread ainda pode acessar o bloco de código de sincronização não sincronizado (this) no objeto.
3. É particularmente crítico que, quando um encadeamento acessar um bloco de código de sincronização sincronizado (this) de um objeto, outros threads serão impedidos de acessar todos os outros blocos de código de sincronização sincronizados (this) no objeto.
4. O terceiro exemplo também se aplica a outros blocos de código de sincronização. Ou seja, quando um thread acessa um bloco de código de sincronização sincronizado (this) de um objeto, ele obtém a trava do objeto desse objeto. Como resultado, outros threads acesso a todas as partes do código síncrono do objeto do objeto serão bloqueados temporariamente.
Trancar
Há um princípio "Venha em primeiro lugar, mais tarde" no Java Multithreading, ou seja, quem quiser a chave primeiro o usará primeiro. Sabemos que, para evitar problemas com a concorrência de recursos, o Java usa mecanismos de sincronização para evitar e os mecanismos de sincronização são controlados usando o conceito de bloqueios. Então, como os bloqueios se refletem nos programas Java? Aqui precisamos descobrir dois conceitos:
O que é um bloqueio? Na vida cotidiana, é um selador adicionado a portas, caixas, gavetas e outros objetos, que impedem que outras pessoas espalhem ou roubem, e desempenham um papel protetor. O mesmo é verdadeiro em Java. Locks desempenham um papel na proteção de objetos. Se um tópico ocupa exclusivamente um determinado recurso, não deseja usar outros threads, mas deseja usá -los? Vamos falar sobre isso quando terminar de usá -lo!
Em um ambiente de execução do programa Java, a JVM precisa coordenar os dados compartilhados por dois tipos de threads:
1. Variáveis de instância salvas na pilha
2. Variáveis de classe salvas na área do método.
Em uma máquina virtual Java, cada objeto e classe estão logicamente associados a um monitor. Para um objeto, o monitor associado protege as variáveis de instância do objeto. Para as aulas, os monitores protegem as variáveis de classe. Se um objeto não tiver variáveis de instância ou uma classe não possui variáveis, o monitor associado não monitorará nada.
Para implementar a capacidade de monitoramento exclusiva do monitor, a Java Virtual Machine associa um bloqueio para cada objeto e classe. Representa os privilégios que apenas um tópico pode ter a qualquer momento. Os threads não precisam bloquear ao acessar variáveis de instância ou variáveis de classe. Se um thread adquirir um bloqueio, é impossível para outros threads adquirir a mesma trava antes de liberar o bloqueio. Um encadeamento pode bloquear o mesmo objeto várias vezes. Para cada objeto, a máquina virtual Java mantém um contador de bloqueio. Toda vez que o thread obtém o objeto, o contador é aumentado em 1 e, toda vez que é liberado, o contador é reduzido em 1. Quando o valor do contador é 0, a trava é completamente liberada.
Os programadores Java não precisam adicionar bloqueios por si mesmos, os bloqueios de objetos são usados internamente pelas máquinas virtuais Java. Em um programa Java, você só precisa usar o bloqueio sincronizado ou o método sincronizado para marcar uma área de monitoramento. Sempre que você insere uma área de monitoramento, a máquina virtual Java bloqueia automaticamente o objeto ou a classe.
Uma trava simples
Ao usar sincronizado, usamos bloqueios como este:
public class ThreadTest {public void test () {sincronizado (this) {// faça algo}}}A sincronizada garante que apenas um thread esteja executando o dosomething ao mesmo tempo. Aqui está o uso de bloqueio em vez de sincronizado:
classe pública ThreadTest {Lock Lock = new Lock (); public void test () {Lock.lock (); // faça algo Lock.unlock (); }}O método Lock () bloqueia o objeto da instância de bloqueio; portanto, todos os threads que chamam o método Lock () no objeto serão bloqueados até que o método desbloqueio () do objeto de bloqueio seja chamado.
O que está bloqueado?
Antes desta pergunta, devemos ficar claros: se a palavra -chave sincronizada é adicionada ao método ou ao objeto, a trava adquira é um objeto. Em Java, todo objeto pode ser usado como uma trava, que se reflete principalmente nos três aspectos a seguir:
Para métodos de sincronização, o bloqueio é o objeto de instância atual.
Para o bloco do método de sincronização, o bloqueio é um objeto configurado nos parênteses sinconizados.
Para métodos de sincronização estática, o bloqueio é o objeto de classe do objeto atual.
Primeiro, vejamos o seguinte exemplo:
public class ThreadTest_01 implementa Runnable {@Override public sincronizado void run () {for (int i = 0; i <3; i ++) {System.out.println (thread.currentThread (). getName ()+"run ......"); }} public static void main (string [] args) {for (int i = 0; i <5; i ++) {new Thread (new ThreadTest_01 (), "thread_"+i) .start (); }}}Resultados de execução parcial:
Thread_2run ... thread_2run ... thread_4run ... thread_4run ... thread_3run ... thread_3run ... thread_3run ... thread_3run ... thread_2run ... thread_4run ...
Esse resultado é um pouco diferente do resultado esperado (esses threads correm por aqui). Logicamente falando, o método Run mais a palavra -chave sincronizada produzirá um efeito de sincronização. Esses tópicos devem executar o método de execução um após o outro. Como mencionado acima, depois de adicionar palavras -chave sincronizadas a um método de membro, na verdade é um bloqueio ao método do membro. O ponto específico é usar o próprio objeto em que o método do membro está localizado como o bloqueio do objeto. No entanto, neste exemplo, temos novos 10 objetos Threadtest, e cada encadeamento manterá a trava do objeto de seu próprio objeto de encadeamento, que definitivamente não produzirá um efeito síncrono. Então: se esses threads devem ser sincronizados, os bloqueios de objetos mantidos por esses threads devem ser compartilhados e únicos!
Qual objeto é sincronizado bloqueado neste momento? O que ele trava é chamar esse objeto de método síncrono. Ou seja, se o objeto ThreadTest executar métodos de sincronização em diferentes encadeamentos, ele se formará mutuamente exclusivo. Alcançar o efeito da sincronização. Portanto, altere o novo thread acima (novo ThreadTest_01 (), "Thread_" + i) .start (); para novo thread (threadTest, "thread_" + i) .start ();
Para métodos de sincronização, o bloqueio é o objeto de instância atual.
O exemplo acima usa o método sincronizado. Vamos dar uma olhada no bloco de código sincronizado:
classe pública threadtest_02 estende o thread {private string bloqueio; nome de string privado; public threadTest_02 (nome da string, string bloqueio) {this.name = name; this.lock = bloqueio; } @Override public void run () {Synchronized (Lock) {for (int i = 0; i <3; i ++) {System.out.println (nome+"run ......"); }}} public static void main (string [] args) {string Lock = new String ("test"); for (int i = 0; i <5; i ++) {new ThreadTest_02 ("ThreadTest_"+i, Lock) .Start (); }}}Resultados em execução:
ThreadTest_0 Run ... ThreadTest_0 Run ... ThreadTest_0 Run ... ThreadTest_1 Run ... ThreadTest_1 Run ... ThreadTest_1 Run ... ThreadTest_1 Run ... ThreadTest_4 Run ... ThreadTest_4 Run ... ThreadTest_4 Run ... ThreadTest_3 Run ... ThreadTest_3 Run ... Run ...
No método principal, criamos um bloqueio de objeto String e atribuímos esse objeto a cada bloqueio de variável privada do objeto ThreadTest2 Thread. Sabemos que existe um pool de cordas em Java, portanto, as variáveis privadas de bloqueio desses encadeamentos apontam para a mesma área na memória da heap, ou seja, a área em que as variáveis de bloqueio na função principal são armazenadas; portanto, a trava do objeto é única e compartilhada. Sincronização de threads! !
O objeto de string de bloqueio que sincronizou os bloqueios aqui.
Para o bloco do método de sincronização, o bloqueio é um objeto configurado nos parênteses sinconizados.
public class ThreadTest_03 estende o thread {public sincronizado estático void test () {for (int i = 0; i <3; i ++) {System.out.println (thread.currentThread (). getName ()+"run ......"); }} @Override public void run () {test (); } public static void main (string [] args) {for (int i = 0; i <5; i ++) {new ThreadTest_03 (). start (); }}}Resultados em execução:
Thread-0 Run ... Thread-0 Run ... Thread-0 Run ... Thread-4 Run ... Thread-4 Run ... Thread-4 Run ... Thread-1 Run ... Thread-1 Run ... Thread-1 Run ... Thread-2 Run ... Thread-2 Run ... Thread-2 Run ... Thread-3 Run ... Thread-3 Run ... Thread-3 Run ... RUN ...
Neste exemplo, o método RUN usa um método de sincronização e um método de sincronização estática. Então, qual é o bloqueio sincronizado aqui? Sabemos que estática está além do objeto e está no nível da classe. Portanto, um bloqueio de objeto é a instância da classe da classe em que a liberação estática está localizada. Como na JVM, todas as classes carregadas têm objetos de classe exclusivos, o único objeto ThreadTest_03.class nesta instância. Não importa quantas instâncias da classe criamos, sua instância de classe ainda é uma! Portanto, o bloqueio do objeto é único e compartilhado. Sincronização de threads! !
Para métodos de sincronização estática, o bloqueio é o objeto de classe do objeto atual.
Se uma classe define uma função estática sincronizada a e uma função de instância sincronizada B, o mesmo objeto obj dessa classe não constituirá sincronização ao acessar dois métodos A e B em vários threads, porque seus bloqueios são diferentes. A fechadura do método A é o objeto Obj, enquanto a fechadura de B é a classe à qual o OBJ pertence.
Atualização de bloqueio
Existem quatro estados em Java: estado livre de trava, estado de trava tendencioso, estado de trava leve e estado de trava pesados, que gradualmente aumentarão com a concorrência. A trava pode ser atualizada, mas não pode ser rebaixada, o que significa que a trava tendenciosa não pode ser rebaixada para a trava tendenciosa após a atualização para uma trava leve. Essa estratégia de atualização de bloqueio, mas não pode ser rebaixada, é melhorar a eficiência de obter e liberar bloqueios. A parte principal abaixo é um resumo do blog: concorrência (ii) sincronizada em Java SE1.6.
Bloquear o rotação
Sabemos que quando um encadeamento entra em um bloqueio de método/código de sincronização, se descobrir que o método de sincronização/bloco de código é ocupado por outros, ele esperará e entrará em um estado de bloqueio. O desempenho desse processo é baixo.
Ao encontrar a competição da fechadura, ou aguardar as coisas, o tópico pode estar menos ansioso para entrar no estado de bloqueio, mas espere e veja se a fechadura é liberada imediatamente. Este é o giro de bloqueio. Até certo ponto, a rotação de bloqueio pode otimizar a rosca.
Bloqueio positivo
Bloqueios positivos são usados principalmente para resolver o problema de desempenho dos bloqueios sem concorrência. Na maioria dos casos, os bloqueios de bloqueio não apenas não possuem competição com vários thread, mas sempre são obtidos várias vezes pelo mesmo thread. Para fazer com que o thread adquirisse bloqueios a um custo menor, bloqueios tendenciosos são introduzidos. Quando um encadeamento obtém um bloqueio, o encadeamento pode travar o objeto várias vezes, mas toda vez que essa operação é executada, algum consumo de sobrecarga devido à operação CAS (Instrução de Compare e Swap da CPU). Para reduzir essa sobrecarga, a fechadura tenderá a ser o primeiro thread a obtê -lo. Se o bloqueio não for adquirido por outros threads durante o próximo processo de execução, o encadeamento que mantém a trava tendenciosa nunca precisará ser sincronizada novamente.
Quando outros threads estão tentando competir pela trava tendenciosa, a rosca que mantém a trava tendenciosa libera a trava.
Expansão de bloqueio
Múltiplas ou múltiplas chamadas para bloqueios com granularidade muito pequena não são tão eficientes quanto chamar bloqueios com uma grande trava de granularidade.
Trava leve
A base para o bloqueio leve para melhorar o desempenho da sincronização do programa é que "para a maioria dos bloqueios, não há concorrência durante todo o ciclo de sincronização", que é um dados empíricos. Um bloqueio leve cria um espaço chamado registro de bloqueio no quadro de pilha do encadeamento atual, usado para armazenar o apontamento atual e o estado do objeto de bloqueio. Se não houver concorrência, a trava leve usa operação CAS para evitar a sobrecarga do uso de mutexes, mas se houver concorrência de bloqueio, além da sobrecarga dos mutexes, a operação do CAS ocorreu adicionalmente; portanto, no caso de competição, a trava leve será mais lenta que a trava tradicional pesada.
A justiça da fechadura
O oposto da justiça é a fome. Então, o que é "fome"? Se um fio não puder obter o tempo de execução da CPU porque outros threads estão sempre ocupando a CPU, chamamos o tópico de "faminto até a morte". A solução para a fome é chamada de "justiça" - todos os tópicos podem obter oportunidades de execução da CPU de maneira justa.
Existem várias razões principais para a fome do tópico:
Os threads de alta prioridade consomem o tempo da CPU de todos os threads de baixa prioridade. Podemos definir sua prioridade individualmente para cada encadeamento, de 1 a 10. Quanto maior o encadeamento de prioridade, mais tempo leva para obter a CPU. Para a maioria dos aplicativos, é melhor não alterar seu valor de prioridade.
O thread é permanentemente bloqueado em um estado esperando para entrar no bloco de sincronização. A área de código síncrono do Java é um fator importante que causa fome no fio. Os blocos de código síncrono de Java não garantem a ordem dos threads que entram nele. Isso significa que, em teoria, existem um ou mais threads que sempre são bloqueados ao tentar entrar na área de código síncrono, porque outros threads são sempre superiores a eles para obter acesso, resultando em não ter a oportunidade de execução da CPU e estar "morrendo de fome".
O tópico está esperando por um objeto que também está permanentemente aguardando a conclusão. Se vários threads estiverem na execução do método wait () e chamando notify () nele não garante que qualquer thread seja despertado, qualquer thread poderá estar em um estado de espera contínua. Portanto, existe o risco de que um tópico de espera nunca seja despertado, porque outros threads de espera sempre podem ser despertados.
Para resolver o problema do tópico "fome", podemos usar bloqueios para alcançar a justiça.
Reentabilidade de bloqueios
Sabemos que, quando um thread solicita um objeto que mantém um bloqueio por outro thread, o thread bloqueará, mas pode ser bem -sucedido quando o thread solicitar um objeto que mantém um bloqueio por si só? A resposta é que o sucesso pode ser bem -sucedido e a garantia de sucesso é a "reentrada" dos bloqueios de threads.
"Reentrável" significa que você pode obter seu próprio bloqueio interno novamente sem bloquear. do seguinte modo:
public class pai {public sincronizado void method () {// faça algo}} public class Child estende o pai {public sincronizado void method () {// faz algo super.method (); }}
Se não for reentrante, o código acima será um impasse, porque chamar o método da criança () adquirirá primeiro o bloqueio interno do pai da classe pai e depois adquirirá o bloqueio interno da criança. Ao chamar o método da classe pai, você precisa voltar ao bloqueio interno da classe pai novamente. Se não for reentrante, você pode cair em um impasse.
A implementação da reentrada do Java Multithreading é associar um cálculo de solicitação e um encadeamento que o ocupa em cada bloqueio. Quando a contagem é 0, acredita -se que a fechadura não esteja ocupada e, em seguida, qualquer fio pode obter a propriedade da fechadura. Quando um thread solicita com sucesso, a JVM registrará o thread que mantém o bloqueio e definirá a contagem como 1. Se outros threads solicitarem o bloqueio, eles deverão esperar. Quando o thread solicita a obtenção do bloqueio novamente, a contagem será +1; Quando o thread de ocupação sair do bloco de código síncrono, a contagem será -1, até que seja 0, o bloqueio será liberado. Somente então outros threads podem ter a oportunidade de ganhar a propriedade da fechadura.
Lock e sua classe de implementação
java.util.concurrent.locks fornece um mecanismo de bloqueio muito flexível, fornecendo a interface e a classe de uma estrutura para condições de bloqueio e espera. É diferente da sincronização e monitores internos, o que permite maior flexibilidade no uso de bloqueio e condições. Seu diagrama de estrutura de classe é o seguinte:
Reentrantlock: um bloqueio mutex reentrante, a principal implementação da interface de bloqueio.
ReentrantreadWriteLock:
ReadWritelock: ReadWritelock mantém um par de bloqueios relacionados, um para operações somente leitura e outra para operações de gravação.
Semáforo: um semáforo de contagem.
Condição: O objetivo do bloqueio é permitir que o encadeamento adquira o bloqueio e ver se uma certa condição espera é atendida.
CyclicBarrier: Uma classe auxiliar síncrona que permite que um grupo de threads espere um pelo outro até atingir um ponto de barreira comum.