Todos sabemos que antes de JDK1.5, cuando se implementaba la concurrencia empresarial en Java, los programadores generalmente necesitaban completar la implementación del código de forma independiente. Por supuesto, existen algunos marcos de código abierto que proporcionan estas funciones, pero todavía no son tan útiles. como las funciones que vienen con JDK. Al diseñar programas concurrentes multiproceso Java de alta calidad, para evitar fenómenos como saltos muertos, como esperar (), notificar () y sincronizar antes de usar Java, a menudo es necesario considerar el rendimiento, el punto muerto y la equidad. y los recursos, muchos factores, como la administración y cómo evitar el daño causado por la seguridad de los subprocesos, a menudo adoptan algunas estrategias de seguridad más complejas, lo que aumenta la carga de desarrollo de los programadores. Afortunadamente, después de la aparición de JDK1.5, Sun Master (Doug. Lea) finalmente presentó el kit de herramientas java.util.concurrent para simplificar la finalización concurrente para nosotros, los pequeños y pobres programadores. Los desarrolladores pueden usar esto para reducir efectivamente las condiciones de carrera y los hilos muertos. El paquete concurrente resuelve muy bien estos problemas y nos proporciona un modelo de programa concurrente más práctico.
Ejecutor: el ejecutor de una tarea ejecutable específica.
ExecutorService: un administrador de grupo de subprocesos, hay muchas clases de implementación, presentaré algunas de ellas. Podemos enviar Runnable y Callable al grupo para su programación.
Semáforo: un semáforo que cuenta
ReentrantLock: un bloqueo reentrante mutuamente excluyente, similar en función al sincronizado, pero mucho más potente.
Futuro: es una interfaz para interactuar con Runnable y Callable, como obtener el resultado devuelto después de la ejecución de un hilo, etc. También proporciona cancelación para terminar el hilo.
BlockingQueue: cola de bloqueo.
CompletionService: extensión de ExecutorService, que puede obtener resultados de ejecución de subprocesos
CountDownLatch: una clase auxiliar de sincronización que permite que uno o más subprocesos esperen hasta que se complete un conjunto de operaciones que se realizan en otros subprocesos.
CyclicBarrier: una clase auxiliar de sincronización que permite que un grupo de subprocesos se esperen entre sí hasta que se alcance algún punto de barrera común
Futuro: El futuro representa el resultado del cálculo asincrónico.
ScheduledExecutorService: un ExecutorService que programa comandos para que se ejecuten después de un retraso determinado o en intervalos regulares.
A continuación, los presentaremos uno por uno.
Descripción del método principal de los ejecutores
newFixedThreadPool (grupo de subprocesos de tamaño fijo)
Cree un grupo de subprocesos que pueda reutilizar un conjunto fijo de subprocesos y ejecutar estos subprocesos en una cola ilimitada compartida (solo aquellos que se soliciten esperarán en una cola para su ejecución). Si algún hilo termina debido a una falla durante la ejecución antes del cierre, un nuevo hilo realizará tareas posteriores en su lugar (si es necesario).
newCachedThreadPool (grupo de subprocesos ilimitado, puede realizar reciclaje automático de subprocesos)
Crea un grupo de subprocesos que crea nuevos subprocesos según sea necesario, pero reutiliza subprocesos construidos previamente a medida que estén disponibles. Para los programas que realizan muchas tareas asincrónicas de corta duración, estos grupos de subprocesos suelen mejorar el rendimiento del programa. Llamar a ejecutar reutilizará el hilo construido previamente (si el hilo está disponible). Si no hay ningún hilo existente disponible, se crea un nuevo hilo y se agrega al grupo. Termine y elimine del caché aquellos subprocesos que no se hayan utilizado durante 60 segundos. Por lo tanto, un grupo de subprocesos que permanece inactivo durante mucho tiempo no utilizará ningún recurso. Tenga en cuenta que puede utilizar el constructor ThreadPoolExecutor para crear un grupo de subprocesos con propiedades similares pero detalles diferentes (como parámetros de tiempo de espera).
newSingleThreadExecutor (subproceso de fondo único)
Cree un ejecutor que utilice un único subproceso de trabajo y ejecute el subproceso en una cola ilimitada. (Tenga en cuenta que si este subproceso único finaliza debido a una falla durante la ejecución antes del cierre, un nuevo subproceso realizará tareas posteriores en su lugar, si es necesario). Se garantiza que las tareas se ejecutarán secuencialmente y no habrá más de un subproceso activo en un momento dado. A diferencia del equivalente newFixedThreadPool(1), se garantiza que el ejecutor devuelto por este método podrá utilizar otros subprocesos sin reconfigurarlo.
Estos métodos devuelven objetos ExecutorService, que pueden entenderse como un grupo de subprocesos.
La función de este grupo de subprocesos es relativamente completa. Puede enviar tareas con enviar() y finalizar el grupo de subprocesos con apagar().
importar java.util.concurrent.ExecutorService;importar java.util.concurrent.Executors;clase pública MyExecutor extiende Thread {private int index;public MyExecutor(int i){ this.index=i;}public void run(){ try{ System.out.println("["+this.index+"] inicio...."); Thread.sleep((int)(Math.random()*)); System.out.println("["+this.index+"] fin."); }}public static void main(String args[]){ ExecutorService service=Executors.newFixedThreadPool() for(int i=;i<;i++){ service.execute(new MyExecutor(i)); //service.submit(new MyExecutor(i)); } System.out.println("enviar finalizar");
Aunque se imprime cierta información, no está muy claro cómo funciona este grupo de subprocesos. Aumentemos el tiempo de suspensión 10 veces.
Thread.sleep((int)(Math.random()*10000));
Mirando más allá, verá claramente que solo se pueden ejecutar 4 subprocesos. Cuando se ejecuta un subproceso, se ejecutará un nuevo subproceso, es decir, después de que enviemos todos los subprocesos, el grupo de subprocesos esperará hasta que se ejecute el cierre final. También encontraremos que el hilo de envío se coloca en una "cola ilimitada". Esta es una cola ordenada (BlockingQueue, que se analizará a continuación).
Además, utiliza la función estática de Ejecutores para generar un grupo de subprocesos fijo. Como sugiere el nombre, los subprocesos en el grupo de subprocesos no se liberarán, incluso si están inactivos.
Esto provocará problemas de rendimiento. Por ejemplo, si el tamaño del grupo de subprocesos es 200, cuando se utilicen todos los subprocesos, todos los subprocesos permanecerán en el grupo y la memoria y los subprocesos correspondientes cambiarán (mientras (verdadero) + bucle de suspensión). ) aumentará.
Si desea evitar este problema, debe usar ThreadPoolExecutor() directamente para construirlo. Puede establecer el "número máximo de subprocesos", el "número mínimo de subprocesos" y el "tiempo de mantenimiento de subprocesos inactivos" como un grupo de subprocesos general.
Este es el uso básico del grupo de subprocesos.
Semáforo
Un semáforo de conteo. Conceptualmente, un semáforo mantiene una colección de permisos. Si es necesario, cada adquisición() se bloquea hasta que el permiso esté disponible y luego se adquiere el permiso. Cada lanzamiento() agrega un permiso, lo que potencialmente libera a un adquirente bloqueador. Sin embargo, en lugar de utilizar objetos de licencia reales, Semaphore simplemente cuenta la cantidad de licencias disponibles y toma las medidas correspondientes.
El semáforo se utiliza a menudo para limitar la cantidad de subprocesos que pueden acceder a ciertos recursos (físicos o lógicos). Por ejemplo, la siguiente clase utiliza semáforos para controlar el acceso a un grupo de contenido:
Esta es una situación real. Todos hacen fila para ir al baño. Solo hay dos lugares en el baño. Cuando vienen 10 personas, necesitan hacer fila.
importar java.util.concurrent.ExecutorService;importar java.util.concurrent.Executors;importar java.util.concurrent.Semaphore;clase pública MySemaphore extiende el hilo {Posición del semáforo;privado int id;público MySemaphore(int i,Semaphore s){ this.id=i; this.position=s;}ejecución pública vacía(){ intentar{ if(position.availablePermits()>){ System.out.println("El cliente["+this.id+"] entra al baño, hay espacio"); else{ System.out.println("Cliente["+ this.id+"] ingresa al baño, no hay espacio, cola"); } position.acquire(); System.out.println("El cliente ["+this.id+"] adquiere un asiento en el foso"); Thread.sleep((int)(Math.random()*)); System.out.println("El cliente ["+this.id+"] ha terminado de usar"); ) { e.printStackTrace(); }} public static void main(String args[]){ ExecutorService list=Executors.newCachedThreadPool(); Semáforo(); for(int i=;i<;i++){ list.submit(new MySemaphore(i+,posición)); lista.shutdown(); posición.acquireUninterruptably(); Listo, es necesario limpiar"); position.release();}}
Bloqueo reentrante
Un bloqueo mutex reentrante que tiene el mismo comportamiento y semántica básicos que el bloqueo de monitor implícito al que se accede mediante declaraciones y métodos sincronizados, pero es más potente.
Un ReentrantLock será propiedad del subproceso que adquirió con éxito el bloqueo más recientemente y aún no lo ha liberado. Cuando el bloqueo no es propiedad de otro hilo, el hilo que llama al bloqueo adquirirá con éxito el bloqueo y regresará. Si el hilo actual ya tiene el bloqueo, este método regresa inmediatamente. Puede utilizar los métodos isHeldByCurrentThread() y getHoldCount() para comprobar si esto ocurre.
El constructor de esta clase acepta un parámetro de equidad opcional.
Cuando se establece en verdadero, bajo la competencia de múltiples subprocesos, estos bloqueos tienden a otorgar acceso al subproceso que ha esperado más tiempo. De lo contrario, este bloqueo no garantizará ningún orden de acceso específico.
En comparación con la configuración predeterminada (que usa bloqueo injusto), un programa que usa bloqueo justo tendrá un rendimiento general muy bajo (es decir, será muy lento, a menudo extremadamente lento) cuando muchos subprocesos accedan a él, pero tendrá un rendimiento deficiente al adquirir bloqueos. y asignaciones de bloqueo garantizadas. La diferencia es pequeña cuando se trata de equilibrio.
Sin embargo, cabe señalar que el bloqueo justo no garantiza la equidad de la programación de subprocesos. Por lo tanto, uno de los muchos subprocesos que utilizan un bloqueo justo puede tener múltiples posibilidades de éxito, lo que ocurre cuando otros subprocesos activos no se están procesando y no mantienen actualmente el bloqueo.
También tenga en cuenta que el método tryLock sin tiempo no utiliza configuraciones de equidad. Porque este método puede tener éxito siempre que el bloqueo esté disponible incluso si hay otros subprocesos esperando.
Se recomienda practicar siempre inmediatamente y utilizar un bloque try para llamar al bloqueo. En la construcción antes/después, el código más típico es el siguiente:
clase X {privado final ReentrantLock lock = new ReentrantLock(); // ... public void m() { lock.lock() // bloquear hasta que se cumpla la condición try { // ... cuerpo del método } finalmente { lock. desbloquear() } }}
Mi ejemplo:
importar java.util.concurrent.ExecutorService;importar java.util.concurrent.Executors;importar java.util.concurrent.locks.ReentrantLock;la clase pública MyReentrantLock extiende Thread{TestReentrantLock lock;private int id;public MyReentrantLock(int i,TestReentrantLock test ){ this.id=i; this.lock=prueba;}ejecución pública vacía(){ lock.print(id);}public static void main(String args[]){ ExecutorService service=Executors.newCachedThreadPool(); TestReentrantLock lock=new TestReentrantLock(); for(int i=;i<;i++){ servicio. enviar(nuevo MyReentrantLock(i,lock)); } service.shutdown();}}clase TestReentrantLock{privado ReentrantLock lock=new ReentrantLock(); public void print(int str){ intentar{ lock.lock(); System.out.println(str+"get"); )); } catch(Exception e){ e.printStackTrace(); finalmente{ System.out.println(str+"release");
Cola de bloqueo
Una cola que admite dos operaciones adicionales: esperar a que la cola no esté vacía al recuperar un elemento y esperar a que haya espacio disponible al almacenar un elemento.
BlockingQueue no acepta elementos nulos. Algunas implementaciones arrojan NullPointerException al intentar agregar, colocar u ofrecer un elemento nulo. null se utiliza como valor de advertencia para indicar que la operación de sondeo falló.
BlockingQueue puede tener una capacidad limitada. Puede tener una Capacidad restante en cualquier momento dado, más allá de la cual no puede colocar elementos adicionales sin bloquear.
Un BlockingQueue sin restricciones de capacidad interna siempre informa Integer.MAX_VALUE de la capacidad restante.
La implementación BlockingQueue se utiliza principalmente para colas de productores-consumidores, pero también admite la interfaz Collection. Entonces, por ejemplo, es posible eliminar un elemento arbitrario de la cola usando remove(x).
Sin embargo, esta operación generalmente no se realiza de manera eficiente y solo se puede usar ocasionalmente y de manera planificada, como cuando se retira un mensaje de la cola.
La implementación de BlockingQueue es segura para subprocesos. Todos los métodos de cola pueden utilizar bloqueo interno u otras formas de control de concurrencia para lograr sus propósitos automáticamente.
Sin embargo, una gran cantidad de operaciones de recopilación (agregar todo, contiene todo, retener todo y eliminar todo) no necesariamente se realizan automáticamente a menos que se indique específicamente en la implementación.
Entonces, por ejemplo, addAll(c) podría fallar (lanzar una excepción) después de agregar solo algunos elementos en c.
BlockingQueue esencialmente no admite ningún tipo de operación de "cierre" o "apagado" para indicar que no se agregarán más elementos.
La necesidad y el uso de esta funcionalidad tienden a depender de la implementación. Por ejemplo, una estrategia común es insertar objetos especiales de final de flujo o envenenados en el productor e interpretarlos en función de cuándo los recibe el consumidor.
El siguiente ejemplo demuestra la funcionalidad básica de esta cola de bloqueo.
import java.util.concurrent.BlockingQueue;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.LinkedBlockingQueue;clase pública MyBlockingQueue extiende el hilo {cola BlockingQueue estática pública <String> = nuevo LinkedBlockingQueue<String>();índice int privado;público MyBlockingQueue(int i) { this.index = i;} public void run() { intentar { queue.put(String.valueOf(this.index)); System.out.println("{" + this.index + " } en cola!"); } catch (Exception e) { e.printStackTrace(); }} public static void main(String args[]) { servicio ExecutorService = Executors.newCachedThreadPool(); para (int i =; i <; i++) { service.submit(new MyBlockingQueue(i)} Thread thread = new Thread() { public void run() { try { while (verdadero) { Thread.sleep((int) (Math.random() * )); if(MyBlockingQueue.queue.isEmpty()) break String str =; MyBlockingQueue.queue.take(); System.out.println(str + "tiene toma!"); service.submit(thread). cerrar();}}
--------------------------Resultados de la ejecución-----------------
¡{0} en cola!
¡{1} en cola!
¡{2} en cola!
¡{3} en cola!
0 ha tomado!
¡{4} en cola!
1 ha tomado!
¡{6} en cola!
2 ha tomado!
¡{7} en cola!
3 ha tomado!
¡{8} en cola!
¡4 ha tomado!
¡{5} en cola!
6 ha tomado!
¡{9} en cola!
¡7 ha tomado!
8 ha tomado!
5 ha tomado!
¡9 ha tomado!
----------------------------------------
Servicio de finalización
Un servicio que separa la producción de nuevas tareas asincrónicas del consumo de los resultados de las tareas completadas. El productor presenta la tarea a realizar. El usuario toma las tareas completadas y procesa sus resultados en el orden en que se completaron. Por ejemplo, se puede usar CompletionService para administrar IO asincrónica. La tarea de realizar una operación de lectura se envía como parte del programa o sistema. Luego, cuando se completa la operación de lectura, se realizan otras operaciones en una parte diferente del programa. , posiblemente en el orden en que se solicitaron las operaciones. El orden es diferente.
Normalmente, un CompletionService depende de un Ejecutor independiente para realizar la tarea, en cuyo caso CompletionService solo gestiona una cola de finalización interna. La clase ExecutorCompletionService proporciona una implementación de este método.
importar java.util.concurrent.Callable; importar java.util.concurrent.CompletionService; importar java.util.concurrent.ExecutorCompletionService; importar java.util.concurrent.ExecutorService; importar java.util.concurrent.Executors; la clase pública MyCompletionService implementa invocable <Cadena> {privado int id;público MyCompletionService(int i){ this.id=i;}public static void main(String[] args) lanza Exception{ ExecutorService service=Executors.newCachedThreadPool(); CompletionService<String> complete=new ExecutorCompletionService<String>(service); i<;i++){ finalización.submit(new MyCompletionService(i) } for(int i=;i<;i++){ System.out.println(completion.take().get()); } service.shutdown();} public String call() lanza una excepción { Tiempo entero=(int)(Math.random()*); System.out.println(this.id+" inicio"); Thread.sleep(time); System.out.println(this.id+" fin"); e.printStackTrace(); } devolver this.id+":"+hora;}}
cuenta atrás
Una clase auxiliar de sincronización que permite que uno o más subprocesos esperen hasta que se complete un conjunto de operaciones que se realizan en otros subprocesos.
Inicializa CountDownLatch con el recuento dado. Debido a que se llama al método countDown(), el método de espera se bloquea hasta que el recuento actual llega a cero.
Luego, todos los subprocesos en espera se liberan y todas las llamadas posteriores en espera regresan inmediatamente. Este comportamiento sólo ocurre una vez; el recuento no se puede restablecer. Si necesita restablecer el recuento, considere usar CyclicBarrier.
CountDownLatch es una herramienta de sincronización general que tiene muchos usos. Utilice un CountDownLatch inicializado con el conteo 1 como un simple pestillo de encendido/apagado o entrada: todos los subprocesos que llaman await esperan en la entrada hasta que el subproceso que llama a countDown() abre la entrada.
Un CountDownLatch inicializado con N puede hacer que un subproceso espere hasta que N subprocesos hayan completado una operación, o que espere hasta que una operación se haya completado N veces.
Una característica útil de CountDownLatch es que no requiere que el subproceso que llama al método countDown espere hasta que el conteo llegue a cero antes de continuar, sino que evita que cualquier subproceso continúe a través de una espera hasta que todos los subprocesos puedan pasar.
El siguiente ejemplo fue escrito por otra persona y es muy vívido.
import java.util.concurrent.CountDownLatch;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;public class TestCountDownLatch {public static void main(String[] args) throws InterruptedException { // Iniciando bloqueo de cuenta regresiva final CountDownLatch start = new CountDownLatch() // Fin del bloqueo de cuenta regresiva final CountDownLatch end = new CountDownLatch(); // Diez concursantes final ExecutorService exec = Executors.newFixedThreadPool(); for (int index = ; index <; index++) { final int NO = index + ; { intentar { comenzar.await();//siempre bloqueando Thread.sleep((long) (Math.random() * )); System.out.println("No." + NO + "llegó"); } catch (InterruptedException e) { } finalmente { end.countDown(); println("Inicio del juego"); comenzar.countDown(); end.await(); System.out.println("Juego terminado");
Los métodos más importantes de CountDownLatch son countDown () y await (). El primero cuenta principalmente una vez y el segundo espera que la cuenta regresiva llegue a 0. Si no llega a 0, solo se bloqueará y esperará.
Barrera cíclica
Una clase auxiliar de sincronización que permite que un grupo de subprocesos se esperen unos a otros hasta que se alcanza un punto de barrera común.
CyclicBarrier es útil en programas que involucran un conjunto de subprocesos de tamaño fijo que deben esperarse entre sí de vez en cuando. Debido a que la barrera se puede reutilizar después de que se libera el hilo en espera, se denomina barrera cíclica.
CyclicBarrier admite un comando Runnable opcional que se ejecuta solo una vez en cada punto de barrera, después de que haya llegado el último subproceso de un conjunto de subprocesos (pero antes de que se liberen todos los subprocesos). Esta operación de barrera es útil al actualizar el estado compartido antes de continuar con todos los subprocesos participantes.
Ejemplo de uso: el siguiente es un ejemplo del uso de barreras en un diseño de descomposición paralela, un ejemplo de grupo turístico muy clásico:
importar java.text.SimpleDateFormat;importar java.util.Date;importar java.util.concurrent.BrokenBarrierException;importar java.util.concurrent.CyclicBarrier;importar java.util.concurrent.ExecutorService;importar java.util.concurrent.Executors; public class TestCyclicBarrier { // Tiempo necesario para la caminata: Shenzhen, Guangzhou, Shaoguan, Changsha, Wuhan private static int[] timeWalk = { , , , , }; // Tour sin conductor private static int[] timeSelf = { , , , } // Tour en autobús privado static int[] timeBus = { , , , , } ; cadena estática ahora() { SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss"); sdf.format(new Date()) + ": "; } clase estática Tour implementa Runnable { privado int[] veces; barrera privada CyclicBarrier tourName público (barrera CyclicBarrier, String tourName, int[] veces) { this.times = times; this.tourName = tourName; this.barrier = barrera; } public void run() { intentar { Thread.sleep(times[] * ); System.out.println(now() + tourName + " Llegó a Shenzhen"); barrera.await(); Thread.sleep(times[] * ); System.out.println(now() + tourName + " Llegó a Guangzhou" ); barrera.await(); Thread.sleep(times[] * ); System.out.println(now() + tourName + " Llegado a Changsha"); System.out.println(now() + tourName + "Alcanzado Wuhan"); barrier.await() } catch (InterruptedException e) { } catch; (BrokenBarrierException e) { } } } public static void main(String[] args) { // Tres grupos de recorrido CyclicBarrier barrier = new CyclicBarrier() exec = Executors.newFixedThreadPool() exec.submit(new Tour(barrier , "WalkTour", timeWalk)); exec.submit(nuevo Tour(barrera, "SelfTour", timeSelf));// Cuando comentamos el siguiente código, encontraremos que el programa está bloqueado y no puede continuar ejecutándose. exec.submit(new Tour(barrera, "BusTour", timeBus)); exec.shutdown();
El atributo más importante de CyclicBarrier es el número de participantes y el método más importante es await(). Cuando todos los subprocesos han llamado await (), significa que estos subprocesos pueden continuar ejecutándose; de lo contrario, esperarán.
Futuro
El futuro representa el resultado del cálculo asincrónico. Proporciona métodos para comprobar si el cálculo está completo, esperar a que se complete y recuperar el resultado del cálculo.
Una vez completado el cálculo, solo se puede utilizar el método get para recuperar los resultados. Si es necesario, este método se puede bloquear antes de que se complete el cálculo. La cancelación se realiza mediante el método de cancelación.
Se proporcionan métodos adicionales para determinar si una tarea se completó normalmente o se canceló. Una vez que se completa un cálculo, no se puede cancelar.
Si está utilizando un Future para la cancelabilidad pero no proporciona un resultado utilizable, puede declarar un tipo formal Future<?> y devolver null como resultado de la tarea subyacente.
Hemos visto esto en CompletionService anteriormente, la función de este Futuro, y esto se puede especificar como un objeto de retorno al enviar el hilo.
Servicio de ejecución programado
Un ExecutorService que programa comandos para que se ejecuten después de un retraso determinado o en intervalos regulares.
El método de programación crea tareas con varios retrasos y devuelve un objeto de tarea que puede usarse para cancelar o verificar la ejecución. Los métodos ScheduleAtFixedRate y ScheduleWithFixedDelay crean y ejecutan ciertas tareas que se ejecutan periódicamente hasta que se cancelan.
Los comandos enviados mediante Executor.execute(java.lang.Runnable) y el método de envío de ExecutorService se programan con el retraso solicitado de 0.
En el método de programación se permiten retrasos cero y negativos (pero no períodos), y estos se tratan como solicitudes que deben ejecutarse inmediatamente.
Todos los métodos de programación aceptan retrasos y períodos relativos como parámetros, en lugar de horas o fechas absolutas. Es fácil convertir la hora absoluta representada por una Fecha al formato requerido.
Por ejemplo, para programar una ejecución en una fecha posterior, use: agendar(tarea, fecha.getTime() - System.currentTimeMillis(), TimeUnit.MILLISECONDS).
Sin embargo, tenga en cuenta que debido a los protocolos de sincronización horaria de la red, la desviación del reloj u otros factores, la fecha de vencimiento relativamente retrasada no tiene que coincidir con la fecha actual de la tarea habilitada.
La clase Executors proporciona métodos de fábrica convenientes para la implementación de ScheduledExecutorService proporcionada en este paquete.
Los siguientes ejemplos también son populares en Internet.
importar java.util.concurrent.TimeUnit.SECONDS estático; importar java.util.Date; importar java.util.concurrent.Executors; importar java.util.concurrent.ScheduledExecutorService; importar java.util.concurrent.ScheduledFuture; clase pública TestScheduledThread { public static void main(String[] args) {planificador final ScheduledExecutorService = Executors.newScheduledThreadPool(); beeper ejecutable final = new Runnable() { int count = ; public void run() { System.out.println(new Date() + " beep " + (++count)); // Ejecutar después de segundos y cada segundo final ScheduledFuture beeperHandle = Scheduler.scheduleAtFixedRate(beeper, , , SECONDS); // Ejecutar después de unos segundos y esperar unos segundos después de que la última tarea haya terminado de ejecutarse, y luego volver a ejecutar cada vez final ScheduledFuture beeperHandle = Scheduler.scheduleWithFixedDelay(beeper, , , SECONDS // Finaliza la tarea después de unos segundos y cierra el programador Scheduler); programar (nuevo Runnable () { ejecución pública vacía () { beeperHandle.cancel (verdadero); beeperHandle.cancel (verdadero); planificador.shutdown(); } }, , SEGUNDOS);}}
De esta manera, hemos resumido las funciones más importantes del paquete concurrente. Esperamos que sea útil para nuestra comprensión.