0. Código de preguntas pionero
El siguiente código demuestra un contador donde se acumulan dos hilos I al mismo tiempo, cada uno de los cuales funciona 1000,000 veces. El resultado que esperamos es definitivamente i = 2000000. Sin embargo, después de ejecutarlo muchas veces, encontraremos que el valor de I siempre será inferior a 2000000. Esto se debe a que cuando dos hilos escriban I al mismo tiempo, el resultado de uno de los hilos sobrescribirá el otro.
Public Class Accountingsync implementos runnables {static int i = 0; public void aumento () {i ++; } @Override public void run () {for (int j = 0; j <1000000; j ++) {aumento (); }} public static void main (string [] args) lanza interruptedException {Accountingsync cuablesync = new AccountingSync (); Thread t1 = new Thread (AccountingSync); Thread t2 = new Thread (AccountingSync); t1.start (); t2.start (); t1.Join (); t2.join (); System.out.println (i); }} Para resolver fundamentalmente este problema, debemos asegurarnos de que múltiples hilos se sincronizen por completo al operar i. Es decir, cuando Thread A escribe i, el hilo B no solo no puede escribir, sino que tampoco puede leerlo.
1. El papel de las palabras clave sincronizadas
La función de la palabra clave sincronizada es realmente realizar la sincronización entre hilos. Su trabajo es bloquear el código sincronizado, para que solo un hilo pueda ingresar el bloque de sincronización a la vez, asegurando así la seguridad entre los hilos. Al igual que en el código anterior, la operación I ++ solo puede ser ejecutada por otro hilo al mismo tiempo.
2. Uso de palabras clave sincronizadas
Especifique el bloqueo del objeto: bloquee el objeto dado, ingrese el bloque de código de sincronización para obtener el bloqueo del objeto dado
Actuando directamente sobre el método de instancia: es equivalente a bloquear la instancia actual. Al ingresar el bloque de código sincrónico, debe obtener el bloqueo de la instancia actual (esto requiere que al crear subproceso, debe usar la misma instancia ejecutable)
Actuando directamente sobre métodos estáticos: es equivalente a bloquear la clase actual. Antes de ingresar el bloque de código sincrónico, debe obtener el bloqueo de la clase actual.
2.1 especifique el objeto para bloquear
El siguiente código se aplica sincronizado a un objeto dado. Aquí hay una nota de que el objeto dado debe ser estático, de lo contrario no compartiremos el objeto entre nosotros cada vez que volvamos a un hilo, por lo que el significado de bloqueo ya no existirá.
Public Class Accountingsync implementa Runnable {Object final de forma estática final = new Object (); static int i = 0; public void aumento () {i ++; } @Override public void run () {for (int j = 0; j <1000000; j ++) {sincronizado (objeto) {aumento (); }}} public static void main (string [] args) lanza interruptedException {hilo t1 = nuevo hilo (new cuentaySync ()); Hilo t2 = nuevo hilo (nuevo AccountingSync ()); t1.start (); t2.start (); t1.Join (); t2.join (); System.out.println (i); }} 2.2 actúa directamente en el método de instancia
La palabra clave sincronizada actúa sobre el método de instancia, es decir, antes de ingresar el método de aumento (), el hilo debe obtener el bloqueo de la instancia actual. Esto requiere que usemos la misma instancia de objeto ejecutable al crear la instancia de hilo. De lo contrario, los bloqueos del hilo no están en la misma instancia, por lo que no hay forma de hablar sobre el problema de bloqueo/sincronización.
Public Class Accountingsync implementos runnables {static int i = 0; public sincronizado void aumentando () {i ++; } @Override public void run () {for (int j = 0; j <1000000; j ++) {aumento (); }} public static void main (string [] args) lanza interruptedException {Accountingsync cuablesync = new AccountingSync (); Thread t1 = new Thread (AccountingSync); Thread t2 = new Thread (AccountingSync); t1.start (); t2.start (); t1.Join (); t2.join (); System.out.println (i); }} Preste atención a las primeras tres líneas del método principal para ilustrar el uso correcto de las palabras clave en el método de instancia.
2.3 actuando directamente sobre métodos estáticos
Para aplicar la palabra clave sincronizada al método estático, no es necesario usar los dos hilos para apuntar al mismo método ejecutable que en el ejemplo anterior. Debido a que el bloque de métodos debe solicitar el bloqueo de la clase actual, no la instancia actual, los subprocesos aún se pueden sincronizar correctamente.
Public Class Accountingsync implementos runnables {static int i = 0; public static sincronizado void aumentando () {i ++; } @Override public void run () {for (int j = 0; j <1000000; j ++) {aumento (); }} public static void main (string [] args) lanza interruptedException {hilo t1 = nuevo hilo (new cuentaySync ()); Hilo t2 = nuevo hilo (nuevo AccountingSync ()); t1.start (); t2.start (); t1.Join (); t2.join (); System.out.println (i); }}3. Bloqueo incorrecto
Del ejemplo anterior, sabemos que si necesitamos una aplicación de contador, para garantizar la corrección de los datos, naturalmente necesitaremos bloquear el contador, por lo que podemos escribir el siguiente código:
clase pública BadlockOnInteger implementa Runnable {Integer static i = 0; @Override public void run () {for (int j = 0; j <1000000; j ++) {sincronizado (i) {i ++; }}} public static void main (string [] args) lanza interruptedException {badlockonInTeger badlockonInTeger = new BadlockonInteger (); Hilo t1 = nuevo hilo (badlockonInteger); Hilo t2 = nuevo hilo (badlockonInteger); t1.start (); t2.start (); t1.Join (); t2.join (); System.out.println (i); }}Cuando ejecutemos el código anterior, encontraremos que la salida I es muy pequeña. Esto significa que el hilo no es seguro.
Para explicar este problema, debemos comenzar con entero: en Java, el entero es un objeto invariante. Al igual que la cadena, una vez que se crea un objeto, no se puede modificar. Si tiene un entero = 1, entonces siempre será 1. ¿Qué pasa si desea este objeto = 2? Solo puedes recrear un entero. Después de cada I ++, es equivalente a llamar al Método Valor de Integer. Echemos un vistazo al código fuente del método Value de Integer:
public static entero valor de (int i) {if (i> = integerCache.low && i <= integerCache.high) return integerCache.cache [i + (-intregerCache.low)]; devolver nuevo entero (i);} Integer.valueOf () es en realidad un método de fábrica, que tiende a devolver un nuevo objeto entero y copiar el valor a i;
Por lo tanto, sabemos la razón del problema. Dado que entre múltiples hilos, dado que i ++ viene después de I, señala un nuevo objeto, el hilo puede cargar diferentes instancias de objeto cada vez que se bloquea. La solución es muy simple. Puede resolverlo utilizando uno de los tres métodos de sincronización anteriores.