1 O que é um problema de simultaneidade.
Vários processos ou threads acessando o mesmo recurso simultaneamente (ou no mesmo período de tempo) causarão problemas de simultaneidade.
Um exemplo típico é que dois operadores bancários operam a mesma conta ao mesmo tempo. Por exemplo, os operadores A e B leem uma conta com um saldo de 1.000 yuans ao mesmo tempo, o operador A adiciona 100 yuan à conta, o Operador B também reduz 50 yuan para a conta, A submetido primeiro e B envia posteriormente. O saldo final da conta real é de 1000-50 = 950 yuan, mas deveria ter sido 1000+100-50 = 1050. Este é um problema típico de simultaneidade. Como resolvê -lo? Pode usar bloqueios.
2Usage de sincronizado em Java
Uso 1
public class Test {public sincronizado void print () {….;}}Se um thread executar o método print (), o objeto será bloqueado. Outros threads não poderão executar todos os blocos sincronizados do objeto.
Uso 2
public class Test {public void print () {sincronizado (this) {// bloqueia este objeto…;}}} O mesmo uso 1, mas pode refletir melhor a essência do uso sincronizado.
Uso 3
public class Test {private string a = "test"; public void print () {sincronizado (a) {// bloqueia um objeto ...;}} public sincronizado void t () {…; // Este bloco de código sincronizado não será bloqueado por causa da impressão ().}}}}}A execução do print () bloqueará o objeto a. Observe que ele não está bloqueando o objeto de teste, o que significa que outros métodos sincronizados do objeto de teste não serão bloqueados devido à impressão (). Após a execução do bloco de código de sincronização, o bloqueio para A é liberado.
Para bloquear o bloco de código de um objeto sem afetar a escrita de alto desempenho de outros blocos sincronizados do objeto:
public class Test {private byte [] Lock = new Byte [0]; public void print () {Synchronized (Lock) {…;}} public sincronizado void t () {…;}}Bloqueio de método estático
public class Test {public sincronizado estático void Execute () {…;}}O mesmo efeito
public class Test {public static void Execute () {Synchronized (testThread.class) {…;}}}3 fechaduras em java e na fila para ir ao banheiro.
O bloqueio é uma maneira de impedir que outros processos ou threads acessem recursos, ou seja, o recurso bloqueado não pode ser acessado por outras solicitações. Em Java, a palavra -chave sycronizada é usada para bloquear um objeto. por exemplo:
classe pública mystack {int idx = 0; char [] dados = novo char [6]; public sincronizado void push (char c) {data [idx] = c; idx ++;} public syncronized char pop () {idx-; dados de retorno [idx];} public mystatic void main (strings []) {{ Estritamente falando, todos os blocos sincronizados do objeto M estão bloqueados. Se houver outro thread t tentando acessar M, não poderá executar os métodos Push e Pop do objeto M. */m.pop (); // objeto m está bloqueado. }}O desbloqueio de bloqueio de Java é exatamente o mesmo que várias pessoas fazendo fila para um banheiro público. A primeira pessoa trancou a porta por dentro depois de entrar, então os outros tiveram que esperar na fila. A porta só abrirá (desbloquear) quando a primeira pessoa sair após o final. Era a vez da segunda pessoa entrar, e ele trancava a porta por dentro novamente, e os outros continuavam esperando na fila.
É fácil entender o uso da teoria do banheiro: quando uma pessoa entra em um banheiro, o banheiro será trancado, mas não fará com que o outro banheiro seja trancado, porque uma pessoa não pode se agachar em dois banheiros ao mesmo tempo. Para Java, isso significa: os bloqueios em Java são direcionados ao mesmo objeto, não na classe. Veja o seguinte exemplo:
MyStatckm1 = newMyStack (); myStatckm2 = newmyStatck (); m1.pop (); m2.pop ();
Os bloqueios dos objetos M1 não afetarão as fechaduras de M2 porque não são a mesma posição do banheiro. Ou seja, se houver 3 threads T1, T2 e T3 operando M1, esses 3 threads só podem fazer fila e esperar no M1. Supondo que os outros 2 threads T8, T9 operando M2, o T8, T9, apenas aguardará o M2. T2 e T8 não têm nada a ver com isso. Mesmo que a trava no M2 seja liberada, T1, T2 e T3 ainda podem ter que fazer fila no M1. Não há razão, não é o mesmo assento no vaso sanitário.
O Java não pode adicionar dois bloqueios a um bloco de código ao mesmo tempo. Isso é diferente do mecanismo de bloqueio do banco de dados. O banco de dados pode adicionar vários bloqueios diferentes a um registro ao mesmo tempo.
4 Quando devo liberar a fechadura?
Geralmente, o bloqueio é liberado após a execução do bloco de código de sincronização (bloco de código bloqueado), ou o bloqueio pode ser liberado no meio do método Wait (). O método wait () é como agachamento no banheiro no meio do caminho e de repente eu descobri que o esgoto estava bloqueado. Eu tive que sair e ficar de lado para que o reparador de esgoto (um fio que esteja pronto para executar notificar) pudesse entrar e limpar o banheiro. Após o desbloqueio, o mestre gritou: "Foi reparado". O camarada que saiu agora se alinhou novamente depois de ouvi -lo. Preste atenção, você deve esperar o mestre sair. Se o mestre não sair, ninguém pode entrar. Ou seja, depois de notificar, outros threads podem entrar na área bloqueada e se mover imediatamente, mas outros threads podem entrar após a área bloqueada onde o código de notificação é executado e liberado para liberar o bloqueio.
Aqui está o exemplo de código de espera e notificação:
public sincronizado char pop () {char c; while (buffer.size () == 0) {try {this.wait (); // fora do banheiro} catch (interruptedException e) {// ignore…}} c = ((caractere) buffer.remove (buffer.size ()-1). Charvalue (); retornar C;} public sincronizado void push (char c) {this.notify (); // notifique esses threads wait () para fazer fila novamente. NOTA: Apenas notifique-os para requeule. Caractere charobj = novo caractere (c); buffer.addElement (charobj);} // execute e libere o bloqueio. Esses tópicos na fila podem entrar.Vá mais fundo.
Devido à operação Wait (), os camaradas que saíram no meio do caminho não faziam fila até receber o sinal de notificação. Ele assistia as pessoas na fila ao lado dele (o mestre de reparo de tubos de água também está entre elas). Observe que o mestre de reparo do tubo de água não pode ir à linha e ele deve fazer fila como aqueles que vão ao banheiro. Não é que, depois que uma pessoa se agacha no meio do caminho, o mestre de reparo do tubo de água pode aparecer de repente e entrar para repará -lo imediatamente. Ele quer competir de maneira justa com as pessoas que estavam originalmente na fila, porque ele também é um tópico comum. Se o mestre de reparo do tubo de água estiver ladeado para trás, a pessoa na frente descobrirá que está bloqueada e aguarde, depois saia e fique de lado, espere, saia, fique de lado e só vá ao mestre para entrar e executar notificar. Dessa forma, depois de um tempo, haverá um monte de pessoas ao lado da fila, esperando a notificação.
Finalmente, o mestre entrou e notifica. O que vem a seguir?
1. Uma pessoa de espera (thread) é notificada.
2. Por que ele é notificado em vez de outra pessoa de espera? Depende da JVM. Não podemos preencher
Determine qual será notificado. Em outras palavras, aqueles com alta prioridade não podem ser despertados primeiro, esperando
Qualquer momento não é necessariamente despertado primeiro, tudo é imprevisível! (Claro, se você conhece a JVM
Se implementado, você pode prever).
3. Ele (o tópico notificado) precisa fazer fila novamente.
4. Ele estará em primeiro lugar na linha? A resposta é: não tenho certeza. Ele será o último? Não necessariamente.
Mas se a prioridade do encadeamento for definida como relativamente alta, a probabilidade de ser classificada em primeiro lugar é relativamente alta.
5. Quando for a sua vez de entrar novamente no assento do banheiro, ele continuará a executar a partir do último local de espera () e não reexecionará.
Para colocá -lo de uma maneira nojenta, ele continuará divagando, não para divagar novamente.
6. Se o Master notifyAll (), todas as pessoas que desistiram no meio do caminho se alinharão novamente. A ordem é desconhecida.
O Javadoc diz que o TheAwakenedThreadWillNotbeableToProecedEnTilthecurrentThreadRelinQuishThelockOnThisObject (o thread despertado não pode ser executado antes que o encadeamento atual libere o bloqueio).
É óbvio explicar o uso da teoria do assento do vaso sanitário.
5lock Uso
Use a palavra -chave sincronizada para bloquear os recursos. A palavra -chave de bloqueio também está ok. É novo no JDK1.5. O uso é o seguinte:
classe limitebuffer {Final Lock = new reentrantlock (); condição final notfull = Lock.NewCondition (); condição final NotEmpty = Lock.NewCondition (); Final Object [] itens = novo objeto [100]; Int Putptr, Takeptr, contagem; notfull.await (); itens [putptr] = x; if (++ putptr == items.length) putptr = 0; ++ count; notEmpty.signal ();} finalmente {Lock.unlock ();}}} objeto público tire () throwSceptException {Lock.Lock ();}; itens [Takeptr]; if (++ Takeptr == Items.length) Takeptr = 0;-count; notfull.signal (); retornar x;} finalmente {Lock.unlock ();}}}}(Nota: este é um exemplo no Javadoc, um exemplo de implementação de uma fila de bloqueio. A chamada fila de bloqueio significa que, se uma fila estiver cheia ou vazia, isso causará bloqueio e espera de threads. O quadro de matriz em Java fornece uma fila de bloqueio pronta e você não precisa escrever um deles especificamente.)
O código entre Lock.lock () e Lock.unlock () de um objeto será bloqueado. Qual é a melhor coisa desse método em comparação com a sincronizar? Em suma, ele classifica os threads de espera. Para descrevê -lo usando a teoria do assento do vaso sanitário, aqueles que se agacham no meio do caminho e saem do assento do vaso sanitário e esperam podem ter diferentes motivos. Alguns são porque o banheiro está bloqueado e outros são porque o banheiro está sem água. Quando notificar, você pode gritar: se você esperar o bloqueio do banheiro, estará de volta na fila (por exemplo, o problema do banheiro bloqueado foi resolvido) ou gritará, se você esperar que o banheiro seja bloqueado, estará de volta na linha (por exemplo, o problema do banheiro a ser bloqueado foi resolvido). Isso permite um controle mais detalhado. Ao contrário da espera e notificar em sincronizar, se o banheiro está bloqueado ou o banheiro não está aquoso, você só pode gritar: apenas esperei aqui para fazer fila! Se as pessoas na fila entram e olham, descobrem que o problema do banheiro está resolvido e o problema de que estão ansiosos para resolver (o banheiro não tem água) ainda não foi resolvido, então precisam voltar e esperar (esperar) e entrarem para uma caminhada em vão, a perda de tempo e os recursos.
A relação correspondente entre o método de bloqueio e sincronizado:
LockawaitSignalSignalall
SynchronizedWaitNotifyNotifyAll
Nota: Não ligue para esperar, notificar, notificar todos em blocos bloqueados por bloqueio
6. Use oleodutos para se comunicar entre threads
O princípio é simples. Dois threads, um opera o PipedInputStream e o outro opera o PipedOutputStream. Os dados escritos pelo PipedOutputStream são em cache no buffer primeiro. Se o buffer estiver cheio, este thread espera. PipedInputStream lê os dados no buffer. Se o buffer não tiver dados, este thread aguardará.
A mesma função pode ser alcançada bloqueando filas no JDK1.5.
pacote io; importar java.io.*; classe pública PipedStreamtest {public static void main (string [] args) {pipedOutputStream ops = new PipedOutputStream (); PipedInputStream Pis = New PipedInputStream (); Try {OpS.Connect (pis); Consumidor (pis) .run ();} catch (Exceção e) {e.printStacktrace ();}}} // Classe de produtora Produtor implementa Runnable {private PipedOutputStream OPS; Public Producer (PoledututputStream Ops) {this.Ops = Ops;} public vaid run () {try {ops.write ("inferno, spell" .getBytes ()); ops.close ();} catch (exceção e) {e.printStacktrace ();}}} // classe de consumidor implementa runnable {private PipedInputream pis; public consumer (popedInputStream pis) {private PipedInputream. run () {tente {byte [] bu = novo byte [100]; int len = pis.read (bu); system.out.println (new string (bu, 0, len)); pis.close ();} catch (exceção e) {e.printstacktrace ();}}}}}Exemplo 2: Uma pequena mudança no programa acima se torna dois threads.
pacote io; importar java.io.*; public class PipedStreamtest {public static void main (string [] args) {pipedOutputStream ops = new PipedOutputStream (); PipedInputStream pis = new PipedInputStream (); tentativa {ops.conect (pis); Thread (p) .start (); consumidor c = novo consumidor (pis); new Thread (c) .start ();} catch (exceção e) {e.printStacktrace ();}}}} // Classe de produtores »OpsTupream Opsnable {private PipedOutputSTream. run () {try {for (;;) {ops.write ("inferno, spell" .getBytes ()); ops.close ();}}} // classe de consumo implementos de consumidor runnable {{isto.pedInputStream pis; public consumer (pipedInputSstream pis) {this.pis = pis; bu = novo byte [100]; int len = pis.read (bu); system.out.println (new string (bu, 0, len));} pis.close ();} catch (exceção e) {e.printstacktrace ();}}}Exemplo 3. Este exemplo é mais apropriado para o aplicativo
importar java.io. pos.connect (pis); // Construa dois threads e inicie. novo remetente (POS, "c: /text2.txt"). start (); novo receptor (pis, "c: /text3.txt"). start ();} catch (ioexception e) {system.out.println ("erros de tubo" + e);}}}} // thread envia a classificação de thread {pip pip pip {pip pip); Remeter (pipedOutputStream pos, string filename) {this.pos = pos; file = new file (nome do arquivo);} // thread run método public void run () {try {// leia o arquivo de conteúdo de conteúdo de arquivo startStream fs = new FileInTStream (file); int; pos.write (dados);} pos.close ();} catch (ioexception e) {System.out.println ("Erro do remetente" +e);}}}} // thread class Receiver.Sende Thread {PipedInputStream pis; Arquivo; Arquivo // Método de construção RECEBION (PIPEDINSTEM PIS, PIS PIS, FILDING, Arquivo (nome do arquivo);} // Thread executa public void run () {try {// grava o fluxo de arquivo objeto FileOutputStream fs = new FileOututputStream (file); int data; // leia while (data = pis.read ())! e) {System.out.println ("Erro do receptor" +e);}}}7 Fila de bloqueio
A fila de bloqueio pode substituir o método de fluxo de tubulação para implementar o modo de tubo de entrada/drenagem (produtor/consumidor). O JDK1.5 fornece várias filas de bloqueio prontas. Agora, vejamos o código de ArrayBlockingQueue da seguinte maneira:
Aqui está uma fila de bloqueio
BlockingQueue BlockingQ = New ArrayBlockingQueue 10;
Um fio retira da fila
para (;;) {objeto o = blockingq.take (); // a fila está vazia, então espere (bloqueando)}Outro tópico é armazenado na fila
para (;;) {BlockingQ.put (new Object ()); // Se a fila estiver cheia, espere (bloqueando)}Pode -se observar que as filas de bloqueio são mais simples de usar do que os pipelines.
8Use Executores, Executor, ExecutorService, ThreadpoolExecutor
Você pode usar tarefas de gerenciamento de threads. Você também pode usar um conjunto de classes fornecidas pelo JDK1.5 para gerenciar mais convenientemente tarefas. A partir dessas categorias, podemos experimentar uma maneira de pensar orientada para tarefas. Essas aulas são:
Interface do executor. Como usar:
Executor Executor = Anexecutor; // Gere uma instância do executor. executor.execute (novo runnableTask1 ());
Intenção: os usuários se concentram apenas na execução de tarefas e não precisam se preocupar com a criação de tarefas e detalhes de execução e outros problemas com os quais os implementadores de terceiros estão preocupados. Ou seja, desacoplar a execução da chamada de tarefas e a implementação da tarefa.
De fato, já existem excelentes implementações dessa interface no JDK1.5. Suficiente.
Os executores são uma classe de fábrica ou classe de ferramentas, como coleções, usadas para gerar instâncias de várias interfaces.
A interface ExecorService é herdada do Executor.Executor apenas lança a tarefa no Executor () para execução e ignora o restante. ExecutorService é diferente, fará mais trabalho de controle. por exemplo:
Classe NetworkService {private final ServerSocket ServerSocket; privado Final ExecutorService Pool; Public NetworkService (Int Port, Int PoolSize) lança IoException {ServerSocket = new ServerSocket (porta); pool = executores.NewfixedThreadpool (Poolsize);} public void servir () {Try {para (; Handler (serversocket.accept ()));}} catch (ioexception ex) {pool.shutdown (); // Nenhuma nova tarefa é executada}}} classe Handler implementa Runnable {} private Socket Socket;Depois que o ExecutorService (ou seja, o objeto do pool no código) executa o desligamento, ele não pode mais executar novas tarefas, mas as tarefas antigas continuarão sendo executadas e aqueles que aguardam a execução não esperarão mais.
Submissor de tarefas e comunicação de artistas
public static void main (string args []) lança exceção {ExecutorService Execor (Executores.NewsingleThreadExecutor (); Task Callable = new Callable () {public String Call () lança exceção {Return "Test";}}; Future f = executor.submit (); System.out.println (resultado); executor.shutdown ();}Instância do Executor obtida por Executores.NewsingLethReadExecutor () tem as seguintes características:
Execução de tarefas sequencialmente. Por exemplo:
executor.submit (Task1); executor.submit (Task2);
Você deve aguardar a execução da tarefa1 antes que o Task2 possa ser executado.
Task1 e Task2 serão colocados em uma fila e processados por um tópico do trabalhador. Ou seja: existem 2 threads no total (encadeamento principal, encadeamento do trabalhador que processa tarefas).
Para outras aulas, consulte Javadoc
9 Controle de processo simultâneo
Os exemplos nesta seção são do tutorial de concorrência Java de Wen Shao e podem ser alterados. Saudação ao Sr. Wen.
Contador de alfinetes de porta de contagem regressiva
Inicie o tópico e aguarde o thread termine. Ou seja, o fio principal comum e outros threads infantis são executados após o final do encadeamento principal.
public static void main (string [] args) lança exceção {// TODO Method Auto-Gerated Stub Final Int count = 10; Final CountdownLatch CompletLatch = new CountdownLatch (contagem); // Defina o número de lascas da porta (Thread (Int I) para (Int i = 0; i <count; i ++) {thread = thread ("trabalhador (" Trama "" I) I) I) {RUN) {thread ("Thread (" Thread (thread) para (Int I = 0; i <count; i ++) {thread = thread ("trabalhador (" completelatch.CountDown (); // Reduza uma trava de porta}}; thread.start ();} completelatch.await (); // Se a trava da porta ainda não foi reduzida, espere. }No JDK1.4, o método comum é definir o status do encadeamento filho e a detecção de loop do encadeamento principal. Facilidade de uso e eficiência não são boas.
Comece muitos tópicos e aguarde as notificações começarem
public static void main (string [] args) lança exceção {// TODO Método Gerado de Auto-Gerado Stub Final CountdownLatch StartLatch = new CountdownLatch (1); // Defina uma trava de porta para (int i = 0; i <10; i ++) {thread thread = thread ("trabalhador"+i) {Void run () {{run (public) {thread thread = thread ("trabalhador"+i) (public) (public) (if; reduzido ainda, espere} catch (interruptedException e) {} // do xxxx}}; thread.start ();} startLatch.countdown (); // reduz uma trava de porta}Ciclibarrier. Somente depois que todos os threads atingem uma linha de partida, eles podem continuar correndo.
public class Cyclibarriert mais implementa Runnable {Barreira de Ciclibarrier Private; Public CyclibarrierTest (barreira de ciclibarrier) {this.Barrier = barrier;} public void run () {// do lames se encadeará se o thread se encaixe. Se você não chegar, continue esperando. When all is reached, execute the contents of the barrier's run function body}catch (Exception e) {}}/** * @param args */public static void main(String[] args) {//parameter 2 means that both threads have reached the starting line before they start to execute together CyclicBarrier barrier = new CyclicBarrier(2, new Runnable() {public void run() {// do xxxx;}}); thread t1 = new Thread (novo ciclibarriertest (barreira)); thread t2 = novo thread (novo ciclibarriertest (barreira)); t1.start (); t2.start ();}}Isso simplifica a maneira tradicional de implementar essa função com o Counter + Wait/NotifyAll.
10 Lei de Concorrência 3
Lei de Amdahl. Dada a escala do problema, a parte da paralelização é responsável por 12%. Então, mesmo que o paralelismo seja aplicado ao extremo, o desempenho do sistema só pode ser melhorado em 1/(1-0,12) = 1,136 vezes no máximo. Ou seja: o paralelismo tem um limite superior para melhorar o desempenho do sistema.
Lei de Gustafson. A lei de Gustafson diz que a lei de Amdahl não considera que mais poder de computação pode ser usado à medida que o número de CPUs aumenta. A essência é alterar a escala do problema, para que os 88% restantes do processamento em série na lei de Amdahl possam ser paralelos, rompendo assim o limite de desempenho. Em essência, é uma espécie de tempo de troca espacial.
Lei Sun-Ni. É uma promoção adicional das duas primeiras leis. A idéia principal é que a velocidade da computação seja limitada pela velocidade do armazenamento e não pela CPU. Portanto, devemos fazer pleno uso de recursos de computação, como espaço de armazenamento e tentar aumentar a escala do problema para produzir soluções melhores/mais precisas.
11 de simultâneo ao paralelo
Os computadores precisam calcular rapidamente ao identificar objetos, para que os chips fiquem quentes e quentes. Quando as pessoas reconhecem objetos, elas são claras de relance, mas não fazem com que uma certa célula cerebral seja queimada (exagerada) e se sinta desconfortável. Isso ocorre porque o cérebro é um sistema de operação paralelo distribuído. Assim como o Google pode usar alguns servidores Linux baratos para executar cálculos enormes e complexos, inúmeros neurônios no cérebro calculam independentemente, compartilhando os resultados entre si e concluir instantaneamente o efeito que requer trilhões de operações para uma única CPU. Imagine, se for criado no campo do processamento paralelo, terá um impacto incomensurável no desenvolvimento e no futuro dos computadores. Obviamente, os desafios também podem ser imaginados: muitos problemas não são facilmente "divididos".
Resumir
O exposto acima é o conteúdo inteiro deste artigo sobre o problema de simultaneidade do Java, e espero que seja útil para todos. Amigos interessados podem continuar se referindo a outros tópicos relacionados neste site. Se houver alguma falha, deixe uma mensagem para apontá -la. Obrigado amigos pelo seu apoio para este site!