Notas de estudio integral de Java Foundation Multithreading y JUC

Conociendo los subprocesos múltiples

¿Qué es subprocesamiento múltiple
Subproceso
Subproceso es la unidad más pequeña que el sistema operativo puede realizar la programación de operaciones.Los hilos están contenidos en los procesos., es la unidad operativa real en el proceso .

Comprensión simple: funciones que son independientes entre sí y pueden ejecutarse simultáneamente en el software de la aplicación

Entonces, ¿qué es un proceso?
Proceso
Un proceso es la entidad básica de ejecución de un programa, por ejemplo, si abre el administrador de tareas, puede ver que hay muchos procesos en él.

多线程作用:提高效率

Dos conceptos de multihilo

Concurrencia Concurrencia
: Al mismo tiempo, múltiples instrucciones se ejecutan alternativamente en una sola CPU

Paralelo
Paralelo: al mismo tiempo, varias instrucciones se ejecutan simultáneamente en varias CPU


Implementación de subprocesos múltiples

① Implementación heredando la clase Thread

La primera forma de comenzar con subprocesos múltiples:

1. Defina una clase usted mismo para heredar Thread
2.Vuelva a escribir el método de ejecución
3. Crea un objeto de la subclase y comienza el hilo.

	public class MyThread extends Thread{
    
    
		@Override
		public void run() {
    
    
		//书写线程要执行代码
			for (int i = e; i < 100; i++) {
    
    
				system.out.println(getName( ) + "Helloworld" );
			}
		}
	}

		public static void main( String[] args) {
    
    
			MyThread t1 = new MyThread();
			MyThread t2 = new MyThread() ;
			t1.setName("线程1");
			t2.setName("线程2");
			t1.start();
			t2.start();
		}


② Darse cuenta implementando la interfaz Runnable

La segunda forma de comenzar con subprocesos múltiples:
1. Defina una clase usted mismo para implementar la interfaz Runnable 2. Reescriba el método de ejecución dentro
3. Cree un objeto de su propia clase
4. Cree un objeto de la clase Subproceso e inicie el subproceso

    public static void main(String[] args) {
    
    
        //创建实现Runnable接口的类的对象
        RunnableInf run = new RunnableInf();
        //创建线程对象
        Thread t1 = new Thread(run);
        Thread t2 = new Thread(run);
        //给线程设置名字
        t1.setName("线程一");
        t2.setName("线程二");
        //开启线程
        t1.start();
        t2.start();

    }
public class RunnableInf implements Runnable{
    
    
    @Override
    public void run() {
    
    
        //书写线程执行代码
        for (int i = 0; i < 100; i++){
    
    
            System.out.println(Thread.currentThread().getName()+"hello world");
        }
    }
}


③ Realizar mediante el uso de la interfaz Callable y la interfaz Future

La tercera implementación de subprocesos múltiples:

Características: puede obtener ejecución de subprocesos múltiplesresultado

Los pasos son los siguientes
1. Cree una clase MyCallable para implementar la interfaz invocable
2. Llamada de reescritura (tiene un valor de retorno, que indica el resultado de la operación de subprocesos múltiples)
3. Cree un objeto de MyCallable (que represente las tareas que se realizarán mediante subprocesos múltiples)
4. Cree el objeto de FutureTask (función para administrar el resultado de la operación de subprocesos múltiples)
5.Cree un objeto de la clase Thread e inícielo (que representa un hilo)

    public static void main(String[] args) throws ExecutionException, InterruptedException {
    
    
        //创建Mycallable的对象(表示多线程要执行的任务)
        MyCallable mc = new MyCallable();
        //创建FutureTask的对象(作用管理多线程运行的结果)
        FutureTask<Integer> ft = new FutureTask<>(mc ) ;
        //创建线程的对象
        Thread t1 = new Thread(ft);
        //启动线程
        t1.start();

        //获取线程运行结果
        Integer result = ft.get();
        System.out.println(result);
    }
public class MyCallable implements Callable<Integer> {
    
    
    @Override
    public Integer call() throws Exception {
    
    
        //求100之间的和
        int sum = 0;
        for (int i = 0; i <= 100; i++ ){
    
    
            sum += i;
        }
        return sum;
    }
}


Comparación de tres implementaciones

inserte la descripción de la imagen aquí


métodos de miembros comunes

inserte la descripción de la imagen aquí

detalles del método

void setName(string name)
establece el nombre del subproceso (el constructor también puede establecer el nombre)
Detalles :
1. Si no establecemos un nombre para el subproceso, el subproceso también tiene un
formato de nombre predeterminado: Subproceso-X (X número de serie, a partir de 0)
2. Si queremos establecer el nombre del subproceso, podemos usar el método set para establecerlo, o podemos establecerlo mediante el método de construcción (recuerde heredar el método de construcción de la clase Thread , la tecla de método abreviado Alt+Ins)


Subproceso estático subproceso actual ()
Obtenga los
detalles del objeto del subproceso actual :
cuando se inicia la máquina virtual JVM, iniciará automáticamente varios subprocesos, uno de los cuales se denomina subproceso principal. Su
función es llamar al método principal y ejecutar el código dentro En el pasado, todo el código que escribíamos en realidad se ejecutaba en el hilo principal.


static void sleep (mucho tiempo)
deja que el subproceso duerma durante el tiempo especificado, la unidad es milisegundos
Detalles :
1. Qué subproceso ejecuta este método, luego qué subproceso permanecerá aquí durante el tiempo correspondiente
2. El parámetro del método: significa dormir El tiempo, en milisegundos, 1 segundo = 1000 milisegundos
3. Cuando se acabe el tiempo, el subproceso se activará automáticamente y continuará ejecutando otros códigos a continuación.


Dos formas de programación de subprocesos

  • La característica más importante de la programación preventiva es la aleatoriedad, segúnprioridadPara aprovechar los recursos de la CPU, cuanto mayor sea la prioridad, mayor será la probabilidad de preferencia
  • Funciones de programación no preventivas: una vez para ti, una vez para mí, regular
    inserte la descripción de la imagen aquí

La prioridad es de 0 a 10, y la prioridad predeterminada es 5, como
puede ver en el código fuente de Thread

inserte la descripción de la imagen aquí


final void setDaemon(boolean on)
se establece en los detalles del subproceso daemon:
cuando se ejecutan otros subprocesos que no son daemon, los subprocesos daemon terminarán uno tras otro, no inmediatamente

Fácil de entender: según el caso en la imagen a continuación,
cuando el hilo de la diosa termina, no hay necesidad de un neumático de repuesto

hilo de diosa
inserte la descripción de la imagen aquí
hilo de rueda de repuesto
inserte la descripción de la imagen aquí

en el método principal
inserte la descripción de la imagen aquí

El resultado final es que cuando el subproceso 1 se repite 10 veces, los subprocesos del daemon terminan uno tras otro (el ciclo no terminará ni se detendrá inmediatamente)


Escenarios de uso de subprocesos de daemon
como chat QQ, transferencia de archivos
inserte la descripción de la imagen aquí


Dejar que los hilos expliquen

inserte la descripción de la imagen aquí


Insertar hilo de demostración

    public static void main(String[] args) throws InterruptedException {
    
    
        //创建实现Runnable接口的类的对象
        RunnableInf run = new RunnableInf();
        //创建线程对象
        Thread t1 = new Thread(run);
        //Thread t2 = new Thread(run);
        //给线程设置名字
        t1.setName("线程一");    
        //开启线程
        t1.start();        
        //把t1线程插入到当前线程之前
        t1.join();  //这个代码运行在哪个线程上,就插入在哪个线程前

        for (int i = 0; i < 10; i++){
    
    
            System.out.println("main线程"+i);
        }
    }

ciclo de vida del hilo

inserte la descripción de la imagen aquí


Temas de seguridad de subprocesos

Dado que los subprocesos toman los recursos de la CPU de forma aleatoria , al realizar operaciones comerciales, varios subprocesos pueden ejecutar el mismo negocio en paralelo, lo que genera confusión o pérdida de datos; por ejemplo, en el caso clásico de comprar boletos, es posible que un subproceso ingrese y ejecute la compra. boletos Lógicamente, también entró otro hilo, resultando en la venta de dos boletos con el mismo número. El problema clásico es la sobreventa, se vende más de un boleto y hay boletos que no se venden.

La solución es, naturalmente, agregar un bloqueo al código comercial ejecutado para garantizar la atomicidad.


Método uno: bloques de código sincronizados

Código de bloqueo que manipula datos compartidos

inserte la descripción de la imagen aquí

特点1:锁默认打卉,有一个线程进去了,锁自动关闭
特点2:里面的代码全部执行完毕,线程出来,锁自动打开

El bloque de código síncrono se utiliza de la siguiente manera, el caso de vender 100 boletos

public class MyThread extends Thread{
    
    

    //表示这个类的所有对象共享ticket数据
    static int ticket = 0;

    //创建锁对象,一定要唯一
    static Object object = new Object();

    @Override
    public void run() {
    
    
        while (true){
    
    
            //同步代码块
            synchronized (object){
    
    
                if (ticket < 100){
    
    
                    ticket++;
                    System.out.println(getName()+"正在卖第" +ticket +"张票");
                }else {
    
    
                    break;
                }
            }
        }
    }
}

public class Treaddemo {
    
    
    public static void main(String[] args) {
    
    
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        t1.setName("窗口1");
        t2.setName("窗口2");

        t1.start();
        t2.start();
    }
}

Método 2: el método de sincronización
inserte la descripción de la imagen aquí
es agregar la palabra clave sincronizada al método

Característica 1: el método de sincronización es bloquear todo el código en el método

Característica 2: el objeto de bloqueo no se puede especificar por sí mismo
No estático: este
estático: el objeto de archivo de código de bytes de la clase actual


El caso de la venta de 100 entradas

public class MyRunnable implements Runnable{
    
    

    int ticket = 0;//由于MyRunnalbe方法只new了一个对象,可以不用把ticket设置为共享变量

    @Override
    public void run() {
    
    
        while (true){
    
    
            if (method()) break;
        }
    }

    private synchronized boolean method() {
    
    
        synchronized (MyRunnable.class){
    
    
            if ( ticket == 100 ){
    
    
                return true;
            }else {
    
    
                try {
    
    
                    Thread.sleep(100);
                } catch (InterruptedException e) {
    
    
                    throw new RuntimeException(e);
                }
                ticket++;
                System.out.println(Thread.currentThread().getName()+ "正在卖第"+ ticket +"张票");
            }
        }
        return false;
    }
}
public class Threaddemo {
    
    
    public static void main(String[] args) {
    
    
        MyRunnable mr = new MyRunnable();

        Thread t1 = new Thread(mr);
        Thread t2 = new Thread(mr);
        Thread t3 = new Thread(mr);

        t1.setName("窗口一");
        t2.setName("窗口二");
        t3.setName("窗口三");

        t1.start();
        t2.start();
        t3.start();
    }
}


Método tres: método síncrono

Para expresar más claramente cómo bloquear y liberar el bloqueo, JDK5 proporciona un nuevo objeto de bloqueo, Bloquear, que puede bloquearse y liberarse manualmente.

La implementación de Lock proporciona una gama más amplia de operaciones de bloqueo
que el uso de declaraciones y métodos sincronizados Lock proporciona métodos para adquirir y liberar bloqueos.

void lock():获得锁
void unlock():释放锁

Lock es una interfaz que no se puede instanciar directamente, y su clase de implementación ReentrantLock generalmente se usa para instanciar el método de construcción de ReentrantLock

ReentrantLock (): crea una instancia de ReentrantLock

Aún tomando el ejemplo de comprar 100 boletos, el código para usar el candado es el siguiente

public class LockThread extends Thread{
    
    
    static int ticket = 0;
    static Lock lock = new ReentrantLock();

    public LockThread() {
    
    
    }

    public LockThread(String name) {
    
    
        super(name);
    }

    @Override
    public void run() {
    
    
        while (true){
    
    
            //这次换用锁的方式
            lock.lock();//上锁
            try {
    
    
                if (ticket < 100){
    
    
                    Thread.sleep(100);
                    ticket++;
                    System.out.println(getName()+"正在卖第" +ticket +"张票");
                }else {
    
    
                    break;
                }
            } catch (InterruptedException e) {
    
    
                throw new RuntimeException(e);
            } finally {
    
    
                lock.unlock();
            }
        }
    }
}

public class MainThread {
    
    
    public static void main(String[] args) {
    
    
        LockThread t1 = new LockThread("窗口一");
        LockThread t2 = new LockThread("窗口二");
        LockThread t3 = new LockThread("窗口三");

        t1.start();
        t2.start();
        t3.start();
    }
}

punto muerto

En términos sencillos, los bloqueos están anidados. El subproceso 1 toma el bloqueo A. Cuando el subproceso 1 toma el bloqueo A, el subproceso 2 ingresa al bloqueo B, y la operación en medio del subproceso 1 necesita ingresar el bloqueo B nuevamente para ejecutar el negocio y finalizar; la operación intermedia del subproceso 2 necesita volver a ingresar el bloqueo A para ejecutar el negocio y finalizar. Eventualmente, ambos están esperando que el otro libere el bloqueo, lo que resulta en una situación de interbloqueo.


productor y consumidor

Esperando el mecanismo de activación
El mecanismo de espera de activación permitirá que dos subprocesos se ejecuten a la vez, el estándar una vez para usted y una vez para mí

inserte la descripción de la imagen aquí

Detalles: notificar () activa un hilo al azar, lo que no es fácil de controlar. Generalmente, se usa el método notificar a todos ()

El siguiente es un código de caso clásico de consumidor y productor, el consumidor es el que come, el productor es el chef, el control del medio es la mesa y el hilo de control se ejecuta

mesa de control central

public class Desk {
    
    
    /**
     * 控制消费者和生产者的执行
     */

    //是否有面条 0:没有面条  1:有面条
    public static int foodFlag = 0;

    //总个数,也就是消费者需要的总个数
    public static int count = 10;

    //锁对象
    public static Object lock = new Object();
}

comensales

public class Eater extends Thread{
    
    
    /**
     * 1.循环
     * 2.同步代码块
     * 3.判断共享数据是否到了末尾(到了末尾执行的逻辑)
     * 4.判断共享数据是否到了末尾(没到末尾执行的逻辑)
     */
    @Override
    public void run() {
    
    
        while (true){
    
    
            if (Desk.count == 0){
    
    
                break;
            }
            synchronized (Desk.lock){
    
    
                if (Desk.foodFlag == 0){
    
    //先看是否有产品,没有就等待
                    try {
    
    
                        Desk.lock.wait();//让当前线程跟锁绑定
                    } catch (InterruptedException e) {
    
    
                        throw new RuntimeException(e);
                    }
                }else {
    
    
                    Desk.count--;
                    System.out.println("消费者正在消费产品,还需要消费数量:"+ Desk.count);
                    //消费完唤醒生产者继续做
                    Desk.lock.notifyAll();
                    //修改桌子状态
                    Desk.foodFlag = 0;

                }
            }
        }
    }
}

cocinero

public class Cooker extends Thread{
    
    
    @Override
    public void run() {
    
    
        while (true){
    
    
            if (Desk.count == 0){
    
    
                break;
            }
            synchronized (Desk.lock){
    
    
                if (Desk.foodFlag == 1){
    
    
                    try {
    
    
                        Desk.lock.wait();
                    } catch (InterruptedException e) {
    
    
                        throw new RuntimeException(e);
                    }
                }else {
    
    
                    //修改桌上食物状态
                    Desk.foodFlag = 1;
                    System.out.println("生产者生产了面条");
                    //唤醒消费者
                    Desk.lock.notifyAll();
                }
            }
        }
    }
}

método principal

public class TestDemo {
    
    
    public static void main(String[] args) {
    
    
        Eater e = new Eater();
        Cooker c = new Cooker();

        e.setName("消费者");
        c.setName("生产者");

        e.start();
        c.start();
    }
}


punto importante:
Desk.lock.wait(); //Permite que el subproceso actual se vincule al bloqueo
Desk.Lock.notifyAll(); //Despierta todos los subprocesos vinculados a este bloqueo
La razón por la que se llama al objeto de bloqueo aquí es para evitar que el método de notificación a todos () active todos los subprocesos (incluidos los que están fuera del método, como los subprocesos del sistema, etc.), llame con un objeto, que puede indicar qué subproceso se activa
(aquí, llame con el objeto de bloqueo en la clase Escritorio, porque el objeto de bloqueo es único y solo es nuevo una vez en la clase Escritorio)


Grupo de subprocesos

inserte la descripción de la imagen aquí
Use el grupo de subprocesos como un contenedor para almacenar subprocesos
① Cree un grupo, que está vacío

②Al enviar una tarea, el grupo creará un nuevo objeto de hilo. Después de ejecutar la tarea, el hilo se devolverá al grupo. Cuando vuelva a enviar la tarea la próxima vez, no es necesario crear un nuevo hilo, y el existente El hilo se puede reutilizar directamente.

③Pero si no hay un subproceso inactivo en el grupo al enviar la tarea y no se puede crear un nuevo subproceso, la tarea se pondrá en cola

Ejecutores: la clase de herramienta de grupo de subprocesos devuelve diferentes tipos de objetos de grupo de subprocesos llamando a métodos.
inserte la descripción de la imagen aquí

inserte la descripción de la imagen aquí


grupo de subprocesos personalizado

El flujo de trabajo del grupo de subprocesos tendrá la cantidad de subprocesos principales, la cantidad de subprocesos temporales, la longitud de la cola y el tiempo de inactividad, lo que afectará la decisión.

Cuando el número de tareas supera el número de subprocesos principales, las tareas redundantes se pondrán en cola en la cola.
Cuando el número de tareas supera el número de subprocesos principales y la cola está llena, se utilizarán subprocesos temporales para procesar tareas.
Cuando el número de tareas supera el número de subprocesos principales, la cola máxima La suma del número y el número de subprocesos temporales Cuando se cargue por completo, las tareas redundantes se descartarán o procesarán de acuerdo con la política de rechazo del grupo de subprocesos

El tiempo de inactividad del subproceso temporal se destruirá cuando exceda el tiempo especificado por la inicialización del grupo de subprocesos,
y el subproceso principal se destruirá solo cuando se destruya el grupo de subprocesos.


La creación de un grupo de subprocesos encontrará que hay 7 parámetros

inserte la descripción de la imagen aquí

La estrategia de rechazo de la tarea final tiene la siguiente

inserte la descripción de la imagen aquí

Cree un código de grupo de subprocesos personalizado de la siguiente manera

inserte la descripción de la imagen aquí


Finalmente, gracias por leer, espero que este artículo pueda despejar sus dudas.

Supongo que te gusta

Origin blog.csdn.net/giveupgivedown/article/details/128965169
Recomendado
Clasificación