Entrevista - ¿Son seguros los beans en Spring thread?

A los entrevistadores a menudo les gusta preguntar si los beans en Spring son seguros para subprocesos o no. Esta pregunta se usa para examinar la comprensión del alcance de los beans en Spring. Comencemos con la conclusión de que los beans en Spring no son seguros para subprocesos .

Ya sea que los beans en el contenedor Spring sean seguros para subprocesos o no, el contenedor en sí no proporciona una política de seguridad para subprocesos para beans, por lo que se puede decir que los beans en el contenedor Spring en sí mismos no tienen las características de seguridad para subprocesos. , pero el alcance específico debe combinarse con el Bean a estudiar.

Alcance de Spring Bean

Hay cinco tipos de ámbitos de frijol en Spring:

  1. singleton: singleton, ámbito predeterminado.

  2. prototipo: Prototipo, cada vez que se crea un nuevo objeto.

  3. solicitud: solicitud, se crea un nuevo objeto para cada solicitud Http, adecuado para el entorno WebApplicationContext.

  4. sesión: sesión, la misma sesión comparte una instancia y diferentes sesiones usan diferentes instancias.

  5. sesión global: sesión global, todas las sesiones comparten una instancia.

El tema de la seguridad de subprocesos debe explicarse por separado de los singletons y los beans prototipo.

Bean prototipo: para los beans prototipo, cada vez que se crea un nuevo objeto, es decir, no se comparten beans entre subprocesos y, naturalmente, no habrá problemas de seguridad de subprocesos.

Bean Singleton: para un bean singleton, todos los subprocesos comparten un bean de instancia singleton, por lo que existe competencia por los recursos.

Si el bean singleton es un bean sin estado, es decir, las operaciones en el subproceso no realizarán operaciones que no sean consultas sobre los miembros del bean, entonces el bean singleton es seguro para subprocesos.

Por ejemplo, Controller, Service, Dao, etc. de Spring mvc, la mayoría de estos beans no tienen estado y solo se enfocan en el método en sí.

Spring singleton, ¿por qué el controlador, el servicio y el dao pueden garantizar la seguridad de subprocesos?

Los beans en Spring son de modo singleton por defecto, y el marco no encapsula beans para subprocesos múltiples. De hecho, la mayoría de las veces los beans no tienen estado (como Dao), por lo que, hasta cierto punto, los beans son realmente seguros.

Sin embargo, si el bean tiene estado, el desarrollador debe garantizar la seguridad del subproceso por sí mismo. La forma más fácil es cambiar el alcance del bean para   singleton cambiarlo  protopyte, de modo que cada vez que se solicite un bean, sea equivalente a new Bean() , lo que garantiza que la seguridad de Thread también.

Stateful significa que hay una función de almacenamiento de datos Stateless significa que no se guardan datos.

Las capas de controlador, servicio y dao en sí mismas no son seguras para subprocesos, pero si solo llama a los métodos internos y varios subprocesos llaman a un método de una instancia, las variables se copiarán en la memoria, que es la memoria de trabajo de su propio subproceso. y es seguro.

Si desea comprender el principio, puede consultar "Comprensión detallada de la máquina virtual JVM", sección 2.2.2:

La pila de la máquina virtual Java es privada para el subproceso y su ciclo de vida es el mismo que el del subproceso. La pila de la máquina virtual describe el modelo de memoria de la ejecución del método Java: cada método creará un marco de pila para almacenar la tabla de variables locales, la pila de operandos, el enlace dinámico, la salida del método y otra información cuando se ejecuta.

Sección 3.2.2 de "Programación Java concurrente en la práctica":

Una de las propiedades inherentes de las variables locales es que están encerradas dentro del hilo de ejecución. Están ubicados en la pila del subproceso en ejecución, al que no pueden acceder otros subprocesos.

Entonces, de hecho, cualquier singleton sin estado es seguro para subprocesos. La esencia de Spring es construir un sistema a través de una gran cantidad de dichos singletons y brindar servicios en forma de scripts de transacciones.

También puede echar un vistazo a este artículo para profundizar su comprensión: Problemas de seguridad de subprocesos con @Controller @Service de Spring, etc.

Primero pregunte si @Controller @Service es seguro para subprocesos.

R: No en la configuración predeterminada.

¿Por qué? Debido a que @Controller no se agrega con @Scope de forma predeterminada, sin @Scope es el valor predeterminado de singleton, singleton. Esto significa que el sistema solo inicializará el contenedor del controlador una vez, por lo que cada solicitud es para el mismo contenedor del controlador, que por supuesto no es seguro para subprocesos. Toma una castaña:

@RestController
public class TestController {

    private int var = 0;
    
    @GetMapping(value = "/test_var")
    public String test() {
        System.out.println("普通变量var:" + (++var));
        return "普通变量var:" + var ;
    }
}

Envía tres solicitudes en cartero, los resultados son los siguientes:

普通变量var:1
普通变量var:2
普通变量var:3

Significa que no es seguro para subprocesos. ¿Cómo hacerlo? Puede agregarle la anotación @Scope mencionada anteriormente, de la siguiente manera:

@RestController
@Scope(value = "prototype") // 加上@Scope注解,他有2个取值:单例-singleton 多实例-prototype
public class TestController {

    private int var = 0;

    @GetMapping(value = "/test_var")
    public String test() {
        System.out.println("普通变量var:" + (++var));
        return "普通变量var:" + var ;
    }
}

De esta manera, cada solicitud crea un contenedor de controlador independiente, por lo que cada solicitud es segura para subprocesos y los resultados de las tres solicitudes:

普通变量var:1
普通变量var:1
普通变量var:1

¿Los prototipos de instancias anotados con @Scope son necesariamente seguros para subprocesos?

@RestController
@Scope(value = "prototype") // 加上@Scope注解,他有2个取值:单例-singleton 多实例-prototype
public class TestController {
    private int var = 0;
    private static int staticVar = 0;

    @GetMapping(value = "/test_var")
    public String test() {
        System.out.println("普通变量var:" + (++var)+ "---静态变量staticVar:" + (++staticVar));
        return "普通变量var:" + var + "静态变量staticVar:" + staticVar;
    }
}

Mire los tres resultados de la solicitud:

普通变量var:1---静态变量staticVar:1
普通变量var:1---静态变量staticVar:2
普通变量var:1---静态变量staticVar:3

Aunque un controlador se crea por separado cada vez, no puede contener que la variable en sí sea estática. Por lo tanto, incluso agregar @Scopeanotaciones puede no garantizar el 100 % de seguridad de subprocesos del controlador. Entonces, si la seguridad de subprocesos depende de cómo definir las variables y la configuración del controlador. Así que hagamos un pequeño experimento, el código es el siguiente:

@RestController
@Scope(value = "singleton") // prototype singleton
public class TestController {

    private int var = 0; // 定义一个普通变量

    private static int staticVar = 0; // 定义一个静态变量

    @Value("${test-int}")
    private int testInt; // 从配置文件中读取变量

    ThreadLocal<Integer> tl = new ThreadLocal<>(); // 用ThreadLocal来封装变量

    @Autowired
    private User user; // 注入一个对象来封装变量

    @GetMapping(value = "/test_var")
    public String test() {
        tl.set(1);
        System.out.println("先取一下user对象中的值:"+user.getAge()+"===再取一下hashCode:"+user.hashCode());
        user.setAge(1);
        System.out.println("普通变量var:" + (++var) + "===静态变量staticVar:" + (++staticVar) + "===配置变量testInt:" + (++testInt)
                           + "===ThreadLocal变量tl:" + tl.get()+"===注入变量user:" + user.getAge());
        return "普通变量var:" + var + ",静态变量staticVar:" + staticVar + ",配置读取变量testInt:" + testInt + ",ThreadLocal变量tl:"
            + tl.get() + "注入变量user:" + user.getAge();
    }
}

Complemente el código fuera del controlador, que se define por sí mismo en la configuraciónBean:User

@Configuration
public class MyConfig {
    @Bean
    public User user(){
        return new User();
    }
}

Hay tantas formas de definir variables que se me ocurren temporalmente. Los resultados de las tres solicitudes http son los siguientes:

先取一下user对象中的值:0===再取一下hashCode:241165852
普通变量var:1===静态变量staticVar:1===配置变量testInt:1===ThreadLocal变量tl:1===注入变量user:1
先取一下user对象中的值:1===再取一下hashCode:241165852
普通变量var:2===静态变量staticVar:2===配置变量testInt:2===ThreadLocal变量tl:1===注入变量user:1
先取一下user对象中的值:1===再取一下hashCode:241165852
普通变量var:3===静态变量staticVar:3===配置变量testInt:3===ThreadLocal变量tl:1===注入变量user:1

Como puede ver, en el modo singleton Controller, solo las ThreadLocalvariables encapsuladas son seguras para subprocesos. ¿Por qué dijiste esto?

Podemos ver que solo el valor de la variable en los tres resultados de la solicitud ThreadLocales de 0+1=1 cada vez, y los demás se acumulan, y el objeto de usuario, el valor predeterminado es 0, cuando se obtiene el segundo valor Ya es 1 , y la clave hashCodees la misma, lo que indica que cada solicitud llama al mismo objeto de usuario.

Cambiemos las propiedades de la anotación @Scope en TestController a instancias múltiples: @Scope(value = "prototype")otras cosas no cambiaron, solicite nuevamente, el resultado es el siguiente:

先取一下user对象中的值:0===再取一下hashCode:853315860
普通变量var:1===静态变量staticVar:1===配置变量testInt:1===ThreadLocal变量tl:1===注入变量user:1
先取一下user对象中的值:1===再取一下hashCode:853315860
普通变量var:1===静态变量staticVar:2===配置变量testInt:1===ThreadLocal变量tl:1===注入变量user:1
先取一下user对象中的值:1===再取一下hashCode:853315860
普通变量var:1===静态变量staticVar:3===配置变量testInt:1===ThreadLocal变量tl:1===注入变量user:1

Al analizar este resultado, se encuentra que en el modo de múltiples instancias, las variables comunes, las variables de configuración y las variables ThreadLocal son todas seguras para subprocesos, mientras que las variables estáticas y las variables de usuario (vea que su código hash es el mismo) en el objeto no son subprocesos. seguro. .

Es decir, aunque TestController inicializa un objeto cada vez que se solicita, siempre hay solo una variable estática y solo hay un objeto de usuario inyectado. Por supuesto, solo hay una variable estática, entonces, ¿hay alguna manera de hacer que el objeto del usuario sea nuevo cada vez? por supuesto puede:

public class MyConfig {
    @Bean
    @Scope(value = "prototype")
    public User user(){
        return new User();
    }    
}

Simplemente agregue la misma anotación al bean inyectado en config @Scope(value = "prototype")y luego pídalo nuevamente:

先取一下user对象中的值:0===再取一下hashCode:1612967699
普通变量var:1===静态变量staticVar:1===配置变量testInt:1===ThreadLocal变量tl:1===注入变量user:1
先取一下user对象中的值:0===再取一下hashCode:985418837
普通变量var:1===静态变量staticVar:2===配置变量testInt:1===ThreadLocal变量tl:1===注入变量user:1
先取一下user对象中的值:0===再取一下hashCode:1958952789
普通变量var:1===静态变量staticVar:3===配置变量testInt:1===ThreadLocal变量tl:1===注入变量user:1

Se puede ver que el código hash del objeto de usuario de cada solicitud no es el mismo, y el valor de la variable en el usuario antes de cada asignación también es el valor predeterminado de 0.

Resumir

  1. En  @Controller/@Service un isocontainer, de forma predeterminada, el valor de alcance es singleton-singleton, que también es inseguro para subprocesos.

  2. Intente no @Controller/@Service definir variables estáticas en el contenedor, ya sea un singleton (singleton) o una instancia múltiple (prototipo), no es seguro para subprocesos.

  3. El objeto Bean inyectado de forma predeterminada scopetambién es inseguro para subprocesos cuando no está configurado.

  4. Si debe definir una variable, se ThreadLocalusa para encapsular, lo cual es seguro para subprocesos.

Supongo que te gusta

Origin blog.csdn.net/qq_34272760/article/details/121112217
Recomendado
Clasificación