High Score es un programa de subprocesos escrito en Qt. La función es consultar datos cada 100 milisegundos. Este subproceso se ejecuta durante todo el programa.
De: Developers Online
Análisis detallado de la programación multiproceso de Java
1. Comprensión del multiproceso
Multiproceso es así Un mecanismo que permite ejecutar múltiples flujos de instrucciones simultáneamente en un programa. Cada flujo de instrucciones se denomina subproceso y es independiente entre sí.
Los subprocesos también se denominan procesos ligeros. Tienen control de ejecución independiente como los procesos y son programados por el sistema operativo. La diferencia es que los subprocesos no tienen espacio de almacenamiento independiente, sino que se comparten con otros subprocesos en el proceso. al que pertenecen. Comparten un espacio de almacenamiento, lo que hace que la comunicación entre subprocesos sea mucho más sencilla que entre procesos.
La ejecución de múltiples subprocesos es concurrente, es decir, lógicamente "simultáneamente", independientemente de si es físicamente "simultáneamente". Si el sistema tiene sólo una CPU, entonces la verdadera "simultaneidad" es imposible, pero como la CPU es muy rápida, los usuarios no pueden sentir la diferencia, por lo que no necesitamos preocuparnos por eso, solo debemos asumir que cada hilo es ejecutado al mismo tiempo.
La mayor diferencia en la programación entre subprocesos múltiples y subprocesos únicos tradicionales es que debido a que los flujos de control de cada subproceso son independientes entre sí, el código entre cada subproceso se ejecuta fuera de orden, lo que resulta en Thread. La programación, la sincronización y otras cuestiones se analizarán más adelante.
Dos: Implementación de subprocesos múltiples en Java
¿También podríamos imaginar lo que debemos hacer para crear un nuevo subproceso? Obviamente, debemos especificar el código que ejecutará este subproceso, ¡y esto es todo lo que necesitamos hacer para implementar subprocesos múltiples en Java!
¡Es increíble! ¿Cómo hace esto Java? ¡Pasa la clase! Como lenguaje completamente orientado a objetos, Java proporciona la clase java.lang.Thread para facilitar la programación multiproceso. Esta clase proporciona una gran cantidad de métodos para facilitarnos el control de nuestros propios subprocesos. Nuestras discusiones futuras se centrarán en esta clase.
Entonces, ¿cómo proporcionamos a Java el código que queremos que ejecute el hilo? Echemos un vistazo a la clase Thread. El método más importante de la clase Thread es run(), que es llamado por el método start() de la clase Thread y proporciona el código que ejecutará nuestro hilo. Para especificar nuestro propio código, ¡simplemente anúlelo!
Método 1: Heredar la clase Thread y anular el método run(). Reescribimos run() en la subclase de la clase Thread creada y agregamos el código que ejecutará el hilo.
El siguiente es un ejemplo:
clase pública MyThread extends Thread
{
int count= 1, number
public MyThread( int num)
{
número = num
System.out.println
("Crear hilo" + número);
}
public void run() {
while(true) {
System.out.println
("hilo" + número + ":cuenta" + cuenta
if(++count== 6) return
}
}
public static void main(String args[])
{
for(int i = 0;
i < 5 ; i++) new MyThread(i+1).start();
}
}
Este método es simple y claro, y está en línea con Los hábitos de todos, pero también tiene una gran desventaja, es decir, si nuestra clase ha heredado de una clase (por ejemplo, el subprograma debe heredar de la clase Applet), ya no puede heredar la clase Thread. si no queremos crear una nueva clase, debemos ¿Qué hacer?
También podríamos explorar un nuevo método: no creamos una subclase de la clase Thread, sino que la usamos directamente, luego solo podemos pasar nuestro método como parámetro a una instancia de la clase Thread. que es un poco similar a la función de devolución de llamada. Pero Java no tiene punteros, sólo podemos pasar una instancia de la clase que contiene este método.
Entonces, ¿cómo restringir esta clase para que incluya este método? ¡Usando la interfaz, por supuesto! (Aunque las clases abstractas también pueden satisfacerse, requieren herencia. ¿No es la razón por la que adoptamos este nuevo método simplemente para evitar las restricciones causadas por la herencia?)
Java proporciona la interfaz java.lang Runnable. para apoyar este enfoque.
Método 2: Implementar la interfaz Runnable
La interfaz Runnable tiene solo un método run(). Declaramos que nuestra clase implementa la interfaz Runnable y proporciona este método para escribir nuestro código de hilo. Entre ellos, esta parte de la tarea se ha completado. Sin embargo, la interfaz Runnable no admite subprocesos. También debemos crear una instancia de la clase Thread, que se logra a través del constructor public Thread (objetivo Runnable de la clase Thread);
Aquí hay un ejemplo:
clase pública MyThread implementa Runnable
{
int count= 1, número
public MyThread(int; num)
{
número = num
System.out.println("Crear hilo" + número
}
public void run()
{
while(true)
{
System.out. println
("hilo" + número + ":count" + count);
if(++count== 6) return
}
}
public static void main(String args[])
{
for(int i = 0; i < 5;
i++) new Thread(new MyThread(i+1)).start();
}
}
Estrictamente Se dice que también es factible crear una instancia de una subclase Thread, pero debe tenerse en cuenta que la subclase no debe anular el método de ejecución de la clase Thread, de lo contrario el hilo ejecutará el método de ejecución de la subclase en lugar del anterior. usamos para implementar Runnable El método de ejecución de la clase de interfaz, también puedes probarlo.
El uso de la interfaz Runnable para implementar subprocesos múltiples nos permite contener todo el código en una clase, lo que favorece la encapsulación. Su desventaja es que solo podemos usar un conjunto de código si queremos crear. múltiples subprocesos y Para que cada subproceso ejecute código diferente, aún debe crear una clase adicional. Si este es el caso, en la mayoría de los casos puede no ser tan compacto como heredar directamente Thread de múltiples clases.
En resumen, los dos métodos tienen sus propios méritos y pueden usarse de manera flexible.
Estudiemos algunas cuestiones en el uso de subprocesos múltiples.
3. Cuatro estados de subprocesos
1. Nuevo estado: el subproceso se ha creado pero aún no se ha ejecutado (aún no se ha llamado a start()).
2. Estado ejecutable: El hilo se puede ejecutar, aunque no necesariamente se está ejecutando. Se puede asignar tiempo de CPU a este subproceso en cualquier momento, lo que hace que se ejecute.
3. Estado de muerte: en circunstancias normales, run() regresa y hace que el hilo muera. Llamar a stop() o destroy() tiene el mismo efecto, pero no se recomienda. El primero generará una excepción y el segundo forzará la terminación y no liberará el bloqueo.
4. Estado bloqueado: al subproceso no se le asignará tiempo de CPU y no se podrá ejecutar.
4. Prioridad del subproceso
La prioridad del subproceso representa la importancia del subproceso. Cuando varios subprocesos están en estado ejecutable al mismo tiempo y esperan obtener tiempo de CPU, la programación del subproceso. El sistema determina a quién asignar tiempo de CPU en función de la prioridad de cada subproceso. Los subprocesos con alta prioridad tienen una mayor probabilidad de obtener tiempo de CPU. Los subprocesos con baja prioridad no tienen ninguna posibilidad, pero la posibilidad es menor.
Puedes llamar a los métodos getPriority() y setPriority() de la clase Thread para acceder a la prioridad del hilo. La prioridad del hilo está entre 1 (MIN_PRIORITY) y 10 (MAX_PRIORITY). es 5(NORM_PRIORITY).
5. Sincronización de subprocesos
Dado que varios subprocesos del mismo proceso comparten el mismo espacio de almacenamiento, no solo brinda comodidad, sino que también genera un grave problema de conflicto de acceso.
El lenguaje Java proporciona un mecanismo especial para resolver este conflicto, evitando 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: sincronizado. Métodos y bloques sincronizados.
1. Método sincronizado: declare el método sincronizado agregando la palabra clave sincronizada a la declaración del método. Por ejemplo:
acceso nulo público sincronizado(int newVal);
El método sincronizado controla el acceso a las variables miembro de la clase: cada instancia de clase corresponde a un bloqueo, y cada método sincronizado debe El método solo se puede ejecutar después de obtener el bloqueo de la instancia de clase que llama al método. De lo contrario, el hilo al que pertenece se bloquea una vez que se ejecuta el método, el bloqueo se ocupará exclusivamente. del método. Después de eso, el hilo bloqueado puede obtener el bloqueo y reiniciarlo. Ingrese al estado ejecutable.
Este mecanismo asegura 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 ), evitando así eficazmente conflictos de acceso de las variables miembro de la clase (siempre que todos los métodos que puedan acceder a las variables miembro de la clase estén declarados sincronizados).
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, afectará en gran medida la eficiencia si el método de clase de hilo run() se declara como sincronizado, porque durante todo el ciclo de vida. del hilo, siempre está en ejecución, por lo que su llamada a cualquier método sincronizado de esta clase nunca tendrá é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:
synchronized(syncObject)
{
//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. 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.
6. Bloqueo de subprocesos
Para resolver el conflicto de acceso al área de almacenamiento compartido, Java introdujo un mecanismo de sincronización. Ahora examinemos el impacto de múltiples subprocesos en el almacenamiento compartido. Obviamente, el mecanismo de sincronización ya no es suficiente para acceder a recursos compartidos, porque los recursos requeridos en cualquier momento pueden no estar listos para ser accedidos y, a la inversa, 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.
Bloquear se refiere a pausar la ejecución de un hilo para esperar a que ocurra una determinada condición (como que un determinado 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 hilo entre en el estado de bloqueo dentro del tiempo especificado y no pueda obtener el tiempo de CPU. El tiempo especificado es Después de eso, 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(): los dos métodos se usan juntos. suspender() hace que el hilo entre en un estado de bloqueo y no se debe llamar a su resume() correspondiente. antes de que pueda Hace que el hilo vuelva 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 Yield(): Yield() hace que el subproceso renuncie al tiempo de CPU asignado actualmente, pero no provoca 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 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. Uno permite especificar un período de tiempo. milisegundos. Como parámetro, el otro no tiene parámetros. En el primero, el hilo vuelve a entrar en el estado ejecutable cuando se llama a la notificación () correspondiente o excede el tiempo especificado, y en el segundo, la notificación () correspondiente debe ser. llamado.
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 principales 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 aleatoriamente se bloquee al llamar al método wait() del objeto a 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 deben llamarse en un método o bloque sincronizado. La razón también es muy simple. método o bloqueo Solo cuando la cerradura está ocupada se puede liberar la cerradura.
Por la misma razón, 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 funciones similares a las primitivas del sistema operativo. Su ejecución no será interferida por el mecanismo de subprocesos múltiples, y este par de métodos es equivalente a las primitivas de bloqueo y activación (ambos 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 wait() y notify():
Primero: llamar al método notify() hace que el hilo se desbloquee del hilo bloqueado llamando al método wait() del objeto. Seleccionado aleatoriamente, no podemos predecir qué hilo se seleccionará, por lo que se debe tener especial cuidado al programar para evitar problemas causados por 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() provocará el método wait(). del objeto que se va a llamar. Todos los hilos bloqueados se desbloquean a la vez. Por supuesto, solo el hilo que adquiere el bloqueo puede ingresar al estado ejecutable.
Cuando hablamos de bloqueo, tenemos que hablar de interbloqueo. Un breve análisis puede revelar que tanto el método suspender() como la llamada del método wait() sin especificar un período de tiempo de espera pueden causar un interbloqueo. 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 distintos métodos de bloqueo de subprocesos en Java. Nos hemos centrado en los métodos wait() y notify() porque son los más potentes y flexibles de usar, pero este también. los hace menos eficientes y más propensos a errores. En el uso real, debemos utilizar varios métodos de manera flexible para lograr mejor nuestros objetivos.
7. Hilos de demonio
Los subprocesos de demonio son un tipo especial de subprocesos. La diferencia entre ellos y los subprocesos ordinarios es que no son la parte central de la aplicación. termina, la aplicación finalizará incluso si todavía hay un subproceso demonio ejecutándose. Por el contrario, mientras haya un subproceso que no sea un demonio ejecutándose, la aplicación no finalizará. Los subprocesos de demonio se utilizan generalmente para proporcionar servicios a otros subprocesos en segundo plano.
Puedes determinar si un hilo es un hilo de demonio llamando al método isDaemon(), o puedes llamar al método setDaemon() para configurar un hilo como un hilo de demonio.
8. Grupo de subprocesos
El grupo de subprocesos es un concepto exclusivo de Java, el grupo de subprocesos es un objeto de la clase ThreadGroup. El grupo se especifica cuando se crea el hilo y no se puede cambiar durante toda la vida útil del hilo.
Puede especificar el grupo de subprocesos al que pertenece el subproceso llamando al constructor de la clase Thread que contiene el 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, o podemos hacerlo. iniciar o bloquear todos los hilos en é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 de cada subproceso en el grupo de subprocesos.
9. Resumen
En este artículo, hablamos de todos los aspectos de la programación multiproceso de Java, incluida la creación de subprocesos y la programación y gestión de múltiples subprocesos. Somos profundamente conscientes de la complejidad de la programación de subprocesos múltiples y la ineficiencia de los programas de subprocesos múltiples causada por la sobrecarga del cambio de subprocesos. Esto también nos lleva a pensar seriamente en una pregunta: ¿necesitamos subprocesos múltiples? ¿Cuándo se necesita subprocesos múltiples?
El núcleo del subproceso múltiple es la ejecución simultánea de múltiples bloques de código. La característica esencial es que el código entre cada bloque de código se ejecuta fuera de orden. Si nuestro programa requiere subprocesos múltiples depende de si esta también es su característica inherente.
Si nuestro programa no requiere la ejecución simultánea de múltiples bloques de código, entonces no es necesario utilizar subprocesos múltiples; si nuestro programa requiere la ejecución simultánea de múltiples bloques de código, pero no es así; requiere ejecución fuera de orden, entonces podemos usar un bucle para implementarlo de manera simple y eficiente, y no es necesario usar subprocesos múltiples solo cuando se ajusta completamente a las características de subprocesos múltiples, el mecanismo de subprocesos múltiples; Un potente soporte para la comunicación entre subprocesos y la gestión de subprocesos puede resultar útil. En este momento, vale la pena utilizar subprocesos múltiples.
De: Desarrolladores en línea