1 ¿Qué es un problema de concurrencia?
Múltiples procesos o hilos que acceden al mismo recurso simultáneamente (o en el mismo período de tiempo) causarán problemas de concurrencia.
Un ejemplo típico es que dos operadores bancarios operan la misma cuenta al mismo tiempo. Por ejemplo, los operadores A y B leen una cuenta con un saldo de 1,000 yuanes al mismo tiempo, el operador A agrega 100 yuanes a la cuenta, el operador B también reduce 50 yuanes para la cuenta, A envía primero y B presenta más tarde. El saldo final de la cuenta real es 1000-50 = 950 yuanes, pero debería haber sido 1000+100-50 = 1050. Este es un problema de concurrencia típico. ¿Cómo resolverlo? Puede usar cerraduras.
2 Usage de sincronizado en Java
Uso 1
Prueba de clase pública {public sincronizado Void print () {….;}}Si un hilo ejecuta el método print (), el objeto se bloqueará. Otros hilos no podrán ejecutar todos los bloques sincronizados del objeto.
Uso 2
Prueba de clase pública {public void print () {sincronizado (this) {// bloquea este objeto ...;}}} El mismo uso 1, pero puede reflejar mejor la esencia del uso sincronizado.
Uso 3
Test de clase pública {String private a = "test"; public void print () {sincronizado (a) {// bloquea un objeto ...;}} public sincronizado vacío t () {...; // Este bloque de código sincronizado no se bloqueará debido a imprime ().}}}}La ejecución de la impresión () bloqueará el objeto a. Tenga en cuenta que no está bloqueando el objeto de prueba, lo que significa que otros métodos sincronizados del objeto de prueba no se bloquearán debido a la impresión (). Después de ejecutar el bloque de código de sincronización, se lanza el bloqueo a A.
Para bloquear el bloque de código de un objeto sin afectar la redacción de alto rendimiento de otros bloques sincronizados del objeto:
Prueba de clase pública {private byte [] bloquear = new byte [0]; public void print () {sincronizado (bloqueo) {...;}} public sincronizado void t () {...;}}Bloqueo de método estático
Prueba de clase pública {public sincronizado static void ejecute () {...;}}El mismo efecto
Prueba de clase pública {public static void ejecutute () {sincronizado (testthread.class) {...;}}}3 bloqueos en Java y cola para ir al baño.
Lock es una forma de evitar que otros procesos o hilos accedan a los recursos, es decir, no se puede acceder al recurso bloqueado por otras solicitudes. En Java, la palabra clave SyCronized se usa para bloquear un objeto. Por ejemplo:
public class MyStack {int idx = 0; char [] data = new Char [6]; public sincronizado void push (char c) {data [idx] = c; idx ++;} public sincronizado char pop () {IDX-; return data [idx];} public estatic estatic main (string args []) {mystack m mystack m myStack (new myStack ();/** Object MIS es bloqueado. Estrictamente hablando, todos los bloques sincronizados del objeto M están bloqueados. Si hay otro hilo t tratando de acceder a M, entonces T no puede ejecutar los métodos Push and POP del objeto M. */m.pop (); // El objeto M está bloqueado. }}El desbloqueo de bloqueo de Java es exactamente lo mismo que varias personas haciendo cola para un baño público. La primera persona cerró la puerta desde adentro después de entrar, por lo que las otras tuvieron que esperar en la fila. La puerta solo se abrirá (desbloquear) cuando salga la primera persona después del final. Era el turno de la segunda persona para entrar, y él cerraba la puerta desde adentro nuevamente, y los demás continuarían esperando en la fila.
Es fácil de entender usando la teoría del baño: cuando una persona entra en un baño, el inodoro estará bloqueado, pero no hará que el otro inodoro esté bloqueado, porque una persona no puede ponerse en cuclillas en dos baños al mismo tiempo. Para Java, significa: las cerraduras en Java están dirigidas al mismo objeto, no a la clase. Vea el siguiente ejemplo:
Mystatckm1 = newmystack (); mystatckm2 = newMyStatck (); m1.pop (); m2.pop ();
Las cerraduras de los objetos M1 no afectarán las cerraduras de M2 porque no son la misma posición del inodoro. Es decir, si hay 3 hilos T1, T2 y T3 operando M1, entonces estos 3 hilos solo pueden hacer cola y esperar en M1. Suponiendo que los otros 2 hilos T8, T9 operan M2, luego T8, T9 solo esperará en M2. T2 y T8 no tienen nada que ver con eso. Incluso si se libera el bloqueo en M2, T1, T2 y T3 aún pueden tener que hacer cola en M1. No hay razón, no es el mismo asiento del inodoro.
Java no puede agregar dos bloqueos a un bloque de código al mismo tiempo. Esto es diferente del mecanismo de bloqueo de la base de datos. La base de datos puede agregar varias cerraduras diferentes a un registro al mismo tiempo.
4 ¿Cuándo debería lanzar la cerradura?
En general, el bloqueo se libera después de ejecutar el bloque de código de sincronización (bloque de código bloqueado) o el bloqueo se puede liberar a la mitad del método Wait (). El método Wait () es como ponerse en cuclillas en el inodoro a mitad de camino y de repente descubrí que la alcantarilla estaba bloqueada. Tuve que salir y pararme a un lado para que el reparador de alcantarillado (un hilo que esté listo para ejecutar notificar) pueda entrar y limpiar el inodoro. Después de la desbloqueo, el maestro gritó: "Se ha reparado". El camarada que salió justo ahora se alineó después de escucharlo. Presta atención, debes esperar a que salga el maestro. Si el maestro no sale, nadie puede entrar. Es decir, después de notificar, otros subprocesos pueden ingresar al área bloqueada y moverse inmediatamente, pero otros hilos pueden ingresar después del área bloqueada donde se ejecuta y liberado el código de notificación para liberar el bloqueo.
Aquí está el ejemplo de código de espera y notificación:
public sincronizado char pop () {char c; while (buffer.size () == 0) {try {this.wait (); // fuera del inodoro} capt (interruptedexception e) {// ignóralo ...}} c = ((caracteres) buffer.remove (buffer.size ()-1)). Charvalue (); return c;} public sincronizado vacío Push (char c) {this.notify (); // notifica esos hilos Wait () para hacer cola nuevamente. Nota: Simplemente notifíqueles que reacondicionen. Personaje Charobj = nuevo carácter (c); buffer.addelement (charobj);} // ejecutar y liberar el bloqueo. Esos hilos en cola pueden entrar.Ir más profundo.
Debido a la operación Wait (), los camaradas que salieron a mitad de camino no harían cola hasta que recibieran la señal de notificación. Observaría a la gente haciendo cola a su lado (el maestro de reparación de tuberías de agua también está entre ellos). Tenga en cuenta que el maestro de reparación de tuberías de agua no puede ir a la línea, y debe hacer cola como aquellos que van al baño. No es que después de que una persona se pone en cuclillas a la mitad, el maestro de reparación de tuberías de agua puede aparecer repentinamente y entrar para repararlo de inmediato. Quiere competir de manera justa con las personas que originalmente estaban haciendo cola, porque también es un hilo ordinario. Si el maestro de reparación de tuberías de agua está alineado, la persona en el frente encontrará que está bloqueada y espere, luego sal y pégase a un lado, espera, salga, pégase a un lado y solo vaya al maestro para entrar y ejecutar notificar. De esta manera, después de un tiempo, habrá un grupo de personas parados junto a la cola, esperando notificar.
Finalmente, el maestro entró y notificó. ¿Qué sigue?
1. Se notifica a una persona de espera (hilo).
2. ¿Por qué es él notificado en lugar de otra persona de espera? Depende de la JVM. No podemos adelantarse
Determine cuál será notificado. En otras palabras, aquellos con alta prioridad pueden no ser despertadas primero, esperando
En cualquier momento no se despierta necesariamente, ¡todo es impredecible! (Por supuesto, si conoces el JVM
Si se implementa, puede predecirlo).
3. Él (el hilo notificado) necesita hacer cola nuevamente.
4. ¿Estará en primer lugar en la línea? La respuesta es: no estoy seguro. ¿Será él el último? No necesariamente.
Pero si la prioridad del hilo es relativamente alta, entonces la probabilidad de que se clasifique primero es relativamente alta.
5. Cuando sea su turno de volver a entrar en el asiento del inodoro, continuará ejecutándose desde el último lugar de espera () y no volverá a ejecutar.
Para decirlo de una manera desagradable, continuará divagando, no divagar nuevamente.
6. Si el maestro notifyall (), entonces todas las personas que se dieron por vencidas a la mitad se alinearán nuevamente. El orden es desconocido.
Javadoc dice que theawakenedThreadswillnotBeAbletOprocedEdiltheCurrentThreadRelInquishThelockThisObject (el hilo despierto no se puede ejecutar antes de que el hilo actual libera el bloqueo).
Esto es obvio para explicar el uso de la teoría del asiento del inodoro.
Uso de 5lock
Use la palabra clave sincronizada para bloquear los recursos. La palabra clave de bloqueo también está bien. Es nuevo en JDK1.5. El uso es el siguiente:
clase BoundedBuffer {Final Lock Lock = New ReEntrantLock (); Condición final Notfull = Lock.NewCondition (); Condición final nogEtimty = Lock.NewCondition (); Object final [] Object = New Object [100]; int PutPtr, TakePtr, Count; public void Put (Object X) arroja interrupción de interrogada {Lock.Lock (); Try {while (count (count (COOK == IMATIS; notfull.await (); elementos [putptr] = x; if (++ putptr == items.length) putptr = 0; ++ count; noTempty.signal ();} finalmente {bloqueo.unlock ();}} public Public Object Take () Take InterruptedException {Lock.Lock (); try {Count == 0) NotEment.await (); (++ TakePtr == items.length) TAKPTR = 0;-Count; NotfUll.Signal (); return x;} finalmente {Lock.unlock ();}}}(Nota: Este es un ejemplo en Javadoc, un ejemplo de implementación de una cola de bloqueo. La llamada cola de bloqueo de la llamada significa que si una cola está llena o vacía, causará un bloqueo de hilos y la espera.
Se bloqueará el código entre Lock.Lock () y Lock.unlock () de un objeto. ¿Qué es lo mejor de este método en comparación con sincronizar? En resumen, clasifica los hilos de espera. Para describirlo usando la teoría del asiento del inodoro, aquellos que se ponen en cuclillas a mitad de camino y salen del asiento del inodoro y esperan pueden tener diferentes razones. Algunos se deben a que el inodoro está bloqueado, y otros se deben a que el inodoro está fuera del agua. Cuando notifique, puede gritar: si espera a que se bloquee el inodoro, volverá a estar en la fila (por ejemplo, el problema del inodoro bloqueado se ha resuelto), o grita, si espera a que el inodoro esté bloqueado, volverá a estar en línea (por ejemplo, el problema del inodoro se ha resuelto). Esto permite un control más detallado. A diferencia de la espera y la notificación en sincronización, si el inodoro está bloqueado o el inodoro no es acuoso, solo puedes gritar: ¡solo esperé aquí para hacer cola! Si las personas en la fila entran y miran, descubren que el problema del baño se resuelve, y el problema que están ansiosos por resolver (el inodoro no tiene agua) aún no se ha resuelto, por lo que tienen que regresar y esperar (esperar), y venir a caminar en vano, perdiendo tiempo y recursos.
La relación correspondiente entre el método de bloqueo y sincronizado:
LockawaitSignalSignalall
sincronizadowaitnotifynotifyall
NOTA: No llame a esperar, notificar, notificar a todos en bloques bloqueados por bloqueo
6. Use tuberías para comunicarse entre hilos
El principio es simple. Dos hilos, uno opera PipeDInputStream y el otro opera PipeDoutputStream. Los datos escritos por PipeDoutputStream se almacenan en caché primero en el búfer. Si el búfer está lleno, este hilo espera. PipeDInputStream lee los datos en el búfer. Si el búfer no tiene datos, este hilo espera.
La misma función se puede lograr bloqueando las colas en JDK1.5.
paquete io; import java.io.*; public class PipedStreamTest {public static void main (string [] args) {pipeDoutputStream Ops = new PipeDoutputStream (); PipeDInputStream PIS = New PipeDInputS (); Try {Ops. Ops. (PIS); // Implementar la conexión de la conexión (). Consumidor (pis) .run ();} capt (excepción e) {e.printstacktrace ();}}} // El productor de la clase de productor implementa runnable {private PipeDoutputStream Ops; Public Producer (PipeDoutputStream Ops) {this.ops = ops;} public Void run () {try {ops.write ("hell, spell" .getBytes ()); ops.close ();} capt (excepción e) {e.printstacktrace ();}}} // clase de consumo consumo implements runnable {private pipeDinputStream Pis; consultor público (consumidor público run () {try {byte [] bu = new byte [100]; int len = pis.read (bu); system.out.println (new String (bu, 0, len)); pis.close ();} catch (excepción e) {e.printstacktrace ();}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}Ejemplo 2: Un pequeño cambio en el programa anterior se convierte en dos hilos.
paquete io; import java.io.*; public class PipedStreamTest {public static void main (string [] args) {pipeDoutputStream Ops = new PipeDoutputStream (); PipeDInputStream PIS = New PipeDInputStream (); Try {Ops.connect (PIS); // Implementar el productor de conexión Pipeler Hild (p) .start (); consumidor c = new Consumer (pis); new Thread (c) .Start ();} Catch (Exception e) {E.PrintStackTRace ();}}}} // La clase del productor implementa el productor de la clase run () {try {for (;;) {ops.write ("hell, spell" .getBytes ()); ops.close ();}}} // clase de consumo implementa el consumidor runnable {private pipeDinputStream pis; public consumer (PipeDInputStream Pis) {this.pis = pis;} public void run () {thy {; bu = new Byte [100]; int Len = Pis.read (bu); System.out.println (new String (Bu, 0, len));} Pis.Close ();} Catch (Exception e) {E.PrintStacktrace ();}}}Ejemplo 3. Este ejemplo es más apropiado para la aplicación
import java.io.*; public class Pipedio {// Después de que el programa se ejecuta, copie el contenido del archivo SendFile en el archivo de receptiverfile public static void void main (string args []) {try {// construye el objeto de flujo de flojo de tubería de tubería de lectura y el objeto de flujo de escritura de tubelina de PIPISTREAM = new PipeDInputStream (); PipeDoutStream POS = New PipeDoutEnput The TipeDInputStream PIS = New PipeDInputStream (); PipeDoutStream POS = New PipeDoutEutseam ()////NEW PipeDInputStream (); pos.connect (PIS); // Construye dos hilos y comience. nuevo remitente (pos, "c: /Text2.txt"). start (); nuevo receptor (pis, "c: /Text3.txt"). start ();} capt (ioexception e) {system.println ("error de tubería" + e);}}}}} // El subproceso envía a la clasificación Extends thread {tipeutstream pos; file archivo de archivo; Remitente (PipeDoutputStream POS, String FileName) {this.pos = pos; file = new File (FileName);} // Subproader Método public void run () {try {// Leer el contenido de archivo FileInputstream FS = New FileInputStream (File); Int Data; While ((Data = fs.Read ()! =-1) {// Escribir en la tubería de pipa en la tubería inicial de la tubería en la tubería inicial de la tubería) pos.Write (data);} pos.close ();} catch (ioException e) {System.out.println ("Error del remitente" +e);}}}}} // El receptor de clase de subproceso extiende el hilo {PipeDInputStream Pis; file; // Método de construcción receptor (PipeDInStream PisStReam PisS) File (nombre de archivo);} // hilo ejecuta public void run () {try {// escribe el archivo de archivo de archivo FileOutputStream fs = new FileOutputStream (archivo); int data; // Read while ((data = pis.read ())! =-1) {// Escribir a archivo local fs.write (data);} pis.close ();} chopcception (ioexception (ioexception (ioexception e) {System.out.println ("Error del receptor" +E);}}}7 cola de bloqueo
La cola de bloqueo puede reemplazar el método de flujo de tubería para implementar el modo de tubería de entrada/drenaje (productor/consumidor). JDK1.5 proporciona varias colas de bloqueo listas para usar. Ahora veamos el Código de ArrayBlockingqueue de la siguiente manera:
Aquí hay una cola de bloqueo
BLOCKINGQUEUE BLOCKINGQ = NUEVO ARRAYBLOINCKINGQUEUE 10;
Un hilo toma de la cola
para (;;) {objeto o = bloquekq.take (); // La cola está vacía, luego espera (bloqueando)}Otro hilo se almacena en la cola
para (;;) {BloquingQ.put (nuevo objeto ()); // Si la cola está llena, espera (bloqueo)}Se puede ver que las colas de bloqueo son más simples de usar que las tuberías.
8USE Ejecutores, Ejecutor, EjecutorService, ThreadPoolExecutor
Puede usar tareas de administración de hilos. También puede usar un conjunto de clases proporcionadas por JDK1.5 para administrar las tareas más convenientemente. De estas categorías podemos experimentar una forma de pensar orientada a tareas. Estas clases son:
Interfaz del Ejecutor. Cómo usar:
Ejecutor Ejecutor = anexecutor; // Generar una instancia de ejecutor. Ejecutor.Execute (new RunNableTask1 ());
Intención: los usuarios solo se centran en la ejecución de tareas, y no tienen que preocuparse por los detalles de la creación de tareas y la ejecución, y otros problemas que los implementadores de terceros están preocupados. Es decir, desacoplar la ejecución de las llamadas de tarea y la implementación de la tarea.
De hecho, ya hay excelentes implementaciones de esta interfaz en JDK1.5. Suficiente.
Los ejecutores son una clase de fábrica o clase de herramientas como colecciones, utilizadas para generar instancias de varias interfaces.
La interfaz EjecutorService se hereda de Ejecutor.executor solo arroja la tarea al ejecutor () para su ejecución e ignora el resto. EjecutorService es diferente, hará más trabajo de control. Por ejemplo:
class NetworkService {private final Serversocket Serversocket; privado Final ExecutorService Pool; public NetworkService (int Port, int PoolSize) lanza IOException {Serversocket = new Serversocket (puerto); Pool = Ejecutors.NewFixedThreadPool (PoolSize);} public Void Servir () {intit {para (;;) {Pool.Execute (NewCute (NewCute (NewCute) Handler (Serversocket.accept ()));}} Catch (IOException ex) {Pool.shutdown (); // No se ejecutan nuevas tareas}}} de clases Handler implementos runnables {private SOCKET SOCKETS;Después de que ExecutorService (es decir, el objeto de grupo en el código) ejecuta el cierre, ya no puede ejecutar nuevas tareas, pero las tareas antiguas continuarán siendo ejecutadas, y aquellos que esperan la ejecución ya no esperarán.
Comunicación de Task Submister y del intérprete
public static void main (string args []) lanza la excepción {EjecutorService Ejecutor = Ejecutors.NeWSingLethreadExeCutor (); task de llamadas = new Callable () {public String Call () lanza la excepción {return "Test";}}; Future F = Ejecutor.subMit (tarea); String Result = F.get (); // Resultado (Bloqueado) Resultado de retorno de retorno de retorno); System.out.println (resultado); ejecutor.shutdown ();}La instancia del ejecutor obtenida por los ejecutores.newsinglethreadExecutor () tiene las siguientes características:
Ejecución de tareas secuencialmente. Por ejemplo:
ejecutor.submit (tarea1); ejecutor.submit (tarea2);
Debe esperar a que se ejecute la tarea1 antes de que se pueda ejecutar la tarea2.
La Tarea1 y la Tarea2 serán colocadas en una cola y se procesarán por un hilo de trabajadores. Es decir: hay 2 hilos en total (hilo principal, hilo de trabajadores que procesa tareas).
Para otras clases, consulte Javadoc
9 Control de procesos concurrentes
Los ejemplos en esta sección son del tutorial de concurrencia Java de Wen Shao y pueden cambiarse. Saludo al Sr. Wen.
CountdownLatch Port Pin Counter
Comience el hilo y espere a que finalice el hilo. Es decir, el hilo principal común y otros hilos infantiles se ejecutan después del final del hilo principal.
public static void main (string [] args) lanza la excepción {// TODO Auto Generado Método Generado final int count = 10; final CountDownLatch completo) completeLatch.countDown (); // reduce un pestillo de la puerta}}; thread.start ();} completaLatch.aWait (); // Si el pestillo de la puerta aún no se ha reducido, espere. }En JDK1.4, el método común es establecer el estado del hilo infantil y la detección de bucle del hilo principal. La facilidad de uso y la eficiencia no son buenas.
Iniciar muchos hilos y esperar a que comiencen las notificaciones
public static void main (string [] args) lanza la excepción {// TODO Auto Generado Generado Stub Final CountDownLatch StartLatch = New CountDownLatch (1); // Defina un pestillo para (int i = 0; i <10; i ++) {thread thread = new Thread ("Worker Thread"+i) {public void Run () {Try {start {startlatch.await () se ha reducido todavía, Wait} Catch (InterruptedException e) {} // do xxxx}}; thread.start ();} startLatch.countdown (); // Reducir una puerta}Cyclibarrier. Solo después de que todos los hilos lleguen a una línea de inicio, pueden continuar corriendo.
clase pública Cyclibarriertest implementa Runnable {Private Cyclibarrier Barrier; public cyclibarriertest (Cyclibarrier Barrier) {this.barrier = Barrier;} public void run () {// do xxxx; intente {this.barrier.await (); // El hilo verificará si todos los otros hilos se llegan. Si no llega, continúe esperando. Cuando se alcance todo, ejecute el contenido del cuerpo de función de ejecución de la barrera} capt (excepción e) {}}/** * @param args */public static void main (string [] args) {// parámetro 2 significa que ambos hilos han alcanzado la línea de salida antes de comenzar a ejecutar juntos ciclicbarrier barrera = newcclicbarrier (2, new, new publicable () {) {// do xxxx;}}); hilo t1 = nuevo hilo (nuevo ciclibarriertest (barrera)); hilo t2 = nuevo hilo (nuevo ciclibarriertest (barrera)); t1.start (); t2.start ();}}Esto simplifica la forma tradicional de implementar esta función con Counter + Wait/Notifyall.
10 Concurrencia 3 Ley
Ley de Amdahl. Dada la escala de problemas, la parte de paralelización representa el 12%. Luego, incluso si el paralelismo se aplica al extremo, el rendimiento del sistema solo puede mejorarse en 1/(1-0.12) = 1.136 veces como máximo. Es decir: el paralelismo tiene un límite superior para mejorar el rendimiento del sistema.
Ley de Gustafson. La ley de Gustafson dice que la ley de Amdahl no considera que se pueda usar más poder informático a medida que aumenta el número de CPU. La esencia es cambiar la escala del problema para que el 88% restante del procesamiento en serie en la ley de AMDAHL pueda ser paralelo, rompiendo así el umbral de rendimiento. En esencia, es una especie de tiempo de intercambio espacial.
Ley Sun-Ni. Es una promoción adicional de las dos primeras leyes. La idea principal es que la velocidad de la informática está limitada por la velocidad del almacenamiento en lugar de la CPU. Por lo tanto, debemos hacer un uso completo de los recursos informáticos, como el espacio de almacenamiento y tratar de aumentar la escala del problema para producir soluciones mejores/más precisas.
11 De concurrente a paralelo
Las computadoras deben calcular rápidamente al identificar objetos, de modo que los chips se calientan y se calientan. Cuando las personas reconocen los objetos, son claros de un vistazo, pero no hacen que una determinada célula cerebral se queme (exagerada) y se sientan incómodas. Esto se debe a que el cerebro es un sistema de operación paralela distribuido. Al igual que Google puede usar algunos servidores de Linux baratos para realizar cálculos enormes y complejos, innumerables neuronas en el cerebro calculan de forma independiente, compartiendo los resultados entre sí y completar instantáneamente el efecto que requiere billones de operaciones para una sola CPU. Imagínense, si se crea en el campo del procesamiento paralelo, tendrá un impacto inconmensurable en el desarrollo y el futuro de las computadoras. Por supuesto, los desafíos también se pueden imaginar: muchos problemas no se "dividen fácilmente.
Resumir
Lo anterior es todo el contenido de este artículo sobre el problema de concurrencia de Java, y espero que sea útil para todos. Los amigos interesados pueden continuar referiéndose a otros temas relacionados en este sitio. Si hay alguna deficiencia, deje un mensaje para señalarlo. ¡Gracias amigos por su apoyo para este sitio!