Semáforo é uma classe comumente usada no pacote JUC. É uma aplicação do modo de compartilhamento de AQS. Ele pode permitir que vários threads operem em recursos compartilhados ao mesmo tempo e podem controlar efetivamente o número de simultaneidade. Pode alcançar um bom controle de tráfego. O Semaphore fornece um conceito de licença, que pode ser considerado uma passagem de ônibus. Somente aqueles que obtêm com sucesso o ingresso podem entrar no ônibus. Há um certo número de ingressos e é impossível emiti -los sem restrições, o que levará à sobrecarga do ônibus. Então, quando o bilhete é emitido (o ônibus está cheio), os outros só podem esperar o próximo trem. Se alguém descer do ônibus no meio do caminho, sua posição será livre; portanto, se outras pessoas quiserem entrar no ônibus neste momento, poderão receber ingressos novamente. Várias piscinas podem ser implementadas usando o semáforo. No final deste artigo, escreveremos um pool de conexão de banco de dados simples. Primeiro, vamos dar uma olhada no construtor de semáforo.
// Construtor 1Public Semaphore (int permissões) {Sync = new NonfairSync (permissões);} // Construtor 2Public Semaphore (Int Permits, Boolean Fair) {Sync = Fair? New FairSync (Permissões): New Non -FairSync (Permissões);}O Semaphore fornece dois construtores sem parâmetros, mas nenhum construtor livre de parâmetros é fornecido. Ambos os construtores devem passar em um número inicial de licenças. O semáforo construído usando o Construtor 1 será obtido de maneira não-fad ao obter a licença. O uso do construtor 2 pode especificar o método de obter a licença através de parâmetros (justos ou injustos). A Semáfore fornece principalmente dois tipos de APIs para o mundo exterior, obtendo licenças e liberando licenças. O padrão é obter e liberar uma licença, e os parâmetros também podem ser transmitidos para obter e liberar várias licenças ao mesmo tempo. Neste artigo, falaremos apenas sobre a situação de obter e liberar uma licença de cada vez.
1. Obtenha uma licença
// obtenha uma licença (interrupção de resposta) public void adquirir () lança interruptedException {sync.acquiraredediousInterruptível (1);} // Obtenha uma licença (não respondendo à interrupção) public void adquireuninterrupttly () {sync.Acquireshared (1);} // try para obter uma licença (não -flauta sync.nonfairtryAcquireshared (1)> = 0;} // Tente obter uma licença (longa data, unidade de unidade de tempo) lança interruptedException {return sync.tryAcquiresharedNanos (1, unit.tonanos (timeout));}A API acima é a operação de aquisição de licença padrão fornecida pela Semaphore. Somente obter uma licença de cada vez também é uma situação comum na vida real. Além da busca direta, ele também fornece tentativa de buscar. A operação de busca direta pode bloquear o encadeamento após a falha, enquanto a tentativa de buscar não. Deve -se notar também que o método TryAcquire é usado para tentar obtê -lo de maneira injusta. O que costumamos usar em tempos normais é obter uma licença. Vamos dar uma olhada em como é obtido. Você pode ver que o método adquirente chama diretamente o Sync.AcquiredarededInterruptível (1). Este método é o método no AQS. Uma vez conversamos sobre os artigos da série de código -fonte do AQS. Vamos revisá -lo novamente.
// adquirindo o bloqueio no modo interrompível (modo compartilhado) public final void adquirente interruptível (int arg) lança interruptedException {// Primeiro, determine se o thread é interrompido, se for sim, jogue uma exceção se (thread.interrupted ()) {lança new interruptException (); } // 1. Tente adquirir o bloqueio se (TryAcquireshared (arg) <0) {// 2. Se a aquisição falhar, digite o método doacquiredarededinterruptível (arg); }}O primeiro método adquirido interruptível é chamar o método TryAcquiredareded para tentar obter. O TryAcquireshared é um método abstrato no AQS. As duas classes derivadas Fairsync e NonFairSync implementam a lógica desse método. A FairSync implementa a lógica da aquisição justa, enquanto o NonFairSync implementa a lógica da aquisição não-falsa.
A classe estática abstrata Sync estende abstratoQueUedSynchronizer {// Tente obter o Final Int Non -FairtryAcquiredared (int adquires) {for (;;) {// obtenha licenças disponíveis int disponível = getState (); // Obtenha licenças restantes no restante = disponível - adquire; // 1. Se permanecer menor que 0, retorne restante diretamente // 2. Se permanecer maior que 0, atualize o status de sincronização primeiro e depois retorne restante se (permanecendo <0 || ComparaDSetState (disponível, restante)) {return restante; }}}} // Classe final estática não -FairSync NonfairSync estende Sync {private estático final serialversionuid = -2694183684443567898l; Não -FairSync (int permissões) {super (permissões); } // Tente obter uma licença protegida int TryAcquireshared (int adquire) {return não -FairtryAcquiredared (adquire); }} // Syncronizador justo estático da classe final FairSync estende Sync {private estático final serialversionuid = 2014338818796000944l; FairSync (int Permissões) {super (permissões); } // Tente obter a licença protegida int TryAcquireshared (int adquire) {for (;;) {// julgue se há alguém em frente à fila de sincronização se (hasqueuedPredeCessors ()) {// se houver algum, retornar -1, indicando que a tentativa de obter retorno falhado --1; } // Obtenha licenças disponíveis int disponível = getState (); // Obtenha as licenças restantes no restante = disponível - adquire; // 1. Se permanecer menor que 0, retorne diretamente ao restante // 2. Se o restante for maior que 0, o status de sincronização será atualizado primeiro e depois retornado ao restante se (permanecendo <0 || ComparaDSetState (disponível, restante)) {retornar restante; }}}}Deve -se notar aqui que o método TryAcquiresar -se o não -FairSync chama diretamente o método não compartilhado de não -FairtryAcquired, que está na sincronização da classe pai. A lógica do bloqueio de aquisição não-fair é primeiro retirar o estado de sincronização atual (o estado síncrono representa o número de licenças), subtraia o parâmetro do estado de sincronização atual. Se o resultado não for inferior a 0, é provado que ainda existem licenças disponíveis, o valor do estado de sincronização será diretamente atualizado usando a operação CAS e, finalmente, o valor do resultado será retornado independentemente de o resultado ser menor que 0. Aqui precisamos entender o significado do valor de retorno do método TryAcquirededed. Retornar um número negativo significa que a aquisição falhou, zero significa que o encadeamento atual é adquirido com sucesso, mas o encadeamento subsequente não pode mais ser obtido e o número positivo significa que o encadeamento atual é adquirido com sucesso e o encadeamento subsequente também pode ser obtido. Vejamos o código do método adquirente interruptível.
// adquirindo bloqueios no modo interrompível (modo compartilhado) Public Final Void AquireSharededInterruptível (int arg) lança interruptedException {// Primeiro, determine se o thread é interrompido, se for o caso, jogue uma exceção se (thread.interrupted ()) {lança new interruptException (); } // 1. Tente adquirir o bloqueio // Número negativo: indica que a aquisição falhou // valor zero: indica que o encadeamento atual é adquirido com sucesso, mas o encadeamento subsequente não pode mais obter // número positivo: indica que o encadeamento atual é adquirido com sucesso, e o encadeamento subsequente também pode obter sucesso se (TryAcquireshared (arg) <0) {/2. Se a aquisição falhar, digite o método doacquiredarededinterruptível (arg); }}Se o restante retornado for menor que 0, significa que a aquisição falhou. Portanto, o TryAcquireshared (arg) <0 é verdadeiro, portanto, o método doacquiraredarededInterruptivelmente será chamado a seguir. Quando conversamos sobre o AQS, ele embrulha o fio atual em um nó e o coloca na cauda da fila de sincronização, e é possível suspender a rosca. Essa também é a razão pela qual os threads serão filmados e bloquearem ao permanecer menos de 0. Se o restante retornado> = 0 significa que o thread atual foi adquirido com sucesso. Portanto, o TryAcquireshared (arg) <0 é uma flase, portanto, o método doacquiredarededi -em -interruptivelmente não será mais chamado para bloquear o thread atual. O exposto acima é toda a lógica da aquisição injusta. Ao aquisição justa, você só precisa chamar o método HasqueedPredeCesss antes disso para determinar se alguém está na fila da sincronização. Nesse caso, o retorno -1 indica diretamente que a aquisição falhou, caso contrário, as etapas a seguir serão continuadas como aquisição injusta.
2. Libere a licença
// Libere uma licença public void Release () {sync.releashareared (1);}Chamar o método de liberação é liberar uma licença. Sua operação é muito simples, por isso chamamos o método RELEASSEDADO DO AQS. Vamos dar uma olhada nesse método.
// Operação de bloqueio de liberação (modo compartilhado) Public Final Boolean RELEASHARED (int arg) {// 1. Tente soltar o bloqueio se (TryReleashared (arg)) {// 2. Se a liberação for bem -sucedida, acorde outros threads doreleashared (); retornar true; } retornar false;}O método relevante do AQS chama primeiro o método TryReleasHareded para tentar liberar o bloqueio. A lógica de implementação desse método está na sincronização da subclasse.
A classe estática abstrata Sync estende abstratoqueedSynchronizer {... // Tente liberar a operação Protected Final Boolean TryReleashared (INT Liberações) {for (;;) {// Obtenha o estado de sincronização atual int current = getState (); // mais o estado de sincronização atual da seguinte forma int a próxima = Current + Liberações; // Se o resultado da adição for menor que o estado de sincronização atual, um erro será relatado se (a seguir <atual) {lançar novo erro ("contagem máxima da licença excedida"); } // Atualize o valor do estado de sincronização no modo CAS e retorne true se a atualização for bem -sucedida; caso contrário, continue a fazer um loop se (comparaandStState (atual, a seguir)) {return true; }}} ...}Você pode ver que o método TryReleashareded usa um loop para girar. Primeiro, obtenha o estado de sincronização, adicione os parâmetros recebidos e atualize o estado de sincronização no CAS. Se a atualização for bem -sucedida, retorne true e pule para fora do método. Caso contrário, o loop continuará até que seja bem -sucedido. Este é o processo de semáforo que libera a licença.
3. Escreva um pool de conexão manualmente
O código semáforo não é muito complicado. A operação comumente usada é obter e liberar uma licença. A lógica de implementação dessas operações é relativamente simples, mas isso não prejudica a aplicação generalizada do semáforo. Em seguida, usaremos o semáforo para implementar um pool de conexão de banco de dados simples. Através deste exemplo, esperamos que os leitores possam ter um entendimento mais profundo do uso do Semáforo.
classe pública ConnectPool {// Tamanho do pool de conexão Private Int Tamanho; // coleção de conexão de banco de dados Private Connect [] conecta; // sinalizador de status de conexão Private boolean [] conectflag; // Número restante das conexões disponíveis Private VOLATILE INT DISPONÍVEL; // Semaphore Private Semaphore Semaphore; // construtor public ConnectPool (int size) {this.size = size; this.Available = size; semáforo = novo semáforo (tamanho, verdadeiro); conecta = new Connect [size]; ConnectFlag = novo booleano [tamanho]; initConnects (); } // inicialize a conexão private void initConnects () {// gerar um número especificado de conexões de banco de dados para (int i = 0; i <this.size; i ++) {conecta [i] = new Connect (); }} // Obtenha conexão de banco de dados Sincronizada Private Connect getConnect () {for (int i = 0; i <ConnectFlag.Length; i ++) {// Transfira a coleção para encontrar conexões não utilizadas se (! Connectflag [i]) {// Defina a conexão como use Connectflag [i] = true; // subtraia o número de conexões disponíveis disponíveis--; System.out.println ("【"+thread.currentThread (). GetName ()+"】 para obter o número de conexões restantes:"+disponível); // retorna o retorno de referência de conexão conecta [i]; }} retornar nulo; } // obtenha uma conexão pública Connect OpenConnect () lança interruptedException {// Get License Semaphore.acquire (); // Obter conexão com o banco de dados retornar getConnect (); } // Libere uma conexão Public Sincronized Void Release (Connect Connect) {for (int i = 0; i <this.size; i ++) {if (Connect == Connects [i]) {// Defina a conexão para não usar ConnectFlag [i] = false; // Adicionar 1 número de conexão disponível; System.out.println ("【"+thread.currentThread (). GetName ()+"] para liberar o número de conexão restante:"+disponível); // Libere a licença semaphore.release (); }}} // Adicione o número de conexões disponíveis public int disponível () {return disponível; }}Código de teste:
classe pública TestThread estende thread {private static connectpool pool = new ConnectPool (3); @Override public void run () {try {Connect Connect = pool.openconnect (); Thread.sleep (100); // Faça um pool de interrupção.Release (Connect); } catch (interruptedException e) {e.printStackTrace (); }} public static void main (string [] args) {for (int i = 0; i <10; i ++) {new testThread (). start (); }}}Resultados do teste:
Usamos uma matriz para armazenar referências a conexões de banco de dados. Ao inicializar o pool de conexões, chamaremos o método InitConnects para criar um número especificado de conexões de banco de dados e armazenar suas referências na matriz. Além disso, há uma matriz do mesmo tamanho para registrar se a conexão está disponível. Sempre que um thread externo solicita para obter uma conexão, primeiro ligue para o método semaphore.acquire () para obter uma licença, defina o status da conexão em uso e, finalmente, retorne a referência à conexão. O número de licenças é determinado pelos parâmetros passados durante a construção. O número de licenças é reduzido em 1 sempre que o método semáforo.acquire () é chamado. Quando o número é reduzido para 0, significa que não há conexão disponível. Neste momento, se outros threads o receberem novamente, ele será bloqueado. Sempre que um thread libera uma conexão, o semáforo.release () será chamado para liberar a licença. No momento, o número total de licenças aumentará novamente, o que significa que o número de conexões disponíveis aumentou. Em seguida, o encadeamento bloqueado anteriormente acordará e continuará a obter a conexão. Neste momento, você pode obter com êxito a conexão obtê -la novamente. No exemplo de teste, um pool de conexões de 3 conexões é inicializado. Podemos ver nos resultados do teste que, sempre que um encadeamento obtiver uma conexão, o número de conexões restantes será reduzido em 1. Quando a rosca reduzir para 0, outros threads não poderão mais obtê -lo. Neste momento, você deve esperar que um thread libere a conexão antes de continuar a obtê -lo. Você pode ver que o número de conexões restantes sempre muda entre 0 e 3, o que significa que nosso teste foi bem -sucedido.
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.