Java5 já contém bloqueios de leitura e gravação no pacote java.util.concurrent. No entanto, devemos entender os princípios por trás de sua implementação.
Implementação de Java do bloqueio de leitura/gravação
Vamos primeiro dar uma visão geral das condições para ler e escrever acesso a recursos:
A leitura de nenhum thread está fazendo a operação de gravação e nenhum thread está solicitando a operação de gravação.
Nenhum thread está fazendo operações de leitura e gravação.
Se um thread quiser ler um recurso, desde que nenhum thread esteja escrevendo para o recurso e nenhuma solicitação de thread que gravasse no recurso. Assumimos que os pedidos de operações de gravação sejam mais importantes do que os pedidos de operações de leitura, por isso devemos aumentar a prioridade das solicitações de gravação. Além disso, se as operações de leitura ocorrerem com frequência e não aumentarmos a prioridade das operações de gravação, a "fome" ocorrerá. O encadeamento solicitando que a operação de gravação será bloqueado até que todos os threads de leitura sejam desbloqueados do ReadWritelock. Se as permissões de operação de leitura do novo thread forem sempre garantidas, o encadeamento aguardando a operação de gravação continuará bloqueando e o resultado será "fome". Portanto, a operação de leitura só pode ser garantida para continuar quando nenhum thread estiver travando o ReadWritelock para operações de gravação, e nenhuma solicitação de thread que o bloqueio esteja pronto para operações de gravação.
Quando outros threads não leem ou escrevem operações no recurso compartilhado, um thread pode obter um bloqueio de gravação para o recurso compartilhado e, em seguida, escrever operações no recurso compartilhado. Não importa quantos tópicos solicitem bloqueios de gravação e em que ordem, a menos que você queira garantir a justiça da solicitação de bloqueio de gravação.
De acordo com a descrição acima, um bloqueio de leitura/gravação é simplesmente implementado, e o código é o seguinte
classe pública ReadWritelock {Private int Readers = 0; Escritores privados int = 0; private int writerequests = 0; public sincronizado void LockRead () lança interruptedException {while (escritores> 0 || writerequests> 0) {wait (); } leitores ++; } public sincronizado void desnLockRead () {Readers--; notifyAll (); } public sincronizado void LockWrite () lança interruptedException {writeRequests ++; while (leitores> 0 || escritores> 0) {wait (); } writerequests--; escritores ++; } public sincronizado void desnLockWrite () lança interruptedException {Writers--; notifyAll (); }}Na aula de ReadWritelock, leia o bloqueio e o bloqueio de escreva, cada um tem um método para adquirir e liberar o bloqueio.
A implementação do Read Lock está em LockRead (). Desde que nenhum thread tenha um bloqueio de gravação (escritores == 0) e nenhum thread está solicitando um bloqueio de gravação (writerequests == 0), todos os threads que desejam obter um bloqueio de leitura podem ser obtidos com sucesso.
A implementação do bloqueio de gravação está em LockWrite (). Quando um thread deseja obter um bloqueio de gravação, ele primeiro adicionará 1 ao número da solicitação de bloqueio de gravação (WriteRequests ++) e, em seguida, determine se ele pode realmente obter um bloqueio de gravação. Quando nenhum thread segura um bloqueio de leitura (leitores == 0) e nenhum thread segura um bloqueio de gravação (escritores == 0), você pode obter um bloqueio de gravação. Não importa quantos tópicos estão solicitando para escrever bloqueios.
Deve -se notar que, em ambos o desbloqueio, desbloqueio, o método notifyAll é chamado em vez de notificar. Para explicar esse motivo, podemos imaginar a seguinte situação:
Se um thread estiver esperando para adquirir o bloqueio de leitura e um thread estiver esperando para adquirir o bloqueio de gravação. Se um dos threads aguardando o bloqueio de leitura for despertado pelo método Notify, mas como ainda existe um thread solicitando o bloqueio de gravação (WriteRequests> 0), o thread despertado entrará no estado de bloqueio novamente. No entanto, nenhum dos threads que aguardam o bloqueio de gravação foram despertados, como se nada tivesse acontecido (nota do tradutor: perda de sinal). Se você usar o método NotifyAll, todos os threads serão despertados e determinará se eles podem obter o bloqueio solicitado.
Há também um benefício em usar o notifyAll. Se vários threads de leitura estiverem aguardando o bloqueio de leitura e nenhum thread estiver aguardando o bloqueio de gravação, depois de ligar para o UnlockWrite (), todos os threads que aguardam o bloqueio de leitura podem adquirir imediatamente o bloqueio de leitura - em vez de apenas um de cada vez.
Reentrada do bloqueio de leitura/gravação
O bloqueio de leitura/gravação implementado acima não é reentrante e será bloqueado quando um thread que já mantém as solicitações de bloqueio de gravação o bloqueio de gravação novamente. A razão é que já existe um tópico de escrita - é ele mesmo. Além disso, considere o seguinte exemplo:
Para tornar o ReadWritelock reentrável, algumas melhorias precisam ser feitas. A seguir, lidará com a reentrada do LEAD Lock e a reentrada do Write Lock, respectivamente.
Leia o reinte da LOED
Para fazer com que o bloqueio de leitura do ReadWritelock Reentrant, devemos primeiro estabelecer regras para o reentrante de leitura do Lock:
Para garantir que o bloqueio de leitura em um thread seja reentrante, atenda às condições para obter o bloqueio de leitura (sem solicitação de gravação ou gravação) ou já mantenha o bloqueio de leitura (independentemente de haver uma solicitação de gravação ou não). Para determinar se um thread já possui um bloqueio de leitura, um mapa pode ser usado para armazenar o thread que já possui um bloqueio de leitura e o número de vezes que o thread correspondente adquire um bloqueio de leitura. Quando é necessário determinar se um thread pode obter um bloqueio de leitura, os dados armazenados no mapa são usados para fazer um julgamento. A seguir, é apresentado o código modificado dos métodos LockRead e UnlockRead:
classe pública ReadWritelock {Private Map <Thread, Integer> ReadingThreads = new Hashmap <Thread, Integer> (); Escritores privados int = 0; private int writerequests = 0; public sincronizado void LockRead () lança interruptedException {thread CallingThread = thread.currentThread (); while (! cangrantetAccess (CallingThread)) {wait (); } ReadingThreads.put (CallingThread, (getAccessCount (CallingThread) + 1)); } public sincronizado void desnLockRead () {thread CallingThread = thread.currentThread (); int accessCount = getAccessCount (CallingThread); if (accessCount == 1) {readingthreads.remove (CallingThread); } else {ReadingThreads.put (CallingThread, (AccessCount -1)); } notifyAll (); } private boolean cangrantreadAccess (thread CallingThread) {if (escritores> 0) retorna false; if (isreader (CallingThread) retorna true; if (writeRequests> 0) retorna false; retorna true;} private int getReadAccessCount (thread CallingThread) {Integer AccessCount = ReadingThreads.Tet (CallingThRead); se (} private boolan == null) return ! = nulo;No código, podemos ver que a reentrada de bloqueios de leitura é permitida apenas se nenhum thread tiver um bloqueio de gravação. Além disso, os bloqueios de leitura reentrante têm maior prioridade do que os bloqueios de gravação.
Escreva o reinte da Lock
A reentrada de bloqueio de gravação é permitida apenas se um thread já possuir um bloqueio de gravação (recupere o bloqueio de gravação). A seguir, é apresentado o código modificado dos métodos LockWrite e UnlockWrite.
classe pública ReadWritelock {Private Map <Thread, Integer> ReadingThreads = new Hashmap <Thread, Integer> (); private int writeacceses = 0; private int writerequests = 0; threads privado writingthread = null; public sincronizado void LockWrite () lança interruptedException {writeRequests ++; Thread CallingThread = Thread.CurrentThread (); while (! canGrantWriteAccess (CallingThread)) {wait (); } writerequests--; WRITEACESSES ++; wringThread = CallingThread; } public sincronizado void desnLockWrite () lança interruptedException {writeacceses--; if (writeAccesses == 0) {wringThread = null; } notifyAll (); } private boolean canGrantWriteAccess (thread CallingThread) {if (hasReaders ()) retorna false; if (writingThread == NULL) retorna true; if (! isWriter (CallingThread)) retorna false; retornar true; } private boolean hasReaders () {return ReadingThreads.size ()> 0; } private boolean isWriter (Thread CallingThread) {return writingThread == CallingThread; }}Preste atenção em como lidar com isso ao determinar se o thread atual pode adquirir o bloqueio de gravação.
Leia a atualização do bloqueio para escrever bloqueio
Às vezes, queremos um thread que tenha um bloqueio de leitura para obter um bloqueio de gravação. Para permitir essas operações, esse thread deve ser o único thread com um bloqueio de leitura. WriteLock () precisa fazer algumas alterações para atingir esse objetivo:
classe pública ReadWritelock {Private Map <Thread, Integer> ReadingThreads = new Hashmap <Thread, Integer> (); private int writeacceses = 0; private int writerequests = 0; threads privado writingthread = null; public sincronizado void LockWrite () lança interruptedException {writeRequests ++; Thread CallingThread = Thread.CurrentThread (); while (! canGrantWriteAccess (CallingThread)) {wait (); } writerequests--; WRITEACESSES ++; wringThread = CallingThread; } public sincronizado void desnLockWrite () lança interruptedException {writeacceses--; if (writeAccesses == 0) {wringThread = null; } notifyAll (); } private boolean canGrantWriteAccess (thread CallingThread) {if (isonlyReader (CallingThread)) retorna true; if (hasReaders ()) retorna false; if (writingThread == NULL) retorna true; if (! isWriter (CallingThread)) retorna false; retornar true; } private boolean hasReaders () {return ReadingThreads.size ()> 0; } private boolean isWriter (Thread CallingThread) {return writingThread == CallingThread; } private boolean isonlyReader (thread) {return Readers == 1 && ReadingThreads.get (CallingThread)! = null; }}Agora, a classe ReadWritelock pode ser atualizada de um bloqueio de leitura para um bloqueio de gravação.
Escreva Lock Down para ler Lock
Às vezes, os threads que possuem bloqueios de gravação também desejam obter bloqueios de leitura. Se um thread tiver um bloqueio de gravação, naturalmente outros threads não podem ter um bloqueio de leitura ou um bloqueio de gravação. Portanto, não há perigo para um thread que tenha um bloqueio de gravação e, em seguida, obtém um bloqueio de leitura. Precisamos apenas fazer uma modificação simples para o método de canGrantetAccess acima:
classe pública ReadWritElock {private boolean cangrantreadAccess (thread CallingThread) {if (iswriter (CallingThread)) return true; if (writingThread! = null) retorna false; if (iSreader (CallingThread) retorna true; if (writerequests> 0) retornar false; retornar true;}}Implementação completa de readwritelock reentrante
Abaixo está a implementação completa do ReadWritelock. Para facilitar a leitura e a compreensão do código, o código acima foi simplesmente refatorado. O código refatorado é o seguinte.
classe pública ReadWritelock {Private Map <Thread, Integer> ReadingThreads = new Hashmap <Thread, Integer> (); private int writeacceses = 0; private int writerequests = 0; threads privado writingthread = null; public sincronizado void LockRead () lança interruptedException {thread CallingThread = thread.currentThread (); while (! cangrantetAccess (CallingThread)) {wait (); } ReadingThreads.put (CallingThread, (getReadAccessCount (CallingThread) + 1)); } private boolean cangrantreadAccess (thread CallingThread) {if (iswriter (CallingThread)) retorna true; if (hasWriter ()) retorna false; if (iSreader (CallingThread)) retorna true; if (hasWriteRequests ()) retorna false; retornar true; } public sincronizado void desnLockRead () {thread CallingThread = thread.currentThread (); if (! isreader (CallingThread)) {lança novo ilegalMonitorStateException ("O tópico de chamada não" + "segura um bloqueio de leitura neste readWritelock"); } int accessCount = getReadAccessCount (CallingThread); if (accessCount == 1) {readingthreads.remove (CallingThread); } else {ReadingThreads.put (CallingThread, (AccessCount -1)); } notifyAll (); } public sincronizado void LockWrite () lança interruptedException {writeRequests ++; Thread CallingThread = Thread.CurrentThread (); while (! canGrantWriteAccess (CallingThread)) {wait (); } writerequests--; WRITEACESSES ++; wringThread = CallingThread; } public sincronizado void desnLockWrite () lança interruptEdException {if (! isWriter (thread.currentThread ()) {lança new ilegalmonitorStateException ("Thread de chamadas não" + ", não é o bloqueio do readWriteLock"); } Private Boolean CanGrantWriteAcess (Thread Calling) if (AccessCount == NULL) Retorno 0; ReadingThreads.get (CallingThread)! = NULL; } private boolean hasWriter () {return writingthread! = null; } private boolean isWriter (Thread CallingThread) {return writingThread == CallingThread; } private boolean hasWriteRequests () {return this.WriteReQuests> 0; }}Ligue para o desbloqueio () em finalmente
Ao usar o ReadWritelock para proteger as zonas críticas, se a zona crítica pode lançar uma exceção, é importante chamar readunlock () e writeunllock () no bloco Finalmente. Isso é feito para garantir que o ReadWritelock possa ser desbloqueado com sucesso e outros threads podem solicitar o bloqueio. Aqui está um exemplo:
Lock.lockWrite (); tente {// faça código de seção crítico, que pode lançar a exceção} finalmente {Lock.unlockwrite ();}A estrutura de código acima pode garantir que o ReadWritelock também seja lançado quando uma exceção for lançada na área crítica. Se o método UnlockWrite não for chamado no bloco Finalmente, quando uma exceção for lançada na seção crítica, o ReadWritelock permanecerá no estado de bloqueio de gravação, o que fará com que todos os threads que chamam LockRead () ou LockWrite () bloqueie. O único fator que pode se unir readwritelock pode ser que o ReadWritelock seja reentrante. Quando uma exceção é lançada, o thread pode adquirir com êxito o bloqueio e executar a seção crítica e ligar para o UnlockWrite () novamente, que liberará o ReadWritelock novamente. Mas e se o fio não adquirir mais a fechadura? Portanto, chamar o UnlockWrite em finalmente é muito importante para escrever código robusto.
O exposto acima é a compilação de informações multi-threades Java. Continuaremos a adicionar informações relevantes no futuro. Obrigado pelo seu apoio a este site!