Programación concurrente de Java: solo hay una forma de implementar subprocesos

La operación de subprocesos múltiples siempre ha sido la principal prioridad de la tecnología de back-end. Para un desarrollador de Java, la familiaridad con la concurrencia de subprocesos múltiples es una operación básica. En el entorno de producción, a menudo hay actividades de picos y la competencia de múltiples subprocesos es esencial.

Cuando lo intento, a menudo pregunto sobre subprocesos múltiples. En el combate real, a menudo hay subprocesos múltiples compitiendo por recursos ... Recientemente, el pico en Moutai es muy popular. La esencia es que varios subprocesos agarran un Moutai, pero algunos la gente usa la captura manual. Algunas personas usan el método de captura de secuencia de comandos. Por supuesto, solo tengo una botella de Moutai en la mano. Naturalmente, no puedo vender más de una docena de botellas de Moutai. Esto está relacionado con el tema de la seguridad del multihilo.

A continuación, echemos un vistazo a varias formas de implementar subprocesos y las diferencias entre ellos. Para dar una conclusión primero, en realidad solo hay una forma de implementar subprocesos.
(Hay súper beneficios en el artículo)

Implementar la interfaz Runnable

class MyThread implements Runnable {   // 定义线程主体类
    private String name;       // 定义类中的属性
    public MyThread(String name) {    // 定义构造方法
      this.name = name;
    }
    @Override
    public void run() {        // 覆写run()方法
        for (int x = 0; x < 200; x++) {
          System.out.println(this.name + " --> " + x);
      }
    }
}

Primero implemente la interfaz Runnable a través de la clase MyThread y luego reescriba el método run (). Después de eso, solo necesita pasar la instancia de MyThread que implementa el método run () a la clase Thread para lograr el multihilo.

Cómo ejecutar el hilo Runnable:

MyThread a = new MyThread();
new Thread(a).start();

Heredar la clase Thread

class MyThread extends Thread {   // 这就是一个多线程的操作类
    private String name ;     // 定义类中的属性
    public MyThread(String name) {  // 定义构造方法
      this.name = name ;
   }
   @Override
   public void run() {      // 覆写run()方法,作为线程的主操作方法
      for (int x = 0 ; x < 200 ; x ++) {
         System.out.println(this.name + " --> " + x);
      }
   }
}

La diferencia con el primer método es que no implementa la interfaz, sino que hereda la clase Thread y reescribe el método run (). Creo que debe estar muy familiarizado con los dos métodos anteriores y, a menudo, utilizarlos en su trabajo.

De la definición de la clase Thread, podemos ver que la clase Thread también es una subclase de la interfaz Runnable:

public class Thread extends implements Runnable

Por lo tanto, hay dos formas de iniciar un subproceso:

new MyThread().start();
MyThread a = new MyThread();
new Thread(a).start();

Si necesita más material para entrevistas de las principales empresas, también puede hacer clic para ingresar directamente y obtenerlo gratis Contraseña: CSDN

Invocable con valor de retorno crea hilo

Permítanme hablar primero sobre java.lang.Runnable. Es una interfaz y solo se declara un método run () en ella:

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

Ni Thread ni Runnable pueden devolver un valor, que es su defecto común. Se propuso invocable después de JDK1.5.

  1. La interfaz Callable se parece más a una versión mejorada de la interfaz Runnable. En comparación con la interfaz Runable, el método Call () agrega la capacidad de capturar y lanzar excepciones; el método Call () puede devolver valores
  2. La interfaz Future proporciona una clase de implementación FutureTask La clase FutureTaks se usa para almacenar el valor de retorno del método Call () y sirve como el objetivo de la clase Thread.
  3. Llame al método get () de FutureTask para obtener el valor de retorno
class CallableTask implements Callable<Integer> {

    @Override

    public Integer call() throws Exception {

        return new Random().nextInt();

    }

}

Sin embargo, no hay forma de aceptar objetos de instancia invocables en la clase Thread. Después de implementar Callable, debe usar la clase FutureTask. Después de JDK1.5, Java proporciona java.util.concurrent.FutureTask.

Echemos un vistazo a los dos métodos de construcción de FutureTask:

public FutureTask(Callable<V> callable)
public FutureTask(Runnable runnable,V result)

La estructura básica de herencia de clases se muestra en la figura. Se puede encontrar que FutureTask implementa la interfaz RunnableFuture y RunnableFuture implementa las interfaces Future y Runnable.
Inserte la descripción de la imagen aquí
Entonces, ya sea invocable o FutureTask, son en primer lugar una tarea como Runnable y necesitan a ejecutar., No es que sean hilos en sí mismos. Se pueden colocar en el grupo de subprocesos para su ejecución, no importa qué método se utilice, finalmente se ejecutan por subprocesos, y la creación de subprocesos sigue siendo inseparable de los dos métodos básicos mencionados al principio, es decir, para implementar el Interfaz ejecutable y hereda la clase Thread.

//创建线程池
ExecutorService service = Executors.newFixedThreadPool(10);
//提交任务,并用 Future提交返回结果
Future<Integer> future = service.submit(new CallableTask());

Hilo de creación de grupo de subprocesos

El grupo de subprocesos implementa múltiples subprocesos. Por ejemplo, si establecemos el número de subprocesos en el grupo de subprocesos en 10, entonces habrá 10 subprocesos secundarios para trabajar para nosotros. A continuación, analizaremos el código fuente en el grupo de subprocesos en profundidad para ver si el grupo de subprocesos es ¿Cómo implementar subprocesos?

static class DefaultThreadFactory implements ThreadFactory {

    DefaultThreadFactory() {

        SecurityManager s = System.getSecurityManager();

        group = (s != null) ? s.getThreadGroup() :

            Thread.currentThread().getThreadGroup();

        namePrefix = "pool-" +

            poolNumber.getAndIncrement() +

            "-thread-";
    }


    public Thread newThread(Runnable r) {

        Thread t = new Thread(group, r,

                    namePrefix + threadNumber.getAndIncrement(),

0);

        if (t.isDaemon())

            t.setDaemon(false);

        if (t.getPriority() != Thread.NORM_PRIORITY)

            t.setPriority(Thread.NORM_PRIORITY);

        return t;

    }

}

Para el grupo de subprocesos, el subproceso se crea a través de la fábrica de subprocesos en esencia. DefaultThreadFactory se utiliza de forma predeterminada. Establecerá algunos valores predeterminados para los subprocesos creados por el grupo de subprocesos, como el nombre del subproceso, si es un hilo de demonio y la prioridad del hilo, etc. Pero no importa cómo establezca estas propiedades, eventualmente crea un subproceso a través de new Thread (), pero el constructor aquí toma más parámetros. Se puede ver que crear un subproceso a través del grupo de subprocesos no se desvía del original. Los dos Los métodos de creación básicos se implementan esencialmente a través de new Thread ().

Entonces, cuando respondemos a la pregunta de la implementación de subprocesos, después de describir los dos primeros métodos, podemos extender aún más que "También sé que el grupo de subprocesos y Callable también pueden crear subprocesos, pero son esencialmente la creación de subprocesos a través de los dos primeros métodos básicos. "Esta respuesta se convertirá en un elemento adicional en la entrevista.

Si necesita más material para entrevistas de las principales empresas, también puede hacer clic para ingresar directamente y obtenerlo gratis Contraseña: CSDN

Resumen 1: solo hay una forma de implementar subprocesos

Con respecto a este problema, no nos centremos en por qué solo hay una forma de crear un hilo. Primero, pensamos que hay dos formas de crear un hilo. Otros métodos de creación, como grupos de hilos o temporizadores, están fuera del nuevo hilo. (). Hicimos una capa de encapsulación. Si los llamamos una nueva forma, entonces la forma de crear subprocesos será siempre cambiante e interminable. Por ejemplo, cuando se actualice el JDK, puede tener algunas clases más, que call new Thread () La reencapsulación, en la superficie, será una nueva forma de implementar subprocesos. Mirando la esencia a través del fenómeno, después de abrir la encapsulación, encontrará que finalmente se implementan basándose en la interfaz Runnable o heredando la clase Thread.

Resumen 2: implementar la interfaz Runnable es mejor que heredar la clase Thread para implementar subprocesos

Comparemos las dos formas de implementar el contenido del hilo que acabamos de mencionar, por lo que es mejor implementar la interfaz Runnable que heredar la clase Thread para implementar el hilo. ¿Qué es lo bueno?

  1. Realice el desacoplamiento de las clases Runnable y Thread. Solo hay un método run () en Runnable, que define el contenido que debe ejecutarse. En este caso, la clase Thread es responsable del inicio del hilo y la configuración de propiedades, etc., con derechos y responsabilidades claros.
  2. Mejorar el rendimiento. Con el método de heredar la clase Thread, debe crear un nuevo hilo independiente cada vez que realiza una tarea. Si desea realizar esta tarea, debe crear una nueva clase que herede la clase Thread. Todo el hilo se crea a partir de se destruye el principio hasta el final de la ejecución Esta serie de operaciones es mucho más cara que el método run () para imprimir el texto en sí, que equivale a recoger semillas de sésamo y perder sandía, que no vale la pena perder. Si implementamos la interfaz Runnable, podemos transferir tareas directamente al grupo de subprocesos y usar algunos subprocesos fijos para completar las tareas. No hay necesidad de crear y destruir subprocesos cada vez, lo que reduce en gran medida la sobrecarga de rendimiento.
  3. El lenguaje Java no admite la doble herencia. Una vez que nuestra clase herede la clase Thread, no podrá heredar otras clases en el futuro. De esta manera, si esta clase necesita heredar otras clases para lograr alguna expansión funcional en el futuro , lo hará. No hay forma de hacerlo, lo que equivale a limitar la escalabilidad futura del código.

En resumen, primero deberíamos elegir crear subprocesos implementando la interfaz Runnable.

Resumen 3: ¿Por qué el inicio de subprocesos múltiples no llama a run () sino a 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() 是启动多线程的关键
     // 这里会创建一个新的线程,是一个 native 方法
     // 执行完成之后,新的线程已经在运行了
        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 */
        }
    }
}

El código fuente del método start no son unas pocas líneas de código, y los comentarios son más detallados, el más importante es el método start0 (), que se explicará más adelante. Echemos un vistazo al código fuente del método run ():

 @Override
    public void run() {
     // 简单的运行,不会新起线程,target 是 Runnable
        if (target != null) {
            target.run();
        }
    }

El código fuente del método run () es relativamente simple, solo una llamada a un método común, lo que también confirma nuestra conclusión anterior.

A continuación, hablemos del método start0 (), que es la clave para el subproceso múltiple real. El código para start0 () es el siguiente:

private native void start0();

start0 está marcado como nativo, es decir, un método local, que no requiere que lo implementemos o comprendamos.

Después de que el método start () llama al método start0 (), el hilo no se ejecuta necesariamente de inmediato, pero lo convierte en un estado ejecutable. El tiempo de ejecución específico depende de la CPU y la CPU se programa de manera uniforme.

También sabemos que Java es multiplataforma y puede ejecutarse en diferentes sistemas. El algoritmo de programación de la CPU de cada sistema es diferente, por lo que es necesario realizar un procesamiento diferente. Este asunto solo se puede lograr con la JVM, start0 () El método es naturalmente marcado como nativo.

Darse cuenta de un verdadero multihilo en Java es el método start0 () en start, y el método run () es solo un método ordinario.

Beneficios del lector

¡Gracias por ver aquí!
He compilado muchas de las últimas preguntas de la entrevista de Java de 2021 (incluidas las respuestas) y notas de estudio de Java aquí, como se muestra a continuación
Inserte la descripción de la imagen aquí

Las respuestas a las preguntas de la entrevista anteriores están organizadas en notas de documentos. Además de las entrevistas, también recopiló información sobre algunos de los fabricantes y entrevistó a la última colección de Zhenti 2021 (ambas documentan una pequeña parte de la captura de pantalla) gratis para que todos la compartan, los que lo necesiten pueden hacer clic para ingresar a la señal: ¡CSDN! Libre para compartir ~

Si te gusta este artículo, reenvíalo y dale me gusta.

¡Recuerda seguirme!
Inserte la descripción de la imagen aquí

Supongo que te gusta

Origin blog.csdn.net/weixin_49527334/article/details/114078576
Recomendado
Clasificación