Resumen de conceptos básicos de Java (87) -Análisis de los principios de implementación invocables

Enlace original

Prefacio

En general, hay dos formas de crear subprocesos que usamos comúnmente:

  1. Heredar hilo, reescribir el método de ejecución
  2. Implementar la interfaz Runnable, volver a ejecutar el método

De hecho, existe otra forma de lograr la asincrónica en el marco Executor, que es implementar la interfaz invocable y reescribir el método de llamada. Aunque implementa Callable, cuando el Executor se está ejecutando, las instancias de Runnable o Callable se convertirán en instancias de RunnableFuture, y RunnableFuture hereda las interfaces Runnable y Future, que se explicarán en detalle a continuación. Sabiendo esto, ¿en qué se diferencia de Runnable? Invocable es diferente de Runnable en los dos puntos siguientes:

  1. Invocable puede proporcionar un valor de retorno al final de la tarea, Runnable no puede proporcionar esta función
  2. El método de llamada invocable puede generar excepciones, mientras que el método de ejecución de Runnable no puede generar excepciones.

Principio de realización

Antes de presentar el principio de implementación de Callable, echemos un vistazo a cómo se usa:

    public class ThreadTest {

        public static void main(String[] args) {
            System.out.println("main start");
            ExecutorService threadPool = Executors.newSingleThreadExecutor();
            // Future<?> future = threadPool.submit(new MyRunnable()) ;
            Future<String> future = threadPool.submit(new MyCallable());
            try {
                // 这里会发生阻塞
                System.out.println(future.get());
            } catch (Exception e) {

            } finally {
                threadPool.shutdown();
            }
            System.out.println("main end");
        }
    }


    public class MyCallable implements Callable<String> {

        @Override
        public String call() throws Exception {
            // 模拟耗时任务
            Thread.sleep(3000);
            System.out.println("MyCallable 线程:" + Thread.currentThread().getName());
            return "MyCallable" ;
        }
    }

    public class MyRunnable implements Runnable {

        @Override
        public void run() {
            // 模拟耗时任务
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("MyRunnable");
        }
    }

Ejecute el código anterior y obtendrá los siguientes resultados:

main start
// 这里会阻塞一段时间,才会打印下面的内容
MyCallable 线程:pool-1-thread-1
MyCallable
main end
  • 1
  • 2
  • 3
  • 4
  • 5

A través del código anterior, hemos verificado la diferencia entre Callable y Runnable, entonces, ¿cómo se implementa Callable? Primero, pasamos la clase de implementación Callable MyCallable a  ExecutorService.submit ()  y ExecutorService es una interfaz, por lo que debe anular el método submit () en su clase de implementación; seguimiento: Executors.newSingleThreadExecutor ()

Escriba la descripción de la imagen aquí

(Foto 1)

Descubrimos que la clase de implementación de ExecutorService es FinalizableDelegatedExecutorService, y luego seguimos:

Escriba la descripción de la imagen aquí

(Figura II)

Descubrimos que no hay un método submit () en FinalizableDelegatedExecutorService, por lo que debe implementarse en la clase principal y seguir con la clase principal:

Escriba la descripción de la imagen aquí

(Imagen 3)

Desde DelegatedExecutorService, podemos ver que la implementación de su método submit () llama a  ExecutorService.submit () . ¿Se siente como si hubiera vuelto al punto original, llamándolo cíclicamente? Por supuesto que este no es el caso Tenga en cuenta que e.submit aquí es un método de una variable local ExecutorService de DelegatedExecutorService, y e es asignado por el constructor. Ahora regresemos a la subclase FinalizableDelegatedExecutorService de DelegatedExecutorService para ver qué se pasa en el método de construcción De la Figura 2 y la Figura 1, podemos ver que la clase de implementación ExecutorService pasada en el método de construcción es ThreadPoolExecutor. Finalmente encontré al propietario correcto, hagamos un seguimiento de cómo ThreadPoolExecutor implementa submit (). Después del seguimiento, encontrará que no hay un método submit () en ThreadPoolExecutor. Luego tenemos que buscar la clase principal:

Escriba la descripción de la imagen aquí

(Imagen 4)


Escriba la descripción de la imagen aquí

(Imagen 5)

Desde el código AbstractExecutorService, podemos ver que la interfaz Runnable y la interfaz Callable se procesan de la misma manera que se convierte a RunnableFuture. Y RunnableFuture es una interfaz, por lo que debe tener una clase de implementación para completar el proceso de conversión. Sigamos estos dos newTaskFor respectivamente para ver cómo se convierten las instancias Runnable y Callable:

Escriba la descripción de la imagen aquí

(Imagen 6)

En el código, podemos ver que el valor de retorno del método newTaskForjia () es del tipo FutureTask. Siga de nuevo:

Escriba la descripción de la imagen aquí

(Imagen 7)

La función de estos dos métodos de construcción de FutureTask es asignar valores a invocables y estados. Hasta ahora, las instancias invocables y ejecutables se procesan de la misma manera; la diferencia es que las instancias ejecutables se pasan a Executors.callable para generar instancias invocables . Echemos un vistazo a este proceso de conversión:

Escriba la descripción de la imagen aquí

(Imagen 8)

RunnableAdapter es una clase de implementación de la interfaz invocable:

Escriba la descripción de la imagen aquí

(Imagen 9)

RunnableAdapter anula el método call () de Callable y ejecuta el método run () de Runnable cuando se ejecuta call (); de esta manera, se logra la unidad de Runnable y Callable.

Ahora que Runnable y Callable están unificados, veamos cómo  se implementa el método de ejecución de subprocesos (Runnable runnable) en la Figura 5  . ¿Cuál es la situación? Lo que ejecutar pasa es una instancia de Runnable, y hemos unificado Runnable y Callable en Callable. ¿Se siente extraño? Tenga en cuenta que en la Figura 5, ejecutar pasa una instancia de FutureTask, la clase de implementación de RunnableFuture. Y RunnableFuture implementa la interfaz Runnable y anula el método run () de Runnable:

Escriba la descripción de la imagen aquí

(Figura 10)

Entonces, ¿cómo implementa la clase de implementación FutureTask de Runnable el método run ()?

Escriba la descripción de la imagen aquí

(Imagen 11)

Callable.call () se llama en el método run (), y run () es el método Runnable run () anulado. Ahora entendemos por qué el método execute () en la Figura 5 puede pasar la instancia RunnableFuture: Primero, ejecute Runnable O las instancias de Callable se unifican en el tipo Callable y luego llaman al método call () de Callable cuando se ejecuta el método run ().

Los lectores atentos pueden haber encontrado un comentario en la demostración al principio del artículo (// el bloqueo ocurrirá aquí), entonces, ¿qué causa el bloqueo? Sigamos la clase de implementación FutureTask of Future (se puede obtener en la Figura 5) y veamos cómo se implementa su método get ():

Escriba la descripción de la imagen aquí

(Imagen 12)

Continuar con el seguimiento:

Escriba la descripción de la imagen aquí

(Imagen 13)

awaitDone es un bucle infinito, solo cuando finaliza el Callable o Runnable a ejecutar, saltará, por lo que no es difícil entender la lógica de la Figura 12, cuando el Callable o Runnable ha terminado, el valor de retorno se da directamente, de lo contrario, se bloqueará. En el método awaitDone (), hay un comentario en el comentario.

Conclusión

Este artículo ha explicado aproximadamente el principio de la implementación de Callable de asincrónico, para una comprensión más conveniente, aquí hay un diagrama de flujo del uso de Callable.

Escriba la descripción de la imagen aquí

apéndice

En cuanto a Executor es el contenido del grupo de subprocesos, no es el enfoque de este artículo, solo necesito saber: el grupo de subprocesos necesita recibir instancias Runnable en uso.

Vuelva a colocar

El invocable rara vez se usa en el desarrollo diario y no es tan vívido de entender. Aquí hay un ejemplo: En el acceso a la red concurrente, a veces necesitamos los resultados de múltiples solicitudes de acceso juntas como parámetros del siguiente método, entonces Callable se puede usar en este momento; si está en Android, preste atención a envolver estas solicitudes con un subproceso que no es de UI (denotado aquí como subproceso A) para evitar bloquear el subproceso de UI al llamar, cuando todo el acceso a la red en el subproceso A ha terminado (incluidas otras operaciones que consumen mucho tiempo), puede enviar un mensaje al subproceso principal para actualizar la interfaz de usuario, por supuesto, usted también puede usar Rxjava para lograr el mismo efecto.

 

 

 

 

Supongo que te gusta

Origin blog.csdn.net/lsx2017/article/details/113922339
Recomendado
Clasificación