Mecanismo de seguridad, estado y sincronización de subprocesos

1. Seguridad del hilo

La seguridad de subprocesos significa que el resultado de cada ejecución de programa es el mismo que el resultado de una ejecución de un solo subproceso, y los valores de otras variables son los mismos que los esperados. Pero la situación peligrosa es que varios subprocesos pueden ejecutar un determinado recurso al mismo tiempo, lo que tiene como resultado consecuencias impredecibles.

Usamos un caso para demostrar el problema de seguridad de los hilos: los cines venden entradas y simulamos el proceso de venta de entradas en los cines. Hay 100 asientos en esta película (solo se pueden vender 100 boletos para esta película). Simulemos la taquilla de una sala de cine y veamos que varias ventanas venden entradas de cine al mismo tiempo (varias ventanas venden estas 100 entradas juntas). La ventana es necesaria y el objeto de hilo se utiliza para simular; la entrada es necesaria y la subclase de interfaz Runnable se utiliza para simular.

package com.itheima.demo06.ThreadSafe;
/*
    模拟卖票案例
    创建3个线程,同时开启,对共享的票进行出售
 */
public class Demo01Ticket {
    
    
    public static void main(String[] args) {
    
    
        //创建Runnable接口的实现类对象
        RunnableImpl run = new RunnableImpl();
        //创建Thread类对象,构造方法中传递Runnable接口的实现类对象
        Thread t0 = new Thread(run);
        Thread t1 = new Thread(run);
        Thread t2 = new Thread(run);
        //调用start方法开启多线程
        t0.start();
        t1.start();
        t2.start();
    }
}

package com.itheima.demo06.ThreadSafe;
/*
    实现卖票案例
 */
public class RunnableImpl implements Runnable{
    
    
    //定义一个多个线程共享的票源
    private  int ticket = 100;


    //设置线程任务:卖票
    @Override
    public void run() {
    
    
        //使用死循环,让卖票操作重复执行
        while(true){
    
    
            //先判断票是否存在
            if(ticket>0){
    
    
                //提高安全问题出现的概率,让程序睡眠
                try {
    
    
                    Thread.sleep(10);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }

                //票存在,卖票 ticket--
                System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
                ticket--;
            }
        }
    }
}

Inserte la descripción de la imagen aquí

Los problemas de seguridad de los subprocesos son causados ​​por variables globales y variables estáticas. Si solo hay operaciones de lectura en variables globales y variables estáticas en cada subproceso, pero no hay operaciones de escritura, en general, esta variable global es segura para subprocesos; si varios subprocesos realizan operaciones de escritura al mismo tiempo, generalmente se debe considerar la sincronización de subprocesos De lo contrario, la seguridad de los subprocesos puede verse afectada.

2. Sincronización de subprocesos

Cuando el subproceso de la ventana 1 ingresa a la operación, los subprocesos de la ventana 2 y la ventana 3 solo pueden esperar afuera, y la operación de la ventana 1 finaliza, y la ventana 1, la ventana 2 y la ventana 3 tienen la oportunidad de ingresar el código a ejecutar. Es decir, cuando un subproceso modifica un recurso compartido, otros subprocesos no pueden modificar el recurso. Una vez completada y sincronizada la modificación, puede tomar el recurso de la CPU y completar la operación correspondiente, lo que asegura la sincronización de datos y resuelve el problema. del fenómeno de la inseguridad del hilo.

Mecanismo de sincronizacion

  1. Sincronizar bloques de código.
  2. Método de sincronización.
  3. Mecanismo de bloqueo.

2.1 Bloque de código de sincronización

Bloque de código sincronizado: la palabra clave sincronizada se puede utilizar en un bloque del método, lo que significa que solo se puede acceder a los recursos de este bloque de forma exclusiva. formato:

sincronizado (bloqueo de sincronización) {código que debe sincronizarse}

Bloqueo de sincronización: el bloqueo de sincronización de un objeto es solo un concepto, que se puede imaginar como marcar un bloqueo en el objeto.

  1. El objeto de bloqueo puede ser de cualquier tipo.
  2. Varios objetos de hilo deben usar el mismo candado.
    Nota: En cualquier momento, como máximo un subproceso puede tener un bloqueo de sincronización, quien obtiene el bloqueo ingresa al bloque de código, y otros subprocesos solo pueden esperar afuera (BLOQUEADO).
    Utilice bloques de código síncronos para resolver el código:
package com.itheima.demo07.Synchronized;
/*
    卖票案例出现了线程安全问题
    卖出了不存在的票和重复的票

    解决线程安全问题的一种方案:使用同步代码块
    格式:
        synchronized(锁对象){
            可能会出现线程安全问题的代码(访问了共享数据的代码)
        }

    注意:
        1.通过代码块中的锁对象,可以使用任意的对象
        2.但是必须保证多个线程使用的锁对象是同一个
        3.锁对象作用:
            把同步代码块锁住,只让一个线程在同步代码块中执行
 */
public class RunnableImpl implements Runnable{
    
    
    //定义一个多个线程共享的票源
    private  int ticket = 100;

    //创建一个锁对象
    Object obj = new Object();

    //设置线程任务:卖票
    @Override
    public void run() {
    
    
        //使用死循环,让卖票操作重复执行
        while(true){
    
    
           //同步代码块
            synchronized (obj){
    
    
                //先判断票是否存在
                if(ticket>0){
    
    
                    //提高安全问题出现的概率,让程序睡眠
                    try {
    
    
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }

                    //票存在,卖票 ticket--
                    System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
                    ticket--;
                }
            }
        }
    }
}

2.2 Método de sincronización

Método de sincronización : el método modificado por sincronizado se denomina método de sincronización, que asegura que cuando el subproceso A ejecuta el método, otros subprocesos solo pueden esperar fuera del método.
formato:

método void sincronizado público () {código que puede causar problemas de seguridad de subprocesos}

¿Quién es el bloqueo de sincronización?
Para los métodos no estáticos, el bloqueo de sincronización es este.
Para el método estático, usamos el objeto de código byte (nombre de clase.clase) de la clase donde se encuentra el método actual.

package com.itheima.demo08.Synchronized;
/*
    卖票案例出现了线程安全问题
    卖出了不存在的票和重复的票

    解决线程安全问题的二种方案:使用同步方法
    使用步骤:
        1.把访问了共享数据的代码抽取出来,放到一个方法中
        2.在方法上添加synchronized修饰符

    格式:定义方法的格式
    修饰符 synchronized 返回值类型 方法名(参数列表){
        可能会出现线程安全问题的代码(访问了共享数据的代码)
    }
 */
public class RunnableImpl implements Runnable{
    
    
    //定义一个多个线程共享的票源
    private static int ticket = 100;


    //设置线程任务:卖票
    @Override
    public void run() {
    
    
        System.out.println("this:"+this);//this:com.itheima.demo08.Synchronized.RunnableImpl@58ceff1
        //使用死循环,让卖票操作重复执行
        while(true){
    
    
            payTicketStatic();
        }
    }

    /*
        静态的同步方法
        锁对象是谁?
        不能是this
        this是创建对象之后产生的,静态方法优先于对象
        静态方法的锁对象是本类的class属性-->class文件对象(反射)
     */
    public static /*synchronized*/ void payTicketStatic(){
    
    
        synchronized (RunnableImpl.class){
    
    
            //先判断票是否存在
            if(ticket>0){
    
    
                //提高安全问题出现的概率,让程序睡眠
                try {
    
    
                    Thread.sleep(10);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }

                //票存在,卖票 ticket--
                System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
                ticket--;
            }
        }

    }

    /*
        定义一个同步方法
        同步方法也会把方法内部的代码锁住
        只让一个线程执行
        同步方法的锁对象是谁?
        就是实现类对象 new RunnableImpl()
        也是就是this
     */
    public /*synchronized*/ void payTicket(){
    
    
        synchronized (this){
    
    
            //先判断票是否存在
            if(ticket>0){
    
    
                //提高安全问题出现的概率,让程序睡眠
                try {
    
    
                    Thread.sleep(10);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }

                //票存在,卖票 ticket--
                System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
                ticket--;
            }
        }

    }
}

2.3 Mecanismo de bloqueo

java.util.concurrent.locks.LockEl mecanismo proporciona una gama más amplia de operaciones de bloqueo que los bloques de código sincronizados y los métodos sincronizados.Los bloques de código sincronizados / métodos sincronizados tienen todas las funciones Lock, además de ser más potentes y más orientados a objetos.
Bloqueo de bloqueo también se denomina bloqueo síncrono Los métodos de bloqueo y liberación del bloqueo se cambian de la siguiente manera:
bloqueo de vacío público (): Agregar bloqueo sincrónico.
Desbloqueo de vacío público (): libera el bloqueo de sincronización.
Úselo de la siguiente manera

package com.itheima.demo09.Lock;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/*
    卖票案例出现了线程安全问题
    卖出了不存在的票和重复的票

    解决线程安全问题的三种方案:使用Lock锁
    java.util.concurrent.locks.Lock接口
    Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。
    Lock接口中的方法:
        void lock()获取锁。
        void unlock()  释放锁。
    java.util.concurrent.locks.ReentrantLock implements Lock接口


    使用步骤:
        1.在成员位置创建一个ReentrantLock对象
        2.在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁
        3.在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁
 */
public class RunnableImpl implements Runnable{
    
    
    //定义一个多个线程共享的票源
    private  int ticket = 100;

    //1.在成员位置创建一个ReentrantLock对象
    Lock l = new ReentrantLock();

    //设置线程任务:卖票
    @Override
    public void run() {
    
    
        //使用死循环,让卖票操作重复执行
        while(true){
    
    
            //2.在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁
            l.lock();

            //先判断票是否存在
            if(ticket>0){
    
    
                //提高安全问题出现的概率,让程序睡眠
                try {
    
    
                    Thread.sleep(10);
                    //票存在,卖票 ticket--
                    System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
                    ticket--;
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }finally {
    
    
                    //3.在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁
                    l.unlock();//无论程序是否异常,都会把锁释放
                }
            }
        }
    }

   
}

3. Estado del hilo

Inserte la descripción de la imagen aquí

3.1 Espera programada (espera programada)

public class MyThread extends Thread {
    
     
	public void run() {
    
     
		for (int i = 0; i < 100; i++) {
    
     
			if ((i) % 10 == 0) {
    
     
				System.out.println("‐‐‐‐‐‐‐" + i);
				}
			System.out.print(i); 
			try {
    
    Thread.sleep(1000); 
			System.out.print(" 线程睡眠1秒!\n"); 
			} catch (InterruptedException e) {
    
     e.printStackTrace(); } 
		} 
	}
	public static void main(String[] args) 
	{
    
     new MyThread().start(); } 
}

Se puede comprobar a través del caso que el uso del método del sueño sigue siendo muy sencillo. Necesitamos recordar los siguientes puntos:

  1. Una situación común para ingresar al estado TIMED_WAITING es llamar al método de suspensión, que también puede ser llamado por un subproceso separado y no necesariamente tiene que tener una relación cooperativa.
  2. Para permitir que otros subprocesos tengan la oportunidad de ejecutarse, puede poner la llamada de Thread.sleep () en el subproceso run (). Esto asegurará que el hilo duerma durante su ejecución.
  3. El sueño no tiene nada que ver con el bloqueo, el hilo se despierta automáticamente cuando expira y vuelve al estado Ejecutable.

Sugerencias: El tiempo especificado en sleep () es el tiempo más corto que el hilo no se ejecutará. Por lo tanto, el método sleep () no puede garantizar que el hilo comience a ejecutarse inmediatamente después de que expire el sueño.

3.2 BLOQUEADO (bloqueo bloqueado)

El estado Bloqueado se introduce en la API como: un subproceso que está bloqueando y esperando un bloqueo de monitor (objeto de bloqueo) se encuentra en este estado.
Por ejemplo, el subproceso A y el subproceso B usan el mismo bloqueo en su código. Si el subproceso A adquiere el bloqueo y el subproceso A entra en el estado Ejecutable, el subproceso B entra en el estado de bloqueo Bloqueado.

3.3 Esperando (espera infinita)

El estado de espera se introduce en la API como: un subproceso que está esperando indefinidamente a que otro subproceso realice una acción especial (de activación) se encuentra en este estado.

package com.itheima.demo10.WaitAndNotify;
/*
    等待唤醒案例:线程之间的通信
        创建一个顾客线程(消费者):告知老板要的包子的种类和数量,调用wait方法,放弃cpu的执行,进入到WAITING状态(无限等待)
        创建一个老板线程(生产者):花了5秒做包子,做好包子之后,调用notify方法,唤醒顾客吃包子

    注意:
        顾客和老板线程必须使用同步代码块包裹起来,保证等待和唤醒只能有一个在执行
        同步使用的锁对象必须保证唯一
        只有锁对象才能调用wait和notify方法

    Obejct类中的方法
    void wait()
          在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。
    void notify()
          唤醒在此对象监视器上等待的单个线程。
          会继续执行wait方法之后的代码
 */
public class Demo01WaitAndNotify {
    
    
    public static void main(String[] args) {
    
    
        //创建锁对象,保证唯一
        Object obj = new Object();
        // 创建一个顾客线程(消费者)
        new Thread(){
    
    
            @Override
            public void run() {
    
    
               //一直等着买包子
               while(true){
    
    
                   //保证等待和唤醒的线程只能有一个执行,需要使用同步技术
                   synchronized (obj){
    
    
                       System.out.println("告知老板要的包子的种类和数量");
                       //调用wait方法,放弃cpu的执行,进入到WAITING状态(无限等待)
                       try {
    
    
                           obj.wait();
                       } catch (InterruptedException e) {
    
    
                           e.printStackTrace();
                       }
                       //唤醒之后执行的代码
                       System.out.println("包子已经做好了,开吃!");
                       System.out.println("---------------------------------------");
                   }
               }
            }
        }.start();

        //创建一个老板线程(生产者)
        new Thread(){
    
    
            @Override
            public void run() {
    
    
                //一直做包子
                while (true){
    
    
                    //花了5秒做包子
                    try {
    
    
                        Thread.sleep(5000);//花5秒钟做包子
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }

                    //保证等待和唤醒的线程只能有一个执行,需要使用同步技术
                    synchronized (obj){
    
    
                        System.out.println("老板5秒钟之后做好包子,告知顾客,可以吃包子了");
                        //做好包子之后,调用notify方法,唤醒顾客吃包子
                        obj.notify();
                    }
                }
            }
        }.start();
    }
}

De hecho, el estado de espera no es una operación de un hilo. Refleja la comunicación entre varios hilos, lo que puede entenderse como una relación cooperativa entre varios hilos. Varios hilos lucharán por bloqueos y existe una relación de cooperación entre ellos. .

Cuando varios subprocesos cooperan, como los subprocesos A y B, si el subproceso A llama al método wait () en el estado Ejecutable, entonces el subproceso A entra en el estado Esperando (espera infinita) y pierde el bloqueo de sincronización. Si en este momento el hilo B adquiere el bloqueo de sincronización y llama al método notificar () en el estado de ejecución, entonces el hilo A que está esperando indefinidamente se despertará. La atención es despertar. Si se adquiere el objeto de bloqueo, el hilo A entrará en el estado Ejecutable después de despertarse, si el objeto de bloqueo no se adquiere, entrará en el estado Bloqueado.
Inserte la descripción de la imagen aquí

Supongo que te gusta

Origin blog.csdn.net/david2000999/article/details/113859621
Recomendado
Clasificación