No desenvolvimento geral, o autor costuma ver que muitos estudantes usam apenas alguns métodos básicos no tratamento do modelo de desenvolvimento concorrente Java. Por exemplo, volátil, sincronizado. Pacotes concorrentes avançados como Lock e Atomic não são frequentemente usados por muitas pessoas. Eu acho que a maioria das razões se deve à falta de atributos para o princípio. No trabalho de desenvolvimento movimentado, quem pode entender com precisão e usar o modelo de simultaneidade correto?
Recentemente, com base nessa idéia, planejo organizar o mecanismo de controle de simultaneidade em um artigo. Não é apenas uma memória de seu próprio conhecimento, mas também espera que o conteúdo mencionado neste artigo possa ajudar a maioria dos desenvolvedores.
O desenvolvimento do programa paralelo envolve inevitavelmente questões como colaboração com múltiplas threading e várias tarefas e compartilhamento de dados. No JDK, são fornecidas várias maneiras para implementar o controle simultâneo entre vários threads. Por exemplo, comumente usado: bloqueio interno, bloqueio de reentrada, trava de leitura e semáforo.
Modelo de memória Java
Em Java, cada encadeamento possui uma área de memória de trabalho, que armazena uma cópia do valor da variável na memória principal compartilhada por todos os threads. Quando um thread é executado, ele opera essas variáveis em sua própria memória de trabalho.
Para acessar uma variável compartilhada, um encadeamento geralmente adquire o bloqueio e limpa sua área de memória de trabalho, o que garante que a variável compartilhada seja carregada corretamente na área de memória compartilhada de todos os threads na área de memória de trabalho do thread. Quando o encadeamento é desbloqueado, é garantido que o valor da variável na área de memória de trabalho esteja associado à memória compartilhada.
Quando um encadeamento usa uma determinada variável, independentemente de o programa usar corretamente as operações de sincronização de threads, o valor que obtém deve ser o valor armazenado na variável por si mesmo ou em outros threads. Por exemplo, se dois threads armazenarem valores diferentes ou referências de objeto na mesma variável compartilhada, o valor da variável é deste encadeamento ou desse encadeamento, e o valor da variável compartilhada não será composto pelos valores de referência dos dois threads.
Um endereço que os programas Java pode acessar quando uma variável é usada. Ele não inclui apenas variáveis de tipo básico e variáveis de tipo de referência, mas também variáveis do tipo de matriz. As variáveis armazenadas na área de memória principal podem ser compartilhadas por todos os threads, mas é impossível para um thread acessar os parâmetros ou variáveis locais de outro thread; portanto, os desenvolvedores não precisam se preocupar com problemas de segurança de threads das variáveis locais.
Variáveis voláteis podem ser vistas entre vários threads
Como cada thread possui sua própria área de memória de trabalho, ele pode ser invisível para outros threads quando um thread altera seus próprios dados de memória de trabalho. Para fazer isso, você pode usar a palavra -chave volátil para quebrar todos os threads para ler e gravar variáveis na memória, para que as variáveis voláteis sejam visíveis entre vários threads.
As variáveis declaradas como voláteis podem ser garantidas da seguinte maneira:
1. As modificações de variáveis por outros threads podem ser prontamente refletidas no encadeamento atual;
2. Verifique se a modificação da variável volátil pelo fio atual pode ser gravada de volta à memória compartilhada no tempo e vista por outros threads;
3. Use variáveis declaradas por volátil, e o compilador garantirá sua ordem.
Palavras -chave sincronizadas
A palavra -chave sincronizada sincronizada é um dos métodos de sincronização mais usados no idioma Java. Nas versões iniciais do JDK, o desempenho de Synchronized não foi muito bom, e o valor era adequado para ocasiões em que a concorrência de bloqueio não era particularmente feroz. No JDK6, a lacuna entre bloqueios sincronizados e injustos diminuiu. Mais importante, o sincronizado é mais conciso e claro, e o código é legível e mantido.
Métodos para bloquear um objeto:
public sincronizado Void Method () {}
Quando o método do método () é chamado, o encadeamento de chamada deve primeiro obter o objeto atual. Se o bloqueio atual do objeto for mantido por outros threads, o thread de chamada esperará. Após o término da violação, o bloqueio do objeto será liberado. O método acima é equivalente ao seguinte método de escrita:
public void method () {sincronizado (this) {// faça algo…}} Em segundo lugar, o sincronizado também pode ser usado para construir blocos de sincronização. Comparados com os métodos de sincronização, os blocos de sincronização podem controlar a faixa do código de sincronização com mais precisão. Um pequeno código de sincronização é muito rápido dentro e fora dos bloqueios, dando ao sistema uma taxa de transferência mais alta.
Método public void (objeto o) {// beforesynchronized (o) {// faça algo ...} // depois} O sincronizado também pode ser usado para funções estáticas:
Método estático sincronizado público () {}
É importante observar neste local que o bloqueio sincronizado é adicionado ao objeto de classe atual; portanto, todas as chamadas para esse método devem obter o bloqueio do objeto de classe.
Embora o sincronizado possa garantir a segurança do encadeamento de objetos ou segmentos de código, o uso de sincronizado sozinho ainda não é suficiente para controlar as interações do encadeamento com a lógica complexa. Para alcançar a interação entre vários threads, também são necessários métodos Wait () e Notify () do objeto do objeto.
Uso típico:
sincronizado (obj) {while (<?>) {obj.wait (); // continua a executar após receber a notificação. }} Antes de usar o método wait (), você precisa obter o bloqueio do objeto. Quando o método wait () é executado, o thread atual pode liberar o bloqueio exclusivo do OBJ para uso por outros threads.
Ao aguardar o thread no OBJ receber obj.Notify (), ele pode recuperar o bloqueio exclusivo da OBJ e continuar em execução. Observe que o método notify () é evocar aleatoriamente um thread aguardando o objeto atual.
Aqui está uma implementação de uma fila de bloqueio:
classe pública BlockQueue {Lista privada Lista = new ArrayList (); public sincronizado objeto pop () lança interruptedException {while (list.size () == 0) {this.wait (); } if (list.size ()> 0) {return list.remove (0); } else {return null; }} objeto sincronizado público (objeto obj) {list.add (obj); this.Notify (); }} Sincronizado e wait () e notify () devem ser uma habilidade básica que os desenvolvedores de Java devem dominar.
Reentrantlock Reentrantlock Lock
Reentrantlock é chamado de reentrantlock. Possui recursos mais poderosos do que sincronizados, pode interromper e o tempo. No caso de alta concorrência, ele tem vantagens óbvias de desempenho sobre sincronizadas.
O ReentrantLock fornece bloqueios justos e injustos. Uma trava justa é a primeira em primeiro lugar da fechadura, e não uma trava justa pode ser cortada na fila. Obviamente, de uma perspectiva de desempenho, o desempenho de bloqueios injustos é muito melhor. Portanto, na ausência de necessidades especiais, os bloqueios injustos devem ser preferidos, mas o sincronizado fornece a indústria de travamento não é absolutamente justo. O ReentrantLock pode especificar se o bloqueio é justo ao construir.
Ao usar um bloqueio de reentrada, libere o bloqueio no final do programa. Geralmente, o código para liberar o bloqueio deve ser escrito finalmente. Caso contrário, se a exceção do programa ocorrer, o Loack nunca será lançado. O bloqueio sincronizado é liberado automaticamente pela JVM no final.
O uso clássico é o seguinte:
tente {if (Lock.Trylock (5, TimeUnit.Seconds)) {// Se estiver bloqueado, tente esperar 5s para ver se o bloqueio pode ser obtido. Se o bloqueio não puder ser obtido após 5s, retorne False para continuar a execução // Lock.lockInterruptível (); pode responder à interrupção do evento, tente {// operação} finalmente {Lock.unlock (); }}} catch (interruptEdException e) {e.printStackTrace (); // Quando o encadeamento atual é interrompido (interrupção), uma interrupção será lançada}}O ReentrantLock fornece uma rica variedade de funções de controle de bloqueio e aplica com flexibilidade esses métodos de controle para melhorar o desempenho do aplicativo. No entanto, não é altamente recomendável usar o Reentrantlock aqui. O bloqueio de reentrada é uma ferramenta de desenvolvimento avançada fornecida no JDK.
ReadWritelock Read and Write Lock
A separação de leitura e escrita é uma ideia de processamento de dados muito comum. Deve ser considerado uma tecnologia necessária no SQL. O ReadWritelock é um bloqueio de separação de leitura e gravação fornecido no JDK5. Ler e gravar bloqueios de separação pode efetivamente ajudar a reduzir a concorrência de bloqueio para melhorar o desempenho do sistema. Os cenários de uso para separação de leitura e gravação estão principalmente se no sistema, o número de operações de leitura é muito maior que as operações de gravação. Como usá -lo é o seguinte:
ReentrantReadWritelock privado readWritelock = new ReentrantreadWritelock (); Private Lock Readlock = ReadWritelock.readlock (); Private Lock WriteLock = readWritelock.Writelock (); Public Object HandleRead () ThrowsceptException {Try {readlock.lock (); Thread.sleep (1000); valor de retorno; } finalmente {readlock.unlock (); }} public Object handleRead () lança interruptedException {try {writeLock.lock (); Thread.sleep (1000); valor de retorno; } finalmente {writelock.unlock (); }} Objeto de condição
O objeto ConditionD é usado para coordenar a colaboração complexa entre vários threads. Principalmente associado a bloqueios. Uma instância de condição ligada ao bloqueio pode ser gerada através do método newcondition () na interface de bloqueio. A relação entre um objeto de condição e um bloqueio é como usar o objeto duas funções.wait (), object.Notify () e as palavras -chave sincronizadas.
Aqui você pode extrair o código -fonte do ArrayBlockingQueue:
A classe pública ArrayblockockQueue estende a abstracia implementa BlockingQueue, java.io.Serializable {/** Lock principal guardando todo o acesso*/Final Reentrantlock Lock;/** Condição para esperar*/Private Condition NotEmempty;/** condição para aguardar*/private condição privada neotl; IlegalargumentException (); this.items = novo objeto [capacidade]; bloqueio = novo reentrantlock (justo); NotEmpty = Lock.NewCondition (); // gerar condição notfull = Lock.newcondition ();} public void put (e e) lança interruptedException {checkNotNull (e); Final ReentrantLock Lock = this.lock; Lock.lockInterruptível (); tente {while (count == items.length) notfull.await (); inserir (e); } finalmente {Lock.unlock (); }} private void insert (e x) {itens [putIndex] = x; putIndex = inc (putIndex); ++ contagem; NotEmpty.signal (); // notificação} public e the the () lança interruptedException {final reentrantlock bloqueio = this.lock; Lock.lockInterruptível (); tente {while (count == 0) // se a fila estiver vazia notepty.await (); // então a fila do consumidor deve aguardar um extrato de retorno de sinal não vazio (); } finalmente {Lock.unlock (); }} private E Extract () {Final Object [] itens = this.items; E x = this. <e> fund (itens [TakeIndex]); itens [TakeIndex] = nulo; TakeIndex = Inc (TakeIndex); --contar; notfull.signal (); // notificar put () que a fila do thread tem espaço livre retorno x;} // outro código} O Semaphore Semaphore <r /> Semaphore fornece um método de controle mais poderoso para colaboração com vários thread. Semáforo é uma extensão da fechadura. Seja o bloqueio interno sincronizado ou o reentrantlock, um thread permite o acesso a um recurso por vez, enquanto o semáforo pode especificar que vários threads acessam um recurso ao mesmo tempo. Do construtor, podemos ver:
public Semaphore (int permission) {}
public Semaphore (Int Permissões, Feira Booleana) {} // pode especificar se é justo
As licenças especificam o livro de acesso para o semáforo, o que significa quantas licenças podem ser aplicadas ao mesmo tempo. Quando cada thread se aplica apenas a uma licença de cada vez, isso equivale a especificar quantos threads podem acessar um determinado recurso ao mesmo tempo. Aqui estão os principais métodos a serem usados:
public void adquirir () lança interruptedException {} // Tente obter uma permissão de acesso. Se não estiver disponível, o thread esperará, sabendo que um thread libera uma permissão ou o thread atual é interrompido.
public void adquiriNInterruptly () {} // semelhante a adquirir (), mas não responde às interrupções.
public boolean tryacquire () {} // Tente obtê -lo, verdadeiro se for bem -sucedido, caso contrário, false. Este método não vai esperar e retornará imediatamente.
public boolean tryacquire (tempo limite de longa data, unidade timeunit) lança interruptedException {} // quanto tempo leva para esperar
public void Release () // é usado para liberar uma licença após a conclusão do recurso de acesso no local, para que outros threads aguardando permissão possam acessar o recurso.
Vamos dar uma olhada nos exemplos de uso de semáforos fornecidos no documento JDK. Este exemplo explica como controlar o acesso de recursos através de semáforos.
public class Pool {private estático final int max_Available = 100; private final semáforo disponível = novo semáforo (max_available, true); public objeto getItem () lança interruptedException {disponível.acquire (); // solicita -se a uma licença // apenas 100 threads podem entrar para obter itens disponíveis ao mesmo tempo, // se mais de 100, você precisará aguardar o retorno getNexTAVALABLABLEItem ();} public void putItem (objeto x) {// coloque o item fornecido de volta no pool e marque -o como não usado se (MarkasuNused (x)) {disponível) // Adicionado um item disponível, libere uma licença e o thread que solicita o recurso é ativado}} // apenas por exemplo, referência, objeto não protegido por dados não real [] itens = novo objeto [max_avilable]; // usado para objetos de multiplexação de pool de objetos boolean protegido [] usado = novo booleano [max_available]; // Função de marcação Objeto sincronizado protegido getNextavailableItem () {for (int i = 0; i <max_available; ++ i) {if (! Usou [i]) {usou [i] = true; devolver itens [i]; }} retornar null;} protegido booleano sincronizado markasunusouse (item do objeto) {for (int i = 0; i <max_available; ++ i) {if (item == itens [i]) {if (usado [i]) {usou [i] = false; retornar true; } else {return false; }}} retornar false;}} Essa instância simplesmente implementa um pool de objetos com uma capacidade máxima de 100. Portanto, quando houver 100 solicitações de objetos ao mesmo tempo, o pool de objetos terá escassez de recursos e threads que não conseguem obter recursos precisam esperar. Quando um thread termina usando um objeto, ele precisa retornar o objeto ao pool de objetos. No momento, como os recursos disponíveis aumentam, um thread aguardando o recurso pode ser ativado.
ThreadLocal Thread Variáveis locais <r /> Depois de começar a entrar em contato com o ThreadLocal, é difícil para mim entender os cenários de uso deste thread local variável. Ao olhar para trás agora, o ThreadLocal é uma solução para acesso simultâneo a variáveis entre vários threads. Diferentemente dos métodos sincronizados e outros métodos de bloqueio, o ThreadLocal não fornece bloqueios, mas usa o método de troca de espaço por tempo para fornecer a cada thread cópias independentes de variáveis para garantir a segurança do encadeamento. Portanto, não é uma solução para compartilhamento de dados.
Threadlocal é uma boa ideia para resolver problemas de segurança de threads. Há um mapa na classe Threadlocal que armazena uma cópia de variáveis para cada thread. A chave do elemento no mapa é um objeto Thread e o valor corresponde à cópia das variáveis para o encadeamento. Como o valor da chave não pode ser repetido, cada "objeto Thread" corresponde à "cópia das variáveis" do encadeamento e atinge a segurança do encadeamento.
É particularmente digno de nota. Em termos de desempenho, o Threadlocal não tem desempenho absoluto. Quando o volume de simultaneidade não for muito alto, o desempenho do bloqueio será melhor. No entanto, como um conjunto de soluções seguras de roscas que não têm relação com bloqueios, o uso do ThreadLocal pode reduzir a concorrência de bloqueio em certa medida em alta concorrência ou concorrência feroz.
Aqui está um uso simples do Threadlocal:
classe pública TestNum {// Substituir o método de threadlocal de threadlocal através da classe interna anônima, especifique o valor inicial privado estático seglocal seqnum = new Threadlocal () {public integer InitialValue () {return 0; }}; // obtenha o próximo valor de sequência public int getNextNum () {seqnum.set (seqnum.get () + 1); retornar seqnum.get ();} public static void main (string [] args) {testnum sn = new testNum (); // 3 threads compartilham SN, cada um gerando um número de sequência TestClient T1 = new TestClient (SN); TestClient T2 = novo TestClient (SN); Testclient t3 = novo testclient (sn); t1.start (); t2.start (); t3.start (); } classe estática privada TestClient estende o thread {private testnum sn; public testclient (testnum sn) {this.sn = sn; } public void run () {for (int i = 0; i <3; i ++) {// Cada encadeamento produz 3 valores de sequência system.out.println ("thread [" + thread.currentThread (). getName () + "] -> sn [" + sn.getnextNum () + "]); }}}} Resultado da saída:
Thread [Thread-0]> SN [1]
Tópico [Thread-1]> SN [1]
Tópico [Thread-2]> SN [1]
Tópico [Thread-1]> SN [2]
Thread [Thread-0]> SN [2]
Tópico [Thread-1]> SN [3]
Tópico [Thread-2]> SN [2]
Tópico [Thread-0]> SN [3]
Tópico [Thread-2]> SN [3]
As informações do resultado da saída podem ser encontradas que, embora os números de sequência gerados por cada encadeamento compartilhem a mesma instância do TestNum, eles não interferem entre si, mas cada um gera números de sequência independentes. Isso ocorre porque o ThreadLocal fornece uma cópia separada para cada thread.
O desempenho e a otimização de bloqueio de "bloqueios" são um dos métodos de sincronização mais usados. No desenvolvimento normal, muitas vezes você pode ver muitos alunos adicionando diretamente uma grande parte do código ao bloqueio. Alguns alunos podem usar apenas um método de bloqueio para resolver todos os problemas de compartilhamento. Obviamente, essa codificação é inaceitável. Especialmente em ambientes de alta concorrência, a concorrência feroz de bloqueio levará a uma degradação mais óbvia do desempenho do programa. Portanto, o uso racional de bloqueios está diretamente relacionado ao desempenho do programa.
1. Sobrecarga de thread <r /> No caso de vários núcleos, o uso de multi-threading pode melhorar significativamente o desempenho do sistema. No entanto, em situações reais, o uso de multi-threading adicionará uma sobrecarga adicional do sistema. Além do consumo de recursos das próprias tarefas de sistema único, os aplicativos com vários threads também precisam manter informações exclusivas adicionais com vários threads. Por exemplo, os metadados do próprio thread, agendamento de threads, comutação de contexto de encadeamento etc.
2. Reduza o tempo de retenção de bloqueio
Nos programas que usam bloqueios para controle simultâneo, quando os bloqueios competem, o tempo de retenção de bloqueio de um único thread tem um relacionamento direto com o desempenho do sistema. Se o thread mantiver a fechadura por um longo tempo, a competição pela fechadura será mais intensa. Portanto, durante o processo de desenvolvimento do programa, o tempo de ocupar uma certa trava deve ser minimizada para reduzir a possibilidade de exclusão mútua entre os threads. Por exemplo, o seguinte código:
public sincronizado void syncmeHod () {beforemethod (); mutexMethod (); pós -meta ();} Se apenas o método mutexMethod () nesse caso for síncrono, mas no beforemethod () e pós -method () não requer controle de sincronização. Se Beforemethod () e Aftermethod () forem métodos de peso pesado, levará muito tempo para a CPU. No momento, se a simultaneidade for grande, o uso desse esquema de sincronização levará a um grande aumento nos threads de espera. Porque o encadeamento atualmente em execução será lançado o bloqueio somente após todas as tarefas terem sido executadas.
A seguir, é apresentada uma solução otimizada, que apenas sincroniza quando necessário, para que o tempo para que os threads mantenham bloqueios possam ser significativamente reduzidos e a taxa de transferência do sistema pode ser melhorada. O código é o seguinte:
public void syncmeHod () {beforemethod (); sincronizado (this) {mutexMethod ();} pós -meta ();} 3. Reduza o tamanho das partículas de travamento
Reduzir a granularidade de trava também é um meio eficaz para enfraquecer a competição por bloqueios com vários threads. O cenário de uso típico dessa tecnologia é a classe ConcurrentHashmap. No hashmap comum, sempre que uma operação add () ou get () é executada em uma coleção, a trava do objeto de coleta é sempre obtida. Esta operação é completamente um comportamento síncrono porque o bloqueio está em todo o objeto de coleção. Portanto, em alta concorrência, a concorrência feroz de bloqueio afetará a taxa de transferência do sistema.
Se você leu o código -fonte, deve saber que o hashmap é implementado em uma lista de matriz + vinculada. Concurrenthashmap divide todo o hashmap em vários segmentos (segmentos) e cada segmento é um sub-hashmap. Se você precisar adicionar uma nova entrada de tabela, não bloqueie o hashmap. A linha de pesquisa vinte obterá a seção na qual a entrada da tabela deve ser armazenada de acordo com o HashCode e, em seguida, bloqueia a seção e conclua a operação put (). Dessa forma, em um ambiente multithread, se vários threads executarem operações de gravação ao mesmo tempo, desde que o item que esteja sendo escrito não exista no mesmo segmento, o paralelismo verdadeiro pode ser alcançado entre os threads. Para uma implementação específica, espero que os leitores levem algum tempo para ler o código -fonte da classe Concurrenthashmap, para não descrever muito aqui.
4. Separação de bloqueio <Br /> Uma readwritelock Read and Write Lock mencionada anteriormente, então a extensão da separação de leitura e gravação é a separação do bloqueio. O código -fonte de separação de bloqueio também pode ser encontrado no JDK.
A classe pública LinkedBlockockQueue estende a abstrato implementa BlockingQueue, java.io.Serializable {/*Bloqueio de retenção por tomada, votação, etc/private final reentrantlock takelock = novo reentrantlock ();/** Espere a fila para a espera*/private condição*não, porttlock; putlock = new reentrantlock ();/** Espera a fila de espera, puts*/condição final privada notfull = putlock.newcondition (); public e the the () lança interruptedException {ex; int c = -1; contagem final do atomicinteger = this.count; Final Reentrantlock Takelock = this.takelock; Takelock.lockInterruptível (); // Não pode haver dois threads para ler dados ao mesmo tempo, tente {while (count.get () == 0) {// Se não houver dados disponíveis, aguarde a notificação de put () notempty.await (); } x = dequeue (); // remova um item c = count.getAndDecRement (); // tamanho menos 1 if (c> 1) notEmpty.signal (); // notificar outras operações de Take ()} finalmente {Takelock.unlock (); // Liberação de bloqueio} if (c == Capacidade) sinalizeNotfull (); // notify put () operação, já existe o retorno do espaço livre x;} public void put (e e) lança interruptedException {if (e == null) lança novo nullPointerException (); // NOTA: Convenção em todos os put/capt/etc é predefinir var // mantendo contagem negativa para indicar falha, a menos que seja definido. int c = -1; Nó <E> nó = novo nó (e); final reentrantlock putlock = this.putLock; contagem final do atomicinteger = this.count; putlock.lockInterruptível (); // Não pode haver dois threads colocar dados ao mesmo tempo, tente { / * * Observe que a contagem é usada no guarda de espera, mesmo que * não esteja protegido pelo bloqueio. Isso funciona porque a contagem pode * diminuir apenas neste momento (todos os outros postos são fechados * de fora por bloqueio), e nós (ou algum outro ponto de espera) somos * assinados se alguma vez mudar de capacidade. Da mesma forma * para todos os outros usos da contagem em outros guardas de espera. */ while (count.get () == Capacidade) {// Se a fila estiver cheia, espere notfull.await (); } Enqueue (nó); // junte -se à fila c = count.getAndIncrement (); // tamanho mais 1 if (c + 1 <capacidade) notfull.signal (); // notifique outros threads se houver espaço suficiente} finalmente {putlock.unlock (); // liberar o bloqueio} if (c == 0) sinalizeNotEmpty (); // Após o sucesso da inserção, notifique a operação () para ler dados} // outro código}O que precisa ser explicado aqui é que as funções de Take () e Put () são independentes uma da outra, e não há relação de concorrência de bloqueio entre elas. Você só precisa competir por Takelock e Putlock nos respectivos métodos de Take () e Put (). Assim, a possibilidade de concorrência de bloqueio é enfraquecida.
5. Bloquear a grosseria <br /> A redução mencionada acima do tempo de bloqueio e a granularidade é feita para atender ao menor tempo para cada thread manter a trava. No entanto, um diploma deve ser compreendido em granularidade. Se um bloqueio for constantemente solicitado, sincronizado e liberado, ele consumirá recursos valiosos do sistema e aumentará a sobrecarga do sistema.
O que precisamos saber é que, quando uma máquina virtual encontra uma série de solicitações e liberações contínuas do mesmo bloqueio, ele integrará todas as operações de bloqueio em uma solicitação ao bloqueio, reduzindo assim o número de solicitações para o bloqueio. Esta operação é chamada de bloqueio de bloqueio. Aqui está uma demonstração do exemplo de integração:
public void syncmeHod () {sincronizado (bloqueio) {method1 ();} sincronizado (bloqueio) {method2 ();}} o formulário após a integração JVM: public void syncmeHod () {synchronized (bloqueio) {Method1 (); Method2 ();}}}Portanto, essa integração oferece aos nossos desenvolvedores um bom efeito de demonstração na compreensão da granularidade da trava.
Computação paralela sem bloqueio <Br /> O acima passou muito tempo conversando sobre bloqueio e também é mencionado que o bloqueio trará sobrecarga de recursos adicionais para certa troca de contexto. Em alta simultaneidade, a feroz competição por "bloqueio" pode se tornar um gargalo do sistema. Portanto, um método de sincronização sem bloqueio pode ser usado aqui. Esse método-livre de bloqueio ainda pode garantir que os dados e programas mantenham consistência entre vários threads em um ambiente de alta simultaneidade.
1. Sincronização não bloqueadora/sem trava
O método de sincronização sem bloqueio é realmente refletido no threadlocal anterior. Cada encadeamento possui sua própria cópia independente de variáveis; portanto, não há necessidade de esperar um pelo outro ao calcular em paralelo. Aqui, o autor recomenda principalmente um método de controle de simultaneidade mais importante de bloqueio com base no algoritmo Compare e Swap Cas.
O processo do algoritmo CAS: ele contém 3 parâmetros CAS (V, E, N). V representa a variável a ser atualizada, e representa o valor esperado e n representa o novo valor. O valor de V será definido como n somente quando o valor V for igual ao valor E. Se o valor V for diferente do valor E, significa que outros threads fizeram atualizações e o thread atual não fará nada. Finalmente, o CAS retorna o valor verdadeiro do V. atual ao operar CAS, é realizado com uma atitude otimista e sempre acredita que pode concluir com êxito a operação. Quando vários threads usam CAS para operar uma variável ao mesmo tempo, apenas um vence e será atualizado com sucesso, enquanto o restante do Junhui falha. O encadeamento falhado não será suspenso, é informado apenas de que a falha é permitida e é permitido tentar novamente e, é claro, o encadeamento falhado também permitirá que a operação seja abandonada. Com base nesse princípio, a operação do CAS é oportuna sem bloqueios e outros threads também podem detectar interferência no encadeamento atual e manipulá -lo adequadamente.
2. Operação de peso atômico
O pacote Java.util.Concurrent.Acurrent. Os alunos interessados podem continuar a rastrear o código de nível nativo. Não vou postar a implementação do código de superfície aqui.
O seguinte usa principalmente um exemplo para mostrar a lacuna de desempenho entre os métodos de sincronização comuns e a sincronização sem bloqueio:
public class Testatomic {private estático final int max_threads = 3; private estático final int task_count = 3; private estático final int alvo_count = 100 * 10000; private atomicinteger conta = new atomicInteger (0); private int count = 0; synchrized int inc () {Return +contagem; {Nome da string; tempo de início longo; Testatomic out; public syncthread (testatomic o, long starttime) {this.out = o; this.startTime = startTime; } @Override public void run () {int v = out.inc (); while (V <Target_Count) {v = out.inc (); } long endtime = system.currenttimemillis (); System.out.println ("SYNCTHREAD SPSE:" + (ENDTime - StartTime) + "MS" + ", V =" + V); }} classe pública AtomicThread implementa Runnable {Nome da String; tempo de início longo; public atomicThread (long starttime) {this.startTime = startTime; } @Override public void run () {int v = Account.incremendget (); while (V <Target_Count) {v = Account.incremendget (); } long endtime = system.currenttimemillis (); System.out.println ("AtomicThread gasto:" + (EndTime - StartTime) + "MS" + ", v =" + V); }}@Testpublic void testsync () lança interruptedException {executorService exe = executores.newfixedthreadpool (max_threads); long startTime = System.currenttimemillis (); SYNCTHREAD SYNC = new SyncThread (this, starttime); for (int i = 0; i <task_count; i ++) {exe.submit (sincronização); } Thread.sleep (10000);}@testpublic void testAtomic () lança interruptedException {executorService exe = executores.newfixedthreadpool (max_threads); long startTime = System.currenttimemillis (); Atomicthread atomic = new atomicThread (starttime); for (int i = 0; i <task_count; i ++) {exe.submit (atomic); } Thread.sleep (10000);}} Os resultados dos testes são os seguintes:
testsync ():
Gasto de síncthread: 201ms, v = 1000002
Gasto de sinchread: 201ms, v = 1000000
Gasto de síncthread: 201ms, v = 1000001
testatomic ():
Gaste AtomicThread: 43ms, v = 1000000
Gaste AtomicThread: 44ms, v = 1000001
Gaste AtomicThread: 46ms, v = 1000002
Acredito que esses resultados dos testes refletirão claramente as diferenças de desempenho entre o bloqueio interno e os algoritmos de sincronização não bloqueadores. Portanto, o autor recomenda considerar diretamente essa classe atômica sob atômico.
Conclusão
Finalmente, resolvi as coisas que quero expressar. De fato, ainda existem algumas classes como o Countdownlatch que não foram mencionadas. No entanto, o que é mencionado acima é definitivamente o núcleo da programação simultânea. Talvez alguns leitores possam ver muitos desses pontos de conhecimento na Internet, mas ainda acho que apenas em comparação o conhecimento pode ser encontrado em um cenário de uso adequado. Portanto, essa também é a razão pela qual o editor compilou este artigo, e espero que este artigo possa ajudar mais alunos.
O exposto acima é todo o conteúdo deste artigo. Espero que seja útil para o aprendizado de todos e espero que todos apoiem mais o wulin.com.