1. Conceito
1. Conceitos básicos
Processos e threads:
- Gráfico:
- carta:
- Processo: uma instância de um programa.Um programa pode ter várias instâncias, conforme a figura abaixo.
Já o programa QQ possui dois processos. - Thread: Um thread é a unidade de execução de um processo e existem vários threads em um processo
- Processo: uma instância de um programa.Um programa pode ter várias instâncias, conforme a figura abaixo.
- conceito:
- Processo: Um processo é a unidade básica para alocar e gerenciar recursos , e diferentes processos competem pelos recursos do sistema de computador
- Thread: thread é a menor unidade de escalonamento (a unidade básica de escalonamento do processador)
- a diferença:
- Independente | Compartilhado
- Os threads compartilham o espaço de endereço e os recursos do processo (memória, CPU, IO, etc.)
- Cada processo tem um espaço de endereço independente e recursos independentes
- robusto?
- Depois que um thread travar, todo o processo falhará
- Após a falha de um processo, ele não afetará outros processos no modo protegido
- a sobrecarga?
- O thread é anexado ao processo a ser executado, obviamente a sobrecarga do processo é alta (incluindo sobrecarga de inicialização, sobrecarga de comutação)
- comunicação
- A comunicação de processos de um mesmo computador é chamada de IPC (Inter-process communication) A comunicação de processos entre diferentes computadores precisa passar pela rede e seguir um protocolo comum, como o HTTP
- Como as threads compartilham recursos de processo, a comunicação é muito simples, como compartilhar um recurso estático
- Independente | Compartilhado
Simultaneidade e paralelismo:
uma CPU de núcleo único pode executar apenas um thread por vez, ou seja, execução serial . O agendador de tarefas no sistema operacional aloca fatias de tempo de CPU (muito curtas) para diferentes threads. Como a CPU alterna entre os threads muito rapidamente, isso dará às pessoas a ilusão de que vários threads estão sendo executados ao mesmo tempo.
- Concorrente: a capacidade de lidar com várias coisas ao mesmo tempo
- Paralelo: a capacidade de fazer várias coisas ao mesmo tempo
Síncrono e assíncrono:
-
Sincronização: ou seja, execução serial, você precisa aguardar o retorno do código anterior antes de continuar a executar o código subsequente.
@Slf4j public class ShowSync { public static void count(int i) { log.info("第" + i + "次执行count方法"); } public static void main(String[] args) { for (int i = 0; i < 3; i++) { count(i); } log.info("线程:" + Thread.currentThread().getName()); } }
-
Assíncrono: o código a seguir pode ser executado sem esperar o retorno do código anterior.
@Slf4j public class ShowAsync { public static void main(String[] args) { for (int i = 0; i < 3; i++) { new Thread(() -> log.info("线程:" + Thread.currentThread().getName()) ).start(); } log.info("线程:" + Thread.currentThread().getName()); } }
2. O estado do encadeamento
Uma classe de enumeração State é fornecida na classe Thread , como segue:
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
Pode-se ver que um thread Java tem seis estados . Mas na verdade, vamos dividir RUNNABLE em dois estados: Ready e RUNNABLE , conforme mostra a figura abaixo, então geralmente dizemos: uma thread Java tem sete estados .
- NOVO
- Thread t1 = new Thread() entra neste estado
- PREPARAR
- t.start() entra neste estado
- Neste ponto, o thread já pode executar, mas ainda não obteve a fatia de tempo da CPU e está no estado pronto
- CORRENDO
- O thread pronto obtém a fatia de tempo da CPU e entra no estado de execução
- BLOQUEADO
- O thread insere o método modificado sincronizado ou bloco de código
- ENCERRADO
- O run() do thread terminou a execução ou main() terminou a execução
- ESPERANDO
- Os threads que entram neste estado não serão alocados em fatias de tempo de CPU e precisam ser acordados por outros threads, caso contrário, eles esperarão para sempre
- TIMED_WAITING
- Os threads que entram nesse estado não receberão fatias de tempo da CPU, não precisam ser ativados por outros threads e serão ativados automaticamente após um determinado período de tempo
Quanto à transição de estado e métodos relacionados no thread, irei apresentá-los mais tarde.
2. Inicialização do thread
Inicialização do thread, ou seja, new Thread() , temos três maneiras de conseguir a inicialização, a saber:
- Direct new Thread() e então reescrever run()
- new Thread(new Runnable()) , monta run() de Runnable em Thread
- new Thread(new FutureTask(new Callable())) , monte call() em Callable para FutureTask primeiro e depois para Thread
1. novo Tópico()
O novo Thread() diretamente para inicializar o thread é vincular o thread (Thread) e a tarefa (run()) juntos. Não é recomendado usá-lo no desenvolvimento real. O código é o seguinte:
@Slf4j
public class JustThread {
public static void main(String[] args) {
Thread thread1 = new Thread("线程1") {
@Override
public void run() {
log.info("线程:" + Thread.currentThread().getName() + " 运行中...");
}
};
thread1.start();
log.info("线程:" + Thread.currentThread().getName() + " 运行中...");
/*
lambda表达式
*/
Thread thread2 = new Thread(() -> {
log.info("线程:" + Thread.currentThread().getName() + " 运行中...");
}, "线程2");
thread2.start();
}
}
2. new Thread(new Runnable())
Inicialize threads (Thread) e tarefas (Runnable) separadamente e, em seguida, combine-os.
@Slf4j
public class RunnableAndThread {
public static void main(String[] args) {
Runnable runnable = new Runnable() {
@Override
public void run() {
log.info("线程:" + Thread.currentThread().getName() + " 运行中...");
}
};
Thread thread = new Thread(runnable, "线程3");
thread.start();
/*
lambda表达式
*/
Runnable runnable1 = () -> log.info("线程:" + Thread.currentThread().getName() + " 运行中...");
Thread thread1 = new Thread(runnable1, "线程4");
thread1.start();
}
}
3. A relação entre Thread e Runnable
Primeiro, observe o código-fonte do Runnable , conforme a seguir:
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
Uma interface funcional com apenas um run() .
Em seguida, observe parte do código-fonte do Thread :
@Override
public void run() {
if (target != null) {
target.run();
}
}
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
private void init(ThreadGroup g, Runnable target, String name,
long stackSize) {
init(g, target, name, stackSize, null, true);
}
Ok, basta olhar para estes poucos parágrafos de métodos, e não farei uma análise aprofundada aqui. A partir do código acima, podemos entender claramente a relação entre Thread e Runnable :
- Quando um novo Thread , se nenhum Runnable for passado , o run() do Thread será reescrito
- Em new Thread , se Runnable for passado em , run() em Runnable será usado
Portanto, a interface Runnable deve fornecer um corpo de método run() para Thread .
No desenvolvimento real, geralmente personalizamos uma classe para implementar Runnable e, em seguida, passamos essa classe para Thread . O caso é o seguinte:
- Defina um thread que imprima a hora atual a cada dez segundos
public class TimePrinter implements Runnable{
private SimpleDateFormat dateFormat= new SimpleDateFormat("hh:mm:ss");
@Override
public void run() {
while (true) {
try {
System.out.println("当前时间为: " + dateFormat.format(new Date()));
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public class TaskDemo {
public static void main(String[] args) {
Thread thread = new Thread(new TimePrinter(), "测试线程");
thread.start();
}
}
4. new Thread(new FutureTask(new Callable()))
@Slf4j
public class FutureTaskAndThread {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<Long> futureTask = new FutureTask<>(new Callable<Long>() {
@Override
public Long call() throws Exception {
long start = System.currentTimeMillis();
log.info(Thread.currentThread().getName() + " 运行中...");
TimeUnit.MILLISECONDS.sleep(123);
long end = System.currentTimeMillis();
return end - start;
}
});
Thread thread = new Thread(futureTask, "线程5");
thread.start();
log.info(thread.getName() + "花费了 " + futureTask.get() + " 毫秒");
}
}
A relação entre Runnable e Callable é a seguinte:
Pode-se ver que, em comparação com run() de Runnable , call() de Callable tem mais um valor de retorno.
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}
3. Métodos comuns
Mudança do estado do thread:
A diferença entre TIMED_WAITING e WAITING:
- TIMED_WAITING Mesmo se não houver sinal externo, o thread será retomado após o tempo de espera expirar
- WAITING precisa esperar um sinal externo para acordar
1. começar
Primeiro, olhe o código-fonte:
// 线程组
private ThreadGroup group;
// NEW 状态的线程的 threadStatus = 0
private volatile int threadStatus = 0;
public synchronized void start() {
if (threadStatus != 0)
throw new IllegalThreadStateException();
// 将要启动(start)的线程装入线程组中
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
// 告诉组,这个线程启动(start)失败
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
}
}
}
// native方法,调用本地操作系统开启一个新的线程
private native void start0();
Depois de ler o código-fonte, as seguintes conclusões podem ser tiradas:
- start para iniciar um thread precisa passar pelas seguintes etapas:
- Carregue o início da chamada do encadeamento no grupo de encadeamentos ThreadGroup
- Chame o método nativo para iniciar o thread
1. Grupo de tópicos
Link de referência aqui: https://zhuanlan.zhihu.com/p/61331974
Grupo de encadeamentos (Grupo de encadeamentos): uma coleção de encadeamentos que podem facilmente gerenciar encadeamentos em um grupo.
Árvore do grupo de tópicos:
-
Grupo de encadeamentos do sistema : Um grupo de encadeamentos que lida com tarefas do sistema JVM, como destruição de objetos, etc.
-
grupo de thread principal : contém pelo menos um thread - main (usado para executar o método principal)
-
Grupo de threads filhos do grupo de threads faciais: o grupo de threads criado pelo aplicativo
public static void main(String[] args) { // 输出当前线程组——main线程组 System.out.println(Thread.currentThread().getThreadGroup().getName()); // 输出当前线程组的父线程组——System System.out.println(Thread.currentThread().getThreadGroup().getParent().getName()); }
Atribua um grupo de encadeamento a um encadeamento:
-
Se você não especificar um array ao inicializar um thread, ele será especificado como o grupo de threads principal por padrão . O código é o seguinte:
private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) { /* 其余代码省略 */ Thread parent = currentThread(); SecurityManager security = System.getSecurityManager(); if (g == null) { if (security != null) { g = security.getThreadGroup(); } if (g == null) { g = parent.getThreadGroup(); } } /* 其余代码省略 */ }
-
Um grupo de threads pode ser atribuído a um thread usando qualquer um dos seguintes construtores:
public Thread(ThreadGroup group, Runnable target) public Thread(ThreadGroup group, Runnable target) public Thread(ThreadGroup group, Runnable target, String name) public Thread(ThreadGroup group, Runnable target, String name, long stackSize)
Demonstre:
public static void main(String[] args) { ThreadGroup group = new ThreadGroup("我的线程组"); Thread thread = new Thread(group, "我的线程"); System.out.println(thread.getName() + " 的线程组是 " + group.getName()); }
Análise do método do grupo de tópicos: https://blog.csdn.net/a1064072510/article/details/87455525
Resumindo: o objetivo de carregar threads em um grupo de threads é facilitar a operação em lote de threads. Por exemplo, se eu quiser notificar vários threads que eles estão prestes a terminar, posso colocar esses threads em um grupo de threads com antecedência, e, em seguida, notifique-os diretamente em lotes por meio do grupo de threads:
// 通知main线程组中的所有线程要终止了
Thread.currentThread().getThreadGroup().interrupt();
2. iniciar e executar
Pergunta: Por que pode iniciar um thread, mas não pode ser executado ?
Resposta: Como o Java não pode atuar diretamente no sistema operacional, é necessário chamar métodos nativos (como métodos escritos em C, C++, etc.) para instruir o sistema operacional a iniciar uma nova thread. No método start , o método nativo start0 é chamado para iniciar o thread. Quanto ao run , este é um método no thread. Quando o thread for executado, ele executará run . Quando o método run for executado, o thread será encerrado.
2. dormir
Primeiro olhe o código-fonte de Thread.sleep :
/**
*让当前线程睡眠指定毫秒数,线程不会丢失任何monitors的所有权
*/
public static native void sleep(long millis) throws InterruptedException;
Ok, vamos usar agora:
@Slf4j
public class Sleep_md{
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
long start = System.currentTimeMillis();
log.info("t1睡眠5s");
try {
Thread.sleep(5 * 1000);
} catch (InterruptedException e) {
}
long end = System.currentTimeMillis();
log.info("已过去 {} 毫秒", (end - start));
});
t1.start();
}
}
Por meio da operação do código acima, pode-se descobrir que Thread.sleep pode fazer com que o thread atual entre em TIMED_WAITING sleep por um período de tempo e, em seguida, insira RUNNABLE . Durante este período, a CPU será liberada para dar uma chance a outras threads.
1. TimeUtil
Além do Thread.sleep , o TimeUtil também possui sleep , que pode fazer com que os threads entrem em suspensão e ambos têm o mesmo efeito.
public void sleep(long timeout) throws InterruptedException {
if (timeout > 0) {
long ms = toMillis(timeout);
int ns = excessNanos(timeout, ms);
Thread.sleep(ms, ns);
}
}
Instruções:
// 睡眠n纳秒
TimeUtil.NANOSECONDS.sleep(long n)
// 睡眠n微秒
TimeUtil.MICROSECONDS.sleep(long n)
// 睡眠n毫秒
TimeUtil.MILLISECONDS.sleep(long n)
// 睡眠n秒
TimeUtil.SECONDS.sleep(long n)
// 睡眠n分钟
TimeUtil.MINUTES.sleep(long n)
// 睡眠n小时
TimeUtil.HOURS.sleep(long n)
// 睡眠n天
TimeUtil.DAYS.sleep(long n)
2. Exceção interrompida
InterruptedException é lançada quando o thread adormecido é interrompido .
@Slf4j
public class Sleep_md{
public static void main(String[] args) {
Thread thread = new Thread(() -> {
log.info("开始睡眠");
long start = System.currentTimeMillis();
try {
// 睡眠 60s
Thread.sleep(1000 * 60);
} catch (InterruptedException e) {
long end = System.currentTimeMillis();
log.info("中途停止睡眠");
}
});
thread.start();
thread.interrupt();
}
}
Visível a olho nu, interrupt() interrompe o sono, lança uma exceção e é capturado.
3. definir prioridade
setPriority() —— Defina a prioridade do thread, o valor é de 1 a 10, quanto menor o valor, maior a prioridade.
Nota: Uma prioridade mais alta indica que há mais oportunidade de competir pela fatia de tempo da CPU, não que quanto mais alta a prioridade, mais rápido ela deve ser executada.
- Se a CPU estiver ocupada, os threads com maior prioridade terão mais fatias de tempo
- Quando a CPU está ociosa, a prioridade quase não tem efeito
@Slf4j
public class Priority_md {
public static void main(String[] args) {
Thread t1 = new Thread(() -> log.info("t1运行中..."), "t1");
t1.setPriority(Thread.MAX_PRIORITY);
t1.start();
Thread t2 = new Thread(() -> log.info("t2运行中..."), "t2");
t2.setPriority(Thread.MIN_PRIORITY);
t2.start();
log.info("main运行中...");
}
}
-
3 constantes
public final static int MIN_PRIORITY = 1; public final static int NORM_PRIORITY = 5; public final static int MAX_PRIORITY = 10;
-
Ao definir a prioridade, se o valor não estiver no intervalo [1,10], será lançada uma exceção IllegalArgumentException
-
A prioridade do thread principal é 5 e o valor inicial da prioridade do thread personalizado também é 5
Thread main = Thread.currentThread(); log.info("main线程的优先级是: " + main.getPriority());
4. rendimento
Thread.yield() —— Avisa ao agendador de encadeamento que o encadeamento atual está disposto a desistir da fatia de tempo da CPU para retornar ao estado pronto, e o agendador de encadeamento pode optar por ignorar o prompt (pode não ceder).
Perceber:
- Desistir da fatia de tempo não é necessariamente bem-sucedido, tudo depende do agendador de threads
- A fatia de tempo cedida pode não ser capturada pelo thread de alta prioridade, porque quando a CPU não está tensa, a prioridade é inútil. Mesmo que a CPU esteja relativamente apertada, isso apenas melhora a capacidade do thread de alta prioridade de capturar a fatia de tempo.
@Slf4j
public class Yield_md {
public static void main(String[] args) {
Thread t1 = new Thread(new Runner(), "张三");
t1.setPriority(Thread.MIN_PRIORITY);
Thread t2 = new Thread(new Runner(), "李四");
t2.setPriority(Thread.MIN_PRIORITY);
Thread t3 = new Thread(new Runner(), "王五");
t3.setPriority(Thread.MIN_PRIORITY);
for (int i = 1; i < 10; i++) {
Thread thread = new Thread(new Runner(), "路人" + i);
thread.setPriority(Thread.MAX_PRIORITY);
thread.start();
}
t1.start();
t2.start();
t3.start();
}
private static class Runner implements Runnable {
@Override
public void run() {
for (int i = 1; i < 8; i++) {
if (i % 4 == 0) {
log.info(Thread.currentThread().getName() + " 摔了一跤!");
Thread.yield();
}
if (i == 7) {
log.info(Thread.currentThread().getName() + " 完成了比赛!");
}
}
}
}
}
rendimento vs. sono:
- Após o rendimento ser bem-sucedido, o thread entra em READY diretamente e sleep entra em WAITING ou TIMED_WAITING antes de entrar. Portanto, a thread de yield pode recuperar a CPU para continuar rodando a qualquer momento, enquanto a sleep precisa aguardar o fim do tempo de sleep.
5. junte-se
join() une o thread A especificado ao thread B atualmente em execução, e o thread B entrará no estado de espera e aguardará que A termine a execução antes de executar B.
@Slf4j
public class Join_md {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
try {
for (int i = 0; i < 3; i++) {
log.info("t1----->" + i);
}
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, "t1");
Thread t2 = new Thread(() -> {
try {
t1.join();
for (int i = 0; i < 3; i++) {
log.info("t2----->" + i);
}
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, "t2");
long start = System.currentTimeMillis();
t1.start();
t2.start();
try {
t2.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
long end = System.currentTimeMillis();
log.debug("t1: {} t2: {} cost: {}", t1, t2, end - start);
}
}
Perceber:
-
Join é equivalente a permitir que dois threads de execução alternativos sejam executados sequencialmente
-
Quando vários threads (como A, B e C) se unem em um thread D , A, B e C serão executados alternadamente, como segue:
@Slf4j public class Join_md { public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(() -> { try { log.info("t1......"); for (int i = 0; i < 5; i++) { log.info("t1: " + i); } Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } }, "t1"); Thread t2 = new Thread(() -> { try { log.info("t2......"); for (int i = 0; i < 5; i++) { log.info("t2: " + i); } Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } }, "t2"); long start = System.currentTimeMillis(); t1.start(); t2.start(); log.info("main..."); try { t1.join(); t2.join(); } catch (InterruptedException e) { throw new RuntimeException(e); } long end = System.currentTimeMillis(); log.debug("t1: {} t2: {} cost: {}", t1, t2, end - start); } }
juntar vs. rendimento:
- Do ponto de vista da função: join bloqueia o thread atual e permite que outros threads sejam executados primeiro; yield faz com que o thread atual desista de sua posição e deixe outros threads executarem
- Do ponto de vista do status: join fará com que o thread atual entre em WAITING ; yield fará com que o thread atual entre em READY
- Do ponto de vista do efeito: a junção não falhará; o rendimento pode permitir que os recursos falhem
6. interromper
1. Introdução ao método
interrupção apenas altera o status de interrupção de um thread ( apenas para definir o sinalizador de interrupção ) e notifica esse thread, não interrompe um thread em execução.
isInterrupted determina o status de interrupção do thread atual.
O isInterrupted do thread atual chamado por Thread.interrupted redefine o status de interrupção do thread atual para verdadeiro.
Após a interrupção, o thread isInterrupted retorna true e Thread.interrupted redefine o estado para false.
@Slf4j
public class Interrupt_md {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
if (Thread.currentThread().isInterrupted()) {
log.info("t1已经被interrupt......");
log.info("重置t1中断状态...");
Thread.interrupted();
if (!Thread.currentThread().isInterrupted()) {
log.info("t1状态恢复...");
}
} else {
log.info("t1还没被interrupt");
}
});
t1.start();
t1.interrupt();
}
}
Interrompido por interrupção durante a execução dos seguintes métodos lançará InterruptedException : Thread.sleep , join , wait .
2. Interrompa o tópico
Julgando o bit flag, return interrupt interrompendo uma thread é o método run
que finaliza sua operação , então apenas retorne diretamente .
-
Use isInterrupted como um sinalizador
@Slf4j public class StopThread { public static void main(String[] args) { Thread t1 = new Thread(() -> { if (Thread.currentThread().isInterrupted()) { return; } for (;;) { log.info("t1 is running..."); } }); t1.start(); t1.interrupt(); } }
-
Personalizar uma bandeira
@Slf4j public class StopThread { private volatile static boolean flag = false; public static void main(String[] args) { Thread t1 = new Thread(() -> { if (flag) { return; } for (;;) { log.info("t1 is running..."); } }); t1.start(); t1.interrupt(); flag = true; } }
7. estacionar
O método de LockSupport.park para fazer o thread dormir (entrando no estado WAITING), não entrará diretamente no estado de espera após a chamada e julgará primeiro de acordo com uma permissão . Se a permissão existir, ela retornará diretamente e, se não existir, ele entrará no estado de suspensão ( desativa o thread atual para fins de agendamento de thread, a menos que a permissão esteja disponível. Se a permissão estiver disponível, ela será consumida e a chamada retornará imediatamente; caso contrário, o thread atual será desativado para fins de agendamento de thread )
Existem três situações que interrompem a hibernação:
- Outro thread chama o método LockSupport.unpark
- interrupção de chamada
- outras razões
LockSupport.parkNanos Comparado com LockSupport.park , LockSupport.park precisa especificar um horário e, em seguida, entrar no estado de suspensão (TIMED_WAITING), não há necessidade de acordar, ele acordará naturalmente quando o tempo acabar.
@Slf4j
public class Park_demo {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> LockSupport.park(), "t1");
Thread t2 = new Thread(() -> LockSupport.parkNanos(1000*1000*3000L), "t2");
t1.start();
t2.start();
TimeUnit.SECONDS.sleep(1);
log.info("t1's state: {}, t2's state: {}", t1.getState(), t2.getState());
}
}
Perceber:
-
LockSupport.parkNanos não lançará uma exceção, portanto, usar interrupção para interromper durante a suspensão não gerará uma exceção
-
LockSupport.unpark pode ser usado para ativar threads adormecidas
@Slf4j public class Park_demo { public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(() -> { LockSupport.park(); log.info("t1苏醒..."); }, "t1"); t1.start(); TimeUnit.SECONDS.sleep(1); log.info("t1's state: {}, t1's status: {}", t1.getState(), t1.isInterrupted()); LockSupport.unpark(t1); log.info("t1's state: {}, t1's status: {}", t1.getState(), t1.isInterrupted()); } }
8. setDaemon
Artigo de referência nesta seção: https://www.cnblogs.com/lixuan1998/p/6937986.html
setDaemon(true) define o encadeamento como um encadeamento daemon.
Classificação do encadeamento Java:
- tópico do usuário
- O thread daemon
é um thread que fornece um serviço geral em segundo plano quando o programa está sendo executado. Por exemplo, o thread de coleta de lixo é um guardião muito competente e esse thread não é parte integrante do programa. Portanto, quando todos os threads não-daemon terminam, o programa termina, matando todos os threads daemon no processo. Por outro lado, enquanto quaisquer threads não-daemon ainda estiverem em execução, o programa não será encerrado. O encadeamento de daemon mais típico: encadeamento de coleta de lixo. - A diferença entre os dois: não há diferença essencial entre o thread daemon e o thread do usuário: a única diferença está na saída da máquina virtual: se todos os threads do usuário tiverem saído e apenas o thread daemon existir, a máquina virtual irá também saia para cima. Como não há guardião, o encadeamento do daemon não tem trabalho a fazer e não há necessidade de continuar executando o programa.
Observações sobre o uso de threads daemon:
- setDaemon(true) deve ser usado antes de start , caso contrário, uma exceção IllegalThreadStateException será lançada
- A nova thread gerada na thread daemon também é uma thread daemon
- Um daemon thread nunca deve acessar recursos inerentes, como arquivos, bancos de dados, porque pode ser interrompido a qualquer momento, mesmo no meio de uma operação
@Slf4j
public class Daemon_md {
public static void main(String[] args) {
Thread t1 = new Thread(() -> log.info("t1.isDaemon: {}", Thread.currentThread().isDaemon()), "t1");
Thread t2 = new Thread(()->{
t1.start();
Thread t3 = new Thread("t3");
t3.start();
log.info("t2.isDaemon: {}", Thread.currentThread().isDaemon());
log.info("t3.isDaemon: {}", t3.isDaemon());
}, "t2");
t2.setDaemon(true);
t2.start();
log.info("main.isDaemon: {}", Thread.currentThread().isDaemon());
}
}