1. ¿Cuándo ocurrirán los problemas de seguridad del hilo?
No habrá problemas de seguridad de subprocesos en un solo hilo, pero en la programación multiproceso, es posible acceder al mismo recurso al mismo tiempo. Este recurso puede ser varios tipos de recursos: una variable, un objeto, un archivo, una tabla de base de datos, etc. Cuando múltiples subprocesos accedan al mismo recurso al mismo tiempo, habrá un problema:
Dado que el proceso ejecutado por cada hilo es incontrolable, es probable que el resultado final sea contrario al deseo real o conducir directamente a errores del programa.
Demos un ejemplo simple:
Ahora hay dos hilos que leen datos de la red por separado y luego los insertan en una tabla de base de datos, lo que requiere que no se puedan insertar datos duplicados.
Entonces debe haber dos operaciones en el proceso de insertar datos:
1) Verifique si los datos existe en la base de datos;
2) Si existe, no se insertará; Si no existe, se insertará en la base de datos.
Si los dos hilos están representados por Thread-1 y Thread-2 respectivamente, y en algún momento, Thread-1 y Thread-2, ambos leen datos x, esto puede suceder:
Thread-1 verifica si los datos X existen en la base de datos, y Thread-2 también verifica si Data X existe en la base de datos.
Como resultado, el resultado de la verificación de los dos subprocesos es que los datos X no existen en la base de datos, por lo que ambos subprocesos insertan datos x en la tabla de la base de datos respectivamente.
Este es un problema de seguridad de hilo, es decir, cuando múltiples subprocesos acceden a un recurso al mismo tiempo, el resultado de ejecución del programa no es el resultado que desea ver.
Aquí, este recurso se llama: Recurso crítico (también conocido como recurso compartido).
Es decir, cuando múltiples hilos acceden a los recursos críticos (un objeto, atributos en un objeto, un archivo, una base de datos, etc.), pueden surgir problemas de seguridad de subprocesos.
Sin embargo, cuando múltiples subprocesos ejecutan un método, las variables locales dentro del método no son recursos críticos, porque el método se ejecuta en la pila, mientras que la pila Java es privada de subprocesos, por lo que no habrá problemas de seguridad de subprocesos.
2. ¿Cómo resolver problemas de seguridad de hilos?
Entonces, en general, ¿cómo resolver los problemas de seguridad de los hilos?
Básicamente, al resolver problemas de seguridad de los subprocesos, todos los modos de concurrencia adoptan la solución de "acceso serializado a recursos críticos", es decir, al mismo tiempo, solo un hilo puede acceder a recursos críticos, también conocido como acceso síncrono mutuamente excluyente.
En términos generales, se agrega un bloqueo antes del código que accede al recurso crítico. Después de acceder al recurso crítico, se libera el bloqueo y otros hilos continúan accediendo.
En Java, se proporcionan dos formas para implementar el acceso síncrono de Mutex: sincronizado y bloqueo.
Este artículo habla principalmente sobre el uso de sincronizado, y el uso de bloqueo se describe en la próxima publicación de blog.
Tres. Método de sincronización sincronizado o bloque de sincronización
Antes de comprender el uso de palabras clave sincronizadas, primero veamos un concepto: bloqueos mutex, como su nombre sugiere: bloqueos que pueden lograr el propósito del acceso mutex.
Para dar un ejemplo simple: si se agrega un recurso crítico a un mutex, cuando un hilo accede al recurso crítico, los otros hilos solo pueden esperar.
En Java, cada objeto tiene una marca de bloqueo (monitor), también conocida como monitor. Cuando múltiples hilos acceden a un objeto al mismo tiempo, el hilo solo puede acceder a él si adquiere el bloqueo del objeto.
En Java, la palabra clave sincronizada se puede usar para marcar un método o bloque de código. Cuando un hilo llama al método sincronizado del objeto o accede al bloque de código sincronizado, el hilo obtiene el bloqueo del objeto. Otros hilos no pueden acceder al método por el momento. Solo cuando se ejecute el método o se ejecute el bloque de código, el subproceso liberará el bloqueo del objeto, y otros subprocesos pueden ejecutar el método o el bloque de código.
Los siguientes son algunos ejemplos simples para ilustrar el uso de palabras clave sincronizadas:
1. Método sincronizado
En el siguiente código, dos subprocesos llaman al objeto InsertData para insertar datos:
Prueba de clase pública {public static void main (string [] args) {final insertData insertData = new InsertData (); new Thread () {public void run () {insertData.insert (thread.currentThread ()); }; }.comenzar(); new Thread () {public void run () {insertData.insert (thread.currentThread ()); }; }.comenzar(); }} clase InsertData {private ArrayList <Integer> ArrayList = new ArrayList <Integer> (); Public void insert (hilo de hilo) {for (int i = 0; i <5; i ++) {System.out.println (thread.getName ()+"insertar datos"+i); ArrayList.Add (i); }}} En este momento, el resultado de salida del programa es:
Esto muestra que dos hilos ejecutan el método de inserción al mismo tiempo.
Si la palabra clave sincronizada se agrega antes del método de inserción, el resultado de ejecución es:
class InsertData {private ArrayList <Integer> ArrayList = new ArrayList <Integer> (); Public sincronizado void insert (hilo de hilo) {for (int i = 0; i <5; i ++) {system.out.println (thread.getName ()+"insertar datos"+i); ArrayList.Add (i); }}}De la salida anterior, los datos insertados en Thread-1 solo se realizan después de que se ha insertado el subproceso-0. Esto muestra que Thread-0 y Thread-1 ejecutan métodos insertar secuencialmente.
Este es el método sincronizado.
Sin embargo, hay algunos puntos a tener en cuenta:
1) Cuando un hilo accede al método sincronizado de un objeto, otros subprocesos no pueden acceder a los otros métodos sincronizados del objeto. Esta razón es muy simple, porque un objeto solo tiene un bloqueo. Cuando un hilo adquiere el bloqueo del objeto, otros hilos no pueden obtener el bloqueo del objeto, por lo que no puede acceder a otros métodos sincronizados del objeto.
2) Cuando un hilo accede al método sincronizado de un objeto, otros subprocesos pueden acceder al método no sincronizado del objeto. La razón de esto es muy simple. Acceder a métodos no sincronizados no requiere el bloqueo del objeto. Si un método no se modifica con la palabra clave sincronizada, significa que no usará recursos críticos, entonces otros hilos pueden acceder a este método.
3) Si un subproceso A necesita para acceder al método sincronizado Fun1 de Object1 Object1, y otro subproceso B necesita acceder al método sincronizado Fun1 de Object2, incluso si Object1 y Object2 son del mismo tipo), no habrá un problema de seguridad de subprocesos, porque están accediendo a diferentes objetos, por lo que no hay problema de exclusión mutua.
2. Bloque de código sincronizado
Los bloques de código sincronizados son similares a la siguiente forma:
sincronizado (syncObject) {
}
Cuando este bloque de código se ejecuta en un subproceso, el hilo adquiere el bloqueo del Synobject de objeto, lo que hace que sea imposible que otros hilos accedan al bloque de código al mismo tiempo.
Synobject puede ser este, que representa el bloqueo que adquiere el objeto actual, o puede ser un atributo en la clase, que representa el bloqueo que adquiere el atributo.
Por ejemplo, el método de inserción anterior se puede cambiar a las siguientes dos formas:
class InsertData {private ArrayList <Integer> ArrayList = new ArrayList <Integer> (); Public void insert (hilo de hilo) {sincronizado (this) {for (int i = 0; i <100; i ++) {System.out.println (thread.getName ()+"insertar datos"+i); ArrayList.Add (i); }}}} clase InsertData {private ArrayList <Integer> ArrayList = new ArrayList <Integer> (); objeto privado objeto = nuevo objeto (); public void insert (hilo de hilo) {sincronizado (objeto) {for (int i = 0; i <100; i ++) {System.out.println (thread.getName ()+"insertar datos"+i); ArrayList.Add (i); }}}}Como se puede ver en lo anterior, los bloques de código sincronizados son mucho más flexibles de usar que los métodos sincronizados. Porque tal vez solo una parte del código en un método debe sincronizarse, si todo el método se sincroniza en este momento, afectará la eficiencia de ejecución del programa. Este problema se puede evitar utilizando bloques de código sincronizados. Los bloques de código sincronizados solo pueden sincronizar dónde se requiere sincronización.
Además, cada clase también tiene un bloqueo, que puede usarse para controlar el acceso concurrente a los miembros de datos estáticos.
Y si un hilo ejecuta el método sincronizado no estático de un objeto, y otro hilo necesita ejecutar el método sincronizado estático de la clase a la que pertenece el objeto, no habrá exclusión mutua en este momento, porque acceder al método sincronizado estático ocupa un bloqueo de clase, al tiempo que accede al método sincronizado no acuático que ocupa un bloqueo de objeto, por lo que no existe una exclusión mutua.
Comprenderá mirando el siguiente código:
Prueba de clase pública {public static void main (string [] args) {final insertData insertData = new InsertData (); new Thread () {@Override public void run () {insertData.insert (); } }.comenzar(); new Thread () {@Override public void run () {insertData.insert1 (); } }.comenzar(); }} clase insertData {public sincronizado void insert () {system.out.println ("ejecutar insert"); intente {Thread.sleep (5000); } catch (InterruptedException e) {E.PrintStackTrace (); } System.out.println ("ejecutar insert1"); } public sincronizado static void insert1 () {system.out.println ("ejecutar insert1"); System.out.println ("ejecutar insert1"); }} Resultados de ejecución;
El método de inserción se ejecuta en el primer hilo, que no hará que el segundo rosca bloquee el método Insert1.
Echemos un vistazo a lo que hace la palabra clave sincronizada. Descompilaremos su bytecodo. El bytecode descompilado del siguiente código es:
public class InsertData {objeto privado objeto = nuevo objeto (); Public void insert (hilo de hilo) {sincronizado (objeto) {}} public sincronizado void insert1 (hilo de hilo) {} public void insert2 (hilo de hilo) {}}Desde el código de bytEnted obtenido por descomposición, se puede ver que el bloque de código sincronizado en realidad tiene dos instrucciones: monitoreador y monitorexit. Cuando se ejecuta la instrucción del monitor, el recuento de bloqueo del objeto se incrementará en 1, mientras que cuando se ejecute la instrucción del monitorexit, el recuento de bloqueo del objeto se reducirá en 1. De hecho, esto es muy similar a la operación PV en el sistema operativo. La operación fotovoltaica en el sistema operativo se utiliza para controlar múltiples hilos para acceder a recursos críticos. Para el método sincronizado, el hilo en ejecución reconoce si la estructura Method_Info del método tiene la configuración del indicador SynChronized ACC_Nncronizado, y luego adquiere automáticamente el bloqueo del objeto, llama al método y finalmente libera el bloqueo. Si se produce una excepción, el hilo libera automáticamente el bloqueo.
Una cosa a tener en cuenta: para métodos sincronizados o bloques de código sincronizados, cuando ocurre una excepción, el JVM liberará automáticamente el bloqueo ocupado por el hilo actual, por lo que no habrá un punto muerto debido a la excepción.
3. Algunas otras cosas notables sobre sincronizado
1. La diferencia entre sincronizado y estático sincronizado
sincronizado bloquea la instancia actual de la clase para evitar que otros hilos accedan a todos los bloques sincronizados de la instancia de la clase al mismo tiempo. Tenga en cuenta que esta es la "instancia actual de la clase", y no existe tal restricción en dos casos diferentes de la clase. Luego, está sincronizado estático para controlar el acceso a todas las instancias de la clase. Sincronizado estático restringe los subprocesos para acceder a todas las instancias de la clase en JVM al mismo tiempo y acceder rápidamente al código correspondiente. De hecho, si hay sincronizado en un método o un bloque de código en una clase, luego, después de generar una instancia de esta clase, la clase tendrá un monitoreo rápido, y el acceso concurrente a la instancia modificada sincronizada está protegida rápidamente. Sincronizado estático es el público de la monitorización, que es la diferencia entre los dos. Es decir, sincronizado es equivalente a esto. Syncronized, y el sincronizado estático es equivalente a algo.
Un autor japonés, el "Patrón de diseño multithreadado de Java Múltiple" de Jie Chenghao tiene una columna como esta:
Clase pulbic algo () {public sincronizado Void issynca () {} sincronizado Void issyncb () {} public static sincronizado void csynca () {} public sincronizado sincronizado void csyncb () {}} Luego, si se agregan dos instancias A y B de algo de clase, ¿por qué se puede acceder simultáneamente al siguiente grupo de métodos simultáneamente? axissynca () y x.issyncb ()
bxissynca () y y.issynca ()
cxcsynca () y y.csyncb ()
dxissynca () y algo.csynca ()
Aquí, está claro que se puede juzgar:
A, ambos acceso al dominio sincronizado de la misma instancia, por lo que no se puede acceder al mismo tiempo. B es para diferentes instancias, por lo que se puede acceder al mismo tiempo. Debido a que está sincronizado estático, todavía se restringirán diferentes instancias, lo cual es equivalente a algo.issynca () y algo.issyncb (), por lo que no se puede acceder al mismo tiempo.
Entonces, ¿qué pasa con D?, Se puede acceder a la respuesta en el libro simultáneamente. La razón de la respuesta es que Synchronzied es que el método de instancia y el método de clase sincronizado son diferentes del bloqueo.
El análisis personal significa que los sincronizados sincronizados y estáticos son equivalentes a dos pandillas, cada una de las cuales tiene su propio control, y no hay restricción entre sí y se puede acceder al mismo tiempo. No está claro cómo se implementa sincronzied en el diseño interno de Java.
Conclusión: A: La estática sincronizada es el alcance de una determinada clase. Csync static {} sincronizado evita que múltiples hilos accedan al método estático sincronizado en esta clase al mismo tiempo. Funciona en todas las instancias de objetos de una clase.
B: Sincronizado es el alcance de una instancia. Sincronized isSync () {} evita que múltiples subprocesos accedan al método sincronizado en esta instancia al mismo tiempo.
2. La diferencia entre el método sincronizado y el código sincronizado rápido
No hay diferencia entre los métodos sincronizados () {} y sincronizado (this) {}, pero los métodos sincronizados () {} son convenientes para la comprensión de lectura, mientras que sincronizado (esto) {} puede controlar con mayor precisión las áreas de acceso de conflicto y, a veces, y a veces funciona de manera más eficiente.
3. No se puede heredar la palabra clave sincronizada