随着计算机硬件性能的不断提升,多核CPU的普及,现代计算机系统的性能越来越强大。在这样的环境下,如何更好地利用计算机系统的性能优势,提高程序的运行效率,是每一个Java开发者需要思考的问题。
Java中提供了多线程编程的支持,但是在多线程编程中,线程的创建、启动、调度等都需要耗费一定的系统资源,如果线程的数量过多,会导致系统性能下降,甚至可能导致系统崩溃。为了解决这个问题,Java提供了线程池的概念,通过线程池可以对线程的数量进行管理,减少线程的创建和销毁次数,提高程序的效率和性能。
本文将介绍Java中的一个常用线程池类——newFixedThreadPool。
一、newFixedThreadPool的概述
newFixedThreadPool是Java中的一个线程池类,它是一个固定大小的线程池,线程的数量在创建线程池时就已经确定。线程池中的线程数量一旦被确定,就不会发生改变。在Java中,newFixedThreadPool()方法创建的线程池是一个固定大小的线程池,线程池中的线程数量是固定的,由构造函数传入的参数指定,而任务队列的大小则由内部的阻塞队列来决定。
在使用newFixedThreadPool()方法创建线程池时,它使用的是LinkedBlockingQueue,这是一个无界的阻塞队列,它的大小是没有限制的。因此,当任务提交到线程池时,如果线程池中的线程正在执行任务,那么新提交的任务将被放入LinkedBlockingQueue中等待执行,直到有可用的线程来执行任务。当队列已经满了时,新提交的任务将会被阻塞,直到有空闲线程来处理队列中的任务。
因此,使用newFixedThreadPool()方法创建线程池时,队列的大小实际上是无限制的,但是需要注意的是,如果任务提交速度过快,队列可能会无限制地增长,导致内存溢出等问题。因此,在实际使用中需要根据具体的场景来合理设置线程池的大小和任务队列的容量,以充分利用系统资源并保证系统的稳定性。
二、newFixedThreadPool的使用
使用newFixedThreadPool非常简单,只需要创建一个ThreadPoolExecutor对象,并向其提交任务即可。线程池会自动分配线程来执行任务,如果线程池中的线程数量不足,任务会被放入阻塞队列中等待执行。
下面是一个使用newFixedThreadPool的示例:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Main {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (int i = 0; i < 10; i++) {
executorService.execute(new Task(i));
}
executorService.shutdown();
}
}
class Task implements Runnable {
private int id;
public Task(int id) {
this.id = id;
}
public void run() {
System.out.println("Task " + id + " is running...");
}
}
在这个例子中,创建了一个大小为5的线程池,并向其中提交了10个任务。每个任务只是简单地输出一条信息。任务的执行结果如下所示:
Task 0 is running...
Task 1 is running...
Task 2 is running...
Task 3 is running...
Task 4 is running...
Task 5 is running...
Task 6 is running...
Task 7 is running...
Task 8 is running...
Task 9 is running...
可以看到,线程池中的5个线程依次执行了任务,并输出了相关信息。
在提交任务时,需要注意线程池的关闭。在本例中,调用了executorService.shutdown()方法,该方法会向线程池中的所有线程发送一个中断信号,线程池中的所有线程都将退出。
三、newFixedThreadPool的优点
简单易用:newFixedThreadPool是Java内置的线程池,使用起来非常简单,不需要手动创建线程、管理线程、调度线程等。
稳定性好:newFixedThreadPool是一个固定大小的线程池,线程数量不会发生变化,因此稳定性较好,不容易因线程数量过多导致系统崩溃。
高效性:newFixedThreadPool在任务队列中维护了一个阻塞队列,可以很好地处理大量的并发请求,提高了程序的效率和性能。
良好的可扩展性:newFixedThreadPool可以通过修改线程池中的线程数量来适应不同的系统负载,提高了系统的可扩展性。
四、newFixedThreadPool的缺点
线程数固定:newFixedThreadPool创建时需要指定线程数量,线程数量不能动态调整,因此无法适应不同的系统负载。
队列长度有限:newFixedThreadPool使用阻塞队列存储任务,队列长度有限,如果队列已满,新的任务将无法提交,可能会导致任务丢失。
任务执行时间不可控:newFixedThreadPool无法控制任务执行的时间,如果任务执行时间过长,会导致其他任务等待的时间过长,降低程序的效率和性能。
五、线程池的两种主要任务提交方法
在Java中,线程池的两种主要任务提交方法是execute()和submit()。它们虽然都是将任务提交到线程池中,但是在使用上有一些区别。
返回值类型不同
execute()方法没有返回值,而submit()方法会返回一个Future对象,可以通过这个对象获取任务的执行结果。
异常处理不同
execute()方法中如果任务执行过程中发生了异常,则异常会被传递到任务提交的地方,并由任务提交的线程来处理。而submit()方法中,如果任务执行过程中发生了异常,异常将被封装在Future对象中,直到调用Future.get()方法时才会将异常抛出。
任务参数不同
execute()方法只能接受Runnable类型的任务,而submit()方法既可以接受Runnable类型的任务,也可以接受Callable类型的任务。
提交方式不同
execute()方法是一种异步提交方式,即提交任务后立即返回,不会等待任务执行完成。而submit()方法是一种同步提交方式,即提交任务后会阻塞当前线程,直到任务执行完成。
能否取消任务
submit()方法返回的Future对象可以用来取消任务,而execute()方法没有提供取消任务的方法。
总之,execute()方法比submit()方法更简单,适用于不需要处理返回值的情况,而submit()方法则更为灵活,可以处理返回值,并且支持取消任务等操作。
六、代码举例
使用 executorService.isTerminated() 可以判断线程池中的任务是否全部执行完毕,但需要注意的是,该方法只有在调用 executorService.shutdown() 方法后才会生效。因此,如果需要在所有任务执行完毕后调用 method06(),代码如下:
public class Main {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(5);
//executorService.execute(() -> method01());
//executorService.execute(() -> method02());
//executorService.execute(() -> method03());
//executorService.execute(() -> method04());
//executorService.execute(() -> method05());
executorService.execute(() -> {
try {
method01();
} catch (Exception e) {
e.printStackTrace();
}
});
executorService.execute(() -> {
try {
method02();
} catch (Exception e) {
e.printStackTrace();
}
});
executorService.execute(() -> {
try {
method03();
} catch (Exception e) {
e.printStackTrace();
}
});
executorService.execute(() -> {
try {
method04();
} catch (Exception e) {
e.printStackTrace();
}
});
executorService.execute(() -> {
try {
method05();
} catch (Exception e) {
e.printStackTrace();
}
});
executorService.shutdown();
try {
executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
method06();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private static void method01() {
// 解析1.txt文件
}
private static void method02() {
// 解析2.txt文件
}
private static void method03() {
// 解析3.txt文件
}
private static void method04() {
// 解析4.txt文件
}
private static void method05() {
// 解析5.txt文件
}
private static void method06() {
// 在5个任务全部执行完毕后调用
}
}
executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS) 是一个阻塞方法,会一直等待直到线程池中所有的任务都执行完毕或者等待超时,其作用是等待线程池中所有任务执行完成后,再继续执行主线程下面的代码。
具体来说,当调用 ExecutorService.shutdown() 方法后,线程池会停止接受新任务,但是还有一些任务正在执行或者在等待执行。当调用 awaitTermination() 方法时,它会一直阻塞,直到线程池中所有任务都执行完毕,或者等待超时,方法会返回 true 或者 false。如果返回 true,则表示线程池中的所有任务都已经执行完成;如果返回 false,则表示等待超时,线程池中还有未执行的任务。
在本题的情境下,我们使用 executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS) 方法可以让主线程一直阻塞等待所有任务完成,然后再继续执行下面的代码。
七、总结
newFixedThreadPool是Java中的一个常用线程池类,它可以有效地管理线程数量,提高程序的效率和性能。使用newFixedThreadPool可以避免线程数量过多导致系统性能下降和崩溃的问题,同时还可以提高系统的稳定性和可扩展性。
在实际使用过程中,需要根据具体的业务需求选择不同的线程池类,并合理设置线程池的参数,才能更好地发挥线程池的作用,提高程序的效率和性能。
八、代码示例:
public void method(HttpServletRequest request) throws Exception {
ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()*2+1);
try {
final String mailno = request.getParameter("mailno");
final HttpServletRequest req = request;
newFixedThreadPool.execute(new Runnable() {
@Override
public void run() {
try {
// 调用的方法体
new Mail().getMailno(mailno,req);
} catch (Exception e) {
e.printStackTrace();
}
}
});
newFixedThreadPool.shutdown();
while (true){
if (newFixedThreadPool.isTerminated()){
break;
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
newFixedThreadPool.shutdown();
}
}