Uma explicação detalhada da programação simultânea em um artigo

Explicação detalhada da programação simultânea

Estudei recentemente: "Compreensão aprofundada da programação de alta simultaneidade" de Binghe ; "A arte da programação simultânea" ;
aqui resume brevemente o estudo para facilitar a subsequente melhoria e consolidação do conhecimento de programação simultânea;
se você quiser entender o estudo em profundidade, você pode ler o original de referência acima;

Threads e pools de threads

processo

Processo é a unidade básica de alocação de recursos no sistema. Quando um sistema operacional moderno executa um programa, ele cria um processo para ele.

O processo é o espaço alocado pela aplicação na memória, que é o programa em execução. Cada processo não interfere entre si.

Um sistema operacional que usa rotação de intervalo de tempo de processo + CPU pode lidar com várias tarefas ao mesmo tempo em nível macro. Em outras palavras, os processos tornam possível a simultaneidade do sistema operacional. Embora a simultaneidade olhe para um nível macro, há várias tarefas em execução, mas na verdade, para uma CPU de núcleo único, apenas uma tarefa ocupa recursos da CPU em um momento específico.

fio

Threads são a unidade básica de escalonamento da CPU. É uma unidade menor que um processo e pode ser executada de forma independente.

Em um processo, vários threads podem ser criados. Esses threads podem ter seus próprios contadores privados, memória de pilha e variáveis ​​locais, e podem acessar variáveis ​​de memória compartilhada.

Multithreading

No mesmo programa, vários threads podem ser executados ao mesmo tempo para executar tarefas diferentes; esses threads podem usar vários núcleos da CPU para serem executados ao mesmo tempo.

Por que usar multithreading? O mais importante é: a programação multithread pode maximizar o uso de recursos de CPU multinúcleo.

mudança de contexto

A troca de contexto refere-se à troca da CPU de um processo (ou thread) para outro processo (ou thread).

Contexto refere-se ao conteúdo dos registros da CPU e do contador do programa em um determinado momento

A troca de contexto geralmente é computacionalmente intensa, o que significa que essa operação consome muito tempo de CPU, portanto, mais threads nem sempre são melhores . Como reduzir o número de trocas de contexto no sistema é um tópico importante para melhorar o desempenho do multithreading.

Como os threads são implementados

  1. Herdar a classe Thread

    public class ThreadDemo {
          
          
    
        public static class TestThread extends Thread {
          
          
            @Override
            public void run() {
          
          
                System.out.println("extends Thread");
            }
        }
    
        public static void main(String[] args) {
          
          
            TestThread testThread = new TestThread();
            testThread.start();
        }
    
        //console:extends Thread
    }
    
  2. Implementar a interface executável

    public class ThreadDemo {
          
          
    
        public static class TestThreadRun implements Runnable {
          
          
            @Override
            public void run() {
          
          
                System.out.println("extends Thread2");
            }
        }
    
        public static void main(String[] args) {
          
          
            Thread thread = new Thread(new TestThreadRun());
            thread.start();
        }
    
        //console:extends Thread2
    }
    
  3. Implementar a interface chamável

    public class ThreadDemo {
          
          
    
        public static class TestThreadCall implements Callable<String> {
          
          
            @Override
            public String call() {
          
          
                System.out.println("extends Thread2");
                return "result:do success";
            }
        }
    
        public static void main(String[] args) {
          
          
            TestThreadCall call = new TestThreadCall();
            String result = call.call();
            System.out.println(result);
        }
    
        //console:
        // extends Thread2
        //result:do success
    }
    

Linha prioritária

Em Java, a prioridade do thread é controlada por meio de uma variável de membro inteiro prioridade, que varia de 1 a 10. Quanto maior o valor, maior a prioridade. O padrão é 5.

Quando threads com alta prioridade recebem intervalos de tempo de CPU, a probabilidade de alocação é maior do que a de threads com baixa prioridade.

Nota: Não se pode confiar na prioridade do thread para a correção do programa (não é garantido que a alta prioridade seja executada antes da baixa prioridade)

Ordem de execução do thread

No mesmo método, após a criação contínua de vários threads, a ordem em que o método start() do thread é chamado não determina a ordem de execução dos threads.

Como garantir a ordem de execução dos threads?

  1. Você pode usar o método join() na classe Thread para garantir a ordem de execução dos threads. join() na verdade faz com que o thread principal espere que o thread filho atual conclua a execução .
  2. O método join() chamará o método wait() local internamente.Quando o método wait() do thread for chamado, o thread principal estará em um estado de espera, aguardando que o thread filho conclua a execução antes de executar

Ciclo de vida do encadeamento

Vários estados importantes no ciclo de vida do thread:

  1. NOVO: O thread foi criado, mas o método start() não foi chamado;

  2. RUNNABLE: Estado executável, incluindo estado pronto e estado em execução;

  3. BLOQUEADO: Estado bloqueado. Threads neste estado precisam esperar que outros threads liberem o bloqueio ou esperar para entrar sincronizados;

  4. WAITTING: Estado de espera. Threads neste estado precisam esperar que outros threads acordem ou interrompam as operações antes de entrar no próximo estado;

    Chamar os três métodos a seguir colocará o thread em estado de espera:

    1. Object.wait(): coloca o thread atual em estado de espera até que outro thread o acorde
    2. Thread.join(): Aguarde a conclusão do thread. A chamada subjacente é o método wait() do Object.
    3. LockSupport.park(): Desativa o thread atual para agendamento de thread, a menos que seja concedida permissão para chamá-lo.
  5. TIME_WAITTING: Estado de espera de tempo limite, que pode retornar sozinho após o tempo especificado;

  6. TERMINATED: Status de término, indicando que a thread concluiu a execução;

Fluxograma do ciclo de vida da rosca (abreviado):

Insira a descrição da imagem aqui

Nota: Use jps combinado com o comando jstack para analisar threadmente informações de exceção de thread Java no ambiente de produção.

Compreensão aprofundada da classe Thread

Definição de classe de thread

public class Thread implements Runnable {
    
    ...}

@FunctionalInterface
public interface Runnable {
    
    
    public abstract void run();
}

A classe Thread implementa a interface Runnable, e a interface Rnnnable possui apenas um método run() e é modificada pela anotação @FunctionalInterface

Carregar recursos locais

/* Make sure registerNatives is the first thing <clinit> does. */
private static native void registerNatives();
static {
    
    
    registerNatives();
}

Este método é usado principalmente para carregar alguns recursos locais e chamar esse método local em um bloco de código estático

Variável de membro do thread

 //线程名称
 private volatile String name;
 //优先级
 private int            priority;
 private Thread         threadQ;
 private long           eetop;

 /* Whether or not to single_step this thread. */
 //是否单步线程
 private boolean     single_step;

 /* Whether or not the thread is a daemon thread. */
 //是否守护线程
 private boolean     daemon = false;

 /* JVM state */
 private boolean     stillborn = false;

 /* What will be run. */
 //实际执行体
 private Runnable target;

 /* The group of this thread */
 private ThreadGroup group;

 /* The context ClassLoader for this thread */
 private ClassLoader contextClassLoader;

 /* The inherited AccessControlContext of this thread */
 //访问控制上下文
 private AccessControlContext inheritedAccessControlContext;

 /* For autonumbering anonymous threads. */
 //为匿名线程生成名称的编号
 private static int threadInitNumber;
 private static synchronized int nextThreadNum() {
    
    
     return threadInitNumber++;
 }

 /* ThreadLocal values pertaining to this thread. This map is maintained
  * by the ThreadLocal class. */
 //与此线程相关的ThreadLocal
 ThreadLocal.ThreadLocalMap threadLocals = null;

 /*
  * InheritableThreadLocal values pertaining to this thread. This map is
  * maintained by the InheritableThreadLocal class.
  */
 ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

 //当前线程请求的堆栈大小
 private long stackSize;
 //线程终止后存在的JVM私有状态
 private long nativeParkEventPointer;
 //线程ID
 private long tid;

 /* For generating thread ID */
 //用于生成线程ID
 private static long threadSeqNumber;

 /* Java thread status for tools,
  * initialized to indicate thread 'not yet started'
  */
 //线程状态,初始为0,表示未启动
 private volatile int threadStatus = 0;

 /**
  * The argument supplied to the current call to
  * java.util.concurrent.locks.LockSupport.park.
  * Set by (private) java.util.concurrent.locks.LockSupport.setBlocker
  * Accessed using java.util.concurrent.locks.LockSupport.getBlocker
  */
 volatile Object parkBlocker;

 /* The object in which this thread is blocked in an interruptible I/O
  * operation, if any.  The blocker's interrupt method should be invoked
  * after setting this thread's interrupt status.
  */
 //Interruptible中定义了中断方法,用来中断特定线程
 private volatile Interruptible blocker;
 //当前线程的内部锁
 private final Object blockerLock = new Object();
 //线程最小优先级
 public final static int MIN_PRIORITY = 1;
 //默认优先级
 public final static int NORM_PRIORITY = 5;
 //最大优先级
 public final static int MAX_PRIORITY = 10;

Pode-se ver pelas variáveis ​​​​de membro que a classe Thread não é uma tarefa, mas um objeto de thread real.A variável de destino do tipo Runnable dentro dela é a tarefa real.

Definição de estado de thread

public enum State {
    
    
    /**
     * Thread state for a thread which has not yet started.
     */
    //新建状态;线程被创建,但是还没有调用start()方法
    NEW,

    /**
     * Thread state for a runnable thread.  A thread in the runnable
     * state is executing in the Java virtual machine but it may
     * be waiting for other resources from the operating system
     * such as processor.
     */
    //可运行状态;包括运行中状态和就绪状态
    RUNNABLE,

    //阻塞状态;此状态的线程需要等待其他线程释放锁,或者等待进入synchronized
    BLOCKED,

    //等待状态;此状态的线程需要其他线程对其进行唤醒或者中断状态,进而进入下一状态
    WAITING,

    //超时等待状态;可以在一定的时间自行返回
    TIMED_WAITING,

    /**
     * Thread state for a terminated thread.
     * The thread has completed execution.
     */
    //终止状态;当前线程执行完毕
    TERMINATED;
}

Método construtor da classe Thread

public Thread() {
    
    
    init(null, null, "Thread-" + nextThreadNum(), 0);
}

public Thread(Runnable target) {
    
    
        init(null, target, "Thread-" + nextThreadNum(), 0);
}

public Thread(ThreadGroup group, Runnable target) {
    
    
        init(group, target, "Thread-" + nextThreadNum(), 0);
}

public Thread(String name) {
    
    
        init(null, null, name, 0);
}

public Thread(ThreadGroup group, String name) {
    
    
        init(group, null, name, 0);
}

public Thread(ThreadGroup group, Runnable target, String name) {
    
    
        init(group, target, name, 0);
}

Através de vários métodos de construção da classe Thread comumente usados, descobrimos que a inicialização da classe Thread é obtida principalmente por meio do método init().

método init()

/**
 * Initializes a Thread.
 *
 * @param g the Thread group
 * @param target the object whose run() method gets called
 * @param name the name of the new Thread
 * @param stackSize the desired stack size for the new thread, or
 *        zero to indicate that this parameter is to be ignored.
 * @param acc the AccessControlContext to inherit, or
 *            AccessController.getContext() if null
 * @param inheritThreadLocals if {@code true}, inherit initial values for
 *            inheritable thread-locals from the constructing thread
 */
private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize, AccessControlContext acc,
                  boolean inheritThreadLocals) {
    
    
    if (name == null) {
    
    
        //名称为空
        throw new NullPointerException("name cannot be null");
    }

    this.name = name;

    Thread parent = currentThread();
    //安全管理器
    SecurityManager security = System.getSecurityManager();
    if (g == null) {
    
    
        /* Determine if it's an applet or not */

        /* If there is a security manager, ask the security manager
           what to do. */
        if (security != null) {
    
    
            //获取线程组
            g = security.getThreadGroup();
        }

        /* If the security doesn't have a strong opinion of the matter
           use the parent thread group. */
        if (g == null) {
    
    
            //线程组为空,从父类获取
            g = parent.getThreadGroup();
        }
    }

    /* checkAccess regardless of whether or not threadgroup is
       explicitly passed in. */
    g.checkAccess();

    /*
     * Do we have the required permissions?
     */
    if (security != null) {
    
    
        if (isCCLOverridden(getClass())) {
    
    
            security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
        }
    }

    g.addUnstarted();
    //当前线程继承父线程相关属性
    this.group = g;
    this.daemon = parent.isDaemon();
    this.priority = parent.getPriority();
    if (security == null || isCCLOverridden(parent.getClass()))
        this.contextClassLoader = parent.getContextClassLoader();
    else
        this.contextClassLoader = parent.contextClassLoader;
    this.inheritedAccessControlContext =
            acc != null ? acc : AccessController.getContext();
    this.target = target;
    setPriority(priority);
    if (inheritThreadLocals && parent.inheritableThreadLocals != null)
        this.inheritableThreadLocals =
            ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
    /* Stash the specified stack size in case the VM cares */
    this.stackSize = stackSize;

    /* Set thread ID */
    tid = nextThreadID();
}

O método de construção da classe Thread é chamado pelo thread principal que cria o thread Thread. Neste momento, o thread principal que chama o método de construção é o thread pai de Thread; no método init(), o thread Thread recém-criado herdará parte do thread pai. Atributos

método run()

@Override
public void run() {
    
    
    if (target != null) {
    
    
        target.run();
    }
}

Pode-se ver que a implementação do método run() do método Thread é relativamente simples, na verdade é implementada executando o método run() em um destino do tipo Runnable;

Deve-se observar que chamar diretamente o método run da interface Runnable não criará um novo thread para executar a tarefa; se você precisar criar um novo thread para executar a tarefa, será necessário chamar o método start() do Thread aula;

método start()

public synchronized void start() {
    
    
    /**
     * This method is not invoked for the main method thread or "system"
     * group threads created/set up by the VM. Any new functionality added
     * to this method in the future may have to also be added to the VM.
     *
     * A zero status value corresponds to state "NEW".
     */
    if (threadStatus != 0)
        throw new IllegalThreadStateException();

    /* Notify the group that this thread is about to be started
     * so that it can be added to the group's list of threads
     * and the group's unstarted count can be decremented. */
    group.add(this);
    //标记线程是否启动
    boolean started = false;
    try {
    
    
        //调用本地方法启动
        start0();
        //变更线程启动标识
        started = true;
    } finally {
    
    
        try {
    
    
            if (!started) {
    
    
                //未启动,则线程组中标记为启动失败
                group.threadStartFailed(this);
            }
        } catch (Throwable ignore) {
    
    
            /* do nothing. If start0 threw a Throwable then
              it will be passed up the call stack */
        }
    }
}

Pode-se observar no código-fonte que o método start() é modificado por sincronizado, indicando que este método é síncrono. Ele verificará o status do thread antes que o thread seja realmente iniciado. Se não for 0 (NOVO status) , ele retornará diretamente uma exceção, portanto, um thread A só pode ser iniciado uma vez. Se for iniciado várias vezes, uma exceção será relatada ;

Após chamar o método start(), o thread recém-criado estará no estado pronto (se não for agendado pela CPU). Quando a CPU estiver ociosa, ele será agendado pela CPU para execução. Neste momento, o thread está no estado de execução e a JVM chamará o método run() do thread para executar tarefas

método dormir()

public static native void sleep(long millis) throws InterruptedException;

public static void sleep(long millis, int nanos) throws InterruptedException {
    
    
    if (millis < 0) {
    
    
        throw new IllegalArgumentException("timeout value is negative");
    }

    if (nanos < 0 || nanos > 999999) {
    
    
        throw new IllegalArgumentException(
                            "nanosecond timeout value out of range");
    }

    if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
    
    
        millis++;
    }

    sleep(millis);
}

O método sleep() fará com que o thread hiberne por um período de tempo. Deve-se observar que o bloqueio não será liberado após chamar o método sleep() para colocar o thread em suspensão.

método join()

O cenário de uso do método join() geralmente é o thread que inicia o thread para executar a tarefa, chama o método join() do thread de execução e espera que o thread de execução execute a tarefa até o tempo limite ou a execução thread termina.

método interromper()

Interrupção do tópico:

Em alguns casos, depois de iniciarmos o thread, descobrimos que não precisamos executá-lo e precisamos interrompê-lo. Atualmente não existe uma maneira segura e direta de interromper um thread em JAVA, mas JAVA introduz um mecanismo de interrupção de thread para lidar com situações em que os threads precisam ser interrompidos.

O mecanismo de interrupção de thread é um mecanismo cooperativo. Deve-se notar que a operação de interrupção não encerra diretamente o thread em execução, mas notifica o thread interrompido para tratá-lo por conta própria.

Vários métodos são fornecidos na classe Thread para lidar com interrupções de thread:

  1. Thread.interrupt(): Interrompe o thread. O thread de interrupção aqui não encerra imediatamente o thread em execução, mas define o sinalizador de interrupção do thread como verdadeiro.
  2. Thread.currentThread.isInterrupt(): testa se o thread atual foi interrompido. O status de interrupção do thread é afetado por esse método, o que significa que chamá-lo uma vez definirá o status de interrupção do thread como verdadeiro, e chamá-lo duas vezes redefinirá o status de interrupção do thread para falso.
  3. Thread.isInterrupt(): testa se o thread atual foi interrompido. Ao contrário do método acima, este método não altera o status de interrupção do thread.

Este método é usado para interromper a execução do thread atual. Ele interrompe o thread atual definindo o bit do sinalizador de interrupção do thread. Neste momento, se o bit do sinalizador de interrupção estiver definido para o thread,

Uma InterruptException pode ser lançada e o status de interrupção do thread atual será limpo. Esta forma de interromper o thread é mais segura, pois permite que a tarefa que está sendo executada continue a ser concluída, ao contrário do stop que força o fechamento do thread.

public void interrupt() {
    
    
    if (this != Thread.currentThread())
        checkAccess();

    synchronized (blockerLock) {
    
    
        Interruptible b = blocker;
        if (b != null) {
    
    
            interrupt0();           // Just to set the interrupt flag
            b.interrupt(this);
            return;
        }
    }
    interrupt0();
}

Suspender()/resume()/stop() expirado

Os três métodos suspend()/resume()/stop() podem ser simplesmente entendidos como suspender, retomar e encerrar threads. Como os métodos estão desatualizados, eles não são recomendados para uso.

Exigível e Futuro

Interface chamável

A interface Callable pode obter o resultado de retorno após a execução do thread; enquanto herdar Thread e implementar a interface Runnable não pode obter o resultado da execução.

@FunctionalInterface
public interface Runnable {
    
    
    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see     java.lang.Thread#run()
     */
    public abstract void run();
}


@FunctionalInterface
public interface Callable<V> {
    
    
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}

Pode-se observar que a interface Callable é semelhante à interface Runnable, sendo também uma interface funcional com apenas um método, a diferença é que o método fornecido por Callable possui valor de retorno e suporta genéricos.

Callable é geralmente usado em conjunto com a classe ExecutorService da subferramenta de thread. Iremos nos aprofundar no uso de pools de threads nos capítulos subsequentes.

Aqui apenas apresentamos que ExecutorService pode chamar o método submit para executar um Callable e retornar um Future.Os programas subsequentes podem obter o resultado da execução através do método get do Future.

public class TestTask implements Callable<String> {
    
    
    @Override
    public String call() throws Exception {
    
    
        //模拟程序执行需要一秒
        Thread.sleep(1000);
        return "do success!";
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
    
    
        ExecutorService executorService = Executors.newCachedThreadPool();
        TestTask testTask = new TestTask();
        Future<String> submitRes = executorService.submit(testTask);

        //注意调用get方法会阻塞当前线程,知道得到结果
        //实际编码中建议使用设有超时时间的重载get方法
        String reslut = submitRes.get();
        System.out.println(reslut);
    }
    
    //console:
    // do success!
}

Modelo assíncrono

  1. Modelo assíncrono sem resultado de retorno

    Tarefas assíncronas que não retornam resultados podem ser lançadas diretamente em threads ou pools de threads para execução. Neste momento, os resultados da execução da tarefa não podem ser obtidos diretamente. Uma maneira é obter os resultados da execução por meio do método de retorno de chamada; o método de implementação é semelhante para o modo observador.

  2. Modelos assíncronos que retornam resultados

    O JDK fornece uma solução que pode obter e retornar resultados assíncronos diretamente:

    1. Use o Future para obter resultados

      A interface Future é frequentemente usada em conjunto com o pool de threads para obter resultados assíncronos.

    2. Obtenha resultados usando FutureTask

      A classe FutureTask pode ser usada em conjunto com a classe Thread ou com o pool de threads.

Interface futura

  1. Interface futura

    public interface Future<V> {
          
          
        
        boolean cancel(boolean mayInterruptIfRunning);
    
        boolean isCancelled();
    
        boolean isDone();
       
        V get() throws InterruptedException, ExecutionException;
        
        V get(long timeout, TimeUnit unit)
    throws InterruptedException, ExecutionException, TimeotException;
    
    1. cancelamento booleano (booleano)
      1. Cancela a execução da tarefa, recebendo um parâmetro do tipo booleano, e retorna verdadeiro se o cancelamento for bem sucedido, caso contrário retorna falso.
      2. A tarefa foi concluída, finalizada ou não pode ser cancelada. Retorna falso, indicando que o cancelamento falhou.
      3. Se a tarefa não tiver sido iniciada, chamar esse método retornará verdadeiro, indicando o cancelamento bem-sucedido.
      4. Se a tarefa tiver sido iniciada, será determinado com base no parâmetro booleano de entrada se a tarefa deve ser cancelada interrompendo o thread.
    2. booleano é cancelado()
      1. A tarefa de julgamento é cancelada antes da conclusão
      2. Se a tarefa for cancelada antes da conclusão, retorne verdadeiro, caso contrário, retorne falso
      3. Nota: True será retornado somente se a tarefa não for iniciada e cancelada antes da conclusão, caso contrário retornará false.
    3. booleano isDone()
      1. Determinar se a tarefa foi concluída
      2. Se a tarefa terminar normalmente, sair com uma exceção ou for cancelada, ela retornará verdadeiro, indicando que a tarefa foi concluída.
    4. V get()
      1. Quando a tarefa for concluída, os dados do resultado da tarefa serão retornados diretamente.
      2. Quando não concluído, aguarde a conclusão da tarefa e retorne o resultado
    5. V get(longo,TimeUnit)
      1. Quando a tarefa for concluída, o resultado da conclusão da tarefa será retornado diretamente.
      2. Se não for concluído, aguardará o resultado do retorno dentro do tempo limite.Se o tempo for excedido, uma exceção TimeOutException será lançada.
  2. Interface RunnableFuture

    public interface RunnableFuture<V> extends Runnable, Future<V> {
          
          
        /**
         * Sets this Future to the result of its computation
         * unless it has been cancelled.
         */
        void run();
    }
    

    A interface RunnabeleFuture não apenas herda a interface Future, mas também herda a interface Runnable, portanto possui as interfaces abstratas de ambas.

  3. Classe FutureTask

    public class FutureTask<V> implements RunnableFuture<V> {
          
          
        //详细源码,后续分析
    }
    

    A classe FutureTask é uma classe de implementação muito importante da interface RunnableFuture. Ela implementa todos os métodos abstratos da interface RunnableFuture, interface Future e interface Runnnable.

    1. Variáveis ​​e constantes na classe FutureTask

      Primeiro, um estado variável modificado volátil é definido.Variáveis ​​voláteis alcançam segurança de thread através de barreiras de memória e proibindo reordenação;

      Em seguida, defina várias constantes de status quando a tarefa estiver em execução;

      (Nos comentários do código, são fornecidas várias alterações de status possíveis)

      	/* Possible state transitions:
           * NEW -> COMPLETING -> NORMAL
           * NEW -> COMPLETING -> EXCEPTIONAL
           * NEW -> CANCELLED
           * NEW -> INTERRUPTING -> INTERRUPTED
           */
          private volatile int state;
          private static final int NEW          = 0;
          private static final int COMPLETING   = 1;
          private static final int NORMAL       = 2;
          private static final int EXCEPTIONAL  = 3;
          private static final int CANCELLED    = 4;
          private static final int INTERRUPTING = 5;
          private static final int INTERRUPTED  = 6;
      

      A seguir, diversas variáveis ​​de membro são definidas;

      	/** The underlying callable; nulled out after running */
          private Callable<V> callable;
          /** The result to return or exception to throw from get() */
          private Object outcome; // non-volatile, protected by state reads/writes
          /** The thread running the callable; CASed during run() */
          private volatile Thread runner;
          /** Treiber stack of waiting threads */
          private volatile WaitNode waiters;
      
      1. chamável: executa tarefas específicas chamando o método run();
      2. outcaom: o resultado de retorno ou informações de exceção obtidas através do método get()
      3. runner: o thread usado para executar a interface que pode ser chamada e garantir a segurança do thread por meio do CAS
      4. waiters: a pilha de threads em espera. Nas classes derivadas, CAS e esta pilha serão usados ​​para mudar o estado de execução.
    2. Método de construção

      Dois métodos de construção diferentes para passagem de parâmetros;

      
          public FutureTask(Callable<V> callable) {
              
              
              if (callable == null)
                  throw new NullPointerException();
              this.callable = callable;
              this.state = NEW;       // ensure visibility of callable
          }
      
          /**
           * Creates a {@code FutureTask} that will, upon running, execute the
           * given {@code Runnable}, and arrange that {@code get} will return the
           * given result on successful completion.
           *
           * @param runnable the runnable task
           * @param result the result to return on successful completion. If
           * you don't need a particular result, consider using
           * constructions of the form:
           * {@code Future<?> f = new FutureTask<Void>(runnable, null)}
           * @throws NullPointerException if the runnable is null
           */
          public FutureTask(Runnable runnable, V result) {
              
              
              this.callable = Executors.callable(runnable, result);
              this.state = NEW;       // ensure visibility of callable
          }
      
    3. isCancelled() e isDone()

          public boolean isCancelled() {
              
              
              return state >= CANCELLED;
          }
      
          public boolean isDone() {
              
              
              return state != NEW;
          }
      

      Em ambos os métodos, se foi cancelado ou concluído é determinado pelo tamanho do valor do status;

      O que pode ser aprendido aqui é que ao definir valores de status no futuro, tente seguir certas regras de mudança.Desta forma, para cenários onde o status muda com frequência, os valores regulares de status podem ter o efeito de obter o dobro do resultado com metade do esforço ao executar a lógica de negócios.

    4. método cancelar (booleano)

      public boolean cancel(boolean mayInterruptIfRunning) {
              
              
              if (!(state == NEW &&
                    UNSAFE.compareAndSwapInt(this, stateOffset, NEW,
                        mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
                  return false;
              try {
              
                  // in case call to interrupt throws exception
                  if (mayInterruptIfRunning) {
              
              
                      try {
              
              
                          Thread t = runner;
                          if (t != null)
                              t.interrupt();
                      } finally {
              
               // final state
                          UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
                      }
                  }
              } finally {
              
              
                  finishCompletion();
              }
              return true;
          }
      

      Primeiro, será avaliado rapidamente se pode ser cancelado com base no julgamento do status ou no resultado da operação do CAS; se o status não for NOVO ou o CAS retornar falso, a falha no cancelamento será retornada diretamente;

      Em seguida, no bloco de código try, primeiro determine se ele pode ser interrompido para cancelar; em caso afirmativo, defina uma referência apontando para a tarefa em execução e determine se a tarefa está vazia. Caso contrário, chame o método de interrupção e modifique a execução status para cancelar;

      Por fim, chame o método finishCompletion() no bloco de código finalmente para encerrar a execução da tarefa;

      /**
           * Removes and signals all waiting threads, invokes done(), and
           * nulls out callable.
           */
          private void finishCompletion() {
              
              
              // assert state > COMPLETING;
              for (WaitNode q; (q = waiters) != null;) {
              
              
                  if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
              
              
                      for (;;) {
              
              
                          Thread t = q.thread;
                          if (t != null) {
              
              
                              q.thread = null;
                              LockSupport.unpark(t);
                          }
                          WaitNode next = q.next;
                          if (next == null)
                              break;
                          q.next = null; // unlink to help gc
                          q = next;
                      }
                      break;
                  }
              }
              done();
              callable = null;        // to reduce footprint
          }
      

      No método finishCompletion(), um loop for é definido primeiro, fazendo um loop de waiters (pilha de espera de thread), e a condição de término do loop é que os waiters estão vazios; dentro do loop específico, primeiro determine se a operação CAS foi bem-sucedida e, se for bem-sucedida , defina um novo loop de spin; no loop de spin, o thread na pilha será despertado para completar sua operação. Após a conclusão, break saltará para fora do loop e, finalmente, o método done() será chamado e o chamável será estar vazio;

    5. método get()

      
          public V get() throws InterruptedException, ExecutionException {
              
              
              int s = state;
              if (s <= COMPLETING)
                  s = awaitDone(false, 0L);
              return report(s);
          }
      
          public V get(long timeout, TimeUnit unit)
              throws InterruptedException, ExecutionException, TimeoutException {
              
              
              if (unit == null)
                  throw new NullPointerException();
              int s = state;
              if (s <= COMPLETING &&
                  (s = awaitDone(true, unit.toNanos(timeout))) <= COMPLETING)
                  throw new TimeoutException();
              return report(s);
          }
      

      O método get() sem parâmetros bloqueará e aguardará a conclusão da tarefa quando a tarefa não for concluída; o método get() com parâmetros bloqueará e aguardará a conclusão, mas após o período de tempo determinado exceder, uma TimeoutException será jogado;

      /**
           * Awaits completion or aborts on interrupt or timeout.
           *
           * @param timed true if use timed waits
           * @param nanos time to wait, if timed
           * @return state upon completion
           */
          private int awaitDone(boolean timed, long nanos)
              throws InterruptedException {
              
              
              final long deadline = timed ? System.nanoTime() + nanos : 0L;
              WaitNode q = null;
              boolean queued = false;
              for (;;) {
              
              
                  if (Thread.interrupted()) {
              
              
                      removeWaiter(q);
                      throw new InterruptedException();
                  }
      
                  int s = state;
                  if (s > COMPLETING) {
              
              
                      if (q != null)
                          q.thread = null;
                      return s;
                  }
                  else if (s == COMPLETING) // cannot time out yet
                      Thread.yield();
                  else if (q == null)
                      q = new WaitNode();
                  else if (!queued)
                      queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
                                                           q.next = waiters, q);
                  else if (timed) {
              
              
                      nanos = deadline - System.nanoTime();
                      if (nanos <= 0L) {
              
              
                          removeWaiter(q);
                          return state;
                      }
                      LockSupport.parkNanos(this, nanos);
                  }
                  else
                      LockSupport.park(this);
              }
          }
      

      O método waitDone() espera principalmente pela conclusão ou interrupção da execução;

      O mais importante é o loop for spin. No loop, ele primeiro determinará se ele foi interrompido. Se for interrompido, removeWaiter() é chamado para remover a pilha de espera e lançar uma exceção de interrupção; se não for interrompido, a lógica é executada para determinar se foi concluída;

    6. set() e setException()

      
          protected void set(V v) {
              
              
              if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
              
              
                  outcome = v;
                  UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
                  finishCompletion();
              }
          }
      
          protected void setException(Throwable t) {
              
              
              if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
              
              
                  outcome = t;
                  UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL);
                  finishCompletion();
              }
          }
      

      A lógica dos dois métodos é quase a mesma, exceto que um é definido como NORMAL ao definir o status da tarefa e o outro é definido como EXCEPCIONAL;

    7. run() e runAndReset()

      public void run() {
              
              
              if (state != NEW ||
                  !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                               null, Thread.currentThread()))
                  return;
              try {
              
              
                  Callable<V> c = callable;
                  if (c != null && state == NEW) {
              
              
                      V result;
                      boolean ran;
                      try {
              
              
                          result = c.call();
                          ran = true;
                      } catch (Throwable ex) {
              
              
                          result = null;
                          ran = false;
                          setException(ex);
                      }
                      if (ran)
                          set(result);
                  }
              } finally {
              
              
                  // runner must be non-null until state is settled to
                  // prevent concurrent calls to run()
                  runner = null;
                  // state must be re-read after nulling runner to prevent
                  // leaked interrupts
                  int s = state;
                  if (s >= INTERRUPTING)
                      handlePossibleCancellationInterrupt(s);
              }
          }
      

      Pode-se dizer que se Future ou FutureTask() for usado, o método run() será inevitavelmente chamado para executar a tarefa;

      No método run(), ele primeiro determinará se está no estado NOVO ou se a operação CAS retornará falso e retornará diretamente sem continuar a execução;

      No próximo bloco de código try, o método call() do callable é executado e o resultado é recebido;

    8. Método removeWaiter()

      
          private void removeWaiter(WaitNode node) {
              
              
              if (node != null) {
              
              
                  node.thread = null;
                  retry:
                  for (;;) {
              
                        // restart on removeWaiter race
                      for (WaitNode pred = null, q = waiters, s; q != null; q = s){
              
              
                          s = q.next;
                          if (q.thread != null)
                              pred = q;
                          else if (pred != null) {
              
              
                              pred.next = s;
                              if (pred.thread == null) // check for race
                                  continue retry;
                          }
                          else if (!UNSAFE.compareAndSwapObject(this, waitersOffset,
                                                                q, s))
                              continue retry;
                      }
                      break;
                  }
              }
          }
      

      Este método remove principalmente o thread no WaitNode (pilha de espera) por meio de um loop de rotação;

Análise aprofundada de ThreadPoolExecutor

A subtecnologia de thread do Java é uma das principais tecnologias do Java. No campo da alta simultaneidade do Java, é um tópico que nunca pode ser evitado.

Desvantagens de Thread criar threads diretamente

  1. Um novo thread é criado toda vez que new Thread() é usado, sem reutilização e com baixo desempenho;
  2. Os threads não possuem gerenciamento unificado e novos threads podem ser criados sem restrições, o que pode ocupar uma grande quantidade de recursos e causar OOM ou travamento;
  3. Falta de mais operações de controle, como mais execuções, execuções regulares, interrupções, etc.;

Benefícios de usar o pool de threads

  1. Threads existentes podem ser reutilizados, reduzindo a sobrecarga causada por novos threads e melhorando o desempenho;
  2. Ele pode controlar efetivamente o número máximo de simultaneidades, melhorar a utilização de recursos e reduzir a competição de recursos causada por muitos threads;
  3. Fornece funções como execução agendada, execução periódica, thread único e controle de simultaneidade;
  4. Fornece métodos para suportar o monitoramento do pool de threads, que pode monitorar o status de execução do pool de threads em tempo real;

Grupo de discussão

  1. Executores
    1. newCachedThreadPool:创建一个可缓冲的线程池,若线程池的大小超出了处理需要,可以灵活回收空闲线程,若无线程可回收,则新建线程;
    2. newFixedThreadPool:创建一个定长的线程池,可以控制最大线程并发数,超过定长的线程将在队列中等待;
    3. newScheduledThreadPool:创建一个定长的线程池,可以定时、定期性的执行;
    4. newSingleThreadPool:创建一个单线程化的线程池,使用唯一的线程执行线程任务,可保证所有任务按照指定顺序执行;
    5. newSingleScheduleThreadPool:创建一个单线程化的线程池,可定时、定期性的执行;
    6. newWorkStealingThreadPool:创建一个具有并行级别的work-stealing线程池;

线程池实例的几种状态

  1. Running

    运行状态,能接受新提交的任务,也能处理阻塞队列中的任务

  2. Shutdown

    关闭状态,不在接收新提交任务,但可以处理阻塞队列中已保存的任务;

    Running --> shutdown() --> Shutdown

  3. Stop

    停止状态,不能接收新任务,也不能处理阻塞队列中的任务,会中断正在处理中的任务;

    Running/Shutdown --> shutdownNow() --> Stop

  4. Tidying

    清空状态,所有的任务都已经终止,有效线程数为0(阻塞队列中的线程数为0,线程池中执行的线程数也为0)

  5. Terminated

    结束状态,Tidying --> terminate() --> Terminated

注意:无须对线程池状态做特殊处理,线程池状态是线程池内部根据方法自行定义和处理的

合理配置线程数的建议

  1. CPU密集型任务,需要压榨cpu,线程数可设置为ncpu+1(cpu数量+1)
  2. IO密集型任务,数量可设置为ncpu*2(2倍的cpu数量)

线程池核心类之ThreadPoolExecutor

  1. 构造方法

    /**
         * Creates a new {@code ThreadPoolExecutor} with the given initial
         * parameters and default thread factory and rejected execution handler.
         * It may be more convenient to use one of the {@link Executors} factory
         * methods instead of this general purpose constructor.
         *
         * @param corePoolSize the number of threads to keep in the pool, even
         *        if they are idle, unless {@code allowCoreThreadTimeOut} is set
         * @param maximumPoolSize the maximum number of threads to allow in the
         *        pool
         * @param keepAliveTime when the number of threads is greater than
         *        the core, this is the maximum time that excess idle threads
         *        will wait for new tasks before terminating.
         * @param unit the time unit for the {@code keepAliveTime} argument
         * @param workQueue the queue to use for holding tasks before they are
         *        executed.  This queue will hold only the {@code Runnable}
         *        tasks submitted by the {@code execute} method.
         * @throws IllegalArgumentException if one of the following holds:<br>
         *         {@code corePoolSize < 0}<br>
         *         {@code keepAliveTime < 0}<br>
         *         {@code maximumPoolSize <= 0}<br>
         *         {@code maximumPoolSize < corePoolSize}
         * @throws NullPointerException if {@code workQueue} is null
         */
        public ThreadPoolExecutor(int corePoolSize,
                                  int maximumPoolSize,
                                  long keepAliveTime,
                                  TimeUnit unit,
                                  BlockingQueue<Runnable> workQueue) {
          
          
            this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
                 Executors.defaultThreadFactory(), defaultHandler);
        }
    

    此为参数最多的构造方法,其他构造都以此重载

    1. corePoolSize: número de threads principais
    2. maxPoolSize: número máximo de threads
    3. workQueue: bloqueando fila, armazenando tarefas esperando para serem executadas

    A relação entre os três parâmetros acima é a seguinte:

    • Se o número de threads em execução for menor que corePoolSize, um novo thread será criado diretamente para execução; mesmo que outros threads no pool de threads estejam ociosos
    • Se o número de threads em execução for maior ou igual a corePoolSize e menor que maximoPoolSize, o número excedente entrará no workQueue e aguardará. Somente quando o workQueue estiver cheio um novo thread será criado.
    • Se corePoolSize for igual a MaximumPoolSize, então o tamanho do pool de threads será fixo neste momento. Se uma nova tarefa for enviada e o workQueue não estiver cheio, ele entrará no workQueue e aguardará que um thread ocioso seja retirado do workQueue por execução.
    • Se o número de threads em execução exceder maxPoolSize e o workQueue estiver cheio, a política será processada por meio da política de rejeição rejeitaHandler

    Com base nos parâmetros acima, o thread processará a tarefa da seguinte forma:

    Quando uma nova tarefa é enviada ao conjunto de encadeamentos, o conjunto de encadeamentos executará diferentes métodos de processamento com base no número de encadeamentos atualmente em execução; existem três métodos principais de processamento: comutação direta, uso de filas ilimitadas e uso de filas limitadas.

    • A troca direta da fila comumente usada é SynchronousQueue
    • Usar uma fila infinita significa usar uma fila baseada em uma lista vinculada. Por exemplo, LinkedBlockingQueue, ao usar esta fila, o número máximo de threads criados no pool de threads é corePoolSize, MaximumPoolSize não funcionará
    • Usar uma fila limitada significa usar uma fila baseada em array. Por exemplo, ArrayBlockingQueue, usando esse método, pode limitar o número máximo de threads no pool de threads para maximumPoolSize, o que pode reduzir o consumo de recursos; mas esse método torna mais difícil agendar threads, porque o número de pools de threads e o número de as filas são fixas

    Com base nos parâmetros acima, podemos simplesmente traçar algumas medidas para reduzir o consumo de recursos:

    • Se você deseja reduzir o consumo de recursos do sistema, a sobrecarga de alternância de contexto, etc., você pode definir uma capacidade de fila maior e uma capacidade menor de pool de threads, o que reduzirá o rendimento das tarefas de processamento de threads.
    • Se as tarefas enviadas forem bloqueadas com frequência, você poderá redefinir o número máximo de threads para um número maior.
    1. keepAliveTime: quando o thread não está executando tarefas, o tempo máximo de espera pelo encerramento

      Quando o número de threads no pool de threads excede corePoolSize, se nenhuma nova tarefa for enviada, os threads que excedem o número de threads principais não serão destruídos imediatamente, mas serão destruídos após aguardar keepAliveTime.

    2. unidade: unidade de tempo de keepAliveTime

    3. threadFactory: fábrica de threads, usada para criar threads

      Por padrão, uma fábrica de threads será criada por padrão. Os threads criados pela fábrica padrão têm a mesma prioridade e não são daemon. O nome do thread também é definido.

    4. rejeitarHandler: estratégia de rejeição

      Se o workQueue estiver cheio e não houver threads ociosos, a política de rejeição será executada.

      O pool de threads fornece um total de quatro estratégias de rejeição:

      • Lance uma exceção diretamente, que também é a estratégia padrão. A classe de implementação é AbortPolicy
      • Use o thread do chamador para executar e a classe de implementação é CallerRunsPolicy
      • Descarte a tarefa mais antiga da fila e execute a tarefa atual. A classe de implementação é DiscardOldestPolicy
      • Descarte diretamente a tarefa atual, a classe de implementação é DiscardPolicy
  2. Como começar e parar

    1. execute(): envia a tarefa ao pool de threads para execução
    2. submit(): envia uma tarefa e pode retornar resultados, equivalente a execute+Future
    3. shutDown(): fecha o pool de threads e espera que o thread conclua a execução
    4. shutDownNow(): fecha o pool de threads imediatamente sem esperar que o thread conclua a execução
  3. Métodos adequados para monitoramento

    1. getTaskCount(): obtém o número total de threads executados e não executados
    2. getCompletedTaskCount(): obtém o número de threads que concluíram a execução
    3. getCorePoolSize(): obtém o número de threads principais
    4. getActiveCount(): ou obtém o número de threads em execução

Análise aprofundada de interfaces de nível superior e classes abstratas em pools de threads

Visão geral de interfaces e classes abstratas

Insira a descrição da imagem aqui

  • Interface do executor: a interface de nível superior do pool de threads, que fornece um método para enviar tarefas sem um valor de retorno.
  • ExecutorService: Herdado do Executor, estende muitas funções, como fechar o pool de threads, enviar tarefas e retornar resultados, ativar tarefas no pool de threads, etc.
  • AbstractExecutotService: Herda de ExecutorService e implementa alguns métodos muito práticos para chamadas de subclasses
  • ScheduledExecutorService: herda de ExecutorService e estende métodos relacionados a tarefas agendadas

Interface do executor

public interface Executor {
    
    
    void execute(Runnable command);
}

A interface do Executor é relativamente simples e fornece um método execute (comando Runnable) para executar as tarefas enviadas.

Interface ExecutorService

A interface ExecutorService é a interface principal do pool de threads de tarefas não agendadas. Por meio dessa interface, as tarefas são enviadas ao pool de threads (suportando métodos de retorno e não retorno), fechando o pool de threads, ativando tarefas de thread, etc.

public interface ExecutorService extends Executor {
    
    
    //关闭线程池,不再接受新提交的任务,但之前提交的任务继续执行,直到完成
    void shutdown();

    //立即关闭线程池,不再接受新提交任务,会尝试停止线程池中正在执行的任务
    List<Runnable> shutdownNow();

    //判断线程池是否已经关闭
    boolean isShutdown();

    //判断线程中所有任务是否已经结束,只有调用shutdown()或shutdownNow()之后,调用此方法才返回true
    boolean isTerminated();

    //等待线程中所有任务执行结束,并设置超时时间
    boolean awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException;
        
    //提交一个Callable类型的任务,并返回一个Future类型的结果
    <T> Future<T> submit(Callable<T> task);

    //提交一个Runnable类型任务,并设置泛型接收结果数据,返回一个Futrue类型结果
    <T> Future<T> submit(Runnable task, T result);

    //提交一个Runnable类型任务,并返回一个Future类型结果
    Future<?> submit(Runnable task);

    //批量提交Callable类型任务,并返回他们的执行结果,Task列表和Future列表一一对应
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
        throws InterruptedException;
        
    //批量提交Callable类型任务,并获取返回结果,并限定处理所有任务的时间
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                                  long timeout, TimeUnit unit)
        throws InterruptedException;

    //批量提交任务,并获得一个已经成功执行任务的结果
    <T> T invokeAny(Collection<? extends Callable<T>> tasks)
        throws InterruptedException, ExecutionException;

    //批量提交任务,并获得一个已经完成任务的结果,并限定处理任务时间
    <T> T invokeAny(Collection<? extends Callable<T>> tasks,
                    long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

AbstractExecutorEservice

Esta classe é uma classe abstrata que herda de ExecutorEservice e, com base nisso, implementa alguns métodos práticos para chamadas de subclasses.

1. novaTaskFor()


    protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
    
    
        return new FutureTask<T>(runnable, value);
    }

    protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
    
    
        return new FutureTask<T>(callable);
    }

FutureTask é usado para obter resultados de execução.Em aplicações práticas, costumamos usar sua subclasse FutureTask;

A função do método newTaskFor é encapsular a tarefa em um objeto FutureTask e, em seguida, enviar o objeto FutureTask ao pool de threads.

2.doInvokeAny()

Este método é um método principal que executa tarefas de pool de threads em lotes e, em última análise, retorna dados de resultado; desde que este método obtenha o resultado de um dos threads, ele cancelará outros threads em execução no pool de threads;

3. invocarAny()

Dentro deste método, o método doInvokeAny() ainda é chamado para enviar um lote de threads, um deles completa e retorna o resultado, e os threads restantes cancelam a operação;

4. invocarTodos()

O método InvokeAll() implementa lógica com e sem configurações de tempo limite;

A lógica do método sem configuração de tempo limite é: encapsular as tarefas em lote enviadas em objetos RunnableFuture, depois chamar o método execute() para executar a tarefa e adicionar o Future resultante à coleção Future. Posteriormente, a coleção Future será percorrida para determinar se a tarefa é A execução é concluída; se não for concluída, o método get será chamado para bloquear até que o resultado seja obtido, e a exceção será ignorada neste momento; finalmente, em finalmente, a identificação da conclusão de todas as tarefas é julgado e, se não for concluído, a execução é cancelada;

5. enviar()

Este método é relativamente simples. Ele encapsula a tarefa em um objeto RunnableFuture. Após chamar execute(), o resultado Future é retornado.

ScheduleExecutorService

Esta interface herda de ExecutorService e, além de herdar os métodos da classe pai, também fornece a função de processamento de tarefas agendadas.

Análise da perspectiva do código-fonte de como criar um pool de threads

Executors.newWorkStealingPool: Este método é um novo método de criação de um pool de threads em Java 8. Ele pode definir o nível paralelo para threads e tem maior simultaneidade e desempenho; além disso, outros métodos de criação de pools de threads, todos são chamados de construtores de ThreadPoolExecutor;

ThreadPoolExecutor cria um pool de threads

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
    
    
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

Observando o código-fonte da classe ThreadPoolExecutor, descobrimos que o thread é finalmente construído chamando o método de construção de exceção.Outros parâmetros de inicialização foram introduzidos anteriormente;

A classe ForkJoinPool cria um pool de threads

public static ExecutorService newWorkStealingPool(int parallelism) {
    
    
        return new ForkJoinPool
            (parallelism,
             ForkJoinPool.defaultForkJoinWorkerThreadFactory,
             null, true);
    }

    public static ExecutorService newWorkStealingPool() {
    
    
        return new ForkJoinPool
            (Runtime.getRuntime().availableProcessors(),
             ForkJoinPool.defaultForkJoinWorkerThreadFactory,
             null, true);
    }

Como pode ser visto no código-fonte acima, o método Executors.newWorkStealingPool(), ao construir o pool de threads, na verdade chama ForkJoinPool para construir o pool de threads;

/**
     * Creates a {@code ForkJoinPool} with the given parameters, without
     * any security checks or parameter validation.  Invoked directly by
     * makeCommonPool.
     */
    private ForkJoinPool(int parallelism,
                         ForkJoinWorkerThreadFactory factory,
                         UncaughtExceptionHandler handler,
                         int mode,
                         String workerNamePrefix) {
    
    
        this.workerNamePrefix = workerNamePrefix;
        this.factory = factory;
        this.ueh = handler;
        this.config = (parallelism & SMASK) | mode;
        long np = (long)(-parallelism); // offset ctl counts
        this.ctl = ((np << AC_SHIFT) & AC_MASK) | ((np << TC_SHIFT) & TC_MASK);
    }

Observando o código-fonte, aprendemos que os vários métodos de construção do ForkJoinPool chamam, em última análise, o método de construção privado mencionado acima;

Os parâmetros de inicialização são os seguintes:

  • paralelismo: nível de simultaneidade
  • fábrica: fábrica para criação de threads
  • manipulador: quando o thread no thread lança uma exceção não detectada, trate-a por meio deste UncaughtExceptionHandler
  • modo: valor indica FIFO_QUEUE ou LIFO_QUEUE
  • trabalhadorNamePrefix: prefixo do nome do thread para execução de tarefas

ScheduledThreadPoolExecutor cria um pool de threads

    public ScheduledThreadPoolExecutor(int corePoolSize,
                                       ThreadFactory threadFactory,
                                       RejectedExecutionHandler handler) {
    
    
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue(), threadFactory, handler);
    }

ScheduledThreadPoolExecutor herda de ThreadPoolExecutor. Olhando seu código-fonte, podemos ver que a essência de seu método de construção é chamar o método de construção de ThreadPoolExecutor, mas a fila é passada por DelayedWorkQueue.

Análise de código-fonte: como executar ThreadPoolExecutor corretamente

Propriedades importantes em ThreadPoolExecutor

  • atributos relacionados ao ctl

    A constante ctl do tipo AomaticInteger é usada durante todo o ciclo de vida do pool de threads.

    	//主要用来保存线程状态和线程数量,前3位保存线程状态,低29位报存线程数量
    	private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    	//线程池中线程数量(32-3)
        private static final int COUNT_BITS = Integer.SIZE - 3;
    	//线程池中的最大线程数量
        private static final int CAPACITY   = (1 << COUNT_BITS) - 1;
    
        // runState is stored in the high-order bits
    	//线程池的运行状态
        private static final int RUNNING    = -1 << COUNT_BITS;
        private static final int SHUTDOWN   =  0 << COUNT_BITS;
        private static final int STOP       =  1 << COUNT_BITS;
        private static final int TIDYING    =  2 << COUNT_BITS;
        private static final int TERMINATED =  3 << COUNT_BITS;
    
        // Packing and unpacking ctl
    	//获取线程状态
        private static int runStateOf(int c)     {
          
           return c & ~CAPACITY; }
    	//获取线程数量
        private static int workerCountOf(int c)  {
          
           return c & CAPACITY; }
        private static int ctlOf(int rs, int wc) {
          
           return rs | wc; }
    
        /*
         * Bit field accessors that don't require unpacking ctl.
         * These depend on the bit layout and on workerCount being never negative.
         */
    
        private static boolean runStateLessThan(int c, int s) {
          
          
            return c < s;
        }
    
        private static boolean runStateAtLeast(int c, int s) {
          
          
            return c >= s;
        }
    
        private static boolean isRunning(int c) {
          
          
            return c < SHUTDOWN;
        }
    
        /**
         * Attempts to CAS-increment the workerCount field of ctl.
         */
        private boolean compareAndIncrementWorkerCount(int expect) {
          
          
            return ctl.compareAndSet(expect, expect + 1);
        }
    
        /**
         * Attempts to CAS-decrement the workerCount field of ctl.
         */
        private boolean compareAndDecrementWorkerCount(int expect) {
          
          
            return ctl.compareAndSet(expect, expect - 1);
        }
    
        /**
         * Decrements the workerCount field of ctl. This is called only on
         * abrupt termination of a thread (see processWorkerExit). Other
         * decrements are performed within getTask.
         */
        private void decrementWorkerCount() {
          
          
            do {
          
          } while (! compareAndDecrementWorkerCount(ctl.get()));
        }
    
  • Outros atributos importantes

    	//用于存放任务的阻塞队列
    	private final BlockingQueue<Runnable> workQueue;
    
    	//可重入锁
        private final ReentrantLock mainLock = new ReentrantLock();
    
        /**
         * Set containing all worker threads in pool. Accessed only when
         * holding mainLock.
         */
    	//存放线程池中线程的集合,访问这个集合时,必须先获得mainLock锁
        private final HashSet<Worker> workers = new HashSet<Worker>();
    
        /**
         * Wait condition to support awaitTermination
         */
    	//在锁内部阻塞等待条件完成
        private final Condition termination = mainLock.newCondition();
    
    	//线程工厂,以此来创建新线程
        private volatile ThreadFactory threadFactory;
    
        /**
         * Handler called when saturated or shutdown in execute.
         */
    	//拒绝策略
        private volatile RejectedExecutionHandler handler;
    	/**
         * The default rejected execution handler
         */
    	//默认的拒绝策略
        private static final RejectedExecutionHandler defaultHandler =
            new AbortPolicy();
    

Classes internas importantes em ThreadPoolExecutor

  • Trabalhador

    private final class Worker extends AbstractQueuedSynchronizer implements Runnable
        {
          
          
            /**
             * This class will never be serialized, but we provide a
             * serialVersionUID to suppress a javac warning.
             */
            private static final long serialVersionUID = 6138294804551838833L;
    
            /** Thread this worker is running in.  Null if factory fails. */
            final Thread thread;
            /** Initial task to run.  Possibly null. */
            Runnable firstTask;
            /** Per-thread task counter */
            volatile long completedTasks;
    
            /**
             * Creates with given first task and thread from ThreadFactory.
             * @param firstTask the first task (null if none)
             */
            Worker(Runnable firstTask) {
          
          
                setState(-1); // inhibit interrupts until runWorker
                this.firstTask = firstTask;
                this.thread = getThreadFactory().newThread(this);
            }
    
            /** Delegates main run loop to outer runWorker  */
            public void run() {
          
          
                runWorker(this);
            }
    
            // Lock methods
            //
            // The value 0 represents the unlocked state.
            // The value 1 represents the locked state.
    
            protected boolean isHeldExclusively() {
          
          
                return getState() != 0;
            }
    
            protected boolean tryAcquire(int unused) {
          
          
                if (compareAndSetState(0, 1)) {
          
          
                    setExclusiveOwnerThread(Thread.currentThread());
                    return true;
                }
                return false;
            }
    
            protected boolean tryRelease(int unused) {
          
          
                setExclusiveOwnerThread(null);
                setState(0);
                return true;
            }
    
            public void lock()        {
          
           acquire(1); }
            public boolean tryLock()  {
          
           return tryAcquire(1); }
            public void unlock()      {
          
           release(1); }
            public boolean isLocked() {
          
           return isHeldExclusively(); }
    
            void interruptIfStarted() {
          
          
                Thread t;
                if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
          
          
                    try {
          
          
                        t.interrupt();
                    } catch (SecurityException ignore) {
          
          
                    }
                }
            }
        }
    

    A classe Work implementa a interface Runnable e precisa reescrever o método run(). A essência do método run do Worker é chamar o método runWorker() de ThreadPoolExecutor.

  • Política de negação

    No pool de threads, se a fila WorkQueue estiver cheia e não houver threads ociosos, quando uma nova tarefa for enviada, a política de rejeição será executada;

    O pool de threads fornece um total de quatro estratégias de rejeição:

    • Lançar uma exceção diretamente também é a política padrão; a classe de implementação é AbortPolicy
    • Use o thread do chamador para executar tarefas; a classe de implementação é CallerRunsPolicy
    • Descarte a tarefa mais à frente na fila e execute a tarefa atual; DiscardOldestPolicy
    • Descarte diretamente a tarefa atual; a classe de implementação é DiscardPolicy

    No ThreadPoolExecutor, quatro classes internas são fornecidas para implementar estratégias correspondentes;

    public static class CallerRunsPolicy implements RejectedExecutionHandler {
          
          
            /**
             * Creates a {@code CallerRunsPolicy}.
             */
            public CallerRunsPolicy() {
          
           }
    
            public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
          
          
                if (!e.isShutdown()) {
          
          
                    r.run();
                }
            }
        }
    
        /**
         * A handler for rejected tasks that throws a
         * {@code RejectedExecutionException}.
         */
        public static class AbortPolicy implements RejectedExecutionHandler {
          
          
            /**
             * Creates an {@code AbortPolicy}.
             */
            public AbortPolicy() {
          
           }
    
            public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
          
          
                throw new RejectedExecutionException("Task " + r.toString() +
                                                     " rejected from " +
                                                     e.toString());
            }
        }
    
        /**
         * A handler for rejected tasks that silently discards the
         * rejected task.
         */
        public static class DiscardPolicy implements RejectedExecutionHandler {
          
          
            /**
             * Creates a {@code DiscardPolicy}.
             */
            public DiscardPolicy() {
          
           }
    
            public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
          
          
            }
        }
    
        /**
         * A handler for rejected tasks that discards the oldest unhandled
         * request and then retries {@code execute}, unless the executor
         * is shut down, in which case the task is discarded.
         */
        public static class DiscardOldestPolicy implements RejectedExecutionHandler {
          
          
            /**
             * Creates a {@code DiscardOldestPolicy} for the given executor.
             */
            public DiscardOldestPolicy() {
          
           }
    
            public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
          
          
                if (!e.isShutdown()) {
          
          
                    e.getQueue().poll();
                    e.execute(r);
                }
            }
        }
    

    Também podemos personalizar a política de rejeição implementando a interface RejectedExecutionHandler e substituindo o método rejeitadoExecution();

    Ao criar um thread, passe nossa política de rejeição customizada através do construtor de ThreadPoolExecutor;

Análise de código-fonte Processo principal ThreadPoolExecutor

Há uma coleção de threads de trabalho em ThreadPoolExecutor. Os usuários podem adicionar tarefas ao pool de threads. Os threads na coleção de trabalhadores podem executar tarefas diretamente ou obter tarefas da fila de tarefas e executá-las;

ThreadPoolExecutor fornece todo o processo do pool de threads, desde a criação, execução da tarefa até a morte;

No ThreadPoolExecutor, a lógica do pool de threads é refletida principalmente em execute (comando Runnable), addWorker (Runnable firstTask, núcleo booleano), addWorkerFailed (Worker w) e outros métodos e estratégias de rejeição; a seguir, analisaremos esses métodos principais em profundidade .

executar (comando executável)

A função deste método é enviar tarefas do tipo Runnable para o pool de threads;

public void execute(Runnable command) {
    
    
        if (command == null)
            //若提交的任务为空,则提交空指针异常
            throw new NullPointerException();
       //获取线程池的状态,和线程池中线程数量
        int c = ctl.get();
    	//若线程池中线程数小于核心线程数
        if (workerCountOf(c) < corePoolSize) {
    
    
            //重新开启线程执行任务
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
    	//若线程池处于RUNNING状态,则将任务添加到阻塞队列中
    	//只有线程池处于RUNNING状态时,才能添加到队列
        if (isRunning(c) && workQueue.offer(command)) {
    
    
            //再次获取线程次状态和线程池数量,用于二次检查
            //向队列中添加线程成功,但由于其他线程可能会修改线程池状态,所以这里需要进行二次检查
            int recheck = ctl.get();
            //如果线程池没有再处于RUNNING状态,则从队列中删除任务
            if (! isRunning(recheck) && remove(command))
                //执行拒绝策略
                reject(command);
            else if (workerCountOf(recheck) == 0)
                //若线程池为空,则新建一个线程加入
                addWorker(null, false);
        }
        else if (!addWorker(command, false))
            //任务队列已满,则新建一个Worker线程,若新增失败,则执行拒绝策略
            reject(command);
    }

addWorker (primeira tarefa executável, núcleo booleano)

Este método geralmente pode ser dividido em três partes. A primeira parte é principalmente sobre a adição segura de threads de trabalho do CAS ao pool de threads; a segunda parte é sobre a adição de novos threads de trabalho; a terceira parte é sobre a adição de threads aos trabalhadores por meio de simultaneidade segura. e inicie o thread para executar a tarefa

private boolean addWorker(Runnable firstTask, boolean core) {
    
    
    //循环标签,重试的标识
    retry:
    for (;;) {
    
    
        int c = ctl.get();
        int rs = runStateOf(c);

        // Check if queue empty only if necessary.
        //检查队列是否在某些特定条件下为空
        if (rs >= SHUTDOWN &&
            ! (rs == SHUTDOWN &&
               firstTask == null &&
               ! workQueue.isEmpty()))
            return false;
		//此循环中主要是通过CAS的方式增加线程个数
        for (;;) {
    
    
            int wc = workerCountOf(c);
            if (wc >= CAPACITY ||
                wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
            //CAS的方式增加线程数量
            if (compareAndIncrementWorkerCount(c))
                break retry;
            c = ctl.get();  // Re-read ctl
            if (runStateOf(c) != rs)
                continue retry;
            // else CAS failed due to workerCount change; retry inner loop
        }
    }
    //跳出最外层循环,说明已通过CAS增加线程成功
    //此时创建新的线程
    boolean workerStarted = false;
    boolean workerAdded = false;
    Worker w = null;
    try {
    
    
        //将新建线程封装成Woker
        w = new Worker(firstTask);
        final Thread t = w.thread;
        if (t != null) {
    
    
            //独占锁,保证操作workers的同步
            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
    
    
                // Recheck while holding lock.
                // Back out on ThreadFactory failure or if
                // shut down before lock acquired.
                int rs = runStateOf(ctl.get());

                if (rs < SHUTDOWN ||
                    (rs == SHUTDOWN && firstTask == null)) {
    
    
                    if (t.isAlive()) // precheck that t is startable
                        throw new IllegalThreadStateException();
                    //将Worker加入队列
                    workers.add(w);
                    int s = workers.size();
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                    workerAdded = true;
                }
            } finally {
    
    
                //释放独占锁
                mainLock.unlock();
            }
            if (workerAdded) {
    
    
                t.start();
                workerStarted = true;
            }
        }
    } finally {
    
    
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}

addWorkerFailed(Trabalhador w)

No método addWorker(Runnable firstTask, boolean core), se a adição do thread de trabalho falhar ou o thread de trabalho falhar ao iniciar, o método addWorkerFailed(Worker w) será chamado; este método é mais simples, obtenha o bloqueio exclusivo e remova-o das tarefas dos trabalhadores, e reduzir o número de tarefas em uma através do CAS, e finalmente liberar o bloqueio;

private void addWorkerFailed(Worker w) {
    
    
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
    
    
        if (w != null)
            workers.remove(w);
        decrementWorkerCount();
        tryTerminate();
    } finally {
    
    
        mainLock.unlock();
    }
}
  • Política de negação

    /**
     * Invokes the rejected execution handler for the given command.
     * Package-protected for use by ScheduledThreadPoolExecutor.
     */
    final void reject(Runnable command) {
          
          
        handler.rejectedExecution(command, this);
    }
    

    As quatro classes de implementação de RejectExecutionHandler são exatamente as classes de implementação das quatro estratégias de rejeição fornecidas pelo thread;

    A estratégia específica deste método é determinada com base nos parâmetros passados ​​​​ao criar o pool de threads, por padrão, a estratégia de rejeição padrão é usada;

Análise do código-fonte do processo de execução do Woker no pool de threads

Análise da classe Woker

Do ponto de vista da estrutura de classes, Woker herda a classe AQS (AbstractQueueSynchronizer) e implementa a interface Runnable, essencialmente a classe Woker é um componente de sincronização e um thread que executa tarefas;

private final class Worker extends AbstractQueuedSynchronizer implements Runnable
{
    
    
    /**
     * This class will never be serialized, but we provide a
     * serialVersionUID to suppress a javac warning.
     */
    private static final long serialVersionUID = 6138294804551838833L;

    /** Thread this worker is running in.  Null if factory fails. */
    final Thread thread;
    /** Initial task to run.  Possibly null. */
    Runnable firstTask;
    /** Per-thread task counter */
    volatile long completedTasks;

    /**
     * Creates with given first task and thread from ThreadFactory.
     * @param firstTask the first task (null if none)
     */
    Worker(Runnable firstTask) {
    
    
        setState(-1); // inhibit interrupts until runWorker
        this.firstTask = firstTask;
        this.thread = getThreadFactory().newThread(this);
    }

    /** Delegates main run loop to outer runWorker  */
    public void run() {
    
    
        runWorker(this);
    }

    // Lock methods
    //
    // The value 0 represents the unlocked state.
    // The value 1 represents the locked state.

    protected boolean isHeldExclusively() {
    
    
        return getState() != 0;
    }

    protected boolean tryAcquire(int unused) {
    
    
        if (compareAndSetState(0, 1)) {
    
    
            setExclusiveOwnerThread(Thread.currentThread());
            return true;
        }
        return false;
    }

    protected boolean tryRelease(int unused) {
    
    
        setExclusiveOwnerThread(null);
        setState(0);
        return true;
    }

    public void lock()        {
    
     acquire(1); }
    public boolean tryLock()  {
    
     return tryAcquire(1); }
    public void unlock()      {
    
     release(1); }
    public boolean isLocked() {
    
     return isHeldExclusively(); }

    void interruptIfStarted() {
    
    
        Thread t;
        if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
    
    
            try {
    
    
                t.interrupt();
            } catch (SecurityException ignore) {
    
    
            }
        }
    }
}

Como pode ser visto no construtor da classe Worker, o estado de sincronização é primeiro definido como -1. Isso evita que o método runWorker seja interrompido antes da execução;

Isso ocorre porque se outros threads chamarem o método shutdownNow() no pool de threads, se o estado na classe Worker for> 0, o thread será interrompido e se o estado for -1, o thread não será interrompido;

A classe Worker implementa a interface Runnable e precisa substituir o método run, que na verdade chama o método runWorker() de ThreadPoolExecutor;

runWorker(Trabalhador w)

final void runWorker(Worker w) {
    
    
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    //释放锁,将state设置为0,允许中断
    w.unlock(); // allow interrupts
    boolean completedAbruptly = true;
    try {
    
    
       //若任务不为空,或队列中获取的任务不为空,则进入循环
        while (task != null || (task = getTask()) != null) {
    
    
            //任务不为空,则先获取woker线程的独占锁
            w.lock();
            // If pool is stopping, ensure thread is interrupted;
            // if not, ensure thread is not interrupted.  This
            // requires a recheck in second case to deal with
            // shutdownNow race while clearing interrupt
            //若线程次已经停止,线程中断时未中断成功
            if ((runStateAtLeast(ctl.get(), STOP) ||
                 (Thread.interrupted() &&
                  runStateAtLeast(ctl.get(), STOP))) &&
                !wt.isInterrupted())
                //执行中断操作
                wt.interrupt();
            try {
    
    
                //任务执行前置逻辑
                beforeExecute(wt, task);
                Throwable thrown = null;
                try {
    
    
                    //任务执行
                    task.run();
                } catch (RuntimeException x) {
    
    
                    thrown = x; throw x;
                } catch (Error x) {
    
    
                    thrown = x; throw x;
                } catch (Throwable x) {
    
    
                    thrown = x; throw new Error(x);
                } finally {
    
    
                    //任务执行后置逻辑
                    afterExecute(task, thrown);
                }
            } finally {
    
    
                //任务执行完成后,将其置空
                task = null;
                //已完成任务数加一
                w.completedTasks++;
                //释放锁
                w.unlock();
            }
        }
        completedAbruptly = false;
    } finally {
    
    
        //执行Worker完成退出逻辑
        processWorkerExit(w, completedAbruptly);
    }
}

A partir da análise do código-fonte acima, podemos saber que quando a tarefa obtida por Woker do thread estiver vazia, o método getTask() será chamado para obter a tarefa da fila;

getTask()

private Runnable getTask() {
    
    
    boolean timedOut = false; // Did the last poll() time out?
    //自旋
    for (;;) {
    
    
        int c = ctl.get();
        //获取线程池状态
        int rs = runStateOf(c);

        // Check if queue empty only if necessary.
        //检测队列在线程池关闭或停止时,是否为空
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
    
    
            //减少Worker线程数量
            decrementWorkerCount();
            return null;
        }
        //获取线程池中线程数量
        int wc = workerCountOf(c);

        // Are workers subject to culling?
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

        if ((wc > maximumPoolSize || (timed && timedOut))
            && (wc > 1 || workQueue.isEmpty())) {
    
    
            if (compareAndDecrementWorkerCount(c))
                return null;
            continue;
        }

        try {
    
    
            //从任务队列中获取任务
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                workQueue.take();
            if (r != null)
                //任务不为空,直接返回
                return r;
            timedOut = true;
        } catch (InterruptedException retry) {
    
    
            timedOut = false;
        }
    }
}
  • beforeExecute(Thread t, Executável r)

    protected void beforeExecute(Thread t, Runnable r) {
          
           }
    

    O corpo deste método está vazio, o que significa que você pode criar uma subclasse de ThreadPoolExecutor para substituir este método, para que nossa pré-lógica personalizada possa ser executada antes que o pool de threads realmente execute a tarefa;

  • afterExecute (Runnable r, Throwable t)

    protected void afterExecute(Runnable r, Throwable t) {
          
           }
    

    Assim como acima, podemos substituir esse método em uma subclasse, para que nossa lógica de pós-processamento customizada possa ser executada após o pool de threads executar a tarefa.

  • processWorkerExit (Trabalhador w, booleano concluído Abruptamente)

    A lógica principal deste método é executar a lógica de saída do thread Worker e realizar algum trabalho de limpeza;

  • tenteTerminar()

    final void tryTerminate() {
          
          
        //自旋
        for (;;) {
          
          
            //获取ctl
            int c = ctl.get();
            if (isRunning(c) ||
                runStateAtLeast(c, TIDYING) ||
                (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
                return;
            if (workerCountOf(c) != 0) {
          
           // Eligible to terminate
                //若当前线程池中线程数量不为0,则中断线程
                interruptIdleWorkers(ONLY_ONE);
                return;
            }
            //获取线程池的全局锁
            final ReentrantLock mainLock = this.mainLock;
            //加锁
            mainLock.lock();
            try {
          
          
                if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
          
          
                    try {
          
          
                        terminated();
                    } finally {
          
          
                        //将线程状态设置为TERMINATED
                        ctl.set(ctlOf(TERMINATED, 0));
                        //唤醒所有因调用awaitTermination()而阻塞的线程
                        termination.signalAll();
                    }
                    return;
                }
            } finally {
          
          
                //释放锁
                mainLock.unlock();
            }
            // else retry on failed CAS
        }
    }
    
  • terminar()

    protected void terminated() {
          
           }
    

    O corpo deste método está vazio. Podemos sobrescrever este método em sua subclasse, para que no método tryTerminated() nosso método customizado possa ser executado;

Como o pool de threads de análise do código-fonte consegue uma saída elegante?

desligar()

Ao usar um pool de threads, quando o método shutdown() é chamado, o pool de threads não aceitará mais tarefas recém-enviadas e os threads já em execução continuarão a ser executados;

Este método é um método sem bloqueio, ele retornará imediatamente após ser chamado e não esperará que todas as tarefas do thread sejam concluídas antes de retornar;

public void shutdown() {
    
    
    //获取线程池的全局锁
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
    
    
        //检查是否有关闭线程池的权限
        checkShutdownAccess();
        //将当前线程池的状态设置为SHUTDOWN
        advanceRunState(SHUTDOWN);
        //中断woker线程
        interruptIdleWorkers();
        //调用ScheduledThreadPoolExecutor的钩子函数
        onShutdown(); // hook for ScheduledThreadPoolExecutor
    } finally {
    
    
        //释放锁
        mainLock.unlock();
    }
    tryTerminate();
}

desligar agora()

Se o método shutdownNow() do pool de threads for chamado, o thread não receberá mais tarefas recém-enviadas, os threads na fila workQueue também serão descartados e os threads em execução serão interrompidos; o método retornará imediatamente e o o resultado retornado é a tarefa Lista de tarefas descartadas na fila workQueue

public List<Runnable> shutdownNow() {
    
    
    List<Runnable> tasks;
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
    
    
        checkShutdownAccess();
        advanceRunState(STOP);
        interruptWorkers();
        //清空、丢弃队列中任务
        tasks = drainQueue();
    } finally {
    
    
        mainLock.unlock();
    }
    tryTerminate();
    //返回任务列表
    return tasks;
}

waitTermination(tempo limite longo, unidade TimeUnit)

Quando o conjunto de encadeamentos chama awaitTermination, ele bloqueará o encadeamento do chamador e não retornará até que o status do conjunto de encadeamentos mude para TERNINATED ou o período de tempo limite seja atingido.

public boolean awaitTermination(long timeout, TimeUnit unit)
    throws InterruptedException {
    
    
    long nanos = unit.toNanos(timeout);
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
    
    
        for (;;) {
    
    
            if (runStateAtLeast(ctl.get(), TERMINATED))
                return true;
            if (nanos <= 0)
                return false;
            nanos = termination.awaitNanos(nanos);
        }
    } finally {
    
    
        //释放锁
        mainLock.unlock();
    }
}

A lógica geral deste método é: primeiro obtenha o bloqueio exclusivo do thread de trabalho, depois gire e determine se o status do pool de threads muda para TERMINATED; em caso afirmativo, retorne verdadeiro, caso contrário, verifique se o tempo limite, se o tempo limite, retorne falso; se o tempo limite não expirar, reinicie Defina o período de tempo limite restante;

Aulas principais em AQS

Trava de contagem regressiva

  • Visão geral

Classe auxiliar de sincronização, que pode bloquear a execução do thread atual. Ou seja, um ou mais threads podem esperar até que outros threads terminem a execução. Use um determinado contador para inicialização. A operação do contador é uma operação atômica, ou seja, apenas um thread pode operar o contador ao mesmo tempo.

O thread que chama o método wait() da classe modificada esperará até que outros threads chamem o método countDown() da classe e tornem o valor do contador atual 0;

Cada vez que o método countDown() desta classe for chamado, o valor do contador será decrementado em um;

Quando o valor do contador diminui para 0, todos os threads bloqueados e aguardando pela chamada do método wait() continuarão a ser executados, esta operação só pode ocorrer uma vez, pois o valor do contador não pode ser zerado;

Se você precisar de uma versão que redefina a contagem, considere usar CyclicBarrier.

CountDownLatch suporta espera por um determinado período de tempo limite e não espera mais do que o tempo determinado, ao usá-lo, você só precisa passar o tempo determinado no método wait();

public int await(long timeout, TimeUnit unit)
    throws InterruptedException,
           BrokenBarrierException,
           TimeoutException {
    
    
    return dowait(true, unit.toNanos(timeout));
}
  • cenas a serem usadas

    Em alguns cenários, o programa precisa aguardar a conclusão de uma ou mais condições antes de continuar a executar operações subsequentes. Uma aplicação típica é a computação paralela: ao processar uma tarefa com uma grande quantidade de cálculos, ela pode ser dividida em várias tarefas pequenas. Depois de esperar que todas as subtarefas sejam concluídas, a tarefa pai obtém os resultados de todas as subtarefas e os resume.

  • exemplo de código

    Chamar o método shutdown() de ExecutorService não destruirá todos os threads imediatamente, mas permitirá que todos os threads existentes sejam executados.

    Em seguida, destrua o pool de threads

    package io.binghe.concurrency.example.aqs;
    
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.concurrent.CountDownLatch;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    @Slf4j
    public class CountDownLatchExample {
          
          
        private static final int threadCount = 200;
    
        public static void main(String[] args) throws InterruptedException {
          
          
            ExecutorService exec = Executors.newCachedThreadPool();
            final CountDownLatch countDownLatch = new CountDownLatch(threadCount);
            for (int i = 0; i < threadCount; i++) {
          
          
                final int threadNum = i;
                exec.execute(() -> {
          
          
                    try {
          
          
                        test(threadNum);
                    } catch (InterruptedException e) {
          
          
                        e.printStackTrace();
                    } finally {
          
          
                        countDownLatch.countDown();
                    }
                });
            }
            countDownLatch.await();
            log.info("finish");
            exec.shutdown();
        }
    
        private static void test(int threadNum) throws InterruptedException {
          
          
            Thread.sleep(100);
            log.info("{}", threadNum);
        }
    }
    

    O código que suporta a espera por um determinado tempo é o seguinte:

    package io.binghe.concurrency.example.aqs;
    
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.concurrent.CountDownLatch;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.TimeUnit;
    
    @Slf4j
    public class CountDownLatchExample {
          
          
        private static final int threadCount = 200;
    
        public static void main(String[] args) throws InterruptedException {
          
          
            ExecutorService exec = Executors.newCachedThreadPool();
            final CountDownLatch countDownLatch = new CountDownLatch(threadCount);
            for (int i = 0; i < threadCount; i++) {
          
          
                final int threadNum = i;
                exec.execute(() -> {
          
          
                    try {
          
          
                        test(threadNum);
                    } catch (InterruptedException e) {
          
          
                        e.printStackTrace();
                    } finally {
          
          
                        countDownLatch.countDown();
                    }
                });
            }
            countDownLatch.await(10, TimeUnit.MICROSECONDS);
            log.info("finish");
            exec.shutdown();
        }
    
        private static void test(int threadNum) throws InterruptedException {
          
          
            Thread.sleep(100);
            log.info("{}", threadNum);
        }
    }
    

Semáforo

  • Visão geral

    Controle o número de threads simultâneos ao mesmo tempo. Ele pode controlar o semáforo e controlar o número de threads que acessam um determinado recurso ao mesmo tempo.

    Fornece dois métodos principais: adquirir() e liberar()

    adquirir () significa obter uma permissão de acesso e, se não for obtida, bloqueará e aguardará; release () liberará uma permissão após a conclusão.

    O semáforo mantém o número atualmente acessível; ele usa um mecanismo de sincronização para controlar o número que pode ser acessado simultaneamente.

    O Semaphore pode implementar listas vinculadas de tamanho limitado

  • cenas a serem usadas

    O semáforo é frequentemente usado para recursos com acesso limitado

  • exemplo de código

    package io.binghe.concurrency.example.aqs;
    
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.Semaphore;
    
    @Slf4j
    public class SemaphoreExample {
          
          
        private static final int threadCount = 200;
    
        public static void main(String[] args) throws InterruptedException {
          
          
            ExecutorService exec = Executors.newCachedThreadPool();
            final Semaphore semaphore = new Semaphore(3);
            for (int i = 0; i < threadCount; i++) {
          
          
                final int threadNum = i;
                exec.execute(() -> {
          
          
                    try {
          
          
                        semaphore.acquire(); //获取一个许可
                        test(threadNum);
                        semaphore.release(); //释放一个许可
                    } catch (InterruptedException e) {
          
          
                        e.printStackTrace();
                    }
                });
            }
            exec.shutdown();
        }
    
        private static void test(int threadNum) throws InterruptedException {
          
          
            log.info("{}", threadNum);
            Thread.sleep(1000);
        }
    }
    

    Adquira e libere várias licenças de uma vez

    package io.binghe.concurrency.example.aqs;
    
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.Semaphore;
    
    @Slf4j
    public class SemaphoreExample {
          
          
        private static final int threadCount = 200;
    
        public static void main(String[] args) throws InterruptedException {
          
          
            ExecutorService exec = Executors.newCachedThreadPool();
            final Semaphore semaphore = new Semaphore(3);
            for (int i = 0; i < threadCount; i++) {
          
          
                final int threadNum = i;
                exec.execute(() -> {
          
          
                    try {
          
          
                        semaphore.acquire(3); //获取多个许可
                        test(threadNum);
                        semaphore.release(3); //释放多个许可
                    } catch (InterruptedException e) {
          
          
                        e.printStackTrace();
                    }
                });
            }
            log.info("finish");
            exec.shutdown();
        }
    
        private static void test(int threadNum) throws InterruptedException {
          
          
            log.info("{}", threadNum);
            Thread.sleep(1000);
        }
    }
    

    Suponha que exista tal cenário. Suponha que o número máximo de simultaneidades permitidas atualmente pelo sistema seja 3. Se exceder 3, ele precisa ser descartado. Tal cenário também pode ser realizado através do Semáforo:

    package io.binghe.concurrency.example.aqs;
    
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.Semaphore;
    
    @Slf4j
    public class SemaphoreExample {
          
          
        private static final int threadCount = 200;
    
        public static void main(String[] args) throws InterruptedException {
          
          
            ExecutorService exec = Executors.newCachedThreadPool();
            final Semaphore semaphore = new Semaphore(3);
            for (int i = 0; i < threadCount; i++) {
          
          
                final int threadNum = i;
                exec.execute(() -> {
          
          
                    try {
          
          
                        //尝试获取一个许可,也可以尝试获取多个许可,
                        //支持尝试获取许可超时设置,超时后不再等待后续线程的执行
                        //具体可以参见Semaphore的源码
                        if (semaphore.tryAcquire()) {
          
          
                            test(threadNum);
                            semaphore.release(); //释放一个许可
                        }
                    } catch (InterruptedException e) {
          
          
                        e.printStackTrace();
                    }
                });
            }
            log.info("finish");
            exec.shutdown();
        }
    
        private static void test(int threadNum) throws InterruptedException {
          
          
            log.info("{}", threadNum);
            Thread.sleep(1000);
        }
    }
    

Barreira Cíclica

  • Visão geral

    É uma classe auxiliar de sincronização que permite que um grupo de threads espere um pelo outro até atingir um ponto de barreira comum; através dela, vários threads podem esperar uns pelos outros. Somente quando cada thread estiver pronto, cada thread poderá continuar a executar. .

    Semelhante ao countDownLatch, todos eles são implementados usando contadores. Quando um thread chama o método wait() de CyclicBarrier, ele entra no estado de espera e o contador executa uma operação de incremento; quando o valor do contador aumenta para o valor inicial definido, todos os threads que entram no estado de espera devido ao método wait() serão despertados e continuarão a realizar suas operações subsequentes. CyclicBarrier pode ser reutilizado após liberar o thread em espera, então CyclicBarrier também é chamado de barreira de ciclo.

  • cenas a serem usadas

    Ele pode ser usado em cenários onde multithreads calculam dados e finalmente mesclam os resultados do cálculo.

  • A diferença entre countDownLatch e countDownLatch

    1. O contador de countDownLatch só pode ser usado uma vez; enquanto o contador de CyclicBarrier pode ser zerado usando reSet() e usado ciclicamente.
    2. O que o countDownLatch implementa é que 1 ou n threads aguardam a conclusão de outros threads antes de continuarem a execução. Ele descreve o relacionamento entre 1 ou mais threads aguardando outros threads; e CyclicBarrier significa principalmente que os threads em um grupo de threads esperam uns pelos outros Cada thread atende às condições comuns antes de continuar a execução, descrevendo o relacionamento de espera interno de vários threads.
    3. CyclicBarrier pode lidar com cenários mais complexos.Quando ocorre um erro de cálculo, o contador pode ser zerado e o thread pode ser executado novamente.
    4. CyclicBarrier fornece métodos mais úteis, como getNumberByWaiting() para obter o número de threads em espera e o método isBroken() para determinar se um thread foi interrompido.
  • exemplo de código

    package io.binghe.concurrency.example.aqs;
    
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.concurrent.CyclicBarrier;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    @Slf4j
    public class CyclicBarrierExample {
          
          
        private static CyclicBarrier cyclicBarrier = new CyclicBarrier(5);
    
        public static void main(String[] args) throws Exception {
          
          
            ExecutorService executorService = Executors.newCachedThreadPool();
            for (int i = 0; i < 10; i++) {
          
          
                final int threadNum = i;
                Thread.sleep(1000);
                executorService.execute(() -> {
          
          
                    try {
          
          
                        race(threadNum);
                    } catch (Exception e) {
          
          
                        e.printStackTrace();
                    }
                });
            }
            executorService.shutdown();
        }
    
        private static void race(int threadNum) throws Exception {
          
          
            Thread.sleep(1000);
            log.info("{} is ready", threadNum);
            cyclicBarrier.await();
            log.info("{} continue", threadNum);
        }
    }
    

    O código de exemplo para definir o tempo limite de espera é o seguinte:

    package io.binghe.concurrency.example.aqs;
    
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.concurrent.*;
    
    @Slf4j
    public class CyclicBarrierExample {
          
          
        private static CyclicBarrier cyclicBarrier = new CyclicBarrier(5);
    
        public static void main(String[] args) throws Exception {
          
          
            ExecutorService executorService = Executors.newCachedThreadPool();
            for (int i = 0; i < 10; i++) {
          
          
                final int threadNum = i;
                Thread.sleep(1000);
                executorService.execute(() -> {
          
          
                    try {
          
          
                        race(threadNum);
                    } catch (Exception e) {
          
          
                        e.printStackTrace();
                    }
                });
            }
            executorService.shutdown();
        }
    
        private static void race(int threadNum) throws Exception {
          
          
            Thread.sleep(1000);
            log.info("{} is ready", threadNum);
            try {
          
          
                cyclicBarrier.await(2000, TimeUnit.MILLISECONDS);
            } catch (BrokenBarrierException | TimeoutException e) {
          
          
                log.warn("BarrierException", e);
            }
            log.info("{} continue", threadNum);
        }
    }
    

    Ao declarar CyclicBarrier, você também pode especificar um Runnable. Quando o thread atinge a barreira, o método Runnable pode ser executado primeiro. O código de exemplo é o seguinte:

    package io.binghe.concurrency.example.aqs;
    
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.concurrent.CyclicBarrier;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    @Slf4j
    public class CyclicBarrierExample {
          
          
        private static CyclicBarrier cyclicBarrier = new CyclicBarrier(5, () -> {
          
          
            log.info("callback is running");
        });
    
        public static void main(String[] args) throws Exception {
          
          
            ExecutorService executorService = Executors.newCachedThreadPool();
            for (int i = 0; i < 10; i++) {
          
          
                final int threadNum = i;
                Thread.sleep(1000);
                executorService.execute(() -> {
          
          
                    try {
          
          
                        race(threadNum);
                    } catch (Exception e) {
          
          
                        e.printStackTrace();
                    }
                });
            }
            executorService.shutdown();
        }
    
        private static void race(int threadNum) throws Exception {
          
          
            Thread.sleep(1000);
            log.info("{} is ready", threadNum);
            cyclicBarrier.await();
            log.info("{} continue", threadNum);
        }
    }
    

Bloqueio de chave no AQS

ReentranteLock

  • Visão geral

    Os bloqueios fornecidos em Java são divididos principalmente em duas categorias, uma é o bloqueio modificado sincronizado, a outra é o bloqueio fornecido no JUC e o bloqueio principal no JUC é ReentrantLock

    A diferença entre ReentrantLock e Sincronizado:

    1. Reentrada

      Quando o mesmo thread entra em ambos uma vez, o contador de bloqueio será incrementado em 1. Quando o contador de bloqueio cair para 0, o bloqueio será liberado.

    2. Implementação de bloqueio

      Synchronized é implementado com base em JVM; ReentrantLock é implementado com base em JDK

    3. Diferença de desempenho

      Antes da otimização sincronizada, o desempenho era muito pior do que o ReentrantLock; mas depois do JDK6, depois que o Synchronized introduziu bloqueios tendenciosos e bloqueios leves (ou seja, bloqueios de rotação), o desempenho foi quase o mesmo.

    4. Diferença funcional

      Conveniência:

      Sincronizado é mais conveniente de usar, e o compilador bloqueia e libera o bloqueio; ReentrantLock requer bloqueio e liberação manual, e é melhor liberar o bloqueio finalmente

      Flexibilidade e granularidade:

      ReentrantLock é melhor que Synchronized aqui

    Recursos exclusivos do ReentrantLock:

    1. ReentrantLock pode especificar um bloqueio justo ou um bloqueio injusto. Sincronizado só pode usar bloqueios injustos. Bloqueio justo significa que o thread que espera primeiro obtém o bloqueio primeiro
    2. Fornece uma classe Condition que pode ativar threads que precisam ser ativados em grupos. Sincronizado só pode ativar um thread aleatoriamente ou ativar todos os threads.
    3. Fornece um mecanismo para interromper threads que aguardam bloqueios, lock.lockInterruptily(). A implementação do ReentrantLock é um spin lock que implementa o bloqueio chamando a operação CAS. O desempenho é melhor porque evita o estado de bloqueio do thread que entra no estado do kernel.
    4. Em geral, o ReentrantLock pode fazer tudo o que o Synchronized pode fazer. Em termos de desempenho, ReentrantLock é melhor que Synchronized

    Vantagens sincronizadas:

    1. Não há necessidade de liberar manualmente o bloqueio, a JVM trata disso automaticamente. Se ocorrer uma exceção, a JVM liberará automaticamente o bloqueio.
    2. Quando a JVM executa solicitações e liberações de gerenciamento de bloqueio, a JVM é capaz de gerar informações de bloqueio ao gerar dumps de thread.Essas informações são muito úteis para depuração porque podem identificar fontes de deadlocks e outros comportamentos anormais. ReentrantLock é apenas uma classe comum. A JVM não sabe qual thread possui o bloqueio.
    3. Sincronizado pode ser usado em todas as versões da JVM. ReentrantLock pode não ser suportado por algumas JVMs anteriores à versão 1.5.

    Descrição de alguns métodos em ReentrantLock:

    1. boolean tryLock(): Adquire o bloqueio somente se o bloqueio não for mantido por outro thread no momento da chamada
    2. boolean tryLock (tempo limite longo, unidade TimeUnit): Adquira este bloqueio se o bloqueio não for mantido por outro thread em um determinado momento e o thread atual não for interrompido
    3. void lockInterruptably(): Se o thread atual não for interrompido, adquira o bloqueio; se for interrompido, lança uma exceção
    4. boolean isLocked(): Consulta se este bloqueio é mantido por algum thread
    5. boolean isHeldByCurrentThread(): Consulta se o thread atual permanece bloqueado
    6. boolean isFair(): Determina se é um fair lock
    7. boolean hasQueuedThread(Thread thread): Consulta se o thread especificado está aguardando para adquirir este bloqueio
    8. boolean hasQueuedThreads(): Existem threads aguardando para adquirir este bloqueio?
    9. int getHoldCount(): consulta o número de bloqueios mantidos pelo thread atual
  • exemplo de código

    package io.binghe.concurrency.example.lock;
    
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.concurrent.CountDownLatch;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.Semaphore;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    import java.util.concurrent.locks.ReentrantReadWriteLock;
    
    @Slf4j
    public class LockExample {
          
          
        //请求总数
        public static int clientTotal = 5000;
        //同时并发执行的线程数
        public static int threadTotal = 200;
        public static int count = 0;
        private static final Lock lock = new ReentrantLock();
    
        public static void main(String[] args) throws InterruptedException {
          
          
            ExecutorService executorService = Executors.newCachedThreadPool();
            final Semaphore semaphore = new Semaphore(threadTotal);
            final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
            for (int i = 0; i < clientTotal; i++) {
          
          
                executorService.execute(() -> {
          
          
                    try {
          
          
                        semaphore.acquire();
                        add();
                        semaphore.release();
                    } catch (Exception e) {
          
          
                        log.error("exception", e);
                    }
                    countDownLatch.countDown();
                });
            }
            countDownLatch.await();
            executorService.shutdown();
            log.info("count:{}", count);
        }
    
        private static void add() {
          
          
            lock.lock();
            try {
          
          
                count++;
            } finally {
          
          
                lock.unlock();
            }
        }
    }
    

ReentranteReadWriteLock

  • Visão geral

    Os bloqueios de leitura e gravação só podem adquirir bloqueios de gravação quando não houver bloqueios de leitura; se o bloqueio de gravação não puder ser adquirido, isso levará à falta de bloqueio de gravação;

    Em cenários onde há mais leitura e menos gravação, o desempenho do ReentrantReadWriteLock é muito maior do que o do ReentrantLock. Ele não afeta um ao outro durante a leitura multithread. Ao contrário do ReentrantLock, mesmo a leitura multithread exige que cada thread adquira uma leitura lock; no entanto, quando qualquer thread está gravando, semelhante ao ReentrantLock, não importa se outros threads estão lendo ou gravando, eles devem adquirir o bloqueio de gravação. Deve-se observar que o mesmo thread pode conter bloqueios de leitura e bloqueios de gravação ao mesmo tempo.

  • exemplo de código

    package io.binghe.concurrency.example.lock;
    
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.Map;
    import java.util.Set;
    import java.util.TreeMap;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    import java.util.concurrent.locks.ReentrantReadWriteLock;
    
    @Slf4j
    public class LockExample {
          
          
        private final Map<String, Data> map = new TreeMap<>();
        private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
        private final Lock readLock = lock.readLock();
        private final Lock writeLock = lock.writeLock();
    
        public Data get(String key) {
          
          
            readLock.lock();
            try {
          
          
                return map.get(key);
            } finally {
          
          
                readLock.unlock();
            }
        }
    
        public Set<String> getAllKeys() {
          
          
            readLock.lock();
            try {
          
          
                return map.keySet();
            } finally {
          
          
                readLock.unlock();
            }
        }
    
        public Data put(String key, Data value) {
          
          
            writeLock.lock();
            try {
          
          
                return map.put(key, value);
            } finally {
          
          
                writeLock.unlock();
            }
        }
    
        class Data {
          
          
    
        }
    }
    

StampedLock

  • Visão geral

StampedLock é a implementação de ReentrantReadWriteLock. A principal diferença é que StampedLock não permite reentrada. Com a adição da função de leitura otimista, será mais complicado de usar, mas terá melhor desempenho.

StampedLock controla três modos de bloqueio: leitura, gravação, leitura otimista

O status do StampedLock consiste em duas partes: versão e modo. O método de aquisição de bloqueio retorna um número chamado ticket, que usa o status de bloqueio correspondente para representar e controlar o acesso relacionado. O número 0 indica que o bloqueio de gravação não está autorizado a acessar .

在读锁上分为悲观锁和乐观锁,乐观读就是在读多写少的场景,乐观的认为写入和读取同时发生的几率很小,因此,乐观的完全用读锁锁定。程序可以查看读取之后,是否遭到写入进行了变更,再采取后续的措施,这样的改进可大幅提升程序的吞吐量

总之,在读线程越来越多的场景下,StampedLock大幅提升了程序的吞吐量

import java.util.concurrent.locks.StampedLock;

class Point {
    
    
    private double x, y;
    private final StampedLock sl = new StampedLock();

    void move(double deltaX, double deltaY) {
    
     // an exclusively locked method
        long stamp = sl.writeLock();
        try {
    
    
            x += deltaX;
            y += deltaY;
        } finally {
    
    
            sl.unlockWrite(stamp);
        }
    }

    //下面看看乐观读锁案例
    double distanceFromOrigin() {
    
     // A read-only method
        long stamp = sl.tryOptimisticRead(); //获得一个乐观读锁
        double currentX = x, currentY = y; //将两个字段读入本地局部变量
        if (!sl.validate(stamp)) {
    
     //检查发出乐观读锁后同时是否有其他写锁发生?
            stamp = sl.readLock(); //如果没有,我们再次获得一个读悲观锁
            try {
    
    
                currentX = x; // 将两个字段读入本地局部变量
                currentY = y; // 将两个字段读入本地局部变量
            } finally {
    
    
                sl.unlockRead(stamp);
            }
        }
        return Math.sqrt(currentX * currentX + currentY * currentY);
    }

    //下面是悲观读锁案例
    void moveIfAtOrigin(double newX, double newY) {
    
     // upgrade
	// Could instead start with optimistic, not read mode
        long stamp = sl.readLock();
        try {
    
    
            while (x == 0.0 && y == 0.0) {
    
     //循环,检查当前状态是否符合
                long ws = sl.tryConvertToWriteLock(stamp); //将读锁转为写锁
                if (ws != 0L) {
    
     //这是确认转为写锁是否成功
                    stamp = ws; //如果成功 替换票据
                    x = newX; //进行状态改变
                    y = newY; //进行状态改变
                    break;
                } else {
    
     //如果不能成功转换为写锁
                    sl.unlockRead(stamp); //我们显式释放读锁
                    stamp = sl.writeLock(); //显式直接进行写锁 然后再通过循环再试
                }
            }
        } finally {
    
    
            sl.unlock(stamp); //释放读锁或写锁
        }
    }
}
  • 代码示例

    package io.binghe.concurrency.example.lock;
    
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.concurrent.CountDownLatch;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.Semaphore;
    import java.util.concurrent.locks.StampedLock;
    
    @Slf4j
    public class LockExample {
          
          
        //请求总数
        public static int clientTotal = 5000;
        //同时并发执行的线程数
        public static int threadTotal = 200;
        public static int count = 0;
        private static final StampedLock lock = new StampedLock();
    
        public static void main(String[] args) throws InterruptedException {
          
          
            ExecutorService executorService = Executors.newCachedThreadPool();
            final Semaphore semaphore = new Semaphore(threadTotal);
            final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
            for (int i = 0; i < clientTotal; i++) {
          
          
                executorService.execute(() -> {
          
          
                    try {
          
          
                        semaphore.acquire();
                        add();
                        semaphore.release();
                    } catch (Exception e) {
          
          
                        log.error("exception", e);
                    }
                    countDownLatch.countDown();
                });
            }
            countDownLatch.await();
            executorService.shutdown();
            log.info("count:{}", count);
        }
    
        private static void add() {
          
          
    //加锁时返回一个long类型的票据
            long stamp = lock.writeLock();
            try {
          
          
                count++;
            } finally {
          
          
    //释放锁的时候带上加锁时返回的票据
                lock.unlock(stamp);
            }
        }
    }
    

    我们可以这样初步判断选择Synchronized还是ReentrantLock:

    1. 当只有少量竞争者时,Synchronized是一个很好的通用锁实现
    2. 竞争者不少,但是线程的增长趋势是可预估的,此时使用ReentrantLock是个很好的通用锁实现
    3. Synchronized不会引发死锁,其他锁使用不当可能会引发死锁

Condition

  • 概述

    Condition是一个多线程间协调通讯的工具类,使用它可以有更好的灵活性,比如可以实现多路通知功能,也就是一个Lock对象里,可以创建多个Condition实例,线程对象可以注册在指定的Condition中,从而选择性的进行线程通知,在调度线程上更加灵活

  • 特点

    1. Condition的前提是Lock,由AQS中的newCondition()方法来创建Condition对象
    2. Condition的await()方法表示线程从AQS中删除,并释放线程获取的锁;并进入Condition等待队列,等待被通知
    3. Condition的signal()方法表示唤醒Condition等待队列中的节点,准备获取锁
  • 代码示例

    package io.binghe.concurrency.example.lock;
    
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.ReentrantLock;
    
    @Slf4j
    public class LockExample {
          
          
        public static void main(String[] args) {
          
          
            ReentrantLock reentrantLock = new ReentrantLock();
            Condition condition = reentrantLock.newCondition();
            new Thread(() -> {
          
          
                try {
          
          
                    reentrantLock.lock();
                    log.info("wait signal"); // 1
                    condition.await();
                } catch (InterruptedException e) {
          
          
                    e.printStackTrace();
                }
                log.info("get signal"); // 4
                reentrantLock.unlock();
            }).start();
            new Thread(() -> {
          
          
                reentrantLock.lock();
                log.info("get lock"); // 2
                try {
          
          
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
          
          
                    e.printStackTrace();
                }
                condition.signalAll();
                log.info("send signal ~ "); // 3
                reentrantLock.unlock();
            }).start();
        }
    }
    

ThreadLocal

  • 概述

ThreadLocal是JDK提供的,支持线程本地变量。意思是ThreadLocal中存放的变量属于当前线程,该变量对其他线程而言是隔离的,也就是说该变量是当前线程独有的变量。

如果我们创建了一个ThreadLocal变量,则访问这个变量的每个线程都会拥有这个变量的本地副本,当多个线程对这个变量进行操作时,实际上是操作的该变量的本地副本,从而避免了线程安全问题

  • 使用示例

    使用ThreadLocal保存并打印相关变量信息

    public class ThreadLocalTest {
          
          
        private static ThreadLocal<String> threadLocal = new ThreadLocal<String>();
    
        public static void main(String[] args) {
          
          
    		//创建第一个线程
            Thread threadA = new Thread(() -> {
          
          
                threadLocal.set("ThreadA:" + Thread.currentThread().getName());
                System.out.println("线程A本地变量中的值为:" + threadLocal.get());
            });
    		//创建第二个线程
            Thread threadB = new Thread(() -> {
          
          
                threadLocal.set("ThreadB:" + Thread.currentThread().getName());
                System.out.println("线程B本地变量中的值为:" + threadLocal.get());
            });
    		//启动线程A和线程B
            threadA.start();
            threadB.start();
        }
    }
    

    运行程序,打印信息如下:

    线程A本地变量中的值为:ThreadAThread-0
    线程B本地变量中的值为:ThreadBThread-1
    

    此时,我们为线程A增加删除变量操作:

    public class ThreadLocalTest {
          
          
        private static ThreadLocal<String> threadLocal = new ThreadLocal<String>();
    
        public static void main(String[] args) {
          
          
    		//创建第一个线程
            Thread threadA = new Thread(() -> {
          
          
                threadLocal.set("ThreadA:" + Thread.currentThread().getName());
                System.out.println("线程A本地变量中的值为:" + threadLocal.get());
                threadLocal.remove();
                System.out.println("线程A删除本地变量后ThreadLocal中的值为:" + threadLocal.get());
            });
    		//创建第二个线程
            Thread threadB = new Thread(() -> {
          
          
                threadLocal.set("ThreadB:" + Thread.currentThread().getName());
                System.out.println("线程B本地变量中的值为:" + threadLocal.get());
                System.out.println("线程B没有删除本地变量:" + threadLocal.get());
            });
    		//启动线程A和线程B
            threadA.start();
            threadB.start();
        }
    }
    

    As informações impressas são as seguintes:

    线程A本地变量中的值为:ThreadAThread-0
    线程B本地变量中的值为:ThreadBThread-1
    线程B没有删除本地变量:ThreadBThread-1
    线程A删除本地变量后ThreadLocal中的值为:null
    

    Através do programa acima, podemos ver que as variáveis ​​​​armazenadas no ThreadLocal pelo thread A e pelo thread B não interferem entre si.As variáveis ​​​​armazenadas pelo thread A só podem ser acessadas pelo thread A, e as variáveis ​​​​armazenadas pelo thread B só podem ser acessado pelo thread B.

  • Princípio ThreadLocal

    public class Thread implements Runnable {
          
          
        /***********省略N行代码*************/
        ThreadLocal.ThreadLocalMap threadLocals = null;
        ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
    /***********省略N行代码*************/
    }
    

    Como pode ser visto no código-fonte acima, existem threadLocals e inheritableThreadLocals na classe ThreadLocal. Ambas as variáveis ​​são variáveis ​​do tipo ThreadLocalMap e os valores iniciais de ambas são nulos. Somente na primeira vez que o thread atual chama o conjunto () ou o método get() instanciará a variável.

    Deve-se notar que as variáveis ​​locais de cada thread não são armazenadas na instância ThreadLocal, mas na variável threadLocals do thread chamador; ou seja, quando o método set() de ThreadLocal é chamado, as variáveis ​​locais armazenadas são armazenadas em No espaço de memória do thread de chamada específico, ThreadLocal fornece apenas métodos get() e set() para acessar valores de variáveis ​​​​locais; quando o método set() é chamado, o valor a ser definido é armazenado nos threadLocals do thread de chamada Ao chamar Quando o método get() é usado para obter o valor, as variáveis ​​​​armazenadas em threadLocals serão obtidas do thread atual;

    1. definir()

      public void set(T value) {
              
              
          //获取当前线程
          Thread t = Thread.currentThread();
          //以当前线程为key,获取ThreadLocalMap对象
          ThreadLocalMap map = getMap(t);
          if (map != null)
              //获取的获取ThreadLocalMap不为空,则赋值操作
              map.set(this, value);
          else
              //获取ThreadLocalMap为空,则为当前线程创建并赋值
              createMap(t, value);
      }
      
      /**
           * Create the map associated with a ThreadLocal. Overridden in
           * InheritableThreadLocal.
           *
           * @param t the current thread
           * @param firstValue value for the initial entry of the map
           */
          void createMap(Thread t, T firstValue) {
              
              
              t.threadLocals = new ThreadLocalMap(this, firstValue);
          }
      
    2. pegar()

      /**
       * Returns the value in the current thread's copy of this
       * thread-local variable.  If the variable has no value for the
       * current thread, it is first initialized to the value returned
       * by an invocation of the {@link #initialValue} method.
       *
       * @return the current thread's value of this thread-local
       */
      public T get() {
              
              
          //获取当前线程
          Thread t = Thread.currentThread();
          //获取当前线程ThreadLocalMap
          ThreadLocalMap map = getMap(t);
          if (map != null) {
              
              
              //ThreadLocalMap不为空,则从中取值
              ThreadLocalMap.Entry e = map.getEntry(this);
              if (e != null) {
              
              
                  @SuppressWarnings("unchecked")
                  T result = (T)e.value;
                  return result;
              }
          }
          //ThreadLocalMap为空,则返回默认值
          return setInitialValue();
      }
      
    3. remover()

      public void remove() {
              
              
          //获取当前线程中的threadLocals
          ThreadLocalMap m = getMap(Thread.currentThread());
          if (m != null)
              //若threadLocals不为空,则清除
              m.remove(this);
      }
      

      Nota: Se o thread nunca terminar, as variáveis ​​locais sempre existirão no ThreadLocal do thread de chamada; portanto, se as variáveis ​​locais não forem necessárias, você pode chamar o método remove() de ThreadLocal para excluí-las e evitar problemas de estouro de memória .

  • Variáveis ​​​​ThreadLocal não são transitivas

    Variáveis ​​locais armazenadas usando ThreadLocal não são transitivas, ou seja, para o mesmo ThreadLocal, após definir o valor no thread pai, o valor não pode ser obtido no thread filho;

    public class ThreadLocalTest {
          
          
            private static ThreadLocal<String> threadLocal = new ThreadLocal<String>();
    
            public static void main(String[] args) {
          
          
    			//在主线程中设置值
                threadLocal.set("ThreadLocalTest");
    			//在子线程中获取值
                Thread thread = new Thread(new Runnable() {
          
          
                    @Override
                    public void run() {
          
          
                        System.out.println("子线程获取值:" + threadLocal.get());
                    }
                });
    			//启动子线程
                thread.start();
    			//在主线程中获取值
                System.out.println("主线程获取值:" + threadLocal.get());
            }
        }
    

    Depois de executar o código de campo acima, os resultados ficam assim:

    主线程获取值:ThreadLocalTest
    子线程获取值:null
    

    Como pode ser visto no exemplo acima, após definir o valor de ThreadLocal no thread pai, o valor não pode ser obtido no thread filho;

    Existe alguma maneira de obter o valor do thread pai no thread filho? Podemos conseguir isso através de InheritableThreadLocal;

  • HerdávelThreadLocal

    A classe InheritableThreadLocal herda de ThreadLocal, que pode obter o valor definido no thread pai no thread filho;

    public class ThreadLocalTest {
          
          
            private static InheritableThreadLocal<String> threadLocal = 
                new InheritableThreadLocal<String>();
    
            public static void main(String[] args) {
          
          
    			//在主线程中设置值
                threadLocal.set("ThreadLocalTest");
    			//在子线程中获取值
                Thread thread = new Thread(new Runnable() {
          
          
                    @Override
                    public void run() {
          
          
                        System.out.println("子线程获取值:" + threadLocal.get());
                    }
                });
    			//启动子线程
                thread.start();
    			//在主线程中获取值
                System.out.println("主线程获取值:" + threadLocal.get());
            }
        }
    

    Executando o programa acima, os resultados são os seguintes:

    主线程获取值:ThreadLocalTest
    子线程获取值:ThreadLocalTest
    

    Pode-se observar que usando InheritableThreadLocal, o thread filho pode obter as variáveis ​​locais definidas no thread pai;

  • Princípio InheritableThreadLocal

    public class InheritableThreadLocal<T> extends ThreadLocal<T> {
          
          
        
        protected T childValue(T parentValue) {
          
          
            return parentValue;
        }
    
        /**
         * Get the map associated with a ThreadLocal.
         *
         * @param t the current thread
         */
        ThreadLocalMap getMap(Thread t) {
          
          
           return t.inheritableThreadLocals;
        }
    
        /**
         * Create the map associated with a ThreadLocal.
         *
         * @param t the current thread
         * @param firstValue value for the initial entry of the table.
         */
        void createMap(Thread t, T firstValue) {
          
          
            t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
        }
    }
    

    Pode ser visto no código-fonte que InheritableThreadLocal herda de ThreadLocal e substitui o método childValue()/getMap()/createMap().

    Ou seja, quando o método set() de ThreadLocal é chamado, a variável inheritableThreadLocals do thread atual é criada em vez da variável threadLocals; neste momento, se o thread pai criar um thread filho, no construtor do Thread classe, as variáveis ​​locais do thread pai na variável inheritableThreadLocals são copiadas para o inheritableThreadLocals do thread filho.

Problemas de simultaneidade

Problemas de visibilidade

Problema de visibilidade, ou seja, se um thread modifica uma variável compartilhada, outro thread não consegue ver a modificação imediatamente, isso é causado pela adição de cache da CPU;

CPUs de núcleo único não têm problemas de visibilidade, apenas CPUs multi-core têm problemas de visibilidade; CPUs de núcleo único, na verdade, são executadas em série devido ao agendamento de intervalo de tempo de núcleo único; enquanto multi-núcleos podem alcançar paralelismo;

Exemplo de código de visibilidade:

public class ConcurrentTest {
    
    

    private int num = 0;

    public static void main(String[] args) throws InterruptedException {
    
    
        ConcurrentTest concurrentTest = new ConcurrentTest();
        concurrentTest.threadsTest();
    }

    public void threadsTest() throws InterruptedException {
    
    
        Thread thread = new Thread("test-1") {
    
    
            @Override
            public void run() {
    
    
                for (int i = 0; i < 20; i++) {
    
    
                    try {
    
    
                        addNum();
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }
                }
            }
        };

        Thread thread2 = new Thread("test-2") {
    
    
            @Override
            public void run() {
    
    
                for (int i = 0; i < 20; i++) {
    
    
                    try {
    
    
                        addNum();

                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }
                }
            }
        };

        thread.start();
        thread2.start();

        thread.join();
        thread2.join();
        System.out.println("执行完毕");
    }

    private void addNum() throws InterruptedException {
    
    
        Thread.sleep(1000);
        num++;
        System.out.println(Thread.currentThread().getName() + ":" + num);
    }
}

problema de atomicidade

Atomicidade refere-se à característica de que uma ou mais operações não são interrompidas durante a execução da CPU. Assim que uma operação atômica começar a ser executada, ela continuará até o final da operação sem interrupção.

Problemas atômicos referem-se à situação em que uma ou mais operações são interrompidas durante a execução da CPU;

Quando um thread está realizando uma operação, a CPU passa a realizar outras tarefas, fazendo com que a tarefa atual seja interrompida, o que causará problemas de atomicidade;

Em JAVA, os programas simultâneos são escritos com base em multi-threading, o que também envolve o problema de alternância da CPU entre threads.É precisamente por causa da alternância da CPU entre threads que podem ocorrer problemas de atomicidade na programação simultânea;

problema de pedido

Ordem significa: o código é executado na ordem de execução

Reordenação de instruções: Para otimizar o desempenho do programa, o compilador ou interpretador às vezes modifica a ordem de execução do programa, mas essa modificação pode causar problemas inesperados;

Em um caso de thread único, a reordenação de instruções ainda pode garantir que o resultado final seja consistente com o resultado da execução sequencial do programa, mas em um caso de multithread, pode haver problemas;

Problema de ordem: Para otimizar o programa, a CPU reordena as instruções, neste momento a ordem de execução pode ser inconsistente com a ordem de codificação, o que pode causar problemas de ordem;

Resumir

Existem três razões principais pelas quais a programação simultânea pode causar problemas: problemas de visibilidade causados ​​pelo cache, problemas de atomicidade causados ​​pela troca de threads da CPU e problemas de ordenação causados ​​pela reordenação de instruções de otimização de desempenho.

Princípio Acontece-Antes

Na versão JDK1.5, o modelo de memória JAVA introduz o princípio Happens-Before

Código de exemplo 1:

class VolatileExample {
    
    
    int x = 0;
    volatile boolean v = false;

    public void writer() {
    
    
        x = 1;
        v = true;
    }

    public void reader() {
    
    
        if (v == true) {
    
    
            //x的值是多少呢?
        }
    }
}
  1. regras de ordem do programa

    ​ Em uma única thread, de acordo com a ordem do código, a operação anterior acontece - antes de qualquer operação subsequente.

    ​ Por exemplo: No Exemplo 1, o código x = 1 será concluído antes de v = true;

  2. Regras de variáveis ​​voláteis

    Para uma operação de gravação volátil, acontece antes das operações de leitura subsequentes nela

  3. Regras de entrega

    Se A acontece antes de B, B acontece antes de C, então A acontece antes de C

    Combinando os princípios 1, 2, 3 e o código de exemplo 1, podemos tirar as seguintes conclusões:

    • x = 1 Acontece-Antes v = verdadeiro, consistente com o princípio 1;
    • Escreva a variável v = verdadeiro Acontece-antes Leia a variável v = verdadeiro, consistente com o princípio 2
    • Então, de acordo com o princípio 3, x = 1 Acontece-Antes de ler a variável v = verdadeiro;
    • Ou seja, se o thread B lê v = true, então x = 1 definido pelo thread A é visível para B, o que significa que o thread B neste momento pode acessar x = 1
  4. Regras de bloqueio

    A operação de desbloqueio de uma fechadura acontece antes da operação de bloqueio subsequente da fechadura.

  5. Regras de inicialização de thread

    Se o thread A chamar start() do thread B para iniciar o thread B, então o método start() acontecerá antes de qualquer operação no thread B.

    //在线程A中初始化线程B
    Thread threadB = new Thread(()->{
          
          
        //此处的变量x的值是多少呢?答案是100
    });
    //线程A在启动线程B之前将共享变量x的值修改为100
    x = 100;
    //启动线程B
    threadB.start();
    
  6. Regras de encerramento de thread

    O thread A espera que o thread B seja concluído (chame o método join() do thread B).Quando o thread B é concluído (o método join() do thread B retorna), o thread A pode acessar a modificação da variável compartilhada do thread B.

    Thread threadB = new Thread(()-{
          
          
    //在线程B中,将共享变量x的值修改为100 
            x = 100;
    });
    //在线程A中启动线程B
    threadB.start();
    //在线程A中等待线程B执行完成
    threadB.join();
    //此处访问共享变量x的值为100
    
  7. Regras de interrupção de thread

    Uma chamada para o método interrupt() do thread acontece antes que o thread interrompido detecte que o evento de interrupção ocorre.

  8. Regras de finalização de objetos

    ​ Acontece-Antes após a inicialização de um objeto ser concluída no início do método finilize() do objeto

Estrutura ForkJoin

  • Visão geral

    Java 1.7 introduz uma nova estrutura de simultaneidade - a estrutura Fork/Join; é usada principalmente para implementar o algoritmo "dividir e conquistar", especialmente as funções que são chamadas recursivamente após dividir e conquistar.

    A essência da estrutura Fork/Join é uma estrutura para executar tarefas em paralelo, que pode dividir uma tarefa grande em várias tarefas pequenas e, finalmente, agregar os resultados de cada tarefa pequena para obter os resultados da tarefa grande. A estrutura Fork/Join coexiste com ThreadPool e não se destina a substituir ThreadPool.

  • princípio

    Fork/Join usa uma fila infinita para salvar as tarefas que precisam ser executadas, e o número de threads é passado através do construtor.Se o número de threads não for passado, o número de CPUs disponíveis no computador atual será definido como o número de threads por padrão.

    ForkjoinPool usa principalmente o método dividir e conquistar para resolver o problema. As aplicações típicas incluem algoritmo de classificação rápida.

  • Implementação da estrutura

    1. ForkJoinPool

      Implementou o pool de threads na estrutura ForkJoin

    2. ForkJoinWorkerThread

      Implementar threads na estrutura ForkJoin

    3. ForkJoinTask

      Ele encapsula dados e suas operações correspondentes e suporta paralelismo de dados refinado.

      ForkJoinTask inclui principalmente dois métodos, fork() e join(), para realizar a divisão e fusão de tarefas, respectivamente;

      O método fork() é semelhante ao método start() no método Thread, mas não executa a tarefa imediatamente, mas coloca a tarefa na fila de execução.

      O método join() é diferente do método join() do Thread. Ele não simplesmente bloqueia o thread; em vez disso, ele usa o thread de trabalho para executar outras tarefas. Quando um thread de trabalho chama join(), ele irá lidar com outras tarefas até que percebe o thread filho. Execução concluída.

    4. Tarefa Recursiva

      ForkJoinTask que retorna resultados implementa Callable

    5. Ação Recursiva

      ForkJoinTask sem resultado de retorno implementa Runnalbe

    6. ContadoCompletador

      Após a conclusão da execução da tarefa, uma função de gancho personalizada será acionada para execução.

  • Código de amostra

    public class ForkJoinTaskExample extends RecursiveTask<Integer> {
          
          
        public static final int threshold = 2;
        private int start;
        private int end;
    
        public ForkJoinTaskExample(int start, int end) {
          
          
            this.start = start;
            this.end = end;
        }
    
        @Override
        protected Integer compute() {
          
          
            int sum = 0;
    		//如果任务足够小就计算任务
            boolean canCompute = (end - start) <= threshold;
            if (canCompute) {
          
          
                for (int i = start; i <= end; i++) {
          
          
                    sum += i;
                }
            } else {
          
          
    			// 如果任务大于阈值,就分裂成两个子任务计算
                int middle = (start + end) / 2;
                ForkJoinTaskExample leftTask = new ForkJoinTaskExample(start, middle);
                ForkJoinTaskExample rightTask = new ForkJoinTaskExample(middle + 1, end);
    			// 执行子任务
                leftTask.fork();
                rightTask.fork();
    			// 等待任务执行结束合并其结果
                int leftResult = leftTask.join();
                int rightResult = rightTask.join();
    			// 合并子任务
                sum = leftResult + rightResult;
            }
            return sum;
        }
    
        public static void main(String[] args) {
          
          
            ForkJoinPool forkjoinPool = new ForkJoinPool();
    		//生成一个计算任务,计算1+2+3+4
            ForkJoinTaskExample task = new ForkJoinTaskExample(1, 100);
    		//执行一个任务
            Future<Integer> result = forkjoinPool.submit(task);
            try {
          
          
                log.info("result:{}", result.get());
            } catch (Exception e) {
          
          
                log.error("exception", e);
            }
        }
    }
    

concluído antes de v = verdadeiro;

  1. Regras de variáveis ​​voláteis

    Para uma operação de gravação volátil, acontece antes das operações de leitura subsequentes nela

  2. Regras de entrega

    Se A acontece antes de B, B acontece antes de C, então A acontece antes de C

    Combinando os princípios 1, 2, 3 e o código de exemplo 1, podemos tirar as seguintes conclusões:

    • x = 1 Acontece-Antes v = verdadeiro, consistente com o princípio 1;
    • Escreva a variável v = verdadeiro Acontece-antes Leia a variável v = verdadeiro, consistente com o princípio 2
    • Então, de acordo com o princípio 3, x = 1 Acontece-Antes de ler a variável v = verdadeiro;
    • Ou seja, se o thread B lê v = true, então x = 1 definido pelo thread A é visível para B, o que significa que o thread B neste momento pode acessar x = 1
  3. Regras de bloqueio

    A operação de desbloqueio de uma fechadura acontece antes da operação de bloqueio subsequente da fechadura.

  4. Regras de inicialização de thread

    Se o thread A chamar start() do thread B para iniciar o thread B, então o método start() acontecerá antes de qualquer operação no thread B.

    //在线程A中初始化线程B
    Thread threadB = new Thread(()->{
          
          
        //此处的变量x的值是多少呢?答案是100
    });
    //线程A在启动线程B之前将共享变量x的值修改为100
    x = 100;
    //启动线程B
    threadB.start();
    
  5. Regras de encerramento de thread

    O thread A espera que o thread B seja concluído (chame o método join() do thread B).Quando o thread B é concluído (o método join() do thread B retorna), o thread A pode acessar a modificação da variável compartilhada do thread B.

    Thread threadB = new Thread(()-{
          
          
    //在线程B中,将共享变量x的值修改为100 
            x = 100;
    });
    //在线程A中启动线程B
    threadB.start();
    //在线程A中等待线程B执行完成
    threadB.join();
    //此处访问共享变量x的值为100
    
  6. Regras de interrupção de thread

    Uma chamada para o método interrupt() do thread acontece antes que o thread interrompido detecte que o evento de interrupção ocorre.

  7. Regras de finalização de objetos

    ​ Acontece-Antes após a inicialização de um objeto ser concluída no início do método finilize() do objeto

Estrutura ForkJoin

  • Visão geral

    Java 1.7 introduz uma nova estrutura de simultaneidade - a estrutura Fork/Join; é usada principalmente para implementar o algoritmo "dividir e conquistar", especialmente as funções que são chamadas recursivamente após dividir e conquistar.

    A essência da estrutura Fork/Join é uma estrutura para executar tarefas em paralelo, que pode dividir uma tarefa grande em várias tarefas pequenas e, finalmente, agregar os resultados de cada tarefa pequena para obter os resultados da tarefa grande. A estrutura Fork/Join coexiste com ThreadPool e não se destina a substituir ThreadPool.

  • princípio

    Fork/Join usa uma fila infinita para salvar as tarefas que precisam ser executadas, e o número de threads é passado através do construtor.Se o número de threads não for passado, o número de CPUs disponíveis no computador atual será definido como o número de threads por padrão.

    ForkjoinPool usa principalmente o método dividir e conquistar para resolver o problema. As aplicações típicas incluem algoritmo de classificação rápida.

  • Implementação da estrutura

    1. ForkJoinPool

      Implementou o pool de threads na estrutura ForkJoin

    2. ForkJoinWorkerThread

      Implementar threads na estrutura ForkJoin

    3. ForkJoinTask

      Ele encapsula dados e suas operações correspondentes e suporta paralelismo de dados refinado.

      ForkJoinTask inclui principalmente dois métodos, fork() e join(), para realizar a divisão e fusão de tarefas, respectivamente;

      O método fork() é semelhante ao método start() no método Thread, mas não executa a tarefa imediatamente, mas coloca a tarefa na fila de execução.

      O método join() é diferente do método join() do Thread. Ele não simplesmente bloqueia o thread; em vez disso, ele usa o thread de trabalho para executar outras tarefas. Quando um thread de trabalho chama join(), ele irá lidar com outras tarefas até que percebe o thread filho. Execução concluída.

    4. Tarefa Recursiva

      ForkJoinTask que retorna resultados implementa Callable

    5. Ação Recursiva

      ForkJoinTask sem resultado de retorno implementa Runnalbe

    6. ContadoCompletador

      Após a conclusão da execução da tarefa, uma função de gancho personalizada será acionada para execução.

  • Código de amostra

    public class ForkJoinTaskExample extends RecursiveTask<Integer> {
          
          
        public static final int threshold = 2;
        private int start;
        private int end;
    
        public ForkJoinTaskExample(int start, int end) {
          
          
            this.start = start;
            this.end = end;
        }
    
        @Override
        protected Integer compute() {
          
          
            int sum = 0;
    		//如果任务足够小就计算任务
            boolean canCompute = (end - start) <= threshold;
            if (canCompute) {
          
          
                for (int i = start; i <= end; i++) {
          
          
                    sum += i;
                }
            } else {
          
          
    			// 如果任务大于阈值,就分裂成两个子任务计算
                int middle = (start + end) / 2;
                ForkJoinTaskExample leftTask = new ForkJoinTaskExample(start, middle);
                ForkJoinTaskExample rightTask = new ForkJoinTaskExample(middle + 1, end);
    			// 执行子任务
                leftTask.fork();
                rightTask.fork();
    			// 等待任务执行结束合并其结果
                int leftResult = leftTask.join();
                int rightResult = rightTask.join();
    			// 合并子任务
                sum = leftResult + rightResult;
            }
            return sum;
        }
    
        public static void main(String[] args) {
          
          
            ForkJoinPool forkjoinPool = new ForkJoinPool();
    		//生成一个计算任务,计算1+2+3+4
            ForkJoinTaskExample task = new ForkJoinTaskExample(1, 100);
    		//执行一个任务
            Future<Integer> result = forkjoinPool.submit(task);
            try {
          
          
                log.info("result:{}", result.get());
            } catch (Exception e) {
          
          
                log.error("exception", e);
            }
        }
    }
    

Acho que você gosta

Origin blog.csdn.net/weixin_40709965/article/details/128160545
Recomendado
Clasificación