Un análisis en profundidad del principio de subprocesamiento múltiple de JAVA

1. Conceptos básicos del hilo


1.1 Hilos y procesos


1.1.1 Proceso


●Un programa consta de instrucciones y datos, pero para ejecutar estas instrucciones y leer y escribir datos, las instrucciones deben cargarse en la CPU y los datos deben cargarse en la memoria. También se necesitan dispositivos como discos y redes durante la ejecución de las instrucciones. Los procesos se utilizan para cargar instrucciones, administrar la memoria y administrar IO.


●Cuando se ejecuta un programa, el código de este programa se carga desde el disco a la memoria y se abre un proceso en este momento. 


● Un proceso puede considerarse como una instancia de un programa. La mayoría de los programas pueden ejecutar varios procesos de instancia al mismo tiempo (como el bloc de notas, el dibujo, el navegador, etc.) y algunos programas solo pueden iniciar un proceso de instancia (como NetEase Cloud Music, 360 Security Guard, etc.).


●El sistema operativo asignará los recursos del sistema (fracción de tiempo de CPU, memoria y otros recursos) en unidades de procesos, y un proceso es la unidad más pequeña de asignación de recursos.


1.1.2 Hilos


●Un subproceso es una entidad en un proceso. Un proceso puede tener varios subprocesos y un subproceso debe tener un proceso principal. 


●Un subproceso es un flujo de instrucciones, y las instrucciones en el flujo de instrucciones se entregan a la CPU en un orden determinado para su ejecución.


● El subproceso, a veces denominado proceso ligero (Lightweight Process, LWP), es la unidad más pequeña de ejecución de la programación del sistema operativo (programación de la CPU).


1.1.3 La diferencia entre proceso e hilo


1 ocupación de recursos
Un proceso tiene recursos compartidos, como espacio de memoria, para que sus subprocesos internos los compartan


2 Dependencia
un proceso es básicamente independiente entre sí, mientras que los subprocesos existen dentro de un proceso y son un subconjunto de un proceso


3 modo de comunicación
una comunicación entre procesos es más complicada


ⅰ La comunicación del proceso de la misma computadora se llama IPC (Comunicación entre procesos)
ⅱ La comunicación del proceso entre diferentes computadoras debe pasar por la red y seguir un protocolo común, como HTTP
b La comunicación de subprocesos es relativamente simple porque comparten la memoria en el proceso, un ejemplo es cuando varios subprocesos pueden acceder a la misma variable compartida


4El cambio de contexto
de un subproceso es más ligero, y el costo del cambio de contexto de subproceso es generalmente más bajo que el del cambio de contexto de proceso
b Cuando dos subprocesos no pertenecen al mismo proceso, el proceso de cambio es el mismo que el cambio de contexto de proceso
c Cuando dos subprocesos pertenecen a Para el mismo proceso, debido a que la memoria virtual se comparte, los recursos como la memoria virtual permanecen sin cambios al cambiar, y solo los datos que no se comparten, como los datos privados y los registros de subprocesos, deben cambiarse


1.1.4 La forma de comunicación entre procesos


Consulte Sistema operativo: comunicación entre procesos para obtener más información .

1 Tubería (tubería) y tubería con nombre (tubería con nombre): Las tuberías se pueden usar para la comunicación entre procesos padre e hijo con parentesco. Además de las funciones de las tuberías, las tuberías con nombre también permiten la comunicación entre procesos no relacionados.


2 Signal (señal): La señal es una simulación del mecanismo de interrupción a nivel de software. Es un método de comunicación relativamente complejo que se utiliza para notificar al proceso que ha ocurrido un determinado evento. Un proceso recibe una señal y el procesador recibe una interrupción. Puede decirse que el efecto de la solicitud es consistente.


3 Cola de mensajes (cola de mensajes): La cola de mensajes es una lista enlazada de mensajes, que supera las deficiencias del semáforo limitado en los dos métodos de comunicación anteriores. Los procesos con permisos de escritura pueden agregar nueva información a la cola de mensajes de acuerdo con ciertas reglas; Los procesos con permiso de lectura en la cola de mensajes pueden leer información de la cola de mensajes.


4 Shared memory (memoria compartida): Se puede decir que esta es la forma más útil de comunicación entre procesos. Permite que múltiples procesos accedan al mismo espacio de memoria, y diferentes procesos pueden ver las actualizaciones de datos en la memoria compartida en el otro proceso a tiempo. Este enfoque debe basarse en algún tipo de operaciones de sincronización, como mutexes y semáforos.


5 Semáforo (semaphore): Se utiliza principalmente como medio de sincronización y exclusión mutua entre procesos y entre diferentes hilos de un mismo proceso.


6 socket (socket): este es un mecanismo de comunicación entre procesos más general, que se puede usar para la comunicación entre procesos entre diferentes máquinas en la red, y es ampliamente utilizado.


1.1.5 Formas de comunicación entre hilos


La exclusión mutua
se refiere a la exclusividad de los recursos del sistema de procesos compartidos cuando se accede a ellos mediante subprocesos individuales. Cuando varios subprocesos desean usar un recurso compartido, solo un subproceso puede usarlo en cualquier momento, y otros subprocesos que desean usar el recurso deben esperar hasta que el ocupante del recurso libere el recurso.


La sincronización
se refiere a una relación de restricción entre subprocesos, por ejemplo:
la ejecución de un subproceso depende del mensaje de otro subproceso. Cuando no recibe el mensaje de otro subproceso, debe esperar hasta que llegue el mensaje antes de despertarse en un amplio sentido

Véase, la exclusión mutua es también una especie de sincronización.

Método de control de sincronización de subprocesos y exclusión mutua


●Sección crítica Acceder a recursos públicos oa un fragmento de código a través de la serialización de multithreading, que es rápido y adecuado para controlar el acceso a datos. Los recursos críticos son recursos compartidos que solo pueden ser utilizados por un proceso a la vez. La sección de código que accede a los recursos críticos en cada proceso se denomina sección crítica Mutex Diseñado
para coordinar el acceso separado a un recurso compartido
Semáforo Diseñado para controlar un recurso con un número limitado de usuarios
Eventos Se utiliza para notificar a un subproceso que ha ocurrido algún evento. iniciando así el inicio de una tarea posterior.


1.2 Cambio de contexto


Un cambio de contexto es el cambio de la CPU (Unidad Central de Procesamiento) de un proceso o subproceso a otro.
El contexto es el contenido de los registros de la CPU y el contador del programa en cualquier momento.
Un registro es una pequeña porción de memoria muy rápida dentro de la CPU (a diferencia de la memoria principal RAM más lenta fuera de la CPU) que acelera la ejecución de los programas de computadora al proporcionar un acceso rápido a los valores de uso frecuente.
El contador de programa es un registro especializado que indica la posición de la CPU en su secuencia de instrucciones y contiene la dirección de la instrucción que se está ejecutando o la dirección de la siguiente instrucción que se ejecutará, según el sistema específico.
Para obtener detalles sobre el cambio de contexto de proceso, consulte Sistema operativo - Cambio de contexto de proceso .

El proceso de cambio se puede describir simplemente como que el núcleo (es decir, el núcleo del sistema operativo) realiza las siguientes actividades en los procesos (incluidos los subprocesos) en la CPU:


1 suspende el procesamiento de un proceso y almacena el estado de la CPU (es decir, el contexto) de ese proceso en algún lugar de la memoria


2 Obtenga el contexto del siguiente proceso de la memoria y restáurelo en los registros de la CPU


3 Regrese a la ubicación indicada por el contador del programa (es decir, regrese a la línea de código donde se interrumpió el proceso) para reanudar el proceso.
 



1.2.1 Características de cambio de contexto


●Solo puede ocurrir en modo kernel.Para obtener más información, consulte Modo Kernel VS Modo de usuario  y  sistema operativo-Administración de memoria Linux
●Es una característica del sistema operativo multitarea.El sistema operativo multitarea tiene dos situaciones: concurrente y paralela. Los cambios de contexto ocurren porque
        ○ los procesos ceden voluntariamente su tiempo en la CPU
        ○ o como resultado del cambio del planificador cuando un proceso ha agotado su segmento de tiempo de CPU.
●Por lo general, ocurre en tareas de uso intensivo de cómputo. El cambio de contexto es un costo enorme para el sistema en términos de tiempo de CPU; de hecho, puede ser la operación más costosa en el sistema operativo. Por lo tanto, un enfoque importante en el diseño del sistema operativo es evitar cambios de contexto innecesarios tanto como sea posible. Una de las muchas ventajas de Linux sobre otros sistemas operativos (incluidos algunos otros sistemas similares a Unix) es su costo extremadamente bajo de cambio de contexto y cambio de modo.


1.2.2 Ver el cambio de contexto de la CPU



El sistema Linux puede ver las estadísticas de  cambio de contexto de la CPU por segundo para todo el sistema operativo mediante estadísticas de comando Datos de cambio de contexto de la CPU 1

vmstat 1 

La columna cs son las estadísticas del cambio de contexto de la CPU. Por supuesto, el cambio de contexto de la CPU no es equivalente al cambio de subprocesos, y muchas operaciones provocarán el cambio de contexto de la CPU:
        ○conmutación de subprocesos, procesos de conmutación
        ○llamada al sistema
        ○interrupción


2 Ver un cambio de contexto de proceso/hilo
usando el comando pidstat

常用的参数:
-u 默认参数,显示各个进程的 CPU 统计信息
-r 显示各个进程的内存使用情况
-d 显示各个进程的 IO 使用
-w 显示各个进程的上下文切换
-p PID 指定 PID

# 显示进程5598每一秒的切换情况
pidstat -w -p 5598 1


Entre ellos, cswch significa conmutación activa y nvcswch significa conmutación pasiva. Se puede ver a partir de las estadísticas que el proceso cambia activamente casi 500 veces por segundo, por lo que hay una gran cantidad de operaciones de suspensión/activación en el código. 


b Ver desde la información de estado del proceso 

gato /proc/5598/estado 


1.3 Ciclo de vida del subproceso a nivel del sistema operativo


El ciclo de vida del subproceso a nivel del sistema operativo se puede describir básicamente mediante el "modelo de cinco estados" que se muestra en la siguiente figura.


●Estado inicial Se ha creado el subproceso, pero la CPU no puede ejecutarse. La creación aquí se refiere a ser creado a nivel de lenguaje de programación, el hilo real, el sistema operativo aún no ha sido creado.


● Estado listo Los subprocesos pueden asignar segmentos de tiempo de CPU para la ejecución. El sistema operativo crea hilos.


● Subprocesos de estado de ejecución asignados a segmentos de tiempo de CPU.


●Estado inactivo Libere el derecho a usar la CPU. El subproceso en estado de ejecución llama a la API de bloqueo, como leer un archivo de forma bloqueada o esperar un evento, como una variable de condición, y el estado del subproceso se convertirá en este estado. Cuando ocurre el tiempo de espera, el subproceso cambiará del estado de suspensión al estado listo.


●Estado de terminación La ejecución del subproceso se completa o se produce una excepción y entra en el estado de terminación. El ciclo de vida del subproceso finaliza.

Estos cinco estados se combinan para simplificar en diferentes lenguajes de programación. Por ejemplo:


●La especificación de subprocesos POSIX del lenguaje C combina el estado inicial y el estado ejecutable


● En el lenguaje Java, el estado ejecutable y el estado en ejecución se combinan. Estos dos estados son útiles en el nivel de programación del sistema operativo, pero el nivel de JVM no se preocupa por estos dos estados, porque la JVM deja la programación de subprocesos al sistema operativo. .


1.4 Cómo ver hilos de proceso


ventanas
●Administrador de tareas para ver la cantidad de procesos y subprocesos, y también puede eliminar la
lista de tareas del proceso para ver el proceso
taskkill para eliminar el subproceso


linux
ps -ef para ver todos los procesos
top presione H mayúscula para cambiar si desea subprocesos explícitos
ps -fT -p <PID> para ver todos los subprocesos de un proceso
top -H -p <PID> para ver todos los subprocesos de un proceso
● matar mata el proceso


Método de implementación de subprocesos del sistema
El paquete LinuxThreads linux/glibc solo implementó LinuxThreads antes de 2.3.2
NPTL (biblioteca de subprocesos POSIX nativos)

puede verificar qué implementación de subprocesos utiliza el sistema a través del siguiente comando getconf GNU_LIBPTHREAD_VERSION 


Java
jps Ver todos los procesos java
jstack <PID> Ver todos los estados de subprocesos de un proceso Java
jconsole vista GUI


2. Explicación detallada de los hilos de Java


2.1 Implementación de subprocesos Java


Use Thread o herede la clase Thread

public void test() {
    Thread newThread = new Thread() {
        @Override
            public void run() {
            System.out.println("new Thread");
        }
    };

    MyThread myThread = new MyThread();

    newThread.start();
    myThread.start();
}

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("extend Thread");
    }
}


Implementar la interfaz Runnable

public void test2() {
    Runnable runnable = new Runnable() {

        @Override
            public void run() {
            System.out.println("implement runnable");
        }
    };

    Thread thread = new Thread(runnable);
    thread.start();
}


Usar la interfaz Callable con un valor de retorno
Método 1

public void test3() {
    ExecutorService executorService = Executors.newFixedThreadPool(10);
    Future<Integer> submit = executorService.submit(new MyCallableTask());
    try {
        System.out.println(submit.get());
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (ExecutionException e) {
        e.printStackTrace();
    }
    executorService.shutdown();
}
class MyCallableTask implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        return new Random().nextInt();
    }
}


forma dos

public void test4() {
    FutureTask<String> task = new FutureTask<>(new Callable<String>() {
        @Override
        public String call() throws Exception {
            return "callable";
        }
    });

    new Thread(task).start();

    try {
        String s = task.get();
        System.out.println(s);
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (ExecutionException e) {
        e.printStackTrace();
    }
}


usar lambdas

new Thread(() -> System.out.println(Thread.currentThread().getName())).start();

Resumen
En esencia, solo hay una forma de implementar hilos en Java, que es crear hilos a través de new Thread() Llamar a Thread#start para iniciar un hilo eventualmente llamará al método Thread#run.


¿Por qué dices eso?
No hay nada que decir sobre los dos primeros, todos están llamando directamente al método Thread#start. La clave está en el método de llamada del tercer grupo de subprocesos:
cuando se llama al método java.util.concurrent.Executors#newFixedThreadPool, se crea una clase ThreadPoolExecutor

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

El constructor de la clase ThreadPoolExecutor utilizará el método Executors.defaultThreadFactory() para crear DefaultThreadFactory, que contiene un método newThread, que finalmente se llamará en el subproceso para completar la inicialización y la llamada del subproceso.

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;
}

Puede ver que va a un nuevo subproceso y, finalmente, se llamará al método Thread#start para iniciar el subproceso.


¿Por qué la ejecución de subprocesos de Java llama al método de inicio en lugar del método de ejecución?


El método de inicio es un método local, que corresponde al proceso de creación del subproceso de Java -> subproceso de JVM -> subproceso del sistema operativo; el método de ejecución es solo un método del objeto Subproceso, y ejecutarlo no provocará la creación del subproceso del sistema operativo.


2.2 Principio de implementación de subprocesos de Java


El siguiente es el proceso completo que un subproceso Java llama al método Thread#start desde el nivel del código Java hasta el nivel del sistema operativo:

 

Las corrutinas
, en inglés Coroutines, es un tipo de existencia basada en hilos, pero más ligera que los hilos. Las corrutinas no son administradas por el kernel del sistema operativo, sino que están completamente controladas por programas (es decir, en ejecución en modo usuario), tienen propiedades que son invisibles para el núcleo
La ventaja de esto es que el rendimiento ha mejorado mucho y no consumirá recursos como el cambio de subprocesos.



Las subrutinas o funciones se llaman jerárquicamente en todos los lenguajes, por ejemplo, A llama a B, B llama a C durante la ejecución, C regresa después de la ejecución, B regresa después de la ejecución y finalmente A completa la ejecución. La llamada de una corrutina es diferente a la de una subrutina. La corrutina es interrumpible dentro de la subrutina, y luego gira para ejecutar otras subrutinas, y luego regresa para continuar la ejecución en el momento apropiado.

Java

def A():
    print '1'
    print '2'
    print '3'
def B():
    print 'x'
    print 'y'
    print 'z'

 

Asumiendo que es ejecutado por una rutina, durante la ejecución de A, puede ser interrumpido en cualquier momento para ejecutar B, y B también puede ser interrumpido durante el proceso de ejecución y luego ejecutar A. El resultado puede ser: 1 2 xy 3 z.
La característica de la rutina es que es ejecutada por un hilo.
Ventajas de las corrutinas
        ● El cambio de subprocesos lo programa el sistema operativo, y las corrutinas las programan los propios usuarios, lo que reduce el cambio de contexto y mejora la eficiencia.
        ●El tamaño de pila predeterminado de un subproceso es 1M, mientras que la corrutina es más ligera, cerca de 1k. Entonces se pueden abrir más corrutinas en la misma memoria.
        ●No es necesario un mecanismo de bloqueo de subprocesos múltiples: debido a que solo hay un subproceso, no hay conflicto de escribir variables al mismo tiempo.En la rutina, los recursos compartidos se controlan sin bloqueo, y solo es necesario juzgar el estado, por lo que la eficiencia de ejecución es mucho mayor que la de subprocesos múltiples.
Las rutinas son adecuadas para escenarios que están bloqueados y requieren mucha concurrencia (network io). No apto para escenarios informáticos pesados.


Marco Coroutine en Java
kilim quasar


2.3 Mecanismo de programación de subprocesos de Java


La programación de subprocesos se refiere al proceso en el que el sistema asigna derechos de uso del procesador para subprocesos.Existen dos métodos principales de programación, a saber, la programación cooperativa de subprocesos y la programación preventiva de subprocesos.


programación cooperativa de subprocesos


El tiempo de ejecución del subproceso está determinado por el subproceso. Una vez que el subproceso termina de ejecutar su tarea, debe notificar activamente al sistema para cambiar a otro subproceso.
Beneficios:
        1 Implementación simple, el cambio de contexto es visible para los subprocesos
        2 Sin problemas de sincronización de subprocesos
Desventajas:
        1 El tiempo de ejecución es incontrolable. Si un subproceso se bloquea, la CPU puede permanecer bloqueada.
Programación preventiva de subprocesos
El sistema operativo asigna un segmento de tiempo de ejecución para cada subproceso, y el sistema operativo determina el cambio de subprocesos. Beneficios:
1. El tiempo de ejecución del subproceso es controlable y la CPU completa no se bloqueará porque un subproceso está bloqueado.


Encarnación de programación de subprocesos preventivos de subprocesos de Java


Se espera que el sistema pueda asignar más tiempo a algunos subprocesos y menos tiempo a algunos subprocesos, lo que se puede hacer configurando la prioridad del subproceso. El lenguaje Java tiene un total de 10 niveles de prioridad de subprocesos (Thread.MIN_PRIORITY a Thread.MAX_PRIORITY).Cuando dos subprocesos están listos al mismo tiempo, es más probable que el subproceso con mayor prioridad sea seleccionado y ejecutado por el sistema. Pero la prioridad no es muy confiable, porque los subprocesos de Java se implementan mediante el mapeo de subprocesos nativos del sistema, por lo que la programación de subprocesos depende en última instancia del sistema operativo.


2.4 Ciclo de vida del subproceso de Java


Hay seis estados de subprocesos en el lenguaje Java, a saber:
1NUEVO (estado de inicialización)
2EJECUTABLE (estado ejecutable + estado en ejecución)
3BLOCKED (estado bloqueado)
4WAITING (espera ilimitada)
5TIMED_WAITING (espera temporizada)
6TERMINATED (estado terminado)
en funcionamiento En el nivel del sistema, BLOCKED, WAITING y TIMED_WAITING en subprocesos de Java son un estado, es decir, el estado inactivo que mencionamos anteriormente. Es decir, mientras el subproceso de Java esté en uno de estos tres estados, el subproceso nunca tendrá derecho a usar la CPU.


yield no será como aparcar, esperar, unirse y otras operaciones, implica operaciones pesadas como el cambio de contexto, y puede volver a ejecutarse pronto.


Desde la perspectiva de JavaThread, JVM define algunos estados para objetos Java Thread (jvm.h)

*/


Desde la perspectiva de OSThread, JVM también define algunos estados de subprocesos para uso externo, como el estado de los subprocesos en la salida de información de la pila de subprocesos por jstack (osThread.hpp)


2.5 Métodos comunes de subprocesos

dormir

  • Llamar a la suspensión hará que el subproceso actual ingrese al estado TIMED_WAITING desde En ejecución, y no liberará el bloqueo del objeto
  • Otros subprocesos pueden usar el método de interrupción para interrumpir el subproceso inactivo. En este momento, el método de suspensión lanzará InterruptedException y borrará el indicador de interrupción.
  • Es posible que el hilo después del final de la suspensión no se ejecute inmediatamente
  • sleep es lo mismo que yield cuando el parámetro entrante es 0

producir

  • El rendimiento liberará los recursos de la CPU, permitirá que el subproceso actual entre en el estado Ejecutable desde En ejecución, permita que el subproceso con mayor prioridad (al menos la misma) obtenga la oportunidad de ejecución y no liberará el bloqueo del objeto;
  • Suponiendo que el proceso actual solo tiene el subproceso principal, después de llamar a yield, el subproceso principal continuará ejecutándose porque no hay ningún subproceso con una prioridad más alta que él;
  • La implementación específica depende del programador de tareas del sistema operativo

unirse

Después de esperar a que finalice el subproceso que llama al método de unión, el programa continúa ejecutándose. Generalmente se usa en escenarios donde el subproceso asíncrono termina de ejecutar el resultado antes de continuar ejecutándose.

public class ThreadJoinDemo {
    public static void main(String[] sure) throws InterruptedException {

        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("t begin");
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("t finished");
            }
        });
        long start = System.currentTimeMillis();
        t.start();
        //主线程等待线程t执行完成
        t.join();

        System.out.println("执行时间:" + (System.currentTimeMillis() - start));
        System.out.println("Main finished");
    }
}

detener

El método stop() ha sido abandonado por jdk, porque el método stop() es demasiado violento, terminando por la fuerza el subproceso ejecutado a medias.

Este método liberará el bloqueo del objeto, lo que hará que el subproceso se vea obligado a finalizar a la mitad de la ejecución, lo que puede generar inconsistencias en los datos.

public class ThreadStopDemo {
    private static Object lock = new Object();

    public static void main(String[] args) throws InterruptedException {

        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock) {
                    System.out.println(Thread.currentThread().getName() + "获取锁");
                    try {
                        Thread.sleep(60000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(Thread.currentThread().getName() + "执行完成");
            }
        });
        thread.start();
        Thread.sleep(2000);
        // 停止thread,并释放锁
        thread.stop();

        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "等待获取锁");
                synchronized (lock) {
                    System.out.println(Thread.currentThread().getName() + "获取锁");
                }
            }
        }).start();

    }
}

¿Cómo detener con gracia un hilo?

Java proporciona un mecanismo de interrupción para detener correctamente los hilos, consulte a continuación.

2.6 Mecanismo de interrupción de subprocesos de Java

Java no proporciona una forma directa y segura de detener un hilo, pero proporciona un mecanismo de interrupción. El mecanismo de interrupción es un mecanismo cooperativo, es decir, no se puede terminar otro subproceso directamente a través de la interrupción, sino que el subproceso interrumpido debe manejarlo por sí mismo. El hilo interrumpido tiene total autonomía, puede optar por detenerse inmediatamente, detenerse después de un período de tiempo o no detenerse en absoluto.

En pocas palabras, cuando el subproceso A se está ejecutando, si desea interrumpir el subproceso B, se establecerá un indicador para el subproceso B. En la implementación del código del subproceso B, puede haber un punto de control para el indicador. encontrado para indicar una interrupción, el subproceso B llama a su propia lógica de código para manejar las solicitudes de interrupción, como detenerse o ignorarlo.

uso de la API

  • interrupción (): establezca el bit de indicador de interrupción del hilo en verdadero sin detener el hilo
  • isInterrupted (): determine si el bit de indicador de interrupción del hilo actual es verdadero y no borrará el bit de indicador de interrupción
  • Thread.interrupted (): determine si el bit de indicador de interrupción del hilo actual es verdadero, borre el bit de indicador de interrupción y reinícielo a fasle

Cuando utilice el mecanismo de interrupción, debe prestar atención a si existe una situación en la que se borre el bit indicador de interrupción, es decir, distinga cuidadosamente las siguientes dos situaciones:

pruebaisInterrupted()

public class ThreadInterruptTest {
    static int i = 0;

    public static void main(String[] args) {
        System.out.println("begin");
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    i++;
                    System.out.println(i);
                    //Thread.interrupted()  清除中断标志位
                    //Thread.currentThread().isInterrupted() 不会清除中断标志位
                    if (Thread.currentThread().isInterrupted()) {
                        System.out.println("=========");
                    }
                    if (i == 10) {
                        break;
                    }

                }
            }
        });

        t1.start();
        //不会停止线程t1,只会设置一个中断标志位 flag=true
        t1.interrupt();
    }
}

pruebaThread.interrupted()

public class ThreadInterruptTest {
    static int i = 0;

    public static void main(String[] args) {
        System.out.println("begin");
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    i++;
                    System.out.println(i);
                    //Thread.interrupted()  清除中断标志位
                    //Thread.currentThread().isInterrupted() 不会清除中断标志位
                    if (Thread.interrupted()) {
                        System.out.println("=========");
                    }
                    if (i == 10) {
                        break;
                    }

                }
            }
        });

        t1.start();
        //不会停止线程t1,只会设置一个中断标志位 flag=true
        t1.interrupt();
    }
}

Use el mecanismo de interrupción para detener hilos con gracia

while (!Thread.currentThread().isInterrupted() && more work to do) {
    do more work
}

public class StopThread implements Runnable {

    @Override
    public void run() {
        int count = 0;
        while (!Thread.currentThread().isInterrupted() && count < 1000) {
            System.out.println("count = " + count++);
        }
        System.out.println("线程停止: stop thread");
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new StopThread());
        thread.start();
        Thread.sleep(5);
        thread.interrupt();
    }
}

¿Puedes sentirte interrumpido durante el sueño?

@Override
public void run() {
    int count = 0;
    while (!Thread.currentThread().isInterrupted() && count < 1000) {
        System.out.println("count = " + count++);

        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    System.out.println("线程停止: stop thread");
}

El subproceso en inactividad se interrumpe, el subproceso puede sentir la señal de interrupción y lanzará una excepción InterruptedException, borrará la señal de interrupción al mismo tiempo y establecerá el bit de indicador de interrupción en falso. Esto hará que la condición while Thread.currentThread().isInterrupted() sea falsa, y el programa se cerrará cuando no se cumpla la condición de recuento < 1000. Si no agrega manualmente la señal de interrupción en la captura y no realiza ningún procesamiento, la solicitud de interrupción se bloqueará, lo que puede provocar que el subproceso no se detenga correctamente.

try {
    Thread.sleep(1);
} catch (InterruptedException e) {
    e.printStackTrace();
    //重新设置线程中断状态为true
    Thread.currentThread().interrupt();
}

Resumir

el sueño se puede interrumpir y se lanza una excepción de interrupción: sueño interrumpido, borre el bit de indicador de interrupción

La espera se puede interrumpir lanzando una excepción de interrupción: InterruptedException, borrando el indicador de interrupción

2.7 Comunicación entre hilos de Java

volátil

Volatile tiene dos características principales, una es la visibilidad y la otra es el orden, que prohíbe el reordenamiento de instrucciones, y la visibilidad permite la comunicación entre subprocesos.

public class VolatileTest {
    private static volatile boolean flag = true;

    public static void main(String[] args) {

        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    if (flag) {
                        System.out.println("trun on");
                        flag = false;
                    }
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    if (!flag) {
                        System.out.println("trun off");
                        flag = true;
                    }
                }
            }
        }).start();
    }
}

Esperando mecanismo de activación/esperando mecanismo de notificación

El mecanismo de activación en espera se puede implementar en función de los métodos de espera y notificación. Llame al método de espera del objeto de bloqueo de subprocesos en un subproceso, y el subproceso ingresará a la cola de espera y esperará hasta que se despierte.

public class WaitDemo {
    private static Object lock = new Object();
    private static boolean flag = true;

    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock) {
                    while (flag) {
                        try {
                            System.out.println("wait start .......");
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }

                    System.out.println("wait end ....... ");
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                if (flag) {
                    synchronized (lock) {
                        if (flag) {
                            lock.notify();
                            System.out.println("notify .......");
                            flag = false;
                        }

                    }
                }
            }
        }).start();
    }
}

LockSupport es una herramienta utilizada en JDK para realizar el bloqueo y reactivación de hilos, tiene las siguientes características:

  • El subproceso llama a park para esperar el "permiso" y llama a unpark para proporcionar "permiso" al subproceso especificado.
  • Úselo para bloquear el hilo en cualquier ocasión, y puede especificar cualquier hilo para despertar, y no tiene que preocuparse por el orden de las operaciones de bloqueo y activación, pero debe prestar atención al efecto de despertar múltiples veces seguidas y despertarme una vez.
public class LockSupportTest {

    public static void main(String[] args) {
        Thread parkThread = new Thread(new ParkThread());
        parkThread.start();

        System.out.println("唤醒parkThread");
        LockSupport.unpark(parkThread);
    }

    static class ParkThread implements Runnable{

        @Override
        public void run() {
            System.out.println("ParkThread开始执行");
            LockSupport.park();
            System.out.println("ParkThread执行完成");
        }
    }
}

Flujos de entrada y salida de canalización

La diferencia entre el flujo de entrada/salida de canalización y el flujo de entrada/salida de archivo normal o el flujo de entrada/salida de red es que se utiliza principalmente para la transmisión de datos entre subprocesos, y el medio de transmisión es la memoria. El flujo de entrada/salida de canalización incluye principalmente las siguientes cuatro implementaciones específicas:

  • PipedOutputStream orientado a bytes
    , PipedInputStream
  • PipedReader orientado a caracteres
    , PipedWriter
public class Piped {
    public static void main(String[] args) throws Exception {
        PipedWriter out = new PipedWriter();
        PipedReader in = new PipedReader();
        // 将输出流和输入流进行连接,否则在使用时会抛出IOException
        out.connect(in);

        Thread printThread = new Thread(new Print(in), "PrintThread");

        printThread.start();
        int receive = 0;
        try {
            while ((receive = System.in.read()) != -1) {
                out.write(receive);
            }
        } finally {
            out.close();
        }
    }

    static class Print implements Runnable {
        private PipedReader in;

        public Print(PipedReader in) {
            this.in = in;
        }

        @Override
        public void run() {
            int receive = 0;
            try {
                while ((receive = in.read()) != -1) {
                    System.out.print((char) receive);
                }
            } catch (IOException ex) {
            }
        }
    }
}

Hilo#unirse()

Join puede entenderse como la fusión de subprocesos. Cuando un subproceso llama al método de unión de otro subproceso, el subproceso actual se bloquea y espera a que el subproceso llamado método de unión termine de ejecutarse antes de continuar. Por lo tanto, los beneficios de unir pueden garantizar el orden de ejecución de subprocesos, pero si El método de unión del subproceso de llamada ha perdido el significado de paralelismo. Aunque hay varios subprocesos, todavía son de naturaleza serial. La implementación de la unión final se basa en realidad en el mecanismo de notificación de espera.

Supongo que te gusta

Origin blog.csdn.net/peterjava123/article/details/130217747
Recomendado
Clasificación