在Java中,创建线程池可以使用java.util.concurrent包中的ExecutorService接口。这个接口可以通过Executors工厂类来创建。Executors提供了许多静态工厂方法来创建不同类型的线程池。例如,Executors.newFixedThreadPool(int nThreads)方法可以创建一个固定大小的线程池,该线程池将重用指定数量的线程。
虽然Executors工厂类提供了一种方便的方式来创建线程池,但是在某些情况下,它可能不是最佳选择。原因如下:
1、无法控制线程池的内部实现:Executors工厂类创建的线程池实现是预定义的,无法进行自定义调整。
2、不支持设置拒绝策略:Executors工厂类创建的线程池默认情况下使用的是抛出RejectedExecutionException的拒绝策略,无法设置其他拒绝策略。
3、不支持设置线程工厂:Executors工厂类创建的线程池默认情况下使用的是Executors.DefaultThreadFactory线程工厂类,无法设置其他线程工厂类。
因此,在某些情况下,使用Executors工厂类创建线程池可能不是最佳选择。在这种情况下,可以手动创建线程池,以便可以控制线程池的内部实现、拒绝策略和线程工厂类。
Executors工厂类提供了一种方便的方式来创建线程池实例。可以使用以下方法之一来创建线程池:
newFixedThreadPool(int nThreads): 创建一个固定大小的线程池,该线程池包含指定数量的线程。
ExecutorService executor = Executors.newFixedThreadPool(5);
newCachedThreadPool(): 创建一个可缓存的线程池,该线程池会根据需要自动创建新的线程。
ExecutorService executor = Executors.newCachedThreadPool();
newSingleThreadExecutor(): 创建一个单线程的线程池。
ExecutorService executor = Executors.newSingleThreadExecutor();
这些方法返回一个ExecutorService对象,它表示线程池实例。可以使用该对象提交任务给线程池来执行。例如:
executor.submit(new Runnable() {
public void run() {
// 任务代码
}
});
需要注意的是,创建的线程池应该在使用完毕后被关闭,可以调用shutdown()方法来关闭线程池:
executor.shutdown();
其中线程池包括一个核心线程池大小为10、最大线程池大小为20、线程空闲时间为60秒、使用阻塞队列作为任务队列、自定义的线程工厂类和自定义的拒绝策略:
package com.lfsun.main.basic.mythreadpool;
import com.lfsun.common.util.ExecutorUtil;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.*;
/**
* 手动创建线程池
*/
@Slf4j
public class MyManualThreadPool {
public static final Logger logger = LoggerFactory.getLogger(MyManualThreadPool.class);
public static void main(String[] args) {
// 核心线程池大小
int corePoolSize = 10;
// 最大线程池大小
int maxPoolSize = 20;
// 空闲线程存活时间
long keepAliveTime = 60L;
// 时间单位
TimeUnit timeUnit = TimeUnit.SECONDS;
// 任务队列,使用有界队列可以避免内存溢出
BlockingQueue queue = new ArrayBlockingQueue<>(100);
// 线程工厂,可以设置线程名称等属性
ThreadFactory threadFactory = new MyThreadFactory();
// 自定义拒绝策略,避免任务丢失
RejectedExecutionHandler handler = new MyRejectedExecutionHandler();
// 创建线程池
ExecutorService executor = new ThreadPoolExecutor(
corePoolSize, maxPoolSize, keepAliveTime, timeUnit, queue, threadFactory, handler);
// 提交任务
for (int i = 1; i <= 100; i++) {
executor.submit(new MyTask(i));
}
ExecutorUtil.shutdown(executor);
}
/**
* 线程工厂,用于创建线程时设置线程名称等属性
*/
private static class MyThreadFactory implements ThreadFactory {
private static int counter = 1;
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "我的线程-" + counter++);
}
}
/**
* 任务类,实现Runnable接口
*/
private static class MyTask implements Runnable {
private final int taskId;
public MyTask(int taskId) {
this.taskId = taskId;
}
@Override
public void run() {
System.out.println("任务 -" + taskId + " 运行,线程 " + Thread.currentThread().getName());
// 执行任务逻辑,避免使用Thread.sleep()阻塞线程
try {
// 模拟任务执行耗时
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("任务 -" + taskId + " 完成,线程 " + Thread.currentThread().getName());
}
}
/**
* 自定义拒绝策略,避免任务丢失
*/
private static class MyRejectedExecutionHandler implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println("任务 " + r.toString() + " 被拒绝,执行器 " + executor.toString());
// 自定义处理逻辑,例如重新提交任务或记录日志
// executor.submit(r);
logger.warn("任务被拒绝:{}", r);
}
}
}
在这个示例代码中,MyThreadFactory和MyRejectedExecutionHandler是自定义的线程工厂类和拒绝策略。MyTask是自定义的任务类,每个任务都会打印出自己的任务ID,并在执行完成后打印出自己的任务ID和执行该任务的线程名称。
该示例创建了一个ThreadPoolExecutor实例,并向其提交100个MyTask任务。当所有任务都完成后,通过调用shutdown()方法来关闭线程池。
package com.lfsun.common.util;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
/**
* 线程池工具类
* @author Administrator
*/
public class ExecutorUtil {
/**
* 关闭线程池,等待已提交的任务执行完毕,不再接受新的任务提交
* shutdown()方法是ThreadPoolExecutor类中用来关闭线程池的方法。调用该方法会将线程池设置为不再接受新的任务,但会等待所有已经提交的任务执行完成。
* 在所有任务完成后,线程池会自动关闭。
*
* 具体来说,shutdown()方法会做以下几件事情:
*
* 停止接受新的任务:将线程池的isShutdown属性设置为true,表示线程池已经关闭,不再接受新的任务。
* 尝试停止所有活动的线程:会尝试中断所有正在执行任务的线程。
* 等待所有任务执行完成:会等待所有已经提交的任务执行完成,并关闭线程池。
* 注意:如果在调用shutdown()方法后又向线程池提交新的任务,这些任务将会被拒绝并抛出RejectedExecutionException异常。
* 如果需要继续向线程池提交任务,可以使用shutdownNow()方法来强制关闭线程池,但这会导致所有未完成的任务被取消并且可能会导致数据丢失。
* @param executor 线程池对象
*/
public static void shutdown(ExecutorService executor) {
executor.shutdown();
}
/**
* 关闭线程池,尝试终止正在执行的任务,不再接受新的任务提交
* executor.shutdown() 方法将不再接受新任务,并等待之前提交的任务完成执行。
* 在这个方法返回之前,所有的线程都将执行完当前正在执行的任务,并且所有的空闲线程将会停止。
* 调用此方法后,可以调用awaitTermination(long timeout, TimeUnit unit)等待所有任务执行完成。
*
* executor.shutdownNow() 方法将尝试停止当前正在执行的任务,并立即停止所有空闲线程。
* 这个方法不会等待所有任务完成执行。它会尝试中断线程池中所有的线程,并返回尚未开始执行的任务的列表。
*
* 需要注意的是,executor.shutdownNow() 并不能保证所有的任务都能被终止,因为一些任务可能会忽略中断请求而继续执行。
*
* @param executor 线程池对象
* @return
*/
public static List shutdownNow(ExecutorService executor) {
// 返回尚未执行的任务(即在调用 shutdownNow() 方法时被取消的任务)。
// 每个元素都是一个实现了 Runnable 接口的对象,表示一个尚未执行的任务。
// 如果 shutdownNow() 方法被调用时,没有任务被取消,则返回空列表。
return executor.shutdownNow();
}
/**
* 等待线程池中已提交的任务在指定的时间内执行完毕
*
* 用于等待所有通过Executor提交的任务完成执行,并且在超时时间内等待所有任务完成。
*
* 具体而言,该方法会阻塞当前线程,直到以下两种情况之一发生:
* * 所有通过executor提交的任务都已经完成执行。
* * 等待时间已经超过timeout指定的时长。
* @param executor 线程池对象
* @param timeout 最长等待时间
* @param unit 时间单位
* @return 如果所有任务都已完成,则返回 true;否则为 false
* @throws InterruptedException 如果等待过程中被中断,则抛出 InterruptedException 异常
*/
public static boolean awaitTermination(ExecutorService executor, long timeout, TimeUnit unit) throws InterruptedException {
// 如果在等待时间内所有任务都完成,则返回true;否则返回false。
// 如果该方法返回false,则不代表所有任务都没有完成,仅代表在等待时间内没有等待所有任务完成。
// 如果需要确保所有任务都完成,可以在该方法返回false之后继续调用shutdownNow()方法来中断未完成的任务。
return executor.awaitTermination(timeout, unit);
}
/**
* 等待线程池中的所有任务执行完毕,不限制等待时间
* 方法使用了 ExecutorService 类的 awaitTermination 方法来实现等待操作。
* 该方法将会一直等待直到所有任务执行完毕,或者等待过程中被中断,此时会抛出 InterruptedException 异常。
*
* 这个方法的作用是确保在某些情况下,线程池中的任务都能够被正确地执行完毕,而不会因为等待超时或者中断等原因而导致任务未能执行完毕。
* @param executor 线程池对象
* @throws InterruptedException 如果等待过程中被中断,则抛出 InterruptedException 异常
*/
public static boolean awaitTerminationForever(ExecutorService executor) throws InterruptedException {
// 如果返回 true,表示所有任务已经执行完毕;如果返回 false,则表示等待过程中被中断。
return executor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
}
}