[Programación avanzada de Java] Multihilo

inserte la descripción de la imagen aquí

1. Conceptos básicos: programa, proceso, hilo

1.1 Procedimiento

  • Concepto: Es una colección de un conjunto de instrucciones escritas en un idioma determinado para completar una tarea específica. Es decir, una pieza de código estático.

1.2 Proceso

  • Concepto: un proceso de ejecución de un programa, o un programa que se está ejecutando.
  • Nota: Los procesos son la unidad de asignación de recursos y el sistema asignará diferentes áreas de memoria para cada proceso durante el tiempo de ejecución.

1.3, hilo

  • Concepto: un proceso se puede refinar aún más en un hilo, que es una ruta de ejecución dentro de un programa.
  • Descripción: El subproceso es la unidad de programación y ejecución. Cada subproceso tiene una pila de ejecución independiente y un contador de programa (pc), y la sobrecarga de la conmutación de subprocesos es pequeña.
  • Cada subproceso tiene su propio independiente: pila de máquina virtual, contador de programa
  • Múltiples subprocesos comparten estructuras en el mismo proceso: área de método, montón

1.4 Comprensión de la CPU de un solo núcleo y la CPU de múltiples núcleos

  • Una CPU de un solo núcleo es en realidad una especie de subprocesos múltiples falsos, ya que solo puede ejecutar la tarea de un subproceso en una unidad de tiempo. Pero debido a que la unidad de tiempo de la CPU es muy corta, no se puede sentir.
  • Si es multinúcleo, la eficiencia de subprocesos múltiples se puede utilizar mejor.
  • Una aplicación Java java.exe en realidad tiene al menos tres subprocesos: subproceso principal main(), subproceso de recolección de basura gc() y subproceso de manejo de excepciones. Por supuesto, si ocurre una excepción, afectará al hilo principal.

1.5 Paralelismo y concurrencia

  • Paralelismo: múltiples CPU ejecutan múltiples tareas al mismo tiempo.
  • Concurrencia: una CPU (usando intervalos de tiempo) ejecuta múltiples tareas al mismo tiempo.

1.6 Ventajas de usar multithreading

  • Antecedentes: tomando una sola CPU como ejemplo, usar solo un único subproceso para completar múltiples tareas una tras otra (llamar a múltiples métodos) es definitivamente más corto que usar múltiples subprocesos para completarlo.
  • Ventajas de los programas multiproceso:
    • 1. Mejorar la respuesta de la aplicación. Tiene más sentido para las interfaces gráficas y mejora la experiencia del usuario.
    • 2. Mejorar la utilización de la CPU del sistema informático
    • 3. Mejorar la estructura del programa. Divida un proceso largo y complejo en varios subprocesos y ejecútelo de forma independiente, lo cual es fácil de entender y modificar.

1.7 ¿Cuándo necesitas subprocesos múltiples?

  • Un programa necesita realizar dos o más tareas simultáneamente.
  • Cuando el programa necesita implementar algunas tareas que necesitan esperar, como la entrada del usuario, operaciones de lectura y escritura de archivos, operaciones de red, búsqueda, etc.
  • Cuando se requieren algunos programas ejecutándose en segundo plano.

2. Creación y uso de hilos

2.1 Una forma de crear subprocesos múltiples: heredar la clase Subproceso

  • 1. Cree una subclase que herede de la clase Thread
  • 2. Reescribe el run() de la clase Thread --> declara las operaciones realizadas por este hilo en run()
  • 3. Crear un objeto de una subclase de la clase Thread
  • 4. Llame a start() a través de este objeto, la función de start():
    • 1. Iniciar el hilo actual
    • 2. Llame al run() del hilo actual
//1、创建一个继承于Thread类的子类
class MyThread extends Thread {
    //2、重写Thread类的run()
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                System.out.println(i);
            }
        }
    }
}

public class ThreadTest {
    public static void main(String[] args) {
        //3、创建Thread类的子类的对象
        MyThread t1 = new MyThread();

        //4、通过此对象调用start()
        t1.start();

        // 或者通过创建Thread类的匿名子类的方式
        new Thread() {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    if (i % 2 == 0) {
                        System.out.println(Thread.currentThread().getName() + ":" + i);
                    }
                }
            }
        }.start();
    }
}
  • Nota 1: no podemos iniciar subprocesos llamando a run() directamente
  • Nota 2: reiniciar un hilo requiere volver a crear un objeto de hilo. No puede iniciar () un hilo que ya ha comenzado (), de lo contrario, se informará una IllegalThreadStateException

2.2 Métodos relacionados de la clase Thread

  • void start (): inicia el hilo actual y llama al run () del hilo actual
  • run (): generalmente es necesario anular este método en la clase Thread y declarar las operaciones que realizará el hilo creado en este método
  • String getName(): Obtiene el nombre del hilo actual
  • void setName(String name): Establece el nombre del hilo actual
  • Subproceso estático subproceso actual (): método estático, obtenga el subproceso que ejecuta el código actual
  • static void yield(): libera el derecho de ejecución de la CPU actual
  • join(): llama al join() del subproceso b en el subproceso a. En este momento, el subproceso a entra en el estado de bloqueo. El subproceso a no finaliza el estado de bloqueo hasta que el subproceso b se ejecuta por completo.
  • static void sleep (long millitime): Deje que el subproceso actual "duerma" durante los milisegundos de milisegundos especificados. Durante los milisegundos especificados, el subproceso actual está bloqueado.
  • detener(): Obsoleto. Cuando se ejecuta este método, el subproceso actual se termina a la fuerza.
  • boolean isAlive(): determina si el subproceso actual está vivo

2.3, programación de subprocesos

  • estrategia de programación
    • porción de tiempo
    • Preventivo: los subprocesos de alta prioridad se apropian de la CPU
  • Método de envío de Java
    • Los subprocesos de la misma prioridad forman una cola de primero en entrar, primero en salir, utilizando la estrategia de intervalo de tiempo
    • Para alta prioridad, use la estrategia preventiva de programación prioritaria

2.4, prioridad de subprocesos

  • Tarea prioritaria
    • PRIORIDAD_MAX:10
    • PRIORIDAD_MIN:1
    • NORM_PRIORITY:5 --> prioridad predeterminada
  • método involucrado
    • getPriority(): Obtener la prioridad del hilo
    • setPriority(int newPriority): establece la prioridad del hilo
  • Cuando se crea un subproceso, hereda la prioridad del subproceso principal
  • La baja prioridad es solo una baja probabilidad de ser programado, no necesariamente programado después de hilos de alta prioridad.

2.5 La segunda forma de crear subprocesos múltiples: implementar la interfaz Runnable

  • 1. Cree una clase que implemente la interfaz Runnable
  • 2. Implemente la clase para implementar el método abstracto en Runnable: run()
  • 3. Crear un objeto de la clase de implementación
  • 4. Pase este objeto como parámetro al constructor de la clase Thread para crear un objeto de la clase Thread
  • 5. Llame a start() a través del objeto de la clase Thread
//1、创建一个实现了Runnable接口的类
class MThread implements Runnable {
    //2、实现类去实现Runnable中的抽象方法:run()
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                System.out.println(i);
            }
        }
    }
}

public class ThreadTest1 {
    public static void main(String[] args) {
        //3、创建实现类的对象
        MThread mThread = new MThread();
        //4、将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
        Thread t1 = new Thread(mThread);
        //5、通过Thread类的对象调用start():启动线程,调用当前线程的run() --> 调用了Runnable类型的target的run()
        t1.start();

        //再启动一个线程
        Thread t2 = new Thread(mThread);
        t2.start();
    }
}

2.6 Compara las dos formas de crear hilos

  • En desarrollo: se prefiere la forma de implementar la interfaz Runnable
    • 1. El método de implementación no tiene limitación de herencia única de clases
    • 2. El método de implementación es más adecuado para manejar la situación en la que varios subprocesos tienen datos compartidos
  • 联系:clase pública Thread implementa Runnable
  • El mismo punto: ambos métodos deben reescribir run() y declarar la lógica que ejecutará el subproceso en run()

3. Ciclo de vida del hilo

graph LR
A(新建) --> |"调用start()"| B(就绪)
B --> |"获取CPU执行权"| C(运行)
C --> |"执行完run();调用线程的stop();出现Error/Exception且没有处理"| D(死亡)
C --> |"失去CPU执行权或yield()"| B
C --> |"sleep(long time);join();等待同步锁;wait();suspend()"| E(阻塞)
E --> |"sleep()时间到;join()结束;获取同步锁;notify()/notifyAll();resume()"| B

4. Sincronización de subprocesos

4.1 Método 1 de sincronización de subprocesos: sincronizar bloques de código

synchronized(同步监视器) {
    // 需要被同步的代码
}
  • 1. El código que opera los datos compartidos es el código que necesita ser sincronizado
  • 2. Datos compartidos: Variables que múltiples subprocesos operan juntos.
  • 3. Monitor de sincronización, comúnmente conocido como: bloqueo. Los objetos de cualquier clase pueden actuar como cerraduras.
    • Requisitos: varios subprocesos deben compartir el mismo bloqueo.
  • Complemento: en la forma de implementar la interfaz Runnable para crear múltiples subprocesos, podemos considerar usar esto como un monitor de sincronización
  • En la forma de heredar la clase Thread para crear subprocesos múltiples, use esto con cuidado como un monitor de sincronización y considere usar la clase actual como un monitor de sincronización.
public class Window1 implements Runnable{
    private int ticket = 100;
    Object obj = new Object();

    @Override
    public void run() {
        while (true) {
            //正确,只要保证多个线程使用同一个对象当做锁即可
            // synchronized (obj) {
            // 此时的this:唯一的Window1的对象
            synchronized (this) {
                if (ticket > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ":" + ticket);
                    ticket--;
                } else {
                    break;
                }
            }
        }
    }
}
public class Window2 extends Thread{
    private static int ticket = 100;
    private static Object obj = new Object();

    @Override
    public void run() {
        while (true) {
            //正确
            // synchronized (obj) {
            // 错误,因为通过继承Thread方式,需要通过不同的对象来创建线程
            // 此时的this代表着不同的对象 
            // synchronized (this) {
            // 正确,Class clazz = Window2.class,Window2.class只会加载一次
            synchronized (Window2.class) {
                if (ticket > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ":" + ticket);
                    ticket--;
                } else {
                    break;
                }
            }
        }
    }
}
  • Beneficios: El método de sincronización resuelve el problema de seguridad de subprocesos.
  • Limitaciones: cuando se opera con código síncrono, solo un subproceso puede participar, mientras que otros subprocesos esperan. Equivalente a un proceso de subproceso único, la eficiencia es baja.

4.2 Método de sincronización de subprocesos 2: método de sincronización

  • Los métodos síncronos aún involucran un monitor de sincronización, solo que no necesitan ser declarados explícitamente por nosotros.
  • Para los métodos de sincronización no estáticos, el monitor de sincronización es: este; para los métodos de sincronización estáticos, el monitor de sincronización es: la propia clase actual
public class Window3 implements Runnable{
    private int ticket = 100;

    @Override
    public void run() {
        while (true) {
            show();
        }
    }

    private synchronized void show () {//同步监视器:this
        if (ticket > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ":" + ticket);
            ticket--;
        }
    }
}
public class Window4 extends Thread {
    private static int ticket = 100;

    @Override
    public void run() {
        while (true) {
            show();
        }
    }

    private static synchronized void show () {//同步监视器:Window4.class
        if (ticket > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ":" + ticket);
            ticket--;
        }
    }
}

4.3 Patrón de diseño: patrón singleton

// 饿汉式
class Bank {
	// 1、私有化类的构造器
	private Bank() {}
	// 2、内部创建类的对象
	// 4、要求此对象也必须声明为静态的
	private static Bank instance = new Bank();
	// 3、提供公共的静态的方法,返回类的对象
	public static Bank getInstance() {
		return instance;
	}
}

// 懒汉式方式一:同步方法
class Order {
	// 1、私有化类的构造器
	private Order() {}
	// 2、声明当前类对象,没有初始化
	// 4、此对象也必须声明为static的
	private static Order instance = null;
	// 3、声明public、static的返回当前类对象的方法
	public static synchronized Order getInstance() {
		if (instance == null) {
			instance = new Order();
		}
		return instance;
	}
}

// 懒汉式方式二:同步代码块
class Order {
	// 1、私有化类的构造器
	private Order() {}
	// 2、声明当前类对象,没有初始化
	// 4、此对象也必须声明为static的
	private static Order instance = null;
	// 3、声明public、static的返回当前类对象的方法
	public static Order getInstance() {
    // 方式一:效率稍差
    // synchronized (Order.class) {
    //     if (instance == null) {
    // 	    instance = new Order();
    //     }
    //     return instance;
    // }
    // 方式二:效率更高
    if (instance == null) {
      synchronized (Order.class) {
        if (instance == null) {
            instance = new Order();
        }
      }
    }
		return instance;
	}
}

4.4, problema de interbloqueo de subprocesos

  • punto muerto
    • Los diferentes subprocesos ocupan los recursos de sincronización que necesita la otra parte y no se dan por vencidos. Todos están esperando que la otra parte renuncie a los recursos de sincronización que necesitan, lo que forma un punto muerto de subproceso.
    • Después de que se produce un interbloqueo, no habrá excepciones ni avisos, pero todos los subprocesos se bloquearán y no podrán continuar.
  • Solución
    • algoritmos y principios especializados
    • Minimizar la definición de recursos de sincronización
    • Trate de evitar la sincronización anidada

4,5, método de sincronización de subprocesos tres: bloqueo (bloqueo)

class Window implements Runnable {
    private int ticket = 100;
    // 1、实例化ReentrantLock
    private ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            try {
                // 2、调用锁定方法lock()
                lock.lock();

                if (ticket > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println(Thread.currentThread().getName() + ":" + ticket);
                    ticket--;
                } else {
                    break;
                }
            } finally {
                // 3、调用解锁方法:unlock()
                lock.unlock();
            }
        }
    }
}
  • Pregunta de la entrevista: ¿Similitudes y diferencias entre sincronizado y bloqueado?
    • Lo mismo: ambos pueden resolver el problema de seguridad del hilo.
    • Diferente: el mecanismo sincronizado libera automáticamente el monitor de sincronización después de ejecutar el código de sincronización correspondiente; Lock debe iniciar manualmente la sincronización (Lock()) y, al mismo tiempo, el final de la sincronización también debe implementarse manualmente (unlock( ))
  • Orden de preferencia:
    • Bloqueo --> Bloque de código de sincronización --> Método de sincronización

5. Hilo de comunicación

  • Tres métodos involucrados:

    • wait (): una vez que se ejecuta este método, el hilo actual entra en un estado bloqueado y libera el monitor de sincronización
    • notificar (): una vez que se ejecuta este método, se despertará un hilo que estaba esperando (). Si se esperan varios subprocesos, despierte el subproceso con mayor prioridad.
    • notificar a todos (): una vez que se ejecuta este método, todos los subprocesos esperados se despertarán
  • ilustrar:

    • esperar(), notificar(), notificarTodos() se deben usar tres métodos en bloques de código síncrono o métodos síncronos
    • Los llamadores de los métodos wait(), notificar() y notificarTodos() deben ser bloques de código sincrónicos o monitores sincrónicos en métodos sincrónicos; de lo contrario, se producirá una IllegalMonitorStateException.
    • Los tres métodos de esperar (), notificar () y notificar a todos () se definen en la clase java.lang.Object
  • Pregunta de la entrevista: ¿similitudes y diferencias entre dormir() y esperar()?

    • El mismo punto: una vez que se ejecuta el método, el hilo actual se puede bloquear
    • diferencia:
      • 1. Las posiciones de las declaraciones de los dos métodos son diferentes: sleep() se declara en la clase Thread y wait() se declara en la clase Object
      • 2. Los requisitos de llamada son diferentes: se puede llamar a sleep() en cualquier escenario requerido. wait() debe usarse en un bloque de código sincronizado
      • 3. Con respecto a si liberar el monitor de sincronización: si ambos métodos se usan en un bloque de código de sincronización o en un método de sincronización, sleep() no liberará el bloqueo y wait() lo liberará

6. JDK5.0 agrega método de creación de subprocesos

6.1 Nuevo método 1: implementar la interfaz Callable

  • Callable es más poderoso que usar Runnable
    • Comparado con el método run(), puede tener un valor de retorno
    • El método puede lanzar una excepción.
    • Compatibilidad con valores de retorno genéricos
    • Debe usar la clase FutureTask, como obtener el resultado devuelto
  • Interfaz futura
    • Puede cancelar los resultados de ejecución de tareas específicas Runnable y Callable, consultar si se completaron, obtener resultados, etc.
    • FutrueTask es la única clase de implementación de la interfaz Futrue
    • FutureTask también implementa las interfaces Runnable y Future. Puede ser ejecutado por un subproceso como Runnable, y también puede usarse como Future para obtener el valor de retorno de Callable
// 1、创建一个实现Callable的实现类
class NumThread implements Callable {
    // 2、实现call方法,将此线程需要执行的操作声明在call()中
    @Override
    public Object call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= 100; i++) {
            if (i % 2 == 0) {
                System.out.println(i);
                sum += i;
            }
        }
        return sum;
    }
}

public class ThreadNew {
    public static void main(String[] args) {
        // 3、创建Callable接口实现类的对象
        NumThread numThread = new NumThread();
        // 4、将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
        FutureTask futureTask = new FutureTask(numThread);
        // 5、将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
        new Thread(futureTask).start();

        try {
            // 6、获取Callable中call方法的返回值
            // get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值
            Object sum = futureTask.get();
            System.out.println("总和为:" + sum);
        } catch (ExecutionException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

6.2 Nuevo método 2: usar grupo de subprocesos

  • Antecedentes: los recursos que se crean y destruyen con frecuencia y que utilizan una gran cantidad de recursos, como subprocesos en situaciones simultáneas, tienen un gran impacto en el rendimiento.
  • Idea: Cree varios subprocesos por adelantado, colóquelos en el grupo de subprocesos, obténgalos directamente cuando los use y vuelva a colocarlos en el grupo después de usarlos. Puede evitar la creación y destrucción frecuentes y lograr la reutilización.
  • beneficio:
    • Capacidad de respuesta mejorada (tiempo reducido para crear nuevos hilos)
    • Reducir el consumo de recursos (reutilizar subprocesos en el grupo de subprocesos, no es necesario crear cada vez)
    • Fácil gestión de hilos
      • corePoolSize: el tamaño del grupo principal
      • MaximumPoolSize: número máximo de subprocesos
      • keepAliveTime: cuando el subproceso no tiene tareas, se terminará después de cuánto tiempo se mantendrá como máximo
  • ExecutorService: la interfaz del grupo de subprocesos reales. Subclase común ThreadPoolExecutor
    • ejecución nula (comando ejecutable): ejecutar tarea/comando, sin valor de retorno, generalmente utilizado para ejecutar ejecutable
    • Envío futuro (tarea invocable): ejecuta la tarea con un valor de retorno, generalmente utilizado para ejecutar invocable
    • void shutdown(): cerrar el grupo de conexiones
  • Ejecutores: clases de herramientas, clases de fábrica para grupos de subprocesos, que se utilizan para crear y devolver diferentes tipos de grupos de subprocesos.
    • Executors.newCachedThreadPool(): crea un grupo de subprocesos que puede crear nuevos subprocesos según sea necesario
    • Executors.newFixedThreadPool(n): crea un grupo de subprocesos con un número fijo de subprocesos reutilizables
    • Executors.newSingleThreadPool(): crea un grupo de subprocesos con un solo subproceso
    • Executors.newScheduledThreadPool(n): crea un grupo de subprocesos que se puede programar para ejecutar comandos después de un retraso determinado o periódicamente
public class ThreadPool {
    public static void main(String[] args) {
        // 1、提供指定线程数量的线程池
        ExecutorService service = Executors.newFixedThreadPool(10);
        // 2、执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
        service.execute(new NumberThread()); // 适用于实现Runnable接口的线程
        service.submit(new NumberThread1()); // 适用于实现Callable接口的线
        // 3、关闭连接池
        service.shutdown();
    }
}

7. Adicional: operaciones en cerraduras

7.1 La operación de desbloqueo

  • La ejecución del método de sincronización y el bloque de código de sincronización del subproceso actual finaliza
  • El subproceso actual encuentra interrupciones y retornos en el bloque de código de sincronización y el método de sincronización para terminar la ejecución del bloque de código y el método.
  • El subproceso actual tiene un error o una excepción no controlados en el bloque de código de sincronización o en el método de sincronización, lo que da como resultado un final anormal
  • El subproceso actual ejecuta el método wait() del objeto subproceso en el bloque de código de sincronización y el método de sincronización, el subproceso actual hace una pausa y libera el bloqueo

7.2 Operaciones que no abren cerraduras

  • Cuando un subproceso ejecuta un bloque de código de sincronización o un método de sincronización, el programa llama a los métodos Thread.sleep() y Thread.yield() para suspender la ejecución del subproceso actual.
  • Cuando un subproceso ejecuta un bloque de código de sincronización, otros subprocesos llaman al método suspend() del subproceso para suspender el subproceso, y el subproceso no liberará el bloqueo (monitor de sincronización)
    • Debe tratar de evitar el uso de suspender () y reanudar () para controlar el hilo

Dos, clases comunes de Java

Supongo que te gusta

Origin blog.csdn.net/qq_51808107/article/details/131436162
Recomendado
Clasificación