Este artículo sigue principalmente dos artículos anteriores de subprocesos múltiples para resumir los problemas de seguridad de los hilos en Java Multi-Threading.
1. Un típico ejemplo de seguridad de hilo de Java
public class ThreadTest {public static void main (string [] args) {cuenta cuenta = nueva cuenta ("123456", 1000); DrawMoneyrunnable DrawMoneyRunnable = new DrawMoneyRunnable (cuenta, 700); Hilo myThread1 = nuevo hilo (drawMoneyRunnable); Hilo myThread2 = new Thread (DrawMoneyRunnable); mythread1.start (); mythread2.start (); }} class DrawMoneyRunnable implementos Runnable {Cuenta privada; Doble dibujo privado; public DrawMoneyRunnable (cuenta de cuenta, doble dibujo drawamount) {super (); this.account = cuenta; this.drawamount = drawamount; } public void run () {if (cuenta.getBalance ()> = drawamount) {// 1 System.out.println ("El retiro fue exitoso, el retiro del dinero es:" + Drawamount); Balance doble = Account.GetBalance () - Drawamount; cuenta.setBalance (saldo); System.out.println ("Balance is:" + Balance); }}} Cuenta de clase {Accountno de cadena privada; doble equilibrio privado; Cuenta pública () {} Cuenta pública (String Accountno, doble saldo) {this.accountno = Accountno; this.balance = balance; } public String getAccountno () {return Accountno; } public void setAcCountno (String Accountno) {this.accountno = Accountno; } public Double GetBalance () {Balance de retorno; } public void setBalance (doble balance) {this.balance = balance; }}El ejemplo anterior es fácil de entender. Hay una tarjeta bancaria con un saldo de 1,000. El programa simula la escena en la que usted y su esposa retiran dinero en el cajero automático al mismo tiempo. Ejecute este programa varias veces y puede tener resultados de salida en múltiples combinaciones diferentes. Una de las salidas posibles es:
1 El retiro del dinero es exitoso, el retiro del dinero es: 700.0
2 El saldo es: 300.0
3 El retiro del dinero es exitoso, el retiro del dinero es: 700.0
4 El saldo es: -400.0
En otras palabras, para una tarjeta bancaria con un saldo de solo 1,000, puede retirar un total de 1,400, lo que obviamente es un problema.
Después del análisis, el problema radica en la incertidumbre de la ejecución en un entorno multiproceso Java. La CPU puede cambiar aleatoriamente entre múltiples hilos en el estado listo, por lo que es muy probable que ocurra la siguiente situación: cuando Thread1 ejecuta el código en // 1, la condición del juicio es verdadera. En este momento, la CPU cambia a Thread2, ejecuta el código AT // 1 y encuentra que todavía es cierto. Luego, Thread2 se ejecuta, luego cambia a Thread1 y luego se completa la ejecución. En este momento, aparecerán los resultados anteriores.
Por lo tanto, cuando se trata de problemas de seguridad de los hilos, en realidad significa que acceder a recursos compartidos en un entorno múltiple puede causar inconsistencia en este recurso compartido. Por lo tanto, para evitar problemas de seguridad de los hilos, se debe evitar el acceso concurrente a este recurso compartido en un entorno múltiple.
2. Método de sincronización
La modificación de palabras clave sincronizada se agrega a la definición del método para acceder a recursos compartidos, lo que hace que este método llamado método de sincronización. Se puede entender simplemente que este método está bloqueado, y su objeto bloqueado es el objeto en sí mismo donde se encuentra el método actual. En un entorno múltiple, al ejecutar este método, primero debe obtener este bloqueo de sincronización (y como máximo solo un hilo puede obtenerlo). Solo cuando el hilo ejecute este método de sincronización se lanzará el objeto de bloqueo, y otros hilos pueden obtener este bloqueo de sincronización, y así sucesivamente ...
En el ejemplo anterior, el recurso compartido es un objeto de cuenta, y al usar el método de sincronización, puede resolver problemas de seguridad de los subprocesos. Simplemente agregue la palabra clave sinshronizada antes del método run ().
public sincronizado void run () {// ....}3. Sincronizar bloques de código
Como se analizó anteriormente, resolver problemas de seguridad de hilos solo requiere limitar la incertidumbre del acceso a los recursos compartidos. Al usar el método de sincronización, todo el cuerpo del método se convierte en un estado de ejecución sincrónica, lo que puede hacer que el rango de sincronización ocurra. Por lo tanto, otro método de sincronización, el bloque de código de sincronización, se puede resolver directamente para el código que necesita sincronización.
El formato del bloque de código síncrono es:
sincronizado (obj) {// ...}Entre ellos, OBJ es el objeto de bloqueo, por lo que es crucial elegir qué objeto se bloqueará. En términos generales, este objeto de recurso compartido se selecciona como objeto de bloqueo.
Como en el ejemplo anterior, es mejor usar el objeto de cuenta como objeto de bloqueo. (Por supuesto, también es posible elegir esto, porque el hilo de creación utiliza el método ejecutable. Si es un hilo creado directamente heredando el método de subproceso, el uso de este objeto como un bloqueo de sincronización en realidad no desempeñará ningún papel porque es un objeto diferente. Por lo tanto, debe tener mucho cuidado al elegir un bloqueo de sincronización ...)
4. Lock de sincronización del objeto de bloqueo
Como podemos ver anteriormente, precisamente porque necesitamos ser muy cuidadosos con la selección de objetos de bloqueo sincrónico, ¿hay alguna solución simple? Puede facilitar el desacoplamiento de objetos de bloqueo sincrónico de recursos compartidos, al tiempo que resuelve bien los problemas de seguridad de los subprocesos.
El uso de bloqueos de sincronización de objetos de bloqueo puede resolver fácilmente este problema. Lo único a tener en cuenta es que el objeto de bloqueo debe tener una relación individual con el objeto de recursos. El formato general del bloqueo de sincronización del objeto de bloqueo es:
Clase X {// Muestra el objeto que define el bloqueo de sincronización de bloqueo, que tiene una relación uno a uno con el bloqueo final de recurso compartido bloqueo final de bloqueo = new ReentrantLock (); public void m () {// Lock Lock.lock (); // ... Código que requiere sincronización a Safe Safe // Relase el bloqueo de bloqueo Lock.unlock (); }}5.Wait ()/notify ()/notifyall () Comunicación de hilo
Estos tres métodos se mencionan en la publicación del blog "Serie de resumen de Java: java.lang.object". Aunque estos tres métodos se utilizan principalmente en la lectura múltiple, en realidad son métodos locales en la clase de objetos. Por lo tanto, en teoría, cualquier objeto de objeto puede usarse como el tono principal de estos tres métodos. En la programación real de subprocesos múltiples, solo sincronizar el objeto de bloqueo para sintonizar estos tres métodos pueden completarse la comunicación entre múltiples hilos.
Wait (): hace que el hilo actual espere y haga que ingrese un estado de bloqueo de espera. Hasta que otro hilo llame al método notify () o notifyall () del objeto de bloqueo sincrónico para despertar el hilo.
Notificar (): despierta un solo hilo esperando en este objeto de bloqueo sincrónico. Si se esperan múltiples hilos en este objeto de bloqueo sincrónico, uno de los hilos se seleccionará para la operación de atención. Solo cuando el hilo actual abandona el bloqueo en el objeto de bloqueo sincrónico se puede ejecutar el hilo despierto.
NotifyAll (): despertar todos los hilos esperando en este objeto de bloqueo sincrónico. Solo cuando el hilo actual abandona el bloqueo en el objeto de bloqueo sincrónico se puede ejecutar el hilo despierto.
paquete com.qqyumidi; public class ThreadTest {public static void main (string [] args) {cuenta cuenta = nueva cuenta ("123456", 0); Thread DrawMoneyThread = new DrawMoneyThread ("Get Money Thread", cuenta, 700); Hilo depositMoneyThread = new DepositMoneyThread ("Save Money Thread", cuenta, 700); DrawMoneyThread.Start (); depósitoMoneyThread.Start (); }} clase DrawMoneyThread extiende el hilo {cuenta de cuenta privada; cantidad doble privada; public DrawMoneyThread (String ThreadName, cuenta de cuenta, monto doble) {super (ThreadName); this.account = cuenta; this.amount = cantidad; } public void run () {for (int i = 0; i <100; i ++) {cuenta.draw (cantidad, i); }}} clase DepositEmoneyThread extiende el hilo {Cuenta privada; cantidad doble privada; Public DepositMoneyThread (String ThreadName, Cuenta de cuenta, Cantidad doble) {Super (ThreadName); this.account = cuenta; this.amount = cantidad; } public void run () {for (int i = 0; i <100; i ++) {cuenta.deposit (cantidad, i); }}} Cuenta de clase {Accountno de cadena privada; doble equilibrio privado; // Identificar si ya hay un depósito en la cuenta de la cuenta privada booleana = false; Cuenta pública () {} Cuenta pública (String Accountno, doble saldo) {this.accountno = Accountno; this.balance = balance; } public String getAccountno () {return Accountno; } public void setAcCountno (String Accountno) {this.accountno = Accountno; } public Double GetBalance () {Balance de retorno; } public void setBalance (doble balance) {this.balance = balance; } / ** * ahorrar dinero * * @param depositamunt * / public sincronized void deposit (doble depósito, int i) {if (flag) {// alguien en la cuenta ya ha ahorrado dinero, y el hilo actual debe esperar para bloquear {system.out.println (thread.currentThread (). GetName () + "inicio a la operación de espera" + " - i =" + " + i); esperar(); // 1 System.out.println (Thread.CurrentThread (). GetName () + "Operación de espera realizada" + " - i =" + i); } catch (InterruptedException e) {E.PrintStackTrace (); }} else {// Comience a guardar System.out.println (Thread.CurrentThread (). GetName () + "Deposits:" + DepositMount + " - i =" + i); setBalance (saldo + depósito); bandera = verdadero; // despertar otros hilos notifyall (); // 2 try {Thread.sleep (3000); } catch (InterruptedException e) {E.PrintStackTrace (); } System.out.println (Thread.CurrentThread (). GetName () + "- Save Money- La ejecución se completa" + "- i =" + i); }} / ** * retirar dinero * * @param drawamount * / public sincronized void draw (double drawamount, int i) {if (! + i); esperar(); System.out.println (Thread.CurrentThread (). GetName () + "Ejecutar la operación de espera" + "Ejecutar la operación de espera" + " - i =" + i); } catch (InterruptedException e) {E.PrintStackTrace (); }} else {// Comience a retirar dinero System.out.println (Thread.CurrentThread (). GetName () + "Retirar dinero:" + Drawamount + " - i =" + i); setBalance (getBalance () - Drawamount); bandera = falso; // despertar otros hilos notifyall (); System.out.println (thread.currentThread (). GetName () + "-retirar dinero-La ejecución se completa" + "-i =" + i); // 3}}} El ejemplo anterior demuestra el uso de Wait ()/notify ()/notifyall (). Algunos resultados de salida son:
El hilo de retiro de dinero comienza a ejecutar la operación de espera y ejecutar la operación de espera- i = 0
Guardar depósito de hilo: 700.0 - i = 0
Ahorrar dinero hilo hiladillo-ejecución de dinero-i = 0
El hilo de ahorro de dinero debe realizar la operación de espera- i = 1
El hilo de retiro de dinero ejecuta la operación de espera y la operación de espera- i = 0
Retirar dinero hilo retirar dinero: 700.0 - i = 1
Hilo de retiro de dinero-Withdrawal-Ejecución-i = 1
El hilo para retirar dinero debe comenzar a ejecutar la operación de espera y ejecutar la operación de espera- i = 2
El hilo de ahorro de dinero ejecuta la operación de espera- i = 1
Guardar depósito de hilo: 700.0 - i = 2
Ahorrar dinero hilo hiladelante-ejecución de dinero-i = 2
El hilo de retiro ejecuta la operación de espera y ejecuta la operación de espera: i = 2
Retirar dinero hilo retiró dinero: 700.0 - i = 3
Hilo de retiro de dinero-Withdrawal-Ejecución-i = 3
El hilo para retirar dinero debe ejecutar la operación de espera y ejecutar la operación de espera- i = 4
Guardar depósito de hilo: 700.0 - i = 3
Ahorre dinero Save-Save Money-Ejecución-i = 3
El hilo de ahorro de dinero debe realizar la operación de espera- i = 4
El hilo de retiro de dinero ejecuta la operación de espera y la operación de espera- i = 4
Retirar dinero hilo retiró dinero: 700.0 - i = 5
Hilo de retiro de dinero-Withdrawal-Ejecución-i = 5
El hilo para retirar dinero debe comenzar a realizar la operación de espera y ejecutar la operación de espera- i = 6
El hilo de ahorro de dinero ejecuta la operación de espera- i = 4
Guardar depósito de hilo: 700.0 - i = 5
Ahorre dinero Save de la ejecución de dinero-i = 5
El hilo de ahorro de dinero debe realizar la operación de espera- i = 6
El hilo de retiro de dinero ejecuta la operación de espera y la operación de espera- i = 6
Retirar dinero hilo retiró dinero: 700.0 - i = 7
Hilo de retirada de dinero-Withdrawal-Ejecución-i = 7
El hilo de retiro de dinero comienza a ejecutar la operación de espera y ejecutar la operación de espera-i = 8
El hilo de ahorro de dinero ejecuta la operación de espera- i = 6
Guardar depósito de hilo: 700.0 - i = 7
Por lo tanto, debemos prestar atención a los siguientes puntos:
1. Después de ejecutar el método Wait (), el hilo actual ingresa inmediatamente al estado de bloqueo de espera, y el código posterior no se ejecutará;
2. Después de ejecutar el método notify ()/notifyall (), el objeto de subproceso (any-notify ()/all-notifyall ()) en este objeto de bloqueo de sincronización se despertará. Sin embargo, el objeto de bloqueo de sincronización no se libera en este momento. Es decir, si hay código detrás de notificar ()/notifyall (), continuará continuando. Solo cuando se ejecuta el hilo actual, se liberará el objeto de bloqueo de sincronización;
3. Después de que Notify ()/notifyAll () se ejecute, si hay un método Sleep () a la derecha, el hilo actual ingresará a un estado de bloqueo, pero el bloqueo del objeto de sincronización no se libera y todavía se retiene por sí mismo. Luego, el hilo continuará siendo ejecutado después de un cierto período de tiempo, los siguientes 2;
4. Wait ()/notify ()/nitifyall () completa la comunicación o la colaboración entre hilos basados en diferentes bloqueos de objetos. Por lo tanto, si se trata de un bloqueo de objeto de sincronización diferente, perderá su significado. Al mismo tiempo, el bloqueo del objeto de sincronización es mejor para mantener una correspondencia uno a uno con el objeto de recurso compartido;
5. Cuando el hilo de espera se despierta y se ejecuta, el código de método Wait () que se ejecutó la última vez continúa ejecutándose.
Por supuesto, el ejemplo anterior es relativamente simple, solo para usar simplemente el método Wait ()/notify ()/noitifyall (), pero en esencia, ya es un modelo simple de consumidor productor.
Serie de artículos:
Explicación de las instancias de Java Multi-Thread (I)
Explicación detallada de las instancias de Java Multi-Thread (ii)
Explicación detallada de las instancias de Java Multi-Thread (iii)