Como sincronizar Métodos ao usar Java Threads

Sempre que você trabalhar em um programa Java que usa threads, você tem que considerar a questão desagradável de simultaneidade. Em particular, o que se dois threads tenta aceder a um método de um objeto precisamente ao mesmo tempo? A menos que você programar com cuidado, o resultado pode ser desastroso. Um método que realiza um cálculo simples retorna resultados imprecisos.

Em um aplicativo bancário on-line, você pode descobrir que alguns depósitos são creditados duas vezes e algumas retiradas não são creditados em tudo. Em um sistema de pedidos on-line, a ordem de um cliente pode ficar gravado na conta de um cliente diferente.

A chave para lidar com problemas de simultaneidade é reconhecer métodos que atualizar dados e que pode ser chamado por mais de um segmento. Depois de identificar esses métodos, a solução é simples. Você acabou de adicionar o sincronizada palavra-chave para a declaração do método, como este:

pública sincronizado someMethod void () ...

Este código diz Java para colocar um trancar sobre o objeto de modo que não há outros métodos pode chamar quaisquer outros métodos sincronizados para o objeto até que este método termina. Em outras palavras, ele desativa temporariamente multithreading para o objeto.

Aqui estão alguns exemplos concretos. Este código cria uma instância da CountDownClock classe.

DoTwoThings classe de importação java.util.concurrent.ScheduledThreadPoolExecutor-públicas {ScheduledThreadPoolExecutor piscina = new ScheduledThreadPoolExecutor (2) -CountDownClock relógio = new CountDownClock (20) -public static void main (String [] args) {novas DoTwoThings () -} DoTwoThings ( ) {pool.execute (relógio) -pool.execute (relógio) -pool.shutdown () -}}

A saída resultante é uma miscelânea imprevisível de saídas de dois fios ', com algumas das contagens duplicadas e outros saltadas, como este:

T menos 20T menos 20T menos 19T menos 19T menos 18T menos 17T menos 16T menos 15T menos 13T menos 13T menos 12T menos 12T menos 11T menos 11T menos 10T menos T9 menos 7T menos 7T menos T6 menos T5 menos 4T menos 3T menos 2T minus 2T 1T menos 0

Os dois segmentos executar seus laços ao mesmo tempo, então depois de um segmento exibe seu T menos 20, o outro segmento exibe sua própria T menos 20. A mesma coisa acontece para T menos 19, T menos 18, e assim por diante.

Em seguida, este código gera dois segmentos, cada um dos quais executa uma cópia do CountDownClock O código de exemplo.

classe de importação java.util.concurrent.ScheduledThreadPoolExecutor-pública DoTwoThingsSync {piscina ScheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor (2) -CountDownClockSync relógio = new CountDownClockSync (20) -public static void main (String [] args) {new DoTwoThingsSync () -} DoTwoThingsSync ( ) {pool.execute (relógio) -pool.execute (relógio) -pool.shutdown () -}}

Java de sincronizada palavra-chave garante que apenas um thread por vez chama a corre método. A saída resultante mostra uma execução completa do corre método seguido por outro.

classe CountDownClockSync estende Thread {private int start-pública CountDownClockSync (início int) {this.start = arranque} sincronizado public void run () {for (int t = arranque t> = 0- t -) {System.out .println ( "T menos" + t) -tentar {Thread.sleep (1000) -} catch (InterruptedException e) {}}}}

chamadas as duas linhas "à corre método não são intercalados, de modo a contagem de saída para baixo de 20 para 0 e, em seguida, faz a contagem regressiva pela segunda vez desde 20 para 0:

T menos 19T 20T menos menos 18

E assim por diante, até

T menos 2T menos 1T menos 0T menos 19T 20T menos menos 18

E assim por diante, até

T menos 1T 2T minus 0

A parte difícil é saber quais os métodos para sincronizar. Qualquer método que atualiza as variáveis ​​de instância está em risco - e precisa ser sincronizado. Isso porque quando dois ou mais threads executar um método, ao mesmo tempo, os fios têm uma cópia comum de variáveis ​​de instância do método.

Mesmo métodos que consistem em apenas uma linha de código estão em risco. Considere este método:

int sequenceNumber = 0-public int getNextSequenceNumber () {return sequenceNumber ++ -}

Você acha que porque este método tem apenas uma declaração, algum outro segmento não pôde interrompê-lo no meio. Infelizmente, esse não é o caso. Este método deve obter o valor do número sequencial campo, adicionar 1 a ele, salvar o valor atualizado de volta para o número sequencial campo, e retornar o valor.

Na verdade, esta única instrução Java compila a 11 instruções de bytecode. Se a linha for preterido entre qualquer dessas bytecodes por outro segmento que chama o mesmo método, os números de série se munged.

Por razões de segurança, porque não basta fazer todos os métodos sincronizados? Você tem duas razões para não fazê-lo:

  • Sincronizando métodos leva tempo. Java tem de adquirir um bloqueio no objeto que está sendo sincronizado, executar o método, em seguida, liberar o bloqueio. Mas antes que ele possa fazer isso, tem que verificar para se certificar de que algum outro segmento não já tem um bloqueio no objeto. Todo este trabalho leva tempo.

  • Mais importante, a sincronização de todos os seus métodos derrota o propósito de multithreading, assim que você deve sincronizar somente os métodos que necessitam dele.

o sincronizada O palavra-chave não bloquear todo o acesso a um objeto. Outros tópicos ainda pode executar métodos não sincronizadas do objeto enquanto o objeto está bloqueado.

o Objeto classe fornece três métodos que podem deixar objetos sincronizadas coordenar suas atividades. o esperar método coloca um segmento no estado de espera até que algum outro segmento chama um objeto de notificar ou (mais comumente) notifyAll método. Estes métodos são úteis quando um segmento tem de esperar por outro segmento que fazer algo antes que ele possa prosseguir.

O exemplo clássico é um sistema bancário em que uma thread faz saques e o outro faz depósitos. Se o saldo da conta de um cliente cai para zero, o segmento que faz levantamentos pode chamar esperar- em seguida, o segmento que faz depósitos pode chamar notifyAll. Dessa forma, cada vez que um depósito é feito, o segmento de retirada poderá verificar o saldo da conta do cliente para ver se ele agora tem dinheiro suficiente.

menu