1. Uso básico de sincronizado
Sincronizado es el método más utilizado para resolver problemas de concurrencia en Java y el método más fácil. Sincronized tiene tres funciones principales: (1) Asegúrese de que el código de sincronización de acceso de hilo de hilo (2) asegure que la modificación de las variables compartidas pueda ser oportuna (3) resolver efectivamente el problema de reordenamiento. Sincronizado tiene tres usos de sincronizado:
(1) Método ordinario de modificación
(2) Modificar métodos estáticos
(3) Modificar el bloque de código
A continuación, usaré algunos programas de ejemplo para ilustrar estos tres métodos de uso (en aras de la comparación, excepto por los diferentes métodos de uso de sincronizado, los otros tres códigos son básicamente consistentes).
1. Sin sincronización:
Fragmento de código 1:
paquete com.paddx.test.concurrent; public class SynchronizedTest {public void Method1 () {System.out.println ("Método 1 Inicio"); intente {System.out.println ("Método 1 ejecutar"); Thread.sleep (3000); } catch (InterruptedException e) {E.PrintStackTrace (); } System.out.println ("Método 1 final"); } public void Method2 () {System.out.println ("Método 2 Inicio"); intente {System.out.println ("Método 2 ejecutar"); Thread.sleep (1000); } catch (InterruptedException e) {E.PrintStackTrace (); } System.out.println ("Método 2 End"); } public static void main (string [] args) {final synchronizedTest test = new SynChronizedTest (); new Thread (new Runnable () {@Override public void run () {test.method1 ();}}). start (); new Thread (new Runnable () {@Override public void run () {test.method2 ();}}). start (); }}El resultado de la ejecución es el siguiente: Thread 1 y Thread 2 Ingrese el estado de ejecución al mismo tiempo. El hilo 2 se ejecuta más rápido que el hilo 1, por lo que el hilo 2 se ejecuta primero. En este proceso, el hilo 1 y el hilo 2 se ejecutan al mismo tiempo.
Método 1 Inicio
Método 1 Ejecutar
Método 2 Inicio
Método 2 Ejecutar
Método 2 End
Método 1 End
2. Sincronice los métodos comunes:
Fragmento de código dos:
paquete com.paddx.test.concurrent; public class SynchronizedTest {public sincronizado Void Method1 () {System.out.println ("Método 1 Inicio"); intente {System.out.println ("Método 1 ejecutar"); Thread.sleep (3000); } catch (InterruptedException e) {E.PrintStackTrace (); } System.out.println ("Método 1 final"); } public sincronizado Void Method2 () {System.out.println ("Método 2 Inicio"); intente {System.out.println ("Método 2 ejecutar"); Thread.sleep (1000); } catch (InterruptedException e) {E.PrintStackTrace (); } System.out.println ("Método 2 End"); } public static void main (string [] args) {final synchronizedTest test = new SynChronizedTest (); new Thread (new Runnable () {@Override public void run () {test.method1 ();}}). start (); new Thread (new Runnable () {@Override public void run () {test.method2 ();}}). start (); }}El resultado de la ejecución es el siguiente. Después de compararlo con el segmento de código, se puede ver claramente que el subproceso 2 debe esperar a que la ejecución del método1 del hilo 1 se complete antes de comenzar a ejecutar el método Method2.
Método 1 Inicio
Método 1 Ejecutar
Método 1 End
Método 2 Inicio
Método 2 Ejecutar
Método 2 End
3. Sincronización del método estático (clase)
Fragmento de código tres:
paquete com.paddx.test.concurrent; clase pública SynchronizedTest {public static static sincronizado void método1 () {system.out.println ("método 1 inicio"); intente {System.out.println ("Método 1 ejecutar"); Thread.sleep (3000); } catch (InterruptedException e) {E.PrintStackTrace (); } System.out.println ("Método 1 final"); } public static static sincronized void Method2 () {System.out.println ("Método 2 Inicio"); intente {System.out.println ("Método 2 ejecutar"); Thread.sleep (1000); } catch (InterruptedException e) {E.PrintStackTrace (); } System.out.println ("Método 2 End"); } public static void main (string [] args) {final synchronizedTest test = new SynChronizedTest (); SynchronizedTest final test2 = new SynChronizedTest (); new Thread (new Runnable () {@Override public void run () {test.method1 ();}}). start (); new Thread (new Runnable () {@Override public void run () {test2.method2 ();}}). start (); }}El resultado de la ejecución es el siguiente. La sincronización de los métodos estáticos es esencialmente una sincronización de clases (los métodos estáticos son esencialmente métodos de clase, no métodos en los objetos). Por lo tanto, incluso si Test y Test2 pertenecen a diferentes objetos, ambos pertenecen a instancias de la clase SynchronizedTest, por lo que Method1 y Method2 solo pueden ejecutarse secuencialmente y no pueden ejecutarse simultáneamente.
Método 1 Inicio
Método 1 Ejecutar
Método 1 End
Método 2 Inicio
Método 2 Ejecutar
Método 2 End
4. Sincronización de bloque de código
Fragmento de código cuatro:
paquete com.paddx.test.concurrent; public class SynchronizedTest {public void Method1 () {System.out.println ("Método 1 Inicio"); intente {sincronizado (this) {System.out.println ("Método 1 ejecutar"); Thread.sleep (3000); }} catch (InterruptedException e) {E.PrintStackTrace (); } System.out.println ("Método 1 final"); } public void Method2 () {System.out.println ("Método 2 Inicio"); intente {sincronizado (this) {System.out.println ("Método 2 ejecutar"); Thread.sleep (1000); }} catch (InterruptedException e) {E.PrintStackTrace (); } System.out.println ("Método 2 End"); } public static void main (string [] args) {final synchronizedTest test = new SynChronizedTest (); new Thread (new Runnable () {@Override public void run () {test.method1 ();}}). start (); new Thread (new Runnable () {@Override public void run () {test.method2 ();}}). start (); }}El resultado de la ejecución es el siguiente. Aunque tanto el subproceso 1 como el subproceso 2 ingresan el método correspondiente y la ejecución de inicio, Thread 2 debe esperar a que la ejecución del bloque de sincronización en el subproceso 1 se complete antes de ingresar el bloque de sincronización.
Método 1 Inicio
Método 1 Ejecutar
Método 2 Inicio
Método 1 End
Método 2 Ejecutar
Método 2 End
2. Principio sincronizado
Si aún tiene alguna pregunta sobre los resultados de ejecución anteriores, no se preocupe. Primero comprendamos el principio de sincronizado y luego vemos hacia atrás en las preguntas anteriores para ver de un vistazo. Primero veamos cómo el código sincronizado sincroniza los bloques de código descomponiendo el siguiente código:
paquete com.paddx.test.concurrent; public class SynchronizedDemo {public void Method () {SynChronized (this) {System.out.println ("Método 1 Inicio"); }}}Resultado de descompilación:
Con respecto al papel de estas dos instrucciones, nos referimos directamente a la descripción en la especificación JVM:
monitor:
Cada objeto está asociado con un monitor. Un monitor está bloqueado si y solo si tiene un propietario. El hilo que ejecuta el monitor intenta obtener la propiedad del monitor asociado con Objectref, de la siguiente manera: • Si el recuento de entrada del monitor asociado con Objectref es cero, el hilo ingresa al monitor y establece su recuento de entrada en uno. El hilo es entonces el propietario del monitor. • Si el hilo ya propusta el monitor asociado con Objectrof, vuelve a entrar en el monitor, aumentando su recuento de entrada. • Si otro hilo ya posee el monitor asociado con Objectrof, el hilo bloquea hasta que el recuento de entrada del monitor es cero y luego intenta volver a obtener la propiedad.
El significado general de este pasaje es:
Cada objeto tiene un bloqueo de monitor (monitor). Cuando el monitor esté ocupado, se bloqueará. Cuando el hilo ejecuta la instrucción del monitor, intenta obtener la propiedad del monitor. El proceso es el siguiente:
1. Si el número de entrada del monitor es 0, el hilo ingresa al monitor y luego establece el número de entrada en 1, el hilo es el propietario del monitor.
2. Si el hilo ya posee el monitor y solo se vuelve a entrar, el número de entrada al monitor se agrega a 1.
3. Si otros hilos han ocupado el monitor, el hilo ingresa a un estado de bloqueo hasta que el número de entrada del monitor sea 0, y luego intente obtener la propiedad del monitor nuevamente.
Monitorexit:
El hilo que ejecuta el monitorexit debe ser el propietario del monitor asociado con la instancia a la que se hace referencia por Objectref. El hilo disminuye el recuento de entrada del monitor asociado con Objectref. Si como resultado, el valor del recuento de entrada es cero, el hilo sale del monitor y ya no es su propietario. Otros hilos que están bloqueando para ingresar al monitor pueden intentar hacerlo.
El significado general de este pasaje es:
El monitorexit que ejecuta el hilo debe ser el propietario del monitor correspondiente a Objectrof.
Cuando se ejecuta la instrucción, el número de la entrada del monitor se reduce en 1. Si el número de la entrada del monitor es 0 después de la disminución en 1, el subproceso sale del monitor y ya no es el propietario de este monitor. Otros hilos bloqueados por este monitor pueden intentar obtener la propiedad de este monitor.
A través de estos dos párrafos de descripción, deberíamos poder ver claramente el principio de implementación de sincronizado. La capa semántica subyacente de sincronizado se completa a través de un objeto de monitor. De hecho, Wait/Notify y otros métodos también se basan en objetos de monitor. Es por eso que solo los métodos como Wait/Notify pueden llamarse en bloques o métodos sincronizados, de lo contrario, se lanzará una excepción de java.lang.illegalmonitorStateException.
Veamos los resultados de descompilación del método de sincronización:
Código fuente:
paquete com.paddx.test.concurrent; public class SynchronizedMethod {public sincronizado Void Method () {System.out.println ("¡Hola mundo!"); }}Resultado de descompilación:
A juzgar por los resultados de la descompilación, la sincronización del método no se completa a través del monitor de instrucciones y el monitorexit (en teoría, también se puede implementar a través de estas dos instrucciones). Sin embargo, en comparación con los métodos ordinarios, el identificador ACC_Syncronizado se agrega a su grupo constante. JVM implementa la sincronización de métodos basados en este identificador: cuando se llama al método, la instrucción de llamadas verificará si se establece el indicador de acceso SynChronizado del método. Si se establece, el hilo de ejecución primero obtendrá el monitor y luego ejecutará el cuerpo del método después de que el método se ejecute con éxito. Después de ejecutar el método, se lanzará el monitor. Durante la ejecución del método, ningún otro hilo puede obtener el mismo objeto de monitor. De hecho, no hay diferencia en la esencia, pero la sincronización del método es una forma implícita de lograrlo sin la necesidad de hacerse a través de bytecode.
3. Explicación de los resultados de la operación
Con una comprensión del principio de sincronizado, puede resolverlo fácilmente mirando el programa anterior.
1. Código Segmento 2 Resultados:
Aunque Method1 y Method2 son métodos diferentes, ambos métodos se sincronizan y se llaman a través del mismo objeto. Por lo tanto, antes de llamar, debe competir por el bloqueo (monitor) en el mismo objeto, por lo que solo puede obtener los bloqueos mutuamente exclusivamente. Por lo tanto, Method1 y Method2 solo se pueden ejecutar secuencialmente.
2. Segmento de código 3 Resultados:
Aunque Test y Test2 pertenecen a diferentes objetos, Test y Test2 pertenecen a diferentes instancias de la misma clase. Dado que Method1 y Method2 pertenecen a métodos de sincronización estática, debe obtener el monitor en la misma clase (cada clase solo corresponde a una clase de objeto), por lo que solo puede ejecutar secuencialmente.
3. Segmento de código 4 Resultados:
Para la sincronización de bloques de código, es esencialmente necesario obtener el monitor del objeto en los soportes después de la palabra clave sincronizada. Dado que los contenidos de los soportes en este código son este, y Method1 y Method2 se llaman a través del mismo objeto, por lo que antes de ingresar el bloque de sincronización, debe competir por los bloqueos en el mismo objeto, por lo que el bloque de sincronización solo se puede ejecutar en secuencia.
Cuatro resumen
Sincronizado es el método más utilizado para la seguridad de los subprocesos en la programación concurrente de Java, y es relativamente simple de usar. Sin embargo, si podemos comprender sus principios en profundidad y tener cierta comprensión de los conocimientos subyacentes, como los bloqueos de monitor, puede ayudarnos a usar correctamente las palabras clave sincronizadas y, por otro lado, también puede ayudarnos a comprender mejor el mecanismo de programación de concurrencia, ayudarnos a elegir mejores estrategias de concurrencia para completar las tareas en diferentes circunstancias. También puede lidiar con calma con varios problemas concurrentes que encuentra en la vida diaria.