文章目录
线程池就是多个可执行任务的线程的集合,在执行大量线程任务的时候,由于线程池里的线程可以复用,因而可以减少新建和调用线程的次数,从而提高性能。
此外,使用线程池让你无需管理线程的生命周期,它让你在利用线程优势的同时,只需专注于任务的执行,而无需关心线程机制。
1. 创建线程池常用的方法
介绍几个常用的创建线程池的方法(使用 Executors
类的构造方法来构建):
-
public static ExecutorService Executors.newSingleThreadExecutor() :
创建一个单线程的Executor来操作任务队列,队列中的任务将依次执行,队列中一次只有一个任务处于活动状态。如果该线程在关闭之前由于执行任务出错而被终止,在执行后续任务的时候将创建一个新的线程来替代该线程。 -
public static ExecutorService Executors.newSingleThreadExecutor(ThreadFactory threadFactory)
和上面的方法效果一样,不同之处在于它支持使用自定义创建的线程。 -
public static ExecutorService Executors.newFixedThreadPool(int nThreads)
创建一个拥有固定个线程数量的线程池。在任意时刻,最多有nThreads
个线程处于活动状态来处理任务。如果任务数量多于nThreads
个或者当所有线程都处于活动状态时又提交了新的任务,那么多余的任务将处于等待状态直到某个线程可以使用。如果任一线程在关闭之前由于执行任务出错而被终止,在执行后续任务的时候将创建一个新的线程来替代该线程。线程池中的所有线程直到被明确地关闭(调用了ExecutorService.shutdown()
)之前都将存在。 -
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory)
和上面的方法类似 -
public static ExecutorService newCachedThreadPool()
创建一个线程池,线程池中线程的数量根据任务需求而定。如果当前有新的任务要执行而线程池中又没有可用的线程,那么就会创建新的线程添加到线程池中并用来执行任务。如果当前有可用线程,就会复用已存在的线程来执行任务。任意一个线程如果60秒之内没有执行任务,就会被自动移除,因此处于长时间(超过60秒)待机状态的线程池不会占用任何资源。对于需要执行大量短周期异步任务的程序,使用这种线程池能显著提高性能。 -
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory)
和上面的方法类似
如果需要创建与上述相似,但细节上有所差异的线程池,可以考虑使用
ThreadPoolExecutor
类的构造方法。
如果需要创建可以执行定时或者延迟任务的线程池,可以考虑使用ScheduledThreadPoolExecutor
类的构造方法
2. 举例比较
下面来举几个例子比较一下不同类型线程池的区别。
先写一个任务类,在任务开始和结束时分别打印任务名称和所在线程:
public class MyRunnable implements Runnable {
private String taskName;
public MyRunnable(String name){
this.taskName = name;
}
@Override
public void run() {
Printer.print(taskName + " start; Current thread is " + Thread.currentThread().getName());
try {
Thread.sleep(1000);
}catch (Exception e){}
Printer.print(taskName + " finished.");
}
}
创建6个任务,同时交给线程池处理
private static void testThreadPool(){
List<Runnable> tasks = new ArrayList<>();
for(int i = 0; i < 6; i++){
Runnable task = new MyRunnable("task" + i);
tasks.add(task);
}
ExecutorService threadPool = Executors.newSingleThreadExecutor(); //case 1
// ExecutorService threadPool = Executors.newFixedThreadPool(4); //case 2
// ExecutorService threadPool = Executors.newCachedThreadPool(); //case 3
for(Runnable runnable : tasks){
threadPool.execute(runnable);
}
}
我们尝试用以上三种不同的方式创建线程池,来执行这6个任务,看输出结果有什么区别:
case 1:
task0 start; Current thread is pool-1-thread-1
task0 finished.
task1 start; Current thread is pool-1-thread-1
task1 finished.
task2 start; Current thread is pool-1-thread-1
task2 finished.
task3 start; Current thread is pool-1-thread-1
task3 finished.
task4 start; Current thread is pool-1-thread-1
task4 finished.
task5 start; Current thread is pool-1-thread-1
task5 finished.
case1 中始终只有一个线程,6个任务一个接一个执行完毕。
case 2:
task0 start; Current thread is pool-1-thread-1
task1 start; Current thread is pool-1-thread-2
task2 start; Current thread is pool-1-thread-3
task3 start; Current thread is pool-1-thread-4
task0 finished.
task2 finished.
task1 finished.
task5 start; Current thread is pool-1-thread-3
task4 start; Current thread is pool-1-thread-1
task3 finished.
task5 finished.
task4 finished.
case2 的线程池中有4个线程,先同时执行了4个任务,执行完任务的线程接着执行剩余的任务。
case 3:
task1 start; Current thread is pool-1-thread-2
task4 start; Current thread is pool-1-thread-5
task0 start; Current thread is pool-1-thread-1
task3 start; Current thread is pool-1-thread-4
task2 start; Current thread is pool-1-thread-3
task5 start; Current thread is pool-1-thread-6
task5 finished.
task1 finished.
task0 finished.
task2 finished.
task4 finished.
task3 finished.
case3 由于一次传入了6个任务,线程池也相应创建了6个线程,同时开始执行任务。
补充一个类
java.lang.ThreadGroup
,任意一个线程都可以通过Thread.getThreadGroup()
方法获取到它所在的线程组,在初始化Thread类的时候可以为其指定ThreadGroup。使用线程组可以方便地对一组线程进行有限的操作。线程组与线程池无关,这里只是提及一下,不做更多介绍。