El subproceso múltiple de Java es una característica muy importante del lenguaje Java, que permite a los programas realizar múltiples tareas al mismo tiempo. Mediante subprocesos múltiples, un programa puede manejar múltiples tareas al mismo tiempo, acortando así el tiempo de ejecución del programa. Además, el subproceso múltiple también ayuda a utilizar procesadores de múltiples núcleos para aprovechar mejor el rendimiento del hardware de la computadora.
Entonces, ¿cómo implementamos subprocesos múltiples en Java?
Thread
Hoy hablaremos de ello desde los más simples Runnable
hasta los más utilizados en la actualidad.CompletableFuture
Hilo
Los subprocesos se crean en Java creando Thread
una instancia de una clase. La forma más sencilla de crear un hilo es extender Thread
la clase y anular run()
el método. Escriba el código que se ejecutará en run()
el método y luego llame al método en el programa start()
para iniciar el hilo. Por ejemplo:
public class MyThread extends Thread {
public void run() {
// 线程要执行的代码
for (int i = 0; i < 10; i++) {
System.out.println("id:" + Thread.currentThread().getId() + ",name:" + Thread.currentThread().getName() + "====={" + i + "}");
}
}
}
public static void main(String[] args) {
System.out.println("start");
MyThread myThread1 = new MyThread();
MyThread myThread2 = new MyThread();
myThread1.start();
myThread2.start();
System.out.println("end");
}
Por supuesto, si le resulta problemático, puede utilizar directamente clases internas anónimas.
// 效果是一样的
System.out.println("start");
new Thread(() -> {
for (int i = 0; i < 10; i++) {
System.out.println("id:" + Thread.currentThread().getId() + ",name:" + Thread.currentThread().getName() + "{" + i + "}");
}
}).start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
System.out.println("id:" + Thread.currentThread().getId() + ",name:" + Thread.currentThread().getName() + "{" + i + "}");
}
}).start();
System.out.println("end");
Tenga en cuenta aquí que no debe utilizar run()
métodos que sincronicen los subprocesos. Al mismo tiempo, si desea ejecutar el código posterior después de que se ejecute el contenido de subprocesos múltiples, puede utilizar este método, que puede hacer que el subproceso se sincronice join()
. terminar de ejecutar Luego realice las siguientes operaciones.
Por supuesto, también puedes usarlo sleep()
para dejar que el hilo principal duerma por un período de tiempo, por supuesto, este tiempo de sueño es subjetivo y lo determinamos nosotros mismos, este método no es recomendado.
Ejecutable
Además, también podemos usar Runnable
interfaces para crear hilos. Runnable
La interfaz tiene un solo run()
método, en el que escribes el código a ejecutar, luego Runnable
pasas el objeto como parámetro al Thread
constructor de la clase y llamas start()
al método para iniciar el hilo. Por ejemplo:
public class MyThread implements Runnable {
@Override
public void run() {
// 线程要执行的代码
for (int i = 0; i < 10; i++) {
System.out.println("id:" + Thread.currentThread().getId() + ",name:" + Thread.currentThread().getName() + "====={" + i + "}");
}
}
}
De hecho, Thread
es casi lo mismo. Es solo una interfaz de implementación y una clase de herencia. Si se usa directamente, no hay mucha diferencia entre los dos.
Sin embargo, algunos lugares dirán que Runnable
es fácil realizar el intercambio de recursos, Thread
pero no lo es Sin embargo, después de mis pruebas, creo que Thread
también se pueden compartir recursos.
código de prueba
public class MyThread extends Thread {
private int ticket = 5;
@Override
public void run() {
// 双检索机制——保证线程的安全
if (ticket > 0) {
synchronized (this) {
if (ticket > 0) {
while (true) {
System.out.println("Thread:" + Thread.currentThread().getName() + "--Thread ticket = " + ticket--);
if (ticket < 0) {
break;
}
}
}
}
}
}
}
public class Test {
public static void main(String[] args) {
MyThread myThread1 = new MyThread();
new Thread(myThread1).start();
new Thread(myThread1).start();
new Thread(myThread1).start();
new Thread(myThread1).start();
new Thread(myThread1).start();
new Thread(myThread1).start();
}
}
执行结果如下:
Thread:Thread-1--Thread ticket = 5
Thread:Thread-1--Thread ticket = 4
Thread:Thread-1--Thread ticket = 3
Thread:Thread-1--Thread ticket = 2
Thread:Thread-1--Thread ticket = 1
Thread:Thread-1--Thread ticket = 0
Cuando miramos Thread
el código fuente, encontramos que: de hecho, Thread
solo implementa Runnable
la interfaz y proporciona más métodos. Entonces no hay diferencia Thread
entre y. Runnable
Si hay alguna diferencia, sería la diferencia entre clases e interfaces, y la diferencia entre herencia e implementación.
Invocable (con futuro)
Para los subprocesos secundarios, a veces podemos tener dos necesidades:
- Obtener el resultado de ejecución del hilo secundario
- Obtener el estado de ejecución del subproceso secundario (éxito, fracaso, excepción)
Thread
Ninguno y Runnable
no cumplen estos dos requisitos, Runnable
se puede obtener el estatus pero no se puede obtener el resultado, así parece Callable
. Callable
Se utilizan juntos Future
para obtener resultados de ejecución de subprocesos.
( Future
Es un método de cálculo asincrónico en subprocesos múltiples de Java, que se puede utilizar para obtener los resultados de los cálculos asincrónicos durante la ejecución de la tarea)
public class MyThread implements Callable<Integer> {
private Integer number;
public Integer getNumber(){
return number;}
public MyThread (Integer number) {
this.number = number;
}
@Override
public Integer call() throws Exception {
int result = 0;
for (int i = 0; i < number; i++) {
result ++;
System.out.println("id:" + Thread.currentThread().getId() + ",name:" + Thread.currentThread().getName() + "====={" + result + "}");
}
return result;
}
}
public static void main(String[] args) throws Exception {
System.out.println("start");
MyThread myThread1= new MyThread (10);
MyThread myThread2= new MyThread (15);
//使用Executors工厂类创建一个简单的线程池(线程数:2)
ExecutorService executor = Executors.newFixedThreadPool(2);
// 将任务提交给线程池
Future<Integer> submit1 = executor.submit(myThread1);
Future<Integer> submit2 = executor.submit(myThread2);
// 获取任务的执行结果
System.err.println(submit1.get());
System.err.println(submit2.get());
executor.shutdown();
System.out.println("end");
}
En este ejemplo, implementamos MyThread
la clase, que implementa Callable
la interfaz y anula call()
los métodos. En call()
el método, simulamos una tarea de larga duración y devolvemos un resultado de cálculo como resultado de la ejecución. En el método principal, primero creamos un grupo de subprocesos con un número fijo de subprocesos ExecutorService
y luego pasamos MyThread
la instancia al submit
método para enviar la tarea y obtener un Future
objeto de tipo. Al llamar get()
al método de este objeto, podemos obtener los resultados de ejecución de la tarea. Finalmente, cerramos el grupo de subprocesos.
Cabe señalar que dado que el Future
método llamado get()
está bloqueando, es posible que debamos procesarlo try-catch
. Además, después de completar la tarea, debemos cerrar el grupo de subprocesos para liberar los recursos.
Este es un ejemplo de uso simple Callable
, pero Future
se pueden lograr cálculos simultáneos más complejos combinando múltiples objetos.
Futuro
Por supuesto, Future
también se puede utilizar solo.
Cuando está en uso Future
, puede llamar get()
a un método para bloquear la espera de que se ejecute la tarea y obtener el resultado del cálculo; también puede llamar a isDone()
un método para determinar si la tarea se ha completado; si ocurre una excepción durante la ejecución de la tarea, puede get()
obtener la información de excepción llamando al método
public static void main(String[] args) throws Exception {
System.out.println("start");
ExecutorService executor = Executors.newFixedThreadPool(2);
Future future1 = executor.submit(() -> {
int result = 0;
for (int i = 0; i < number; i++) {
result ++;
System.out.println("id:" + Thread.currentThread().getId() + ",name:" + Thread.currentThread().getName() + "====={" + result + "}");
}
return result;
});
Future future2 = executor.submit(() -> {
int result = 0;
for (int i = 0; i < number; i++) {
result ++;
System.out.println("id:" + Thread.currentThread().getId() + ",name:" + Thread.currentThread().getName() + "====={" + result + "}");
}
return result;
});
String result1 = (String) future1.get();
String result2 = (String) future2.get();
System.out.println(result1);
System.out.println(result2);
executor.shutdown();
System.out.println("end");
}
De hecho, es lo mismo que el método anterior, excepto que call()
la lógica de negocios en el método es redundante en el código principal y el acoplamiento es mayor. Si solo se pueden usar algunos códigos simples, si es complicado, se recomienda usarlos callable
juntos para comparar. bueno
Futuro Completable
CompletableFuture
es una característica muy útil introducida en Java 8 que nos permite manejar operaciones asincrónicas más fácilmente, especialmente cuando estas operaciones involucran múltiples etapas.
CompletableFuture
Heredado de la Future
interfaz, los resultados del cálculo se pueden obtener una vez completado el cálculo, por lo que también tiene Future
las características de. Además, proporciona algunas otras características:
- Funciones de ejecución asincrónica y ejecución en serie.
- El mecanismo de observador se puede activar después de ejecutar la tarea.
- Se pueden combinar y ejecutar dos tareas juntas para manejar escenarios asincrónicos más complejos.
Hoy en día, es común utilizar una lógica más compleja en el desarrollo.CompletableFuture
crear
CompletableFuture
Podemos usar runAsync()
el método para ejecutar directamente de forma asincrónica.
CompletableFuture.runAsync(() -> {
// 在这里执行一些耗时的操作
});
CompletableFuture
El resultado del procesamiento asincrónico.
Podemos usar CompletableFuture
el método estático supplyAsync()
para crear una ejecución asincrónica CompletableFuture
y proporcionar una lambda
expresión como generador de cálculo:
CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
// 在这里执行一些耗时的操作
return "some result";
});
completableFuture.thenAccept(result -> {
System.out.println("Got result: " + result);
});
// 这里可以继续执行其他任务,completableFuture将在后台继续执行并在完成后触发回调函数。
Operar dos o más
CompletableFuture
Si necesitamos esperar a que CompletableFuture
se completen varias tareas antes de ejecutar ciertas tareas, entonces podemos usar CompletableFuture
métodos estáticos allOf()
o anyOf()
:
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
// 在这里执行一些耗时的操作
return "Result of future 1";
});
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
// 在这里执行一些耗时的操作
return "Result of future 2";
});
CompletableFuture<Void> allFutures = CompletableFuture.allOf(future1, future2);
allFutures.thenRun(() -> {
//这边join和get方法都可以 只是抛出异常的区别
String result1 = future1.join();
String result2 = future2.join();
System.out.println("Got both results: " + result1 + " and " + result2);
});
CompletableFuture
El grupo de subprocesos se utiliza de forma predeterminadaForkJoinPool
. Si desea definir un grupo de subprocesos usted mismo, puede crearExecutors
uno directamente. Sin embargo,Executors
es una clase de fábrica que proporciona algunos métodos estáticos para crear grupos de subprocesos, no un grupo de subprocesos en sí, por lo que el subproceso no se puede configurar Parámetros detallados como: número de subprocesos principales, número máximo de subprocesos, política de rechazo, etc. Si necesita configurar estos parámetros, debeThreadPoolExecutor
crear manualmente el grupo de subprocesos utilizando el
/**
* public ThreadPoolExecutor(int corePoolSize,
* int maximumPoolSize,
* long keepAliveTime,
* TimeUnit unit,
* BlockingQueue<Runnable> workQueue,
* ThreadFactory threadFactory,
* RejectedExecutionHandler handler) {}
* 1. corePoolSize:核心线程数;当提交的任务数大于 corePoolSize 时,线程池会自动扩容。
*
* 2. maximumPoolSize:线程池最大线程数;当活动线程数达到该值,并且 workQueue 队列已满,则执行拒绝策略。
*
* 3. keepAliveTime:线程空闲超时时间,超过该时间则进行回收。
*
* 4. unit:keepAliveTime 的时间单位。
*
* 5. workQueue:任务阻塞队列,用于存储提交但尚未执行的任务。
*
* 6. threadFactory:线程工厂,用于创建线程。
*
* 7. handler:拒绝策略,当线程数量已经达到 maximumPoolSize 并且队列已满时,采取的策略。
*/
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5,
10,
10,
TimeUnit.SECONDS,
new LinkedBlockingQueue(),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy());
Hay
springboot
un método que también se usa con mucha frecuencia:@async
solo necesita agregar esta anotación al método y agregarla a la clase de inicio@Enableasync
para implementar directamente operaciones de excepción de subprocesos múltiples. Sin embargo, este método de subprocesos múltiples sigue siendo algo diferente del Como se mencionó anteriormente, la diferencia es@async
que cuando se llama a un método, se realizan operaciones asincrónicas de subprocesos múltiples, pero la lógica de negocios en el método aún es sincrónica, por lo que se pueden usar juntas normalmente (por supuesto, también puede extraerComplatableFuture
la lógica de negocios). en el medio y agrega un@async
jajajaja) )
El mecanismo de subprocesos múltiples de Java es una parte indispensable de la programación Java. Comprender y familiarizarse con las capacidades de programación de subprocesos múltiples de Java mejorará en gran medida la eficiencia del trabajo y el nivel de programación de los programadores.