目录
线程池应用——实现读取指定路径下的前多少个文件夹下文件的数量
了解线程池
什么是线程池?
当编写程序时,如果我们的线程创建的过多,就容易引发内存溢出,因此我们就有必要使用线程池的技术了。
在 Java 中,线程池是一种用于管理和复用线程的机制。使用线程池可以避免频繁创建和销毁线程带来的开销,提高程序的性能和资源利用率。
线程池的优势
总体来说,线程池有如下的优势:
(1)降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
(2)提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
(3)提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
线程池的参数
- corePoolSize(必需):核心线程数。默认情况下,核心线程会一直存活,但是当将 allowCoreThreadTimeout 设置为 true 时,核心线程也会超时回收。
- maximumPoolSize(必需):线程池所能容纳的最大线程数。当活跃线程数达到该数值后,后续的新任务将会阻塞。
- keepAliveTime(必需):线程闲置超时时长。如果超过该时长,非核心线程就会被回收。如果将 allowCoreThreadTimeout 设置为 true 时,核心线程也会超时回收。
- unit(必需):指定 keepAliveTime 参数的时间单位。常用的有:TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECONDS(秒)、TimeUnit.MINUTES(分)。
- workQueue(必需):任务队列。通过线程池的 execute() 方法提交的 Runnable 对象将存储在该参数中。其采用阻塞队列实现。
- threadFactory(可选):线程工厂。用于指定为线程池创建新线程的方式。
- handler(可选):拒绝策略。当达到最大线程数时需要执行的饱和策略。
工作原理图
流程描述
线程池的运作大致流程如下。
- CORE_POOL_SIZE:核心线程数为 5,表示线程池中最少保持的线程数量。
- MAX_POOL_SIZE:最大线程数为 10,表示线程池中可同时运行的最大线程数量。
- QUEUE_CAPACITY:任务队列容量为 100,表示能够存放等待执行任务的队列的最大长度。
- KEEP_ALIVE_TIME:线程的空闲时间为 1 秒。如果线程池中的线程数量超过核心线程数,并且在此空闲时间内没有新任务到达,则这些多余的线程将被终止。
- 在这些参数下,假设有这样一个场景——此时有200个任务提交给了线程池要进行执行。
- 由于设定了核心线程数为5,所以线程池中的前五个任务会立即启动然后执行,因为他们的个数没有超过核心线程数。
- 然后剩下的195个任务会尝试放在任务队列中等待执行,但是因为已经设定了任务队列的最大容量为100,所以此时应该开辟新的线程。在该场景下,根据最大线程数10,可以得出开辟5个新的线程来执行第6,7,8,9,10个任务
- 之后如果线程池中的线程数量在一段时间内没有新任务到达,并且此时存在的线程超过了核心线程数,那么多余的线程会被终止。在模拟场景中,这些多余的线程在空闲 1 秒后会被终止并从线程池中移除。
- 如果活动线程数已经达到最大线程数,并且任务队列也一直已满,那么会根据设置的饱和策略来处理新的任务。在本例中采用的是CallerRunsPolicy策略,即由提交任务的线程来执行该任务。
假设有一个线程池核心线程数为5,最大线程数为10,队列容量为200,当线程池的核心线程数已经满员(5个线程正在工作),且任务队列已满(有200个任务在阻塞队列上等待执行),这时再有新的任务提交到线程池时,线程池会根据拒绝策略来处理这些任务。因为最大线程数为10,所以在线程池中的线程数量达到10个时,新提交的任务将会被拒绝。故此时一般情况下有第211个任务的时候,就会采用拒绝策略了。
线程池应用——实现读取指定路径下的前多少个文件夹下文件的数量
先把完整的代码贴到这里
package com.qcby.demoforspringboot.test;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
class FileCountTask implements Runnable {
private final String folderPath;
private final String folderName;
private int fileCount;
public FileCountTask(String folderPath, String folderName) {
this.folderPath = folderPath;
this.folderName = folderName;
this.fileCount = 0;
}
public int getFileCount() {
return fileCount;
}
@Override
public void run() {
countFiles(new File(folderPath));
}
private void countFiles(File folder) {
if (folder.isFile()) {
fileCount++;
} else if (folder.isDirectory()) {
File[] fileList = folder.listFiles();
if (fileList != null) {
for (File file : fileList) {
countFiles(file);
}
}
}
}
public String getFolderName() {
return folderName;
}
}
public class FolderFileCount {
public static void main(String[] args) {
// 根文件夹路径
File rootFolder = new File("D:\\");
// 获取根文件夹下的子文件夹
File[] subFolders = rootFolder.listFiles(File::isDirectory);
if (subFolders == null || subFolders.length < 5) { // 如果子文件夹数量小于5,则输出提示信息并退出程序
System.out.println("There are not enough subfolders in the root folder.");
return;
}
/* // 按最后修改时间对子文件夹进行排序
Arrays.sort(subFolders, Comparator.comparingLong(File::lastModified).reversed());*/
Arrays.sort(subFolders, Comparator.comparing(File::getName));
// 创建任务列表
List<FileCountTask> tasks = new ArrayList<>();
for (int i = 0; i < 5; i++) { // 只处理前5个子文件夹
File subFolder = subFolders[i];
FileCountTask task = new FileCountTask(subFolder.getPath(), subFolder.getName());
tasks.add(task);
}
// 创建线程池,并执行任务
ExecutorService executorService = Executors.newFixedThreadPool(3); // 创建固定线程数为3的线程池
for (FileCountTask task : tasks) {
executorService.execute(task); // 提交任务给线程池执行
}
executorService.shutdown(); // 关闭线程池,不再接受新的任务
try {
// 等待任务执行完成或超时
boolean finished = executorService.awaitTermination(1, TimeUnit.MINUTES); // 最多等待1分钟
if (finished) { // 如果任务执行完成
// 输出文件夹及对应的文件数量
for (int i = 0; i < tasks.size(); i++) {
FileCountTask task = tasks.get(i);
System.out.println("Folder " + task.getFolderName() + ": " + task.getFileCount() + " files");
}
} else { // 如果任务执行超时
System.out.println("Task execution timeout!");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
任务类解析
首先,这个类有以下成员变量
folderPath
:文件夹路径,表示要进行文件数量统计的文件夹的路径。folderName
:文件夹名称,表示要进行文件数量统计的文件夹的名称。fileCount
:文件数量,表示统计得到的文件的总数。
构造函数
FileCountTask(String folderPath, String folderName)
:构造函数,用于初始化文件夹路径和文件夹名称,并将文件数量初始化为0。
public FileCountTask(String folderPath, String folderName) { this.folderPath = folderPath; this.folderName = folderName; this.fileCount = 0; }
getFileCount()方法
获取文件数量,返回文件数量的值。
public int getFileCount() { return fileCount; }
实现Runnable接口重写其中的run()方法
实现Runnable接口的run()方法,用于启动文件数量统计任务。在这个方法中,会调用countFiles()
方法来进行文件数量统计。
private void countFiles(File folder) { if (folder.isFile()) { fileCount++; } else if (folder.isDirectory()) { File[] fileList = folder.listFiles(); if (fileList != null) { for (File file : fileList) { countFiles(file); } } } }
getFolderName()方法
获取文件夹名称,返回文件夹名称的值。
public String getFolderName() { return folderName; }
FolderFileCount类解析
这个FolderFileCount
类用于统计指定根文件夹下前5个子文件夹的文件数量。
第一部分
- 创建了一个
File
对象rootFolder
,表示根文件夹的路径。 - 使用
listFiles
方法获取根文件夹下的所有子文件夹,存储在File
数组subFolders
中。 - 然后判断
subFolders
是否为空或子文件夹数量是否小于5,如果是,则输出提示信息并退出程序。
// 根文件夹路径 File rootFolder = new File("D:\\"); // 获取根文件夹下的子文件夹 File[] subFolders = rootFolder.listFiles(File::isDirectory); if (subFolders == null || subFolders.length < 5) { // 如果子文件夹数量小于5,则输出提示信息并退出程序 System.out.println("There are not enough subfolders in the root folder."); return; }
第二部分——两种对文件的排序方法
- 使用
Arrays.sort
方法对subFolders
数组进行排序,排序的依据是子文件夹的名称(按字母顺序)这里也注释掉的是排序依据最后修改时间的代码。
/* // 按最后修改时间对子文件夹进行排序 Arrays.sort(subFolders, Comparator.comparingLong(File::lastModified).reversed());*/ Arrays.sort(subFolders, Comparator.comparing(File::getName));
第三部分——创建任务列表
- 创建了一个空的任务列表
tasks
,用于存储FileCountTask
任务对象。 - 使用一个循环,只处理前5个子文件夹:
- 获取每个子文件夹的路径和名称。
- 创建一个
FileCountTask
任务对象,并将其加入到tasks
列表中。
List<FileCountTask> tasks = new ArrayList<>(); for (int i = 0; i < 5; i++) { // 只处理前5个子文件夹 File subFolder = subFolders[i]; FileCountTask task = new FileCountTask(subFolder.getPath(), subFolder.getName()); tasks.add(task); }
第四部分——创建线程池和执行任务
- 使用
Executors.newFixedThreadPool
方法创建了一个固定线程数为5的线程池executorService
。 - 使用一个循环,遍历
tasks
列表中的每个任务对象:- 调用
executorService.execute
方法提交任务给线程池执行。
- 调用
- 调用
executorService.shutdown
方法,关闭线程池,不再接受新的任务。
// 创建线程池,并执行任务 ExecutorService executorService = Executors.newFixedThreadPool(5); // 创建固定线程数为3的线程池 for (FileCountTask task : tasks) { executorService.execute(task); // 提交任务给线程池执行 } executorService.shutdown(); // 关闭线程池,不再接受新的任务
第五部分——等待任务执行完成或超时并做出回应
- 使用
executorService.awaitTermination
方法等待任务执行完成或超时。- 这里设置最多等待1分钟。
- 如果任务执行完成,则输出每个文件夹及对应的文件数量:
- 使用一个循环,遍历
tasks
列表中的每个任务对象。 - 调用
task.getFolderName
方法获取文件夹名称。 - 调用
task.getFileCount
方法获取文件数量。 - 输出文件夹名称和文件数量的信息。
- 使用一个循环,遍历
- 如果任务执行超时,则输出提示信息。
try {
// 等待任务执行完成或超时
boolean finished = executorService.awaitTermination(1, TimeUnit.MINUTES); // 最多等待1分钟
if (finished) { // 如果任务执行完成
// 输出文件夹及对应的文件数量
for (int i = 0; i < tasks.size(); i++) {
FileCountTask task = tasks.get(i);
System.out.println("Folder " + task.getFolderName() + ": " + task.getFileCount() + " files");
}
} else { // 如果任务执行超时
System.out.println("Task execution timeout!");
}
} catch (InterruptedException e) {
e.printStackTrace();
}