Antecedentes técnicos del grupo de subprocesos
En la programación orientada a objetos, crear y destruir objetos lleva mucho tiempo, porque crear un objeto requiere recursos de memoria u otros recursos más. Esto es aún más en Java, donde la máquina virtual intentará rastrear cada objeto para que pueda ser recolectado de basura después de que se destruya el objeto.
Por lo tanto, una forma de mejorar la eficiencia de los programas de servicio es minimizar el número de veces de crear y destruir objetos, especialmente algunos objetos que requieren recursos, creación y destrucción de objetos. Cómo usar objetos existentes para servir es un problema clave que debe resolverse. De hecho, esta es la razón por la cual se producen algunas tecnologías de "recursos de agrupación".
Por ejemplo, muchos componentes comunes comúnmente vistos en Android son generalmente inseparables del concepto de "grupo", como varias bibliotecas de carga de imágenes y bibliotecas de solicitudes de red. Incluso si MeaAsge en el mecanismo de mensajería de Android utiliza MeAaSge.obtain (), es el objeto en el grupo de MeaAsge utilizado, por lo que este concepto es muy importante. La tecnología de agrupación de hilos introducida en este artículo también se ajusta a esta idea.
Ventajas del grupo de hilos:
1. Reutilizar hilos en el grupo de hilos para reducir la sobrecarga de rendimiento causada por la creación y destrucción de objetos;
2. Puede controlar efectivamente el número máximo de concurrencia de hilos, mejorar la utilización de recursos del sistema y evitar la competencia excesiva de recursos y evitar el bloqueo;
3. Capacidad para realizar una gestión simple de múltiples hilos, lo que hace que el uso de hilos sea simple y eficiente.
Ejecutor de marco de grupo de hilos
El grupo de subprocesos en Java se implementa a través del marco del Ejecutor. El marco del ejecutor incluye clases: ejecutor, ejecutores, eCutororService, Threadpoolexecutor, llamable y futuro, uso de FutureTask, etc.
Ejecutor: solo hay un método para todas las interfaces de grupo de hilos.
Ejecutor de interfaz pública {Void Execute (comando runnable); }EjecutorService: Agregar el comportamiento del ejecutor es la interfaz más directa de la clase de implementación del ejecutor.
Ejecutores: proporciona una serie de métodos de fábrica para crear el grupo de subprocesos, y los grupos de subprocesos devueltos implementan la interfaz EjecutorService.
ThreadPoolExecutor: la clase de implementación específica de grupos de hilos. Los diversos grupos de subprocesos generalmente utilizados se implementan en función de esta clase. El método de construcción es el siguiente:
Public ThreadPoolExeCutor (int corePoolSize, int MaximumummoolSize, Long KeepAlivEtime, TimeUnit Unit, Bloquingqueue <Runnable> WorkQuqueue) {this (corepoolSize, MaximumumupoolSize, KeepAlivetime, Unidad, Workqueue, ejecutores.defaulthreadory (), defaulthandler);}CorePoolSize: el número de hilos centrales en el grupo de subprocesos y el número de hilos que se ejecutan en el grupo de subprocesos nunca excederán CorePoolSize y pueden sobrevivir de forma predeterminada. Puede establecer ToDodCorethreadTimeOut en verdadero, el número de hilos principales es 0, y KeepAlInVeTime controla el tiempo de tiempo de espera de todos los hilos.
MaximuMuboolSize: el número máximo de hilos permitidos por la piscina de subprocesos;
KeepAlivetime: se refiere al tiempo de tiempo de espera cuando termina el hilo inactivo;
Unidad: es un enum que representa la unidad de KeepAlivetime;
WorkQueue: representa la cola Bloquingqueue <runnable que almacena la tarea.
Bloqueque: Bloquingqueue es una herramienta que se usa principalmente para controlar la sincronización de hilos bajo java.util.concurrent. Si la Blockqueue está vacía, la operación de obtener algo de la Bloqueja se bloqueará y no se despertará hasta que la Bloquea entre en la cosa. Del mismo modo, si el bloqueo de bloqueo está lleno, cualquier operación que intente almacenar cosas se bloqueará y no se despertará para continuar las operaciones hasta que haya espacio en la Bloquea. Las colas de bloqueo a menudo se usan en escenarios de productores y consumidores. Los productores son hilos que agregan elementos a las colas, y los consumidores son hilos que toman elementos de las colas. Una cola de bloqueo es el contenedor donde el productor almacena elementos, y el consumidor solo toma elementos del contenedor. Las clases de implementación específicas incluyen Linked Bloquingqueue, ArrayBlokingqueed, etc. En general, el bloqueo interno y el despertar se logran a través del bloqueo y la condición (aprendizaje y uso de bloqueos y condiciones de visualización).
El proceso de trabajo del grupo de subprocesos es el siguiente:
Cuando se creó la piscina de hilo por primera vez, no había hilo adentro. La cola de tareas se pasa como parámetro. Sin embargo, incluso si hay tareas en la cola, el grupo de subprocesos no las ejecutará de inmediato.
Al agregar una tarea llamando al método Execute (), el grupo de subprocesos hará el siguiente juicio:
Si el número de hilos en ejecución es menor que CorePoolSize, cree un hilo para ejecutar esta tarea de inmediato;
Si el número de subprocesos en ejecución es mayor o igual a CorePoolSize, entonces coloque esta tarea en la cola;
Si la cola está llena en este momento y el número de subprocesos en ejecución es menor que MaximummoolSize, entonces aún necesita crear un hilo no básico para ejecutar la tarea de inmediato;
Si la cola está llena y el número de hilos en ejecución es mayor o igual a MaximoMuboolSize, el grupo de subprocesos lanzará una excepción de DelechExecutionException.
Cuando un hilo completa una tarea, toma la siguiente tarea de la cola para ejecutar.
Cuando un hilo no tiene nada que hacer, y después de un cierto período de tiempo (KeepAlivetime), el grupo de hilos juzgará que si el número de hilos que se ejecuta actualmente es mayor que CorePoolSize, entonces el hilo se detendrá. Entonces, después de que se completen todas las tareas del grupo de subprocesos, eventualmente se reducirá al tamaño de CorePoolSize.
Creación y uso de piscinas de hilos
El grupo de subprocesos de generación utiliza el método estático de los ejecutores de clase de herramientas. Los siguientes son varias piscinas de hilos comunes.
SinglethreadExecutor: hilo de fondo único (la cola de búfer está ilimitada)
public static EjecutorService NewsingLethreadExeCutor () {return new FinalInseDeLegedExecutorService (new ThreadPoolExecutor (1, 1, 0L, TimeUnit.MilliseConds, New LinkedBlockqueue <Runnable> ())); }Crea un solo grupo de hilo. Este grupo de subprocesos tiene solo un hilo central de trabajo, que es equivalente a un solo hilo que realiza todas las tareas en serie. Si este hilo único termina debido a la excepción, entonces habrá un nuevo hilo para reemplazarlo. Este grupo de subprocesos asegura que la orden de ejecución de todas las tareas se ejecute en el orden de la presentación de la tarea.
FixedThreadPool: solo el grupo de roscas de hilos de núcleo, con tamaño fijo (la cola de búfer está ilimitada).
Public static EjecutorService NewfixedThreadPool (int nthreads) {
devolver nuevo ThreadPoolExecutor (nthreads, nthreads,
0l, TimeUnit.MilliseConds,
nuevo LinkedBlowingqueue <Runnable> ());
}
Cree un grupo de subprocesos de tamaño fijo. Cada vez que se envía una tarea, se crea un hilo hasta que el hilo alcanza el tamaño máximo del grupo de subprocesos. El tamaño del grupo de subprocesos sigue siendo el mismo una vez que alcanza su valor máximo. Si un hilo termina debido a una excepción de ejecución, el grupo de subprocesos agregará un nuevo hilo.
CachedThreadPool: grupo de subprocesos ilimitados, puede realizar un reciclaje automático de hilos.
public static EjecutorService NewCachedThreadPool () {return New ThreadPoolExeCutor (0, Integer.max_value, 60L, TimeUnit.Seconds, New SynChonousqueue <Runnable> ()); }Si el tamaño del grupo de subprocesos excede el hilo requerido para procesar la tarea, algunos de los hilos inactivos (sin ejecución de la tarea en 60 segundos) se reciclarán. Cuando aumenta el número de tareas, este grupo de subprocesos puede agregar inteligentemente nuevos hilos para procesar la tarea. Este grupo de subprocesos no limita el tamaño del grupo de subprocesos, que depende completamente del tamaño máximo de hilo que el sistema operativo (o JVM) puede crear. Synchronousqueue es una cola de bloqueo con tampón de 1.
ProchuledThreadPool: un grupo de subprocesos de núcleo con grupo de subprocesos de núcleo fijo, tamaño ilimitado. Este grupo de hilos admite la necesidad de realizar tareas periódica y periódicamente.
Public static ejecutorservice NewsCheduledThreadPool (int corePoolSize) {return New SchedulEdThreadPool (corePoolSize, Integer.max_value, default_keepalive_millis, MilliseConds, New DardedWorkqueue ()); }Cree un grupo de hilos que realice tareas periódicamente. Si está inactivo, el grupo de subprocesos no básicos se reciclará dentro del tiempo default_keepalivemillis.
Hay dos métodos más utilizados para enviar tareas en grupos de subprocesos:
ejecutar:
EjecutorService.Execute (Runnable Runable);
Entregar:
FutUreTask Task = ExecutorService.subMit (runnable runnable);
FutUreTask <T> tarea = ExecutorService.subMit (runnable runnable, t resultados);
FutUreTask <T> tarea = ExecutorService.submit (llamable <t> llamable);
Lo mismo se aplica a la implementación de Subt (llameable Callable) y lo mismo se aplica a Subt (Runnable Runnable).
public <t> Future <T> Subt (Callable <T> tarea) {if (task == null) Throw New NullPointerException (); FutUreTask <T> ftask = newTaskfor (tarea); ejecutar (ftask); devolver ftask;}Se puede ver que enviar es una tarea que devuelve un resultado y devolverá un objeto FuturetAk, para que el resultado se pueda obtener a través del método get (). La llamada final a enviar también es ejecutada (ejecutable en ejecución). Enviar solo encapsule el objeto llamable o se ejecuta en un objeto FutUreTask. Debido a que FutUreTask es un ejecutable, se puede ejecutar en ejecución. Para cómo los objetos llamables y los runnables se encapsulan en objetos FutureTask, consulte el uso de FutureTask, futuro, Futuretask.
El principio de la implementación del grupo de hilos
Si solo habla sobre el uso de grupos de hilos, entonces este blog no tiene un gran valor. En el mejor de los casos, es solo un proceso para familiarizarse con la API relacionada con el albacea. El proceso de implementación del grupo de subprocesos no utiliza la palabra clave sincronizada, sino que usa colas volátiles, de bloqueo y sincrónicas (de bloqueo), clases atómicas relacionadas, FutureTask, etc., porque este último tiene un mejor rendimiento. El proceso de comprensión puede aprender la idea del control concurrente en el código fuente.
Las ventajas de la agrupación de hilos mencionadas al principio se resumen de la siguiente manera:
Reutilización de hilo
Controlar el número máximo de concurrencias
Administrar hilos
1. Proceso de multiplexación de hilos
Para comprender el principio de la multiplexación de hilos, primero debe comprender el ciclo de vida del hilo.
Durante el ciclo de vida de un hilo, debe pasar por cinco estados: nuevo, ejecutable, corriendo, bloqueado y muerto.
Thread crea un nuevo hilo a través de nuevo. Este proceso es inicializar alguna información de hilo, como el nombre del hilo, la identificación, el grupo al que pertenece el hilo, etc., que puede considerarse como un objeto ordinario. Después de llamar al inicio () de Thread, la máquina virtual Java crea una pila de llamadas de método y un contador de programas para ella, y al mismo tiempo, ha sido verdadero, y luego habrá una excepción al llamar al método de inicio.
El hilo en este estado no comienza a funcionar, sino que solo significa que el hilo puede ejecutarse. En cuanto a cuando el hilo comienza a funcionar, depende del planificador en el JVM. Cuando el hilo obtiene la CPU, se llamará al método run (). No llame al método run () de hilo usted mismo. Luego, cambie entre el bloqueo de carrera listo de acuerdo con la programación de la CPU, hasta que finalice el método Run () u otros métodos detengan el hilo e ingrese al estado muerto.
Por lo tanto, el principio de implementar la reutilización del hilo debe ser mantener vivo el hilo (listo, ejecutando o bloqueando). A continuación, echemos un vistazo a cómo ThreadPoolExeCutor implementa la reutilización de Thread.
La clase principal de trabajadores en ThreadPoolExeCutor controla la reutilización de subprocesos. Eche un vistazo al código simplificado de la clase de trabajadores, por lo que es fácil de entender:
Private Final Clase Worker implementa Runnable {Final Thread Thread; Runnable FirstTask; Worker (Runnable FirstTask) {this.FirstTask = FirstTask; this.thread = getThreadFactory (). NewThread (this);} public void run () {runworker (this);} Final void runworker (trabajador w) {runnable tarea = w.firstTask; w.firstTask; null; while (task! = null || (tarea = getTask ())! = null) {task.run ();}}El trabajador es ejecutable y tiene un hilo al mismo tiempo. Este hilo es el hilo a abrir. Al crear un nuevo objeto de trabajador, se crea un nuevo objeto de subproceso al mismo tiempo, y el trabajador mismo se pasa a Tthread como un parámetro. De esta manera, cuando se llama el método Start () de hilo, el método Run () de trabajador realmente se está ejecutando. Luego, en RunWorker (), hay un bucle de tiempo, que sigue obteniendo el objeto Runnable de getTask () y lo ejecuta en secuencia. ¿Cómo obtiene getTask () el objeto Runnable?
Sigue siendo el código simplificado:
private runnable getTask () {if (algunos casos especiales) {return null; } Runnable r = workqueue.take (); return r;}Esta trabajo de trabajo es la cola de bloqueo que almacena tareas al inicializar Threadpoolexecutor. La cola almacena las tareas ejecutables que se ejecutarán. Debido a que Bloquingqueue es una cola de bloqueo, Bloquingqueue.take () se vacía y ingresa al estado de espera hasta que se agrega un nuevo objeto en Bloquingqueue para despertar el hilo bloqueado. Por lo tanto, en general, el método run () de hilo no finalizará, pero continuará ejecutando tareas ejecutables de WorkQuqueue, que logra el principio de reutilización de hilos.
2. Controle el número máximo de concurrencias
Entonces, ¿cuándo se pone en funcionamiento en trabajo? ¿Cuándo se crea el trabajador? ¿Cuándo se llama el hilo en el trabajador llamado inicio () para abrir un nuevo hilo para ejecutar el método Worker Run ()? El análisis anterior muestra que el RunWorker () en el trabajador realiza tareas una tras otra, en serie, entonces, ¿cómo se manifiesta la concurrencia?
Es fácil pensar que realizará algunas de las tareas anteriores cuando ejecute (runnable runnable). Veamos cómo se hace en ejecución.
ejecutar:
Código simplificado
public void Execute (comando runnable) {if (command == null) throw new nullPointerException (); int c = ctl.get (); // Número actual de hilos <corepoolSizeIF (WorkerCountOf (c) <corePoolSize) {// Inicie directamente un nuevo hilo. if (addWorker (command, true)) return; c = ctl.get ();} // número de hilos activos> = corePoolSize // runstate está ejecutando && queue no está lleno if (isrunning (c) && workqueue.offer (command)) {int reconocido = ctl.get (); // verificar si está ejecutando status status // el statuse no rasural (! isRunning (reclace) && remove (command)) rechazar (comando); // Use la estrategia especificada por el grupo de subprocesos para rechazar la tarea // dos casos: // 1. Estado que no es de running rechaza nuevas tareas // 2. La cola está completa y no se pudo iniciar un nuevo hilo (WorkCount> Maximumumumumsize)} más si (! ADDWORKER (comando)) rechazado (comando);};}AddWorker:
Código simplificado
Private Boolean addWorker (Runnable FirstTask, Boolean Core) {int wc = WorkerCountOf (c); if (wc> = (core? corePoolSize: maximumumpolSize)) {return false;} w = new Worker (FirstTask); Hilo final t = W.Thread; T.Start ();}Según el Código, veamos la situación mencionada anteriormente de agregar tareas durante el trabajo del grupo de subprocesos:
* Si el número de hilos en ejecución es menor que CorePoolSize, cree un hilo para ejecutar esta tarea de inmediato;
* Si el número de subprocesos en ejecución es mayor o igual a CorePoolSize, entonces coloque esta tarea en la cola;
* Si la cola está llena en este momento y el número de subprocesos en ejecución es menor que MaximoMuboolSize, entonces aún necesita crear un hilo no básico para ejecutar la tarea de inmediato;
* Si la cola está llena y el número de subprocesos en ejecución es mayor o igual a MaximoMuboolSize, el grupo de subprocesos lanzará una excepción de DelechExecutionException.
Esta es la razón por la cual AsyncTask de Android se ejecuta en paralelo y excede el número máximo de tareas y arroja DelechExecutionException. Para más detalles, consulte la última versión de la interpretación del código fuente de AsyncTask y el lado oscuro de AsyncTask
Si un nuevo hilo se crea con éxito a través de AddWorker, Start () y use FirstTask como la primera tarea ejecutada en Run () en este trabajador.
Aunque la tarea de cada trabajador es el procesamiento en serie, si se crean múltiples trabajadores, ya que comparten un trabajo de trabajo, se procesarán en paralelo.
Por lo tanto, el número de concurrencia máximo se controla de acuerdo con CorePoolSize y MaximoMuboolSize. El proceso general puede representarse por la figura a continuación.
La explicación y las imágenes anteriores pueden entenderse bien.
Si se dedica al desarrollo de Android y está familiarizado con los principios del controlador, puede pensar que esta imagen es bastante familiar. Algunos de los procesos son muy similares a los utilizados por Handler, Looper y MEAASGE. Handler.send (mensaje) es equivalente a ejecutar (runnuble). La cola MeaAsge mantenida en Looper es equivalente a Bloquingqueue. Sin embargo, debe mantener esta cola por sincronización. La función Loop () en Looper Loops para tomar MeaAsge de la cola MeaAsge y el Runwork () en el trabajador se puede ejecutar continuamente desde Bloquingqueue.
3. Administre hilos
A través del grupo de subprocesos, podemos administrar la reutilización de subprocesos, controlar el número de concurrencia y destruir procesos. La reutilización de hilo y la concurrencia de control se han discutido anteriormente, y el proceso de gestión de hilos se ha intercalado en él, lo cual es fácil de entender.
Hay una variable CTL AtomicInteger en ThreadpoolExecutor. Se guardan dos contenidos a través de esta variable:
El número de todos los hilos. Cada hilo está en un estado donde se almacenan los 29 bits de hilos inferiores y se almacenan los 3 bits más altos de Runstate. Se obtienen diferentes valores a través de operaciones de bits.
Private final AtomicInteger ctl = new AtomiCInteger (ctlof (running, 0))); // Obtener el estado estatal de hilo está estático privado int runStateOf (int c) {return c & ~ capacidad;} // Obtener el número de trabajadores estatales privados int workerCountOf (int c) {return c & capacidad;} // Obtener el número de trabajadores de los trabajadores en el trabajo está en el trabajo static intent de trabajo (int c) { El hilo está ejecutando un booleano estático privado (int c) {return c <shutdown;}Aquí, el proceso de apagado del grupo de hilos se analiza principalmente por Shutdown y ShutdownNow (). En primer lugar, el grupo de subprocesos tiene cinco estados para controlar la adición y ejecución de tareas. Se introducen los siguientes tres tipos principales:
Estado de ejecución: el grupo de subprocesos se ejecuta normalmente y puede aceptar nuevas tareas y procesar tareas en la cola;
Estado de cierre: no se aceptan nuevas tareas, pero se ejecutarán tareas en la cola;
Estado de parada: ya no se aceptan nuevas tareas, y el apagado de la tarea no se procesa en la cola. Este método establecerá RunState para apagar y terminará todos los hilos inactivos, mientras que los hilos que aún funcionan no se ven afectados, por lo que se ejecutará la persona de tarea en la cola.
El método SHUTDOWNNOW establece RunState para detener. La diferencia entre el método de apagado, este método terminará todos los hilos, por lo que las tareas en la cola no se ejecutarán.
Resumen: a través del análisis del código fuente de ThreadPoolExeCutor, tenemos una comprensión general del proceso de crear grupos de subprocesos, agregar tareas y ejecutar grupos de hilos. Si está familiarizado con estos procesos, será más fácil usar grupos de subprocesos.
Parte del control de la concurrencia aprendió de él y el uso del procesamiento de tareas de los modelos de consumidores productores será de gran ayuda para comprender o resolver otros problemas relacionados en el futuro. Por ejemplo, el mecanismo del controlador en Android, y la cola de Messager en Looper también está bien para usar una Blookqueue para manejarlo. Esta es la ganancia de leer el código fuente.
Lo anterior es la información que clasifica el grupo de hilos Java. Continuaremos agregando información relevante en el futuro. ¡Gracias por su apoyo para este sitio web!