-
Dado que varios subprocesos del mismo proceso comparten el mismo espacio de almacenamiento, si bien brinda comodidad, también genera el grave problema de los conflictos de acceso. El lenguaje Java proporciona un mecanismo especial para resolver este conflicto, impidiendo efectivamente que varios subprocesos accedan al mismo objeto de datos al mismo tiempo.
Dado que podemos usar la palabra clave privada para garantizar que solo se pueda acceder al objeto de datos mediante métodos, solo necesitamos proponer un mecanismo para los métodos. Este mecanismo es la palabra clave sincronizada, que incluye dos usos: método sincronizado y bloque sincronizado.
1. método sincronizado: declare el método sincronizado agregando la palabra clave sincronizada en la declaración del método. como:
acceso vacío sincronizado público (int newVal);
El método sincronizado controla el acceso a las variables miembro de la clase: cada instancia de clase corresponde a un bloqueo. Cada método sincronizado debe obtener el bloqueo de la instancia de clase que llama al método antes de que pueda ejecutarse. Una vez que se ejecuta el método, ocupará exclusivamente la variable miembro de la clase. El bloqueo no se libera hasta que regresa de este método. Después de eso, el hilo bloqueado puede obtener el bloqueo y volver a ingresar al estado ejecutable. Este mecanismo garantiza que para cada instancia de clase al mismo tiempo, como máximo una de todas sus funciones miembro declaradas como sincronizadas esté en estado ejecutable (porque como máximo se puede obtener el bloqueo correspondiente a la instancia de clase) (preste atención a esta declaración ! Debido a que es un bloqueo para una instancia de clase, por lo que para un objeto a la vez, solo un hilo ejecuta un método sincronizado a la vez), evitando así efectivamente conflictos de acceso de las variables miembro de la clase (siempre que todos los métodos que puedan las variables miembro de la clase de acceso se declaran como sincronizadas).
En Java, no solo instancias de clase, cada clase también corresponde a un bloqueo. De esta manera, también podemos declarar las funciones miembro estáticas de la clase como sincronizadas para controlar su acceso a las variables miembro estáticas de la clase.
Defectos del método sincronizado: si un método grande se declara como sincronizado, la eficiencia se verá muy afectada. Normalmente, si el método de clase de subproceso run () se declara como sincronizado, continuará ejecutándose durante todo el ciclo de vida del subproceso. Hará que sus llamadas a cualquier método sincronizado de esta clase nunca tengan éxito. Por supuesto, podemos resolver este problema colocando el código que accede a las variables miembro de la clase en un método especial, declarándolo sincronizado y llamándolo en el método principal, pero Java nos proporciona una mejor solución, es decir, el bloque sincronizado.
2. bloque sincronizado: declara el bloque sincronizado mediante la palabra clave sincronizada. La sintaxis es la siguiente:
sincronizado (objeto de sincronización) {
//Código para permitir el control de acceso
}
Un bloque sincronizado es un bloque de código en el que el código debe obtener el bloqueo del objeto syncObject (como se mencionó anteriormente, puede ser una instancia de clase o una clase) antes de que pueda ejecutarse. El mecanismo específico es el mismo que el mencionado anteriormente. Debido a que puede apuntar a cualquier bloque de código y especificar el objeto bloqueado de forma arbitraria, tiene una gran flexibilidad.
Bloqueo de hilos (sincronización)
Para resolver conflictos de acceso a áreas de almacenamiento compartido, Java introdujo un mecanismo de sincronización. Ahora examinemos el acceso de múltiples subprocesos a recursos compartidos. Obviamente, el mecanismo de sincronización ya no es suficiente, porque los recursos necesarios en cualquier momento pueden no serlo. ready Para poder ser accedido, a su vez, puede haber más de un recurso listo al mismo tiempo. Para resolver el problema del control de acceso en este caso, Java introdujo soporte para el mecanismo de bloqueo.
El bloqueo se refiere a pausar la ejecución de un hilo para esperar a que ocurra una determinada condición (como que un recurso esté listo). Los estudiantes que han estudiado sistemas operativos deben estar familiarizados con él. Java proporciona una gran cantidad de métodos para admitir el bloqueo, analicémoslos uno por uno.
1. Método sleep(): sleep() le permite especificar un período de tiempo en milisegundos como parámetro. Hace que el subproceso entre en el estado de bloqueo dentro del tiempo especificado y no pueda obtener el tiempo de CPU. El hilo vuelve a entrar en el estado ejecutable.
Normalmente, dormir() se utiliza cuando se espera que un recurso esté listo: después de que la prueba descubre que no se cumple la condición, deja que el subproceso se bloquee durante un período de tiempo y luego vuelve a probar hasta que se cumpla la condición.
2. Métodos suspender () y resume () (fácil de provocar un punto muerto, desactualizado): los dos métodos se usan juntos. suspender () hace que el hilo entre en un estado de bloqueo y no se recuperará automáticamente. llamado, ¿puede el hilo volver a entrar en el estado ejecutable? Normalmente, suspender() y resume() se utilizan cuando se espera un resultado producido por otro subproceso: después de que la prueba descubre que el resultado no se ha producido, el subproceso se bloquea y después de que otro subproceso produce el resultado, se llama a resume() para retomarlo.
3. Método de rendimiento (): rendimiento () hace que el subproceso abandone el tiempo de CPU asignado actualmente, pero no hace que el subproceso se bloquee, es decir, el subproceso todavía está en un estado ejecutable y se le puede asignar tiempo de CPU nuevamente en en cualquier momento. El efecto de llamar a rendimiento() es equivalente a que el programador considere que el subproceso se ha ejecutado el tiempo suficiente para pasar a otro subproceso.
4. Métodos wait() y notify(): Los dos métodos se usan juntos. wait() hace que el hilo entre en un estado de bloqueo. Tiene dos formas: una permite especificar un período de tiempo en milisegundos como parámetro. otros no tienen parámetros, el primero volverá a entrar en el estado ejecutable cuando se llame al notify () correspondiente o se exceda el tiempo especificado, y el segundo deberá llamarse cuando se llame al notify () correspondiente.
A primera vista, no parecen ser diferentes de los pares de métodos suspender() y resume(), pero en realidad son completamente diferentes. La diferencia principal es que todos los métodos descritos anteriormente no liberarán el candado ocupado (si está ocupado) al bloquear, mientras que esta regla opuesta es la opuesta.
Las diferencias fundamentales anteriores conducen a una serie de diferencias detalladas.
En primer lugar, todos los métodos descritos anteriormente pertenecen a la clase Thread, pero este par pertenece directamente a la clase Object, es decir, todos los objetos tienen este par de métodos. Esto puede parecer increíble al principio, pero en realidad es muy natural, porque cuando este par de métodos se bloquea, el bloqueo ocupado debe liberarse y cualquier objeto posee el bloqueo. Llamar al método wait () de cualquier objeto provoca el. hilo para bloquear y se libera el bloqueo del objeto. Llamar al método notify() de cualquier objeto provocará que un hilo seleccionado al azar se bloquee al llamar al método wait() del objeto que se desbloqueará (pero no se ejecutará hasta que se obtenga el bloqueo).
En segundo lugar, todos los métodos descritos anteriormente se pueden llamar en cualquier ubicación, pero este par de métodos (esperar () y notificar ()) deben llamarse en un método o bloque sincronizado. La razón también es muy simple, solo en un método sincronizado. o bloquear Solo el hilo actual ocupa el bloqueo y el bloqueo se puede liberar. De la misma manera, el bloqueo del objeto que llama a este par de métodos debe ser propiedad del hilo actual, para que se pueda liberar el bloqueo. Por lo tanto, el par de llamadas a métodos debe colocarse en un método o bloque sincronizado cuyo objeto bloqueado sea el objeto que llama al par de métodos. Si no se cumple esta condición, aunque el programa aún puede compilarse, se producirá una excepción IllegalMonitorStateException en tiempo de ejecución.
Las características anteriores de los métodos wait () y notify () determinan que a menudo se usan junto con métodos o bloques sincronizados. Compararlos con el mecanismo de comunicación entre procesos del sistema operativo revelará sus similitudes: los métodos o bloques sincronizados proporcionan Similares. a las funciones de las primitivas del sistema operativo, su ejecución no será interferida por el mecanismo de subprocesos múltiples, y esta contraparte es equivalente a las primitivas de bloqueo y activación (este par de métodos se declaran sincronizados). Su combinación nos permite implementar una serie de exquisitos algoritmos de comunicación entre procesos en el sistema operativo (como algoritmos de semáforo) y pueden usarse para resolver varios problemas complejos de comunicación entre subprocesos.
Dos puntos finales sobre los métodos esperar() y notificar():
Primero: el hilo desbloqueado causado por llamar al método notify () se selecciona aleatoriamente de los hilos bloqueados llamando al método wait () del objeto. No podemos predecir qué hilo se seleccionará, así que tenga especial cuidado al programar para evitarlo. problemas que surgen de esta incertidumbre.
Segundo: además de notify(), también existe un método notifyAll() que también puede desempeñar un papel similar. La única diferencia es que llamar al método notifyAll() eliminará todos los subprocesos bloqueados llamando al método wait() del. objeto a la vez. Todo desbloqueado. Por supuesto, solo el hilo que adquiere el bloqueo puede ingresar al estado ejecutable.
Cuando hablamos de bloqueo, tenemos que hablar de punto muerto. Un breve análisis puede revelar que tanto el método suspender () como la llamada del método esperar () sin especificar un período de tiempo de espera pueden causar un punto muerto. Desafortunadamente, Java no permite evitar interbloqueos a nivel de lenguaje y debemos tener cuidado para evitar interbloqueos en la programación.
Arriba hemos analizado los diversos métodos de bloqueo de subprocesos en Java. Nos centramos en los métodos wait() y notify() porque son los más potentes y flexibles de usar, pero esto también hace que su eficiencia sea menor y. es más propenso a errores. En el uso real, debemos utilizar varios métodos de manera flexible para lograr mejor nuestros objetivos.
hilo demonio
Los subprocesos de demonio son un tipo especial de subproceso. La diferencia entre ellos y los subprocesos ordinarios es que no son la parte central de la aplicación. Cuando todos los subprocesos que no son de demonio de una aplicación terminan, incluso si todavía hay subprocesos de demonio en ejecución, la aplicación. Terminará, por otro lado, la aplicación no terminará mientras haya un subproceso que no sea un demonio ejecutándose. Los subprocesos de demonio se utilizan generalmente para proporcionar servicios a otros subprocesos en segundo plano.
Puede determinar si un subproceso es un subproceso de demonio llamando al método isDaemon(), o puede llamar al método setDaemon() para configurar un subproceso como un subproceso de demonio.
grupo de hilos
El grupo de subprocesos es un concepto exclusivo de Java. En Java, el grupo de subprocesos es un objeto de la clase ThreadGroup. Cada subproceso pertenece a un grupo de subprocesos único. Este grupo de subprocesos se especifica cuando se crea el subproceso y no se puede utilizar durante todo el ciclo de vida. el hilo. Puede especificar el grupo de subprocesos al que pertenece el subproceso llamando al constructor de la clase Thread que contiene un parámetro de tipo ThreadGroup. Si no se especifica, el subproceso toma de forma predeterminada el grupo de subprocesos del sistema denominado sistema.
En Java, todos los grupos de subprocesos deben crearse explícitamente, excepto los grupos de subprocesos del sistema prediseñados. En Java, cada grupo de subprocesos, excepto el grupo de subprocesos del sistema, pertenece a otro grupo de subprocesos. Puede especificar el grupo de subprocesos al que pertenece al crear un grupo de subprocesos. Si no se especifica, pertenece al grupo de subprocesos del sistema. De esta manera, todos los grupos de subprocesos forman un árbol con el grupo de subprocesos del sistema como raíz.
Java nos permite operar en todos los subprocesos de un grupo de subprocesos al mismo tiempo. Por ejemplo, podemos establecer la prioridad de todos los subprocesos llamando al método correspondiente del grupo de subprocesos, y también podemos iniciar o bloquear todos los subprocesos. él.
Otra función importante del mecanismo de grupo de subprocesos de Java es la seguridad de los subprocesos. El mecanismo de grupo de subprocesos nos permite distinguir subprocesos con diferentes características de seguridad a través de la agrupación, manejar subprocesos en diferentes grupos de manera diferente y admitir la adopción de medidas de seguridad desiguales a través de la estructura jerárquica de grupos de subprocesos. La clase ThreadGroup de Java proporciona una gran cantidad de métodos para facilitarnos la operación de cada grupo de subprocesos en el árbol de grupos de subprocesos y cada subproceso en el grupo de subprocesos.
Estado del hilo En un momento dado, un hilo sólo puede estar en un estado.
NUEVO
Los hilos que aún no se han iniciado se encuentran en este estado.
EJECUTABLE
Los subprocesos que se ejecutan en la máquina virtual Java se encuentran en este estado.
OBSTRUIDO
Un subproceso que está bloqueado y esperando un bloqueo del monitor se encuentra en este estado.
ESPERA
Un subproceso que espera indefinidamente a que otro subproceso realice una operación específica se encuentra en este estado.
El estado del hilo de un hilo en espera. Un hilo está en estado de espera porque llamó a uno de los siguientes métodos:
Object.wait sin valor de tiempo de espera
Thread.join sin valor de tiempo de espera
LockSupport.parque
Un subproceso en estado de espera está esperando que otro subproceso realice una operación específica. Por ejemplo, un hilo que ha llamado a Object.wait() en un objeto está esperando a que otro hilo llame a Object.notify() o Object.notifyAll() en ese objeto. El hilo que ha llamado Thread.join() está esperando a que finalice el hilo especificado.
TIMED_WAITING
Un subproceso que está esperando que otro subproceso realice una operación que depende del tiempo de espera especificado se encuentra en este estado.
El estado del hilo de un hilo en espera con un tiempo de espera específico. Un subproceso está en un estado de espera cronometrado llamando a uno de los siguientes métodos con un tiempo de espera positivo especificado:
Hilo.dormir
Object.wait con valor de tiempo de espera
Thread.join con valor de tiempo de espera
LockSupport.parkNanos
LockSupport.parkHasta
FINALIZADO
Un hilo salido se encuentra en este estado.