Programação Simultânea Java (Ⅰ)


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
  • 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

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:
    1. Carregue o início da chamada do encadeamento no grupo de encadeamentos ThreadGroup
    2. 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());
    }
}

Acho que você gosta

Origin blog.csdn.net/m0_54355172/article/details/128712835
Recomendado
Clasificación