线程池使用实战
1. 必看
1.1 实现功能
使用线程池,批量等比改变某文件夹下所有照片宽度。
其中,改变后的照片宽度为 1000.
1.2 适合人群
有一定的 Java 基础,不适合小白。
2. 技术点
2.1 线程池的创建
private static final ExecutorService executorService =
new ThreadPoolExecutor(4, 8, 30, TimeUnit.SECONDS, new ArrayBlockingQueue<>(50));
【线程池的创建方式】:ThreadPoolExecutor
【基础问题一】:
上面的参数分别代表什么意思?
【基础问题二】:
为什么不用 Executors 创建线程池?
是与队列有关吗?
【高级问题】:
如何自定义线程池?
自定义工厂 和 守护线程 如何关联?
2.2 线程池的关闭
// 优雅关闭线程池
executorService.shutdown();
// 调用上面方法后。再循环调用 awaitTermination 方法,等待线程池真正终止
boolean loop;
do {
/**
* 等待所有线程执行完毕
* executorService.awaitTermination(1, TimeUnit.SECONDS); 会每隔一秒钟检查一次是否执行完毕(状态为 TERMINATED),
* 当从 while 循环退出时就表明线程池已经完全终止了。
*/
loop = !executorService.awaitTermination(1, TimeUnit.SECONDS);
// 阻塞,直到线程池里所有任务执行完毕
// 如果一直阻塞,则代表代码有问题,请检查代码
} while (loop);
2.3 守护线程
除了优雅关闭线程池外,还可以用【守护线程】关闭线程池
【问题】:
守护线程有哪些实际应用场景呢?
2.4 ThreadLocal 的线程安全
private static SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
private static final DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
【基础问题:】
SimpleDateFormat 、DateTimeFormatter 哪个是线程安全的?
【高级问题】:
ThreadLocal 会内存泄漏吗?
有什么解决方案?
2.5 file
获取目录下单层级文件,而不继续向下获取
File[] files = file.listFiles(); // 列出当前目录下的所有文件(不包括子目录)
if (null == files) {
return;
}
List<File> list = Arrays.asList(files);
// 保留以 .jpg .png 结尾的文件
list = list.stream().filter(a -> {
String fileName = a.toString();
return fileName.endsWith(".jpg") || fileName.endsWith(".png");
}).collect(Collectors.toList());
获取目录下所有项,包括子项的子项
Stream<Path> path = Files.walk(Paths.get(folderPath));
path = path.filter(a -> {
String fileName = a.toString();
return fileName.endsWith(".jpg") || fileName.endsWith(".png");
});
3. 不加守护线程的代码
public class ImageUtils {
/**
* DateTimeFormatter : 线程安全
*/
private static final DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
/**
* 线程池的核心线程数为4
* 线程池的最大线程为8
* 线程池的空闲时间为 30 s
* 线程池的阻塞队列 ArrayBlockingQueue 容量为50
*/
private static final ExecutorService executorService = new ThreadPoolExecutor(4, 8, 30, TimeUnit.SECONDS, new ArrayBlockingQueue<>(50));
/**
* 替换为你的文件夹路径
*/
public static final String folderPath = "D:\\02-document\\image";
/**
* targetWidth:照片宽度
* 建议宽度:1000
*/
public static final int targetWidth = 1000;
public static void main(String[] args) throws IOException {
try {
LocalDateTime startTime = LocalDateTime.now();
File file = new File(folderPath);
File[] files = file.listFiles(); // 列出当前目录下的所有文件(不包括子目录)
if (null == files) {
return;
}
List<File> list = Arrays.asList(files);
// 保留以 .jpg .png 结尾的文件
list = list.stream().filter(a -> {
String fileName = a.toString();
return fileName.endsWith(".jpg") || fileName.endsWith(".png");
}).collect(Collectors.toList());
list.forEach(data -> {
Thread myThread = new Thread(() -> {
try {
convertImage(folderPath, "\\" + data.getName());
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(System.currentTimeMillis() + "sub Thread Name:" + Thread.currentThread().getName());
});
// 我们创建的线程放入到线程池中去
executorService.execute(myThread);
});
// 优雅关闭线程池
executorService.shutdown();
// 调用上面方法后。再循环调用 awaitTermination 方法,等待线程池真正终止
boolean loop;
do {
/**
* 等待所有线程执行完毕
* executorService.awaitTermination(1, TimeUnit.SECONDS); 会每隔一秒钟检查一次是否执行完毕(状态为 TERMINATED),
* 当从 while 循环退出时就表明线程池已经完全终止了。
*/
loop = !executorService.awaitTermination(1, TimeUnit.SECONDS);
// 阻塞,直到线程池里所有任务执行完毕
// 如果一直阻塞,则代表代码有问题,请检查代码
} while (loop);
LocalDateTime endTime = LocalDateTime.now();
Duration duration = Duration.between(startTime, endTime);
System.out.println("开始时间:" + startTime.format(dtf));
System.out.println("结束时间:" + endTime.format(dtf));
System.out.println("持续时间/S:" + duration.toMillis() + "毫秒");
} catch (Exception ignored) {
}
}
/**
* 通过BufferedImage图片流调整图片大小
*/
public static BufferedImage resizeImage(BufferedImage originalImage, int targetWidth, int targetHeight) throws IOException {
Image resultingImage = originalImage.getScaledInstance(targetWidth, targetHeight, Image.SCALE_AREA_AVERAGING);
BufferedImage outputImage = new BufferedImage(targetWidth, targetHeight, BufferedImage.TYPE_INT_RGB);
outputImage.getGraphics().drawImage(resultingImage, 0, 0, null);
return outputImage;
}
public static void convertImage(String url, String imageName) {
try {
// 睡眠 1 S,仅作测试时使用
Thread.sleep(1000);
BufferedImage image = ImageIO.read(Files.newInputStream(Paths.get(url + imageName)));
int width = image.getWidth();
int height = image.getHeight();
int targetHeight = height * targetWidth / width;
BufferedImage newImage = ImageUtils.resizeImage(image, targetWidth, targetHeight);
url = url + "\\" + "convert";
File folder = new File(url);
// 文件不存在,则新建。如果没有权限,则创建文件夹失败
if (!folder.exists()) {
if (!folder.mkdir()) {
throw new RuntimeException("请检查是否有权限创建文件夹!");
}
}
System.out.println(url + imageName);
ImageIO.write(newImage, "jpg", new File(url + imageName));
} catch (Exception e) {
e.printStackTrace();
}
}
}
4. 加入守护线程的代码
public class ImageUtils {
/**
* DateTimeFormatter : 线程安全
*/
private static final DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
/**
* 设置线程池名称
*/
private static final ThreadFactory dealImageFactory = new CustomizableThreadFactory("dealImagePoolExecutor-");
/**
* 线程池的核心线程数为4
* 线程池的最大线程为8
* 线程池的空闲时间为 30 s
* 线程池的阻塞队列 ArrayBlockingQueue 容量为50
*/
private static final ExecutorService dealImagePoolExecutor = new ThreadPoolExecutor(4, 8, 30, TimeUnit.SECONDS, new ArrayBlockingQueue<>(50),dealImageFactory);
/**
* 替换为你的文件夹路径
*/
public static final String folderPath = "D:\\02-document\\image";
/**
* targetWidth:照片宽度
* 建议宽度:1000
*/
public static final int targetWidth = 1000;
public static void main(String[] args) {
try {
Thread daemonThread = new Thread(() -> {
Thread.currentThread().setName("守护线程");
System.out.println("Thread Name:" + Thread.currentThread().getName() + "-开始1");
dealImageByPool();
});
daemonThread.setDaemon(true);
daemonThread.start();
// 守护线程执行 5 秒
Thread.sleep(5000);
System.out.println("Thread Name:" + daemonThread.getName() + "-结束1");
System.out.println("Thread Name:" + Thread.currentThread().getName() + "-结束2");
} catch (Exception ignored) {
}
}
private static void dealImageByPool() {
try {
LocalDateTime startTime = LocalDateTime.now();
File file = new File(folderPath);
File[] files = file.listFiles(); // 列出当前目录下的所有文件(不包括子目录)
if (null == files) {
return;
}
List<File> list = Arrays.asList(files);
// 保留以 .jpg .png 结尾的文件
list = list.stream().filter(a -> {
String fileName = a.toString();
return fileName.endsWith(".jpg") || fileName.endsWith(".png");
}).collect(Collectors.toList());
list.forEach(data -> {
Thread myThread = new Thread(() -> {
try {
convertImage(folderPath, "\\" + data.getName());
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("Thread Name:" + Thread.currentThread().getName());
});
// 我们创建的线程放入到线程池中去
dealImagePoolExecutor.execute(myThread);
});
// 优雅关闭线程池
dealImagePoolExecutor.shutdown();
// 调用上面方法后。再循环调用 awaitTermination 方法,等待线程池真正终止
boolean loop;
do {
/**
* 等待所有线程执行完毕
* executorService.awaitTermination(1, TimeUnit.SECONDS); 会每隔一秒钟检查一次是否执行完毕(状态为 TERMINATED),
* 当从 while 循环退出时就表明线程池已经完全终止了。
*/
loop = !dealImagePoolExecutor.awaitTermination(1, TimeUnit.SECONDS);
// 阻塞,直到线程池里所有任务执行完毕
// 如果一直阻塞,则代表代码有问题,请检查代码
} while (loop);
LocalDateTime endTime = LocalDateTime.now();
Duration duration = Duration.between(startTime, endTime);
System.out.println(Thread.currentThread().getName() + "-开始时间:" + startTime.format(dtf));
System.out.println(Thread.currentThread().getName() + "-结束时间:" + endTime.format(dtf));
System.out.println(Thread.currentThread().getName() + "-持续时间/S:" + duration.toMillis() + "毫秒");
} catch (Exception ignored) {
}
}
/**
* 通过BufferedImage图片流调整图片大小
*/
public static BufferedImage resizeImage(BufferedImage originalImage, int targetWidth, int targetHeight) throws IOException {
Image resultingImage = originalImage.getScaledInstance(targetWidth, targetHeight, Image.SCALE_AREA_AVERAGING);
BufferedImage outputImage = new BufferedImage(targetWidth, targetHeight, BufferedImage.TYPE_INT_RGB);
outputImage.getGraphics().drawImage(resultingImage, 0, 0, null);
return outputImage;
}
public static void convertImage(String url, String imageName) {
try {
BufferedImage image = ImageIO.read(Files.newInputStream(Paths.get(url + imageName)));
int width = image.getWidth();
int height = image.getHeight();
int targetHeight = height * targetWidth / width;
BufferedImage newImage = ImageUtils.resizeImage(image, targetWidth, targetHeight);
url = url + "\\" + "convert";
File folder = new File(url);
// 文件不存在,则新建。如果没有权限,则创建文件夹失败
if (!folder.exists()) {
if (!folder.mkdir()) {
throw new RuntimeException("请检查是否有权限创建文件夹!");
}
}
System.out.println(url + imageName);
ImageIO.write(newImage, "jpg", new File(url + imageName));
} catch (Exception e) {
e.printStackTrace();
}
}
}