[Serie de alta concurrencia] 1. Conceptos imprescindibles de la programación concurrente en Java

Algunos conceptos que se deben conocer en programación Java de alta concurrencia~

1. Concurrencia y paralelismo

concurrencia

        Alterne varias tareas en una CPU de uno o varios núcleos.

Paralelismo

        Una CPU multinúcleo maneja múltiples tareas simultáneamente. Tenga en cuenta que no hay paralelismo para las CPU de un solo núcleo.

la diferencia  

        La diferencia importante entre los conceptos de concurrencia y paralelismo radica en "un período de tiempo" y "al mismo tiempo".

        La simultaneidad se centra en la ejecución alterna de múltiples tareas, haciendo varias cosas al mismo tiempo durante un período de tiempo, como los fines de semana por la mañana, engatusando al bebé durante un rato, jugando con el teléfono móvil durante un rato, el bebé hace un escándalo , y juega con el móvil cuando el bebé está tranquilo..., así que repetidamente (dolor de cabeza~).

        El paralelismo se refiere a la ejecución de múltiples tareas al mismo tiempo. Por ejemplo, sostengo un bolígrafo en cada mano, dibujo un cuadrado con la mano izquierda y dibujo un círculo con la mano derecha (Xiuer~).

2. Síncrono y asíncrono

Los dos se utilizan para describir una llamada de método.

Síncrono Síncrono

        Es decir, el método de llamada se inicia y, una vez que se llama, debe esperar a que el método se complete y regrese antes de continuar con las operaciones posteriores.

        Por ejemplo, si vas al cajero de un banco a sacar dinero, tienes que esperar hasta que el cajero haya terminado de escupir el dinero y saques el dinero y retires la tarjeta antes de poder salir.

Asíncrono Asíncrono

        Es más como un mensaje que pasa. No es necesario que te preocupes por el proceso de ejecución específico del método. Una vez activado, el resultado se devolverá inmediatamente y la persona que llama puede continuar con las operaciones posteriores.

        Por ejemplo, si desea retirar dinero hoy y la cantidad es grande, puede llamar directamente o hacer una cita con el banco en la aplicación para decirle al banco cuánto efectivo desea retirar. Durante este tiempo, el banco preparará el dinero para usted, y este proceso de preparación no tiene nada que ver con usted. Entonces simplemente lo recoge a la hora programada. Para usted, acaba de desencadenar una acción asíncrona o pasar un mensaje.

3. Proceso e hilo

concepto

Proceso: la unidad más pequeña para la asignación de recursos por parte del sistema operativo, donde los recursos incluyen: CPU, espacio de memoria, disco IO, etc. Múltiples subprocesos en el mismo proceso comparten todos los recursos del sistema en el proceso y los procesos son independientes entre sí.

Subproceso: La unidad más pequeña de programación de CPU, que debe existir según el proceso.

la diferencia

  • Definición: Un proceso es el proceso en ejecución de una entidad en la que se ejecuta un programa, y ​​es una unidad independiente para la asignación y programación de recursos por parte del sistema; un subproceso es la unidad de programación más pequeña para la ejecución y ejecución del proceso.
  • Liveness: el proceso no está activo (es solo un contenedor de hilos); el hilo está activo y se puede crear y destruir en cualquier momento.
  • Sobrecarga del sistema: la creación, cancelación y cambio de procesos son costosos y los recursos deben reasignarse y recuperarse. En comparación con el proceso, el subproceso solo guarda menos contenido de registro, tiene una sobrecarga baja y ejecuta código en el espacio de direcciones del proceso.
  • Posesión de activos: Una conducta es la unidad básica de propiedad de los recursos. En comparación con el proceso, el subproceso básicamente no posee recursos, pero ocupará la CPU.
  • Programación: un proceso es solo la unidad básica de asignación de recursos. Thread es la unidad básica de programación y despacho independientes.
  • Seguridad: Los procesos son relativamente independientes y no se afectarán entre sí. Los subprocesos comparten recursos bajo el mismo proceso y pueden comunicarse e influirse entre sí.

4. Sección crítica

        Una sección crítica se usa para representar un recurso común o datos compartidos que pueden usar varios subprocesos, pero solo un subproceso puede usarlo a la vez.

         Una vez que el recurso de la sección crítica está ocupado, otros hilos deben esperar si quieren usar este recurso.

5. Bloqueo y no bloqueo

        El bloqueo y el no bloqueo generalmente se usan para describir el impacto entre múltiples subprocesos.

Bloqueo Bloqueo

        Si un subproceso ocupa un recurso común y no libera el bloqueo sobre él, otros subprocesos solo pueden esperar a que libere el bloqueo si quieren continuar con la ejecución. Esperar hará que el subproceso se cuelgue, lo que provocará el bloqueo en este momento.

Sin bloqueo Sin bloqueo

        Es decir, no hay bloqueo, los subprocesos pueden ejecutarse libremente, no hay recursos comunes bloqueados y no se bloquean entre sí.

6. Seguridad del hilo

Comprensión de la seguridad de subprocesos de Java:

        Cuando varios subprocesos acceden a un objeto Java al mismo tiempo, independientemente de cómo programe el sistema estos subprocesos y de cómo operen alternativamente estos subprocesos, este objeto puede mostrar un comportamiento coherente y correcto, por lo que la operación en este objeto es segura para subprocesos. Si el objeto muestra un comportamiento erróneo e incoherente, las operaciones en el objeto no son seguras para subprocesos y se ha producido un problema de seguridad de subprocesos.

7. Programación Java de alta concurrencia

        Después de Java 5, se introdujeron funciones avanzadas de concurrencia. La mayoría de las funciones se encuentran en el paquete java.util.concurrent, que se utiliza especialmente para la programación de subprocesos múltiples, aprovechando al máximo las funciones de los sistemas multiprocesador y multinúcleo modernos para escribir aplicaciones concurrentes a gran escala. Incluye principalmente peso atómico, recopilación concurrente, sincronizador, bloqueo de reentrada y proporciona un fuerte soporte para la construcción de un grupo de subprocesos.

Significado y ventajas

  1. Aproveche al máximo los recursos de la CPU;

  2. Acelerar el tiempo de respuesta a los usuarios;

  3. Haga que el código sea modular, asíncrono y simple.

 Cuestiones a tener en cuenta

  1. Seguridad entre hilos;

  2. El proceso de bucle infinito entre hilos;

  3. Demasiados subprocesos agotarán los recursos del servidor y provocarán un bloqueo.

8. Tres problemas principales en la programación concurrente

atomicidad

Definición: Significa que la operación de un hilo no puede ser interrumpida por otros hilos, y solo un hilo opera sobre una variable al mismo tiempo.

        En el caso de subprocesos múltiples, el resultado de ejecución de cada subproceso no se ve interferido por otros subprocesos. Por ejemplo, varios subprocesos llaman simultáneamente a la misma variable miembro compartida n++ 100 veces. Si el valor inicial de n es 0, el valor final de n debe ser 100, para que no interfieran entre sí, que es atomicidad.

        De hecho, n++ no es una operación atómica, y AtomicInteger puede usarse para asegurar su atomicidad.

visibilidad

Definición: se refiere a si un subproceso modifica el valor de una variable compartida y si otros subprocesos pueden ver el valor modificado de la variable compartida.

        Cada subproceso tiene su propia memoria de trabajo. El subproceso primero lee el valor de la variable compartida de la memoria principal a la memoria de trabajo para formar una copia. La memoria es un proceso. Cuando la memoria principal no se ha vaciado, es invisible para otros subprocesos en este momento, por lo que el valor leído por otros subprocesos de la memoria principal es el valor anterior antes de la modificación.

        Surgirán problemas de visibilidad en la optimización de la memoria caché de la CPU, la optimización del hardware, la reorganización de instrucciones y la optimización del compilador JVM.

orden

Definición: La secuencia de ejecución del programa se ejecuta en la secuencia de códigos.

        Todas las operaciones están ordenadas si se observan dentro de este hilo; todas las operaciones están desordenadas si se observan en otro hilo. La primera mitad de la oración se refiere a "semántica en serie dentro de un hilo", y la segunda mitad de la oración se refiere al fenómeno de "reordenación de instrucciones" y "retraso de sincronización de memoria principal en la memoria de trabajo".

        La palabra clave volátil de Java en sí misma contiene la semántica de prohibir el reordenamiento de instrucciones, mientras que la sincronización se obtiene mediante la regla "solo se permite que un subproceso bloquee una variable al mismo tiempo", lo que determina que mantener los mismos dos bloques sincronizados de un bloqueo solo puede introducirse en serie.

9. Bloqueo y estado integrados de Java

describir

        Antes de Java6, todos los bloqueos incorporados de Java eran bloqueos pesados, y los bloqueos pesados ​​hacían que la CPU cambiara con frecuencia entre el modo de usuario y el modo central, lo cual es costoso e ineficiente. Para reducir el consumo de rendimiento causado por la adquisición y liberación de bloqueos, Java6 introduce la implementación de bloqueos sesgados y bloqueos ligeros. Por lo tanto, el bloqueo integrado de Java tiene un total de cuatro estados: estado sin bloqueo, estado de bloqueo sesgado, estado de bloqueo ligero y estado de bloqueo pesado Estos estados aumentarán gradualmente con la competencia. Los bloqueos incorporados se pueden actualizar pero no degradar, lo que significa que después de que un bloqueo sesgado se actualiza a un bloqueo liviano, no se puede degradar a un bloqueo sesgado. Esta estrategia puede mejorar la eficiencia de adquirir y liberar bloqueos.

sin estado de bloqueo

        Cuando se crea un objeto Java por primera vez, no hay subprocesos para competir por él, lo que indica que el objeto está en un estado sin bloqueo. En este momento, el indicador de bloqueo sesgado es 0 y el estado de bloqueo es 01.

Estado de bloqueo de polarización

        Bloqueo sesgado significa que el mismo subproceso ha accedido a una parte del código de sincronización, luego el subproceso adquirirá automáticamente el bloqueo, reduciendo así el costo de adquirir el bloqueo. Si el bloqueo incorporado está en un estado sesgado, cuando hay un subproceso compitiendo por el bloqueo, el bloqueo sesgado se usa primero, lo que indica que el bloqueo incorporado prefiere este subproceso. Cuando este subproceso desea ejecutar el código de sincronización asociado con la cerradura, no es necesario realizar ninguna comprobación ni conmutación. Los bloqueos sesgados son muy eficientes cuando la competencia no es feroz, porque Mark Word en el estado de bloqueo sesgado registrará la identificación del subproceso que prefiere el bloqueo integrado, y el bloqueo integrado considerará el subproceso como su conocido.

estado de bloqueo ligero

        Cuando dos subprocesos comienzan a competir por este objeto de bloqueo, la situación cambia. Ya no es un bloqueo sesgado (exclusivo). El bloqueo se actualizará a un bloqueo ligero. Dos subprocesos compiten de manera justa. ¿Qué subproceso ocupa el objeto de bloqueo primero? Marcar Palabra del objeto de bloqueo apunta al registro de bloqueo en el marco de pila del subproceso.

        Cuando el bloqueo se encuentra en un bloqueo sesgado y otro subproceso intenta apropiarse de él, el bloqueo sesgado se actualizará a un bloqueo ligero. El subproceso que intenta apropiarse intentará adquirir el bloqueo en forma de giro y no bloqueará el subproceso que captura el bloqueo, a fin de mejorar el rendimiento.

        El principio de giro es muy simple: si el subproceso que mantiene el bloqueo puede liberar el recurso de bloqueo en poco tiempo, entonces los subprocesos que esperan el bloqueo competidor no necesitan cambiar entre el estado del kernel y el estado del usuario para ingresar al bloqueo y la suspensión. Solo necesita esperar (girar), y el bloqueo se puede adquirir inmediatamente después de que el subproceso que mantiene el bloqueo lo libere, evitando así el consumo del subproceso del usuario y el cambio de kernel.

        Sin embargo, el giro del subproceso necesita consumir CPU.Si no se puede adquirir el bloqueo, el subproceso no siempre puede ocupar la CPU para girar por trabajo inútil, por lo que es necesario establecer un tiempo máximo para la espera de giro. La elección del ciclo de giro de JVM, Java6 introdujo bloqueos de giro adaptables, los bloqueos de giro adaptables significan que el tiempo de giro no es fijo, sino el tiempo de giro anterior en el mismo bloqueo y el tiempo del bloqueo depende del estado del propietario. Si el hilo gira con éxito, el número de giros será mayor la próxima vez, y si el giro falla, el número de giros se reducirá.

        Si el tiempo de ejecución del subproceso que mantiene el bloqueo excede el tiempo máximo de espera de giro y el bloqueo no se libera, otros subprocesos que compiten por el bloqueo aún no podrán adquirir el bloqueo dentro del tiempo máximo de espera y el giro no continuará. Para siempre Cuando el subproceso de contención deja de girar y entra en el estado de bloqueo, el bloqueo se expande en un bloqueo de peso pesado.

Estado de bloqueo pesado

        Los candados pesados ​​bloquearán otros subprocesos aplicados y reducirán el rendimiento. Los bloqueos pesados ​​también se denominan bloqueos de sincronización.Este objeto de bloqueo Mark Word cambia de nuevo y apunta a un objeto de monitor que registra y administra subprocesos en cola en forma de colección.

Resumir

Al usar el bloqueo incorporado de Java, no es necesario adelantarse y liberar explícitamente el monitor del objeto de sincronización a través del código Java.Estas tareas las realiza la JVM subyacente, y cualquier objeto Java se puede usar como un bloqueo incorporado. entonces el bloqueo de objetos de Java es muy conveniente de usar. Sin embargo, los bloqueos incorporados de Java tienen funciones relativamente simples y no tienen algunas funciones de bloqueo más avanzadas, como tomas de bloqueo de tiempo limitado, tomas de bloqueo interrumpibles y múltiples colas de espera. Además de estos problemas funcionales, existen problemas de rendimiento con el bloqueo de objetos de Java. En el caso de una competencia levemente feroz, los bloqueos de objetos de Java se expandirán a bloqueos pesados, y las operaciones de activación y bloqueo de subprocesos de los bloqueos pesados ​​requieren que el proceso cambie entre el modo kernel y el modo usuario, lo que resulta en un rendimiento muy bajo. Por lo tanto, es urgente proporcionar una nueva cerradura para mejorar el rendimiento de las cerraduras en escenarios de contención intensa.

10. Bloqueo explícito JUC

        A diferencia del bloqueo integrado de Java, el bloqueo explícito de JUC es un bloqueo muy flexible implementado en lenguaje Java puro. El uso de este bloqueo es muy flexible y puede realizar bloqueos incondicionales, consultables, temporizados e interrumpibles. Operaciones de adquisición y liberación. Dado que los métodos de bloqueo y desbloqueo de JUC se realizan explícitamente a través de la API de Java, también se denomina bloqueo explícito.

        Java5 introdujo la interfaz Lock, que es un bloqueo a nivel de código Java. Para distinguirlo de los bloqueos de objetos de Java, la interfaz de bloqueo se denomina interfaz de bloqueo explícito y su instancia de objeto se denomina objeto de bloqueo explícito. Se presentará en detalle más adelante.

11. Bloqueo exclusivo y bloqueo compartido

        La operación de bloqueo se realiza antes de acceder al recurso compartido y la operación de desbloqueo se realiza después de que se completa el acceso. De acuerdo con "si se permite que varios subprocesos lo retengan al mismo tiempo", los bloqueos se pueden dividir en bloqueos compartidos y bloqueos exclusivos.

        Los bloqueos exclusivos también se denominan bloqueos exclusivos, bloqueos mutex y bloqueos exclusivos, lo que significa que un bloqueo solo puede ser mantenido por un subproceso a la vez. Después de que un subproceso se bloquea, cualquier otro subproceso que intente bloquearlo nuevamente se bloqueará hasta que el subproceso que mantiene el bloqueo lo desbloquee. En términos sencillos, solo se puede acceder a los recursos compartidos por un subproceso a la vez, y el resto de los subprocesos están bloqueados y esperando.

        Si es un bloqueo exclusivo justo, si más de un subproceso está bloqueando y esperando cuando el subproceso que mantiene el bloqueo está desbloqueado, entonces el subproceso que agarra el bloqueo primero se despertará y estará listo para realizar la operación de bloqueo, y otros subprocesos lo harán. todavía bloquear y esperar. Tanto el bloqueo incorporado sincronizado como el bloqueo explícito ReentrantLock en Java son bloqueos exclusivos.

        Los bloqueos compartidos son bloqueos que permiten que varios subprocesos se mantengan al mismo tiempo. Por supuesto, el subproceso que adquiere el bloqueo compartido solo puede leer los datos en la sección crítica y no puede modificar los datos en la sección crítica. Los bloqueos compartidos en JUC incluyen Semaphore (semáforo), bloqueo de lectura en ReadLock (bloqueo de lectura y escritura), CountDownLatch, etc.

12. Bloqueo pesimista y bloqueo optimista

        Un bloqueo exclusivo es en realidad un bloqueo pesimista, y el sincronizado de Java es un bloqueo pesimista. El bloqueo pesimista garantiza el acceso exclusivo a las secciones críticas, independientemente del subproceso que mantenga el bloqueo. Aunque la lógica del bloqueo pesimista es muy simple, existen muchos problemas.

        El bloqueo pesimista siempre asume que sucederá lo peor, y cada vez que un subproceso lee datos, también se bloquea. De esta manera, otros hilos se bloquearán al leer datos hasta que obtenga el bloqueo. Las bases de datos relacionales tradicionales utilizan muchos bloqueos pesimistas, como bloqueos de fila, bloqueos de tabla, bloqueos de lectura y bloqueos de escritura.

El mecanismo de bloqueo pesimista tiene los siguientes problemas:

(1) Bajo la competencia de subprocesos múltiples, agregar y liberar bloqueos provocará más cambios de contexto y retrasos en la programación, lo que provocará problemas de rendimiento.

(2) Después de que un subproceso mantiene el bloqueo, hará que todos los demás subprocesos que se apropian del bloqueo se cuelguen.

(3) Si un subproceso de alta prioridad espera a que un subproceso de baja prioridad libere el bloqueo, la prioridad del subproceso se invertirá, provocando riesgos de rendimiento.

        Una forma efectiva de resolver los problemas anteriores del bloqueo pesimista es usar el bloqueo optimista en lugar del bloqueo pesimista. De manera similar, las actualizaciones de datos con números de versión en las operaciones de la base de datos y las clases atómicas en los paquetes JUC utilizan el bloqueo optimista para mejorar el rendimiento.

13. Sincronizador abstracto AQS

        En una escena con contención intensa, el uso de un bloqueo ligero basado en giros de CAS dará como resultado giros vacíos de CAS viciosos que desperdiciarán una gran cantidad de recursos de la CPU. Hay dos soluciones a este problema: puntos de acceso de operación descentralizados y recorte de picos utilizando colas. El paquete concurrente JUC utiliza la solución de reducción de picos de cola para resolver el problema de rendimiento de CAS y proporciona una clase base de reducción de picos basada en una cola bidireccional: la clase base abstracta AbstractQueuedSynchronizer (clase sincronizadora abstracta, denominada AQS) .

        AQS es una clase básica proporcionada por JUC para construir bloqueos y contenedores de sincronización. Muchas clases en el paquete JUC están basadas en AQS, como ReentrantLock, Semaphore, CountDownLatch, ReentrantReadWriteLock, FutureTask, etc. AQS se ocupa de muchos de los detalles del diseño al implementar contenedores sincrónicos.

        La cola AQS mantiene internamente una lista FIFO doblemente enlazada.La característica de esta estructura es que cada estructura de datos tiene dos punteros, que apuntan al nodo predecesor directo y al nodo sucesor directo respectivamente. Por lo tanto, la lista doblemente enlazada puede acceder fácilmente al nodo predecesor y al nodo sucesor desde cualquier nodo. Cada nodo está encapsulado por un subproceso. Cuando el subproceso no puede competir por el bloqueo, se encapsulará en un nodo y se agregará a la cola AQS; cuando el subproceso que adquiere el bloqueo lo libera, se despertará un subproceso bloqueado. de la cola

Finalmente~

Hay muchos conceptos imprescindibles relacionados con la programación de alta concurrencia de Java. Aquí, se resumen y clasifican 13 conceptos más importantes, y solo se hace una descripción simple del concepto, y no involucra ejemplos de código, principios de implementación subyacentes, etc.

[Sin acumular silicio, no hay manera de llegar a mil millas, avancemos juntos~]

Supongo que te gusta

Origin blog.csdn.net/qq_29119581/article/details/129271987
Recomendado
Clasificación