1. Análise de código -fonte de classe insegura
A classe insegura no pacote RT.Jar da JDK fornece operações atômicas no nível de hardware. Os métodos em inseguros são todos métodos nativos, e as bibliotecas locais de implementação de C ++ são acessadas usando o JNI.
Explicação das principais funções da classe insegura em Rt.Jar. A classe insegura fornece operações atômicas no nível de hardware e pode operar com segurança variáveis de memória diretamente. É amplamente utilizado no código -fonte JUC. Compreender seus princípios estabelece as bases para o estudo do código -fonte da JUC.
Primeiro, vamos entender o uso dos principais métodos na classe insegura, como segue:
1. Long ObjectFielfOffset (campo de campo) Método: Retorna o endereço de deslocamento da memória da variável especificada na classe à qual ela pertence. O endereço de deslocamento é usado apenas ao acessar o campo especificado na função insegura. O código a seguir usa inseguro para obter o deslocamento da memória do valor variável em atômico no objeto atômico. O código é o seguinte:
static {try {valueOffSet = insefa.ObjectFieldOffset (atomiclong.class.getDecaredfield ("value")); } catch (Exceção ex) {lança novo erro (ex); }}2.Int ArrayBaseoffset (Class ArrayClass) Método: Obtenha o endereço do primeiro elemento na matriz
3.Int ArrayIndexScale (Class ArrayClass) Método: Obtenha o número de bytes ocupados por um único elemento na matriz
3.Boolean Comparanswaplong (Object OBJ, Long Offset, Longo Espere, Atualização Longa) Método: Compare se o valor da variável do deslocamento de deslocamento no objeto Obj é igual a esperar. Se for igual, será atualizado com o valor da atualização e retornará true, caso contrário, retornará false.
4. Método de GetlongVolative Long Long (Object OBJ, Long Offset): Obtenha o valor da semântica volátil da memória correspondente à variável do deslocamento do deslocamento no objeto Obj.
5. Void PutOrderEdlong (Object OBJ, Long Offset, Longo Valor) Método: Defina o valor do campo longo correspondente ao endereço de deslocamento do deslocamento no objeto OBJ como valor. Este é o método PutlongVolatile com atraso e não garante que a modificação do valor seja imediatamente visível para outros threads. As variáveis só são úteis se forem modificadas com voláteis e deverão ser modificadas inesperadamente.
6. Void Park (Método Booleano Isabsolute, Long Time): Bloqueie o thread atual. Quando o parâmetro isabsolute é igual a falso, o tempo igual a 0 significa bloquear o tempo todo. Tempo maior que 0 significa que o encadeamento de bloqueio será despertado após esperar o tempo especificado. Desta vez, é um valor relativo, um valor incremental, ou seja, o encadeamento atual será despertado após o acumulação de tempo em relação ao tempo atual. Se o isabsolute for igual ao verdadeiro e o tempo for maior que 0, significa que será despertado após o bloqueio para o ponto de tempo especificado. Aqui é o tempo é um tempo absoluto, que é o valor convertido em MS em um determinado momento. Além disso, quando outros threads chamam o método de interrupção do encadeamento de bloqueio atual e interrompe o encadeamento atual, o encadeamento atual também retornará. Quando outros threads chamam o método UMPARK e tomam o thread atual como um parâmetro, o thread atual também retornará.
7. Void UMPARK (Thread) Método: Acorde o segmento de bloqueio após o parque de chamadas, e os parâmetros são os threads que precisam ser acordados.
Vários novos métodos foram adicionados ao JDK1.8. Aqui está uma lista simples dos métodos para operar o tipo longo da seguinte forma:
8. Método GetAndSetLong (Object OBJ, Long Offset, Longa Atualização): obtenha o valor da semântica volátil variável com deslocamento no objeto OBJ e defina o valor da semântica volátil variável para atualizar. O método de uso é o seguinte:
Public final Long GetandSetlong (objeto obj, deslocamento longo, atualização longa) {Long L; do {l = getLongVolatile (obj, deslocamento); // (1)} while (! comparandswaplong (obj, deslocamento, l, atualização)); retornar l; }A partir do código interno (1), você pode usar o GetLongVolative para obter o valor da variável atual e, em seguida, usar a operação atômica do CAS para definir o novo valor. Aqui, o uso de enquanto o Loops leva em consideração a situação em que vários threads chamam ao mesmo tempo e, em seguida, é necessária a retransferência de spin após a falha do CAS.
9. Long GetAndaddlong (Object OBJ, Longo Offset, Longo AddValue) Método: Obtenha o valor da semântica da variável volátil com deslocamento no objeto Obj e defina o valor variável no valor original + addValue. O método de uso é o seguinte:
Public final Long Getandaddlong (objeto obj, deslocamento longo, addValue longo) {Long L; do {l = getlongVolatile (obj, deslocamento); } while (! comparandswaplong (obj, deslocamento, l, l + addValue)); retornar l; }Semelhante à implementação do GetAndSetlong, exceto que, ao usar o CAS aqui, o valor original + o valor do parâmetro incremental transferido AddValue é usado.
Então, como usar a classe insegura?
Vendo que inseguro é tão incrível, você realmente quer praticar? Ok, vamos primeiro olhar para o seguinte código:
pacote com.hjc; import sun.misc.unsfe;/*** Criado por Cong em 2018/6/6. */public class TestUNSafe {// Obtenha a instância de inseguro (2.2.1) estático não seguro // Registre o valor de deslocamento do estado variável na classe TestUNSAFE (2.2.2) estático final StateOffset; // variável (2.2.3) Estado longo volátil privado = 0; estático {try {// Obtenha o valor de deslocamento da variável de estado na classe TestUNSAFE (2.2.4) StateOffset = insefa.ObjectFieldOffset (testunsafe.class.getDecaredfield ("estado"); } catch (Exceção ex) {System.out.println (ex.getLocalizedMessage ()); lançar um novo erro (ex); }} public static void main (string [] args) {// Crie uma instância e defina o valor do estado como 1 (2.2.5) testUNSafe test = new TestUNSafe (); //(2.2.6) Sucess boolean = insefe.comparandswapint (teste, StateOffset, 0, 1); System.out.println (sucessão); }}O código (2.2.1) obtém uma instância de inseguro e o código (2.2.3) cria um estado variável inicializado para 0.
Código (2.2.4) usa inseguro.ObjectFieldOffset para obter o endereço de deslocamento da memória da variável de estado na classe TestUNSAVE no objeto TestUNSafe e salvá -lo na variável StateOffset.
Código (2.2.6) chama o método ComparaDSWAPINT da instância criada insegura e define o valor da variável de estado do objeto de teste. Especificamente, se a variável de estado cuja deslocamento de memória do objeto de teste for StateOffset for 0, o valor de atualização será alterado para 1.
Queremos inserir true no código acima, mas após a execução, os seguintes resultados serão emitidos:
Por que isso está acontecendo? Você deve inserir o código GetUNSafe, como ver o que é feito nele:
private estático final inseguro theunsfe = novo inseguro (); public static inseguro getUNSAFE () {// (2.2.7) classe localClass = refletion.getCalerClass (); // (2.2.8) if (! Vm.issystemdomainloader (localClass.getClassLoader ())) {lança a nova segurança ("inseguro"); } retornar theUNSafe;} // JUDE se o ParamClassLoadeler é um carregador de classe de bootstrap (2.2.9) public static boolean IssystemomemOnloader (classloader paramClassLoadeler) {return paramclassloadler == null; }Código (2.2.7) recebe o objeto de classe do objeto que chama o getUNSafe, aqui está o testUSafe.Cals.
O código (2.2.8) determina se é a classe local carregada pelo carregador de classe de bootstrap. A chave aqui é se o carregador de bootstrap carrega testUSSafe.class. Aqueles que viram o mecanismo de carregamento de classe da Machine Virtual Java veem claramente que é porque o testUNSafe.class é carregado usando o AppClassLoader, portanto, uma exceção é jogada diretamente aqui.
Portanto, a questão é: por que você precisa fazer esse julgamento?
Sabemos que a classe insegura é fornecida na RT.Jar, e a classe na Rt.jar é carregada usando o carregador de classe de bootstrap. A classe em que iniciamos a função principal é carregada usando o AppClassLoader; portanto, ao carregar a classe insegura na função principal, considerando que o mecanismo de delegação dos pais delegará para bootstrap para carregar a classe insegura.
Se não houver autenticação do código (2.2.8), nosso aplicativo poderá usar inseguro para fazer as coisas à vontade. A classe insegura pode operar a memória diretamente, o que é muito inseguro. Portanto, a equipe de desenvolvimento JDK fez essa restrição especialmente, não permitindo que os desenvolvedores usem aula insegura em canais regulares, mas use funções inseguras na classe Core em Rt.jar.
A questão é: o que devemos fazer se realmente queremos instanciar a classe insegura e usar a função insegura?
Não devemos esquecer a tecnologia negra da reflexão e usar a reflexão universal para obter o método de instância da UNSAGE. O código é o seguinte:
pacote com.hjc; import sun.misc.unsfe; importar java.lang.reflect.field;/*** criado por Cong em 2018/6/6. */public class TestUNSafe {estático final inseguro inseguro; estático final Long Sadoffset; Estado longo volátil privado = 0; estático {try {// refletir para obter o campo do membro do membro do membro da vida theUNSafe (2.2.10) Campo = insefa.class.getDecaredfield ("theUsafe"); // defina como acessível (2.2.11) field.setAccessible (true); // Obtenha o valor dessa variável (2.2.12) inseguro = (inseguro) field.get (null); // obtém o deslocamento do estado no testunsafe (2.2.13) stateoffset = unsefe.objectfieldOffset (testunsafe.class.getDecaredfield ("estado")); } catch (Exceção ex) {System.out.println (ex.getLocalizedMessage ()); lançar um novo erro (ex); }} public static void main (string [] args) {testUNSafe test = new TestUNSafe (); Sucesso booleano = insefe.compareandswapint (teste, StateOffset, 0, 1); System.out.println (sucessão); }}Se o código acima (2.2.10 2.2.11 2.2.12) refletir o exemplo de inseguro, o resultado em execução é o seguinte:
2. Pesquisa sobre o código -fonte da classe Locksupport
O LockSupport em Rt.jar no JDK é uma classe de ferramentas, e sua principal função é suspender e acordar threads. É a base para criar bloqueios e outras classes de sincronização.
A classe LockSupport estará associada a cada encadeamento que o usa. O encadeamento que chama o método da classe LockSupport por padrão não possui uma licença. O LockSupport é implementado internamente usando a classe insegura.
Aqui, devemos prestar atenção a várias funções importantes do LockSupport, como segue:
1. Void Park () Método: Se o parque de chamadas de thread () obteve a licença associada ao LockSupport, chamando LockSupport.park () retornará imediatamente. Caso contrário, o encadeamento de chamada será proibido de participar da programação do thread, ou seja, será bloqueada e suspensa. O código a seguir é o seguinte exemplo:
pacote com.hjc; import java.util.concurrent.locks.locksupport;/*** Criado por Cong em 2018/6/6. */public class Locksupporttest {public static void main (String [] args) {System.out.println ("Park Start!"); LockSupport.park (); System.out.println ("Park Stop!"); }}Conforme mostrado no código acima, chame diretamente o método do parque na função principal e o resultado final produzirá apenas o Parque Start! Em seguida, o thread atual será suspenso, porque o encadeamento de chamada não possui uma licença por padrão. Os resultados da operação são os seguintes:
Quando você vê outros threads chamam o método UMPARK (Thread) e o thread atual é usado como um parâmetro, o thread que chama o método do parque retornará. Além disso, outros threads chamam o método de interrupção () do thread de bloqueio. Quando o sinalizador de interrupção é definido ou o encadeamento de bloqueio retornará após o despertar falso do thread, é melhor usar as condições do loop para julgar.
Deve -se notar que o thread que chama o método park () bloqueado é interrompido por outros threads e o retorno de encadeamento bloqueado, não lançará uma exceção interruptedException.
2.Void UMPARK (Thread Thread) Método Quando um thread chama Usk, se o thread do parâmetro não mantiver a licença associada ao encadeamento e à classe LockSupport, deixe o thread segurar. Se o thread chamado parque () for suspenso antes e o thread for chamado, o thread será despertado depois que o UMPARK for chamado.
Se o tópico não já chamou o Park antes, depois de chamar o método UMPARK, o método park () será retornado imediatamente. O código acima é modificado da seguinte forma:
pacote com.hjc; import java.util.concurrent.locks.locksupport;/*** Criado por Cong em 2018/6/6. */public class Locksupporttest {public static void main (String [] args) {System.out.println ("Park Start!"); // Faça o thread atual obter o License LockSupport.unpark (Thread.currentThread ()); // Ligue para o parque Locksupport.park () novamente; System.out.println ("Park Stop!"); }}Os resultados da operação são os seguintes:
Em seguida, estamos analisando um exemplo para aprofundar nossa compreensão do parque, UMPARK, o código é o seguinte:
importar java.util.concurrent.locks.locksupport;/*** Criado por Cong em 2018/6/6. */public class LockSupportTest {public static void main (string [] args) lança interruptEdException {threads thread = new Thread (new runnable () {@Override public void run () {SystemnNout.println ("Child Thread Start!); }}); // Inicie o thread do filho Child.start (); // O fio principal dorme 1s Thread.Sleep (1000); System.out.println ("Principal Upk Start!"); // Ligue para UMPARK para deixar o thread segurar a licença e, em seguida, o método do parque retornará LockSupport.unpark (Thread); }}Os resultados da operação são os seguintes:
O código acima primeiro cria um tópico infantil. Após a startup, o tópico infantil chama o método do parque. Como o thread infantil padrão não possui uma licença, ele se pendurará.
O fio principal dorme para 1s. O objetivo é que o encadeamento principal chama o método UMPARK e permita que o encadeamento infantil seja lançado o Parque Child Thread Park! e bloco.
O thread principal executa o método UMPK, com o parâmetro sendo o thread infantil, o objetivo é deixar o encadeamento filho manter a licença e, em seguida, o método do parque chamado pelo encadeamento filho retorna.
Ao retornar o método do parque, ele não lhe dirá por que motivo está retornando. Portanto, o chamador precisa verificar novamente se a condição é atendida com base no método do parque ele na chamada atual. Se não for atendido, ele precisará chamar o método do parque novamente.
Por exemplo, o estado de interrupção de um thread quando ele retorna, pode -se determinar se ele retorna porque é interrompido com base na comparação do estado de interrupção antes e depois da chamada.
Para ilustrar que o thread depois de chamar o método do parque retornará após a interrupção, modifique o código de exemplo acima e exclua o LockSupport.unpark (Thread); e adicione thread.interrupt (); O código é o seguinte:
importar java.util.concurrent.locks.locksupport;/*** Criado por Cong em 2018/6/6. */public class LocksupportTest {public static void main (string [] args) lança interruptedException {threads thread = new Thread (new runnable () {@Override public void run () {System.out.println ("Subthread park! (! Thread.CurrentThread (). IsInterrupted ()) {Locksupport.park (); // Iniciar o thread Child Child.start (); // A linha principal dorme 1s Thread.Sleep (1000); System.out.println ("Principal Upk Start!"); // interromper o thread Child Thread.Interrup (); }}Os resultados da operação são os seguintes:
Como o código acima, o encadeamento infantil só terminará após o interrupção do thread. Se o thread infantil não for interrompido, mesmo se você ligar para UMPARK (Thread), o thread infantil não terminará.
3.Void Parknanos (Long Nanos) Método: Semelhante ao parque, se o parque de chamadas de thread obtiver a licença associada ao LockSupport, chamando LockSupport.park () retornará imediatamente. A diferença é que, se o encadeamento chamando o thread não for obtido, ele será suspenso e retornará após o tempo dos nanos.
O Park também suporta três métodos com parâmetros do bloqueador. Quando um thread chama parque sem manter uma licença e é bloqueado e suspenso, o objeto do bloqueador será gravado dentro do thread.
Use ferramentas de diagnóstico para observar o motivo pelo qual o thread está bloqueado. As ferramentas de diagnóstico usam o método getBlocker (Thread) para obter o objeto Bloqueador. Portanto, o JDK recomenda que usemos o método do parque com os parâmetros do bloqueador e defina o bloqueador para isso; portanto, quando o despejo de memória soluciona o problema, podemos saber qual classe está bloqueada.
Exemplos são os seguintes:
importar java.util.concurrent.locks.locksupport;/*** Criado por Cong em 2018/6/6. */public class TestPark {public void testPark () {LockSupport.park (); // (1)} public static void main (String [] args) {testPark testPark = new TestPark (); testPark.testpark (); }}Os resultados da operação são os seguintes:
Você pode ver que ele está executando o bloqueio, então precisamos usar as ferramentas no diretório JDK/BIN para dar uma olhada. Se você não sabe, é recomendável dar uma olhada nas ferramentas de monitoramento da JVM primeiro.
Ao usar o jstack pid para visualizar a pilha de threads após a execução, os resultados que você pode ver são os seguintes:
Em seguida, modificamos o código acima (1) da seguinte forma:
Locksupport.park (this); // (1)
Execute novamente e use o Jstack PID para visualizar os resultados da seguinte forma:
Pode -se observar que, após o método do parque com o bloqueador, a pilha de threads pode fornecer mais informações sobre o bloqueio de objetos.
Em seguida, verificaremos a função Código -fonte do parque (bloqueador de objetos), o código -fonte é o seguinte:
Public Static Void Park (bloqueador de objetos) {// Obtenha o thread de chamada Thread t = Thread.currentThread (); // Defina a variável Blocker SetBlocker (t, bloqueador); // pendure o thread insefe.park (false, 0l); // Limpe a variável do bloqueador após a ativação do encadeamento, porque o motivo é geralmente analisado quando o thread é bloqueado SetBlocker (t, nulo);}Há uma variável na classe de threads voláteis parkblocker. É usado para armazenar o objeto Bloqueador passado pelo parque, ou seja, a variável do bloqueador é armazenada na variável de membro do encadeamento que chama o método do parque.
4. A função Void Parknanos (Bloqueador de Objetos, Nanos Long) tem um tempo de tempo limite adicional em comparação com o parque (bloqueador de objetos).
5. Void Parkuntil (bloqueador de objetos, prazo longo) O código -fonte do Parkuntil é o seguinte:
public static void parkuntil (bloqueador de objetos, prazo longo) {thread t = thread.currentThread (); setBlocker (t, bloqueador); // isabsolute = true, tempo = prazo; significa que inseguro.park (verdadeiro, prazo); setblocker (t, nulo); }Você pode ver que é um prazo definido, a unidade de tempo é milissegundos, que é convertida em um valor após milissegundos de 1970 até o momento atual. A diferença entre isso e Parknanos (bloqueador de objetos, nanos longos) é que o último calcula o tempo de espera dos nanos a partir do horário atual, enquanto o primeiro especifica um ponto no tempo.
Por exemplo, precisamos esperar até 20:34 em 2018.06.06 e depois converter esse ponto no número total de milissegundos de 1970 até esse momento.
Vejamos outro exemplo, o código é o seguinte:
importar java.util.queue; importar java.util.Concurrent.ConcurrentLinkedQueue; importar java.util.concurrent.atomic.atomicboonen; importar java.util.concurrent.locks.locksupport; */public class Fifomutex {private final atomicBoolean Locked = new AtomicBoolean (false); fila final privada <Thread> Wariters = new ConcurrentLinkedQueue <Thread> (); public void Lock () {boolean foi interrompido = false; Thread atual = thread.currentThread (); Waiters.add (atual); // Somente o encadeamento da cabeça da equipe pode obter o bloqueio (1) while (Waiters.Peek ()! = Current ||! Locked.comparenderndSet (false, true)) {LockSupport.Park (this); if (thread.interrupted ()) // (2) foi interrompido = true; } waiters.remove (); if (foi interrompido) // (3) current.Interrup (); } public void desblock () {Locked.set (false); Locksupport.unpark (Waiters.Peek ()); }}Você pode ver que este é o primeiro bloqueio da primeira saída, ou seja, apenas o elemento de cabeçalho da fila pode obtê-lo. Código (1) Se o encadeamento atual não for o cabeçalho da fila ou o bloqueio atual foi adquirido por outros threads, chame o método do parque para se suspender.
Então o código (2) faz um julgamento. Se o método do parque retornar porque for interrompido, a interrupção será ignorada e o sinalizador de interrupção é redefinido e apenas um sinalizador é feito e, novamente, determine se o encadeamento atual é o elemento da cabeça da fila ou se o bloqueio foi adquirido por outros threads. Nesse caso, continue chamando o método do parque para se enforcar.
Então, se a marca for verdadeira no código (3), o thread será interrompido. Como você entende isso? De fato, outros threads interromperam o fio. Embora eu não esteja interessado no sinal de interrupção e ignore -o, isso não significa que outros threads não estejam interessados na bandeira, então eu preciso restaurá -lo.
Resumir
O acima é o conteúdo inteiro deste artigo. Espero que o conteúdo deste artigo tenha certo valor de referência para o estudo ou trabalho de todos. Se você tiver alguma dúvida, pode deixar uma mensagem para se comunicar. Obrigado pelo seu apoio ao wulin.com.