Si su Fundación Java es débil o no tiene una buena comprensión de Java Multithreading, lea este artículo "Aprenda la definición de hilo, el estado y las propiedades de Java multithreading"
La sincronización siempre ha sido un punto difícil para Java Multi-Threading, y rara vez se usa cuando estamos haciendo el desarrollo de Android, pero esta no es una razón por la que no estamos familiarizados con la sincronización. Espero que este artículo pueda permitir que más personas entiendan y apliquen la sincronización de Java.
En aplicaciones multiproceso, dos o más subprocesos deben compartir el acceso a los mismos datos. Esto generalmente se convierte en una condición de carrera si dos hilos acceden al mismo objeto y cada hilo llama a un método que modifica el objeto.
El ejemplo más fácil de las condiciones de competencia es: por ejemplo, los boletos de tren son ciertos, pero hay ventanas para vender boletos de tren en todas partes, cada ventana es equivalente a un hilo, y muchos hilos comparten todos los recursos de boletos de tren. Y no puede garantizar su atomicidad. Si dos hilos usan este recurso en un momento, los boletos de tren que sacan son los mismos (los números de los asientos son los mismos), lo que causará problemas para los pasajeros. La solución es que cuando un hilo quiere usar el recurso de boletos de tren, le damos un bloqueo, y después de que termine el trabajo, le daremos el bloqueo a otro hilo que quiere usar este recurso. De esta manera, la situación anterior no ocurrirá.
1. Bloquear el objeto
La palabra clave sincronizada proporciona automáticamente bloqueos y condiciones relacionadas. Es muy conveniente usar sincronizado en la mayoría de los casos donde se requieren bloqueos explícitos. Sin embargo, cuando entendemos la clase Reentrantlock y los objetos condicionales, podemos comprender mejor la palabra clave sincronizada. Reentrantlock se introduce en Java SE 5.0. La estructura del bloque de código está protegida utilizando ReentrantLock de la siguiente manera:
mlock.lock (); try {...} finalmente {mlock.unlock ();}Esta estructura asegura que solo un hilo ingrese al área crítica en cualquier momento. Una vez que un hilo bloquea el objeto de bloqueo, ningún otro hilo puede pasar la instrucción de bloqueo. Cuando otros hilos llaman al bloqueo, se bloquean hasta que el primer hilo libera el objeto de bloqueo. Es muy necesario poner la operación de desbloqueo finalmente. Si se produce una excepción en el área crítica, el bloqueo debe liberarse, de lo contrario, otros hilos se bloquearán para siempre.
2. Objeto condicional <Br /> Al ingresar al área crítica, se encuentra que solo se puede ejecutar después de cumplir una determinada condición. Use un objeto condicional para administrar hilos que han obtenido un bloqueo pero que no pueden hacer un trabajo útil. Los objetos condicionales también se llaman variables condicionales.
Echemos un vistazo al siguiente ejemplo para ver por qué se necesitan objetos condicionales
Supongamos que en un escenario necesitamos usar transferencias bancarias, primero escribimos la clase bancaria y su constructor debe transferirse al número de cuentas y la cantidad de cuentas.
Public Class Bank {private double [] cuentas; Lock Banklock privado; Public Bank (int n, doble balance inicial) {cuentas = new Double [n]; BankLock = new ReentrantLock (); for (int i = 0; i <cuentas.length; i ++) {cuentas [i] = inicialBALANCE; }}}A continuación, queremos retirar dinero y escribir un método de retiro. Desde el transferidor, el receptor y la cantidad de transferencia de la cantidad. Como resultado, encontramos que el equilibrio del transferidor es insuficiente. Si otros hilos ahorran suficiente dinero al transferidor, la transferencia puede tener éxito. Sin embargo, este hilo ha adquirido el bloqueo, que es exclusivo, y otros hilos no pueden adquirir el bloqueo para realizar operaciones de depósito. Es por eso que necesitamos introducir objetos condicionales.
Public void Transfer (int from, int to, int monta) {banklock.lock (); intente {while (cuentas [desde] <monton) {// espera}} finalmente {banklock.unlock (); }}Un objeto de bloqueo tiene múltiples objetos de condición relacionados. Puede usar el método de trituración para obtener un objeto de condición. Después de obtener el objeto de condición, llamamos el método de espera y el hilo actual está bloqueado y el bloqueo se abandona.
Public Class Bank {private double [] cuentas; Lock Banklock privado; condición privada condición; Public Bank (int n, doble balance inicial) {cuentas = new Double [n]; BankLock = new ReentrantLock (); // Obtener el objeto de condición condición = banklock.newcondition (); for (int i = 0; i <cuentas.length; i ++) {cuentas [i] = inicialBALANCE; }} Public void Transfer (int from, int to, int intente {while (cuentas [desde] <cantidad) {// bloquea el hilo actual y renuncia a la condición de bloqueo.await (); }} finalmente {bankLock.unlock (); }}} El hilo que espera el bloqueo es esencialmente diferente del hilo que llama al método de espera. Una vez que un hilo llame al método de espera, ingresará al conjunto de espera de esa condición. Cuando el bloqueo está disponible, el hilo no puede desbloquearse de inmediato, sino que está en un estado de bloqueo hasta que otro hilo llame al método de señalización en la misma condición. Cuando otro hilo esté listo para transferir dinero a nuestro transferidor anterior, simplemente llame a condición.signalall (); Esta llamada reactivará todos los hilos esperando esta condición.
Cuando un hilo llama al método de espera, no puede reactivarse y espera que otros hilos llamen al método de señalización para activarse. Si ningún otro hilo active el hilo de espera, entonces se producirá un punto muerto. Si todos los demás hilos están bloqueados, y las últimas llamadas de hilo activo esperan antes de desbloquear otros hilos, también se bloqueará. Ningún hilo puede desbloquear otros hilos y el programa se suspenderá.
Entonces, ¿cuándo se llamará Signalall? Normalmente, debe ser beneficioso llamar a la señalización cuando se espera que cambie la dirección del hilo. En este ejemplo, cuando cambia el saldo de una cuenta, el hilo de espera debe tener la oportunidad de verificar el saldo.
Public void Transfer (int from, int to, int intente {while (cuentas [desde] <cantidad) {// bloquea el hilo actual y renuncia a la condición de bloqueo.await (); } // Operación de transferencia ... condicion.signAlLAl (); } finalmente {bankLock.unlock (); }}Cuando se llama al método de señalall, un hilo de espera no se activa de inmediato. Simplemente desbloquea los hilos de espera para que estos hilos puedan lograr el acceso al objeto compitiendo después de que el hilo actual sale del método sincrónico. Otro método es la señal, que desbloquea aleatoriamente un hilo. Si el hilo aún no puede ejecutarse, se bloqueará nuevamente. Si no hay otra señal de llamadas de hilo nuevamente, el sistema estará bloqueado.
3. Palabras clave sincronizadas
Las interfaces de bloqueo y condición proporcionan a los programadores un alto grado de control de bloqueo, sin embargo, en la mayoría de los casos, no se requiere dicho control y se puede usar un mecanismo integrado dentro del lenguaje Java. A partir de Java versión 1.0, cada objeto en Java tiene un bloqueo interno. Si se declara un método con la palabra clave sincronizada, el bloqueo del objeto protegerá todo el método. Es decir, para llamar a este método, el hilo debe obtener el bloqueo de objeto interno.
en otras palabras,
Método void sincronizado público () {}Equivalente a
Public void Method () {this.lock.lock (); try {} finalmente {this.lock.unlock ();} En el ejemplo del banco anterior, podemos declarar el método de transferencia de la clase bancaria como sincronizado en lugar de usar un bloqueo mostrado.
Solo hay una condición relacionada para el bloqueo de objeto interno. Espera la ampliación se agrega a un hilo al conjunto de espera. Notifyall o notifique a los métodos desbloquear el hilo de espera. En otras palabras, Wait es equivalente a la condición de llamada.
Nuestro método de transferencia de ejemplo anterior también se puede escribir así:
Public sincronizada transferencia void (int from, int to, int } // Operación de transferencia ... notifyAll (); }
Puede ver que usar la palabra clave sincronizada para escribir código es mucho más simple. Por supuesto, para comprender este código, debe comprender que cada objeto tiene un bloqueo interno y que el bloqueo tiene una condición interna. El bloqueo gestiona aquellos hilos que intentan ingresar al método sincronizado, y las condiciones administran los hilos que llaman espera.
4. Bloqueo sincrónico <Br /> arriba dijimos que cada objeto Java tiene un bloqueo, y un hilo puede llamar al método de sincronización para obtener el bloqueo, y hay otro mecanismo para obtener el bloqueo. Al ingresar un bloque de sincronización, cuando el hilo ingresa la siguiente forma de bloqueo:
sincronizado (obj) {}Entonces obtuvo la cerradura del OBJ. Echemos un vistazo a la clase bancaria
Public Class Bank {private double [] cuentas; privado objeto bloqueo = nuevo objeto (); Public Bank (int n, doble balance inicial) {cuentas = new Double [n]; for (int i = 0; i <cuentas.length; i ++) {cuentas [i] = inicialBALANCE; }} Public void Transfer (int from, int to, int monta) {sincronizado (bloqueo) {// operación de transferencia ...}}}Aquí, la creación de objetos de bloqueo simplemente se usa para usar los bloqueos sostenidos por cada objeto Java. A veces, los desarrolladores usan el bloqueo de un objeto para implementar operaciones atómicas adicionales, llamadas bloqueo del cliente. Por ejemplo, la clase vectorial, sus métodos son sincrónicos. Ahora suponga que el saldo bancario se almacena en Vector
Public void Transfer (vector <doble> cuentas, int from, int to, int monta) {cuentas.set (from, cuentas.get (from) -amount); cuentas.set (a, cuentas.get (a)+cantidad;}Los métodos GET and Set de la clase Vecror son sincrónicos, pero esto no nos ha ayudado. Una vez que se completa la primera llamada a Get, es completamente posible que un hilo se niegue el derecho de ejecutarse en el método de transferencia, por lo que otro hilo puede haber almacenado diferentes valores en la misma ubicación de almacenamiento, pero podemos interceptar este bloqueo
Public void Transfer (vector <doble> cuentas, int from, int to, int monta) {sincronizado (cuentas) {cuentas.set (from, cuentas.get (from) -amount); cuentas.set (a, cuentasEl bloqueo del cliente (bloques de código sincrónicos) es muy frágil y generalmente no se recomienda. En general, es mejor usar las clases proporcionadas bajo el paquete Java.UTIL.CONCURRENT, como las colas de bloqueo. Si el método de sincronización es adecuado para su programa, intente usar el método de sincronización. Puede reducir el número de código escrito y reducir la posibilidad de errores. Si necesita usar las características únicas proporcionadas por la estructura de bloqueo/condición, use solo bloqueo/condición.
Lo anterior se trata de este artículo, espero que sea útil para el aprendizaje de todos.