为什么要使用线程池
在Java中使用线程池的主要原因有以下几点:
-
提高性能:使用线程池可以减少线程的创建和销毁过程的开销。线程的创建和销毁是比较昂贵的操作,如果每次需要执行任务时都创建一个新线程,会造成系统资源的浪费。而线程池可以在程序启动时就创建一定数量的线程,然后重复使用这些线程来执行多个任务。这样可以减少线程创建和销毁的次数,提高系统的性能。
-
控制并发数量:线程池可以控制同时执行的任务数量。通过设置线程池的大小,可以限制并发执行的线程数量,避免系统资源被过度占用,导致整个系统的性能下降。
-
提供可管理的线程:线程池提供了一种管理和监视线程的机制。通过使用线程池,可以方便地对线程进行管理,比如动态调整线程池大小、监控线程的运行状态等。
-
提供线程复用:线程池可以重复利用已有的线程来执行任务,避免了线程的创建和销毁过程,提高了系统的效率。
-
提供任务排队机制:线程池可以提供一个任务队列,用于存放等待执行的任务。当线程池中的线程都在执行任务时,新的任务可以被放入队列中,等待线程空闲时执行。这样可以避免任务因为没有可用线程而被丢弃或者阻塞。
常用的几种线程池以及其中的工作原理
在Java中,常用的几种线程池有以下几种:
-
FixedThreadPool:固定大小的线程池,线程数量固定不变,适合执行长期的任务。如果所有线程都处于繁忙状态,新任务将在队列中等待。
-
CachedThreadPool:可缓存的线程池,线程数量不固定,适合执行大量的短期任务。如果线程池中有可用的线程,就会重用它;如果没有可用线程,就会创建一个新线程。当线程闲置时间超过60秒时,线程将被终止并从池中移出。
-
SingleThreadPool:单线程的线程池,只会创建一个线程来执行任务。适合需要顺序执行任务的场景。
-
ScheduledThreadPool:用于延时或定时执行任务的线程池。可以按照指定的周期来执行任务,或者在指定的延迟时间之后执行任务。
线程池的工作原理如下:
-
创建线程池时,会初始化一定数量的线程,这些线程会一直存在,等待任务的到来。
-
当有任务提交给线程池时,会从线程池中选择一个空闲的线程来执行任务。如果没有空闲线程,任务将被添加到线程池的任务队列中等待执行。
-
如果任务队列已满,且线程池中的线程数未达到线程池的最大线程数限制,线程池会创建一个新的线程来执行任务。
-
当线程执行完任务后,会检查任务队列中是否还有等待执行的任务。如果有,继续从任务队列中选择任务来执行。如果没有,线程将进入空闲状态,等待下一个任务的到来。
-
当线程池不再接收新的任务时,可以调用
shutdown()
方法来关闭线程池。关闭线程池后,线程池中的线程会等待任务队列中的任务执行完毕后退出。
线程池启动线程 submit()和 execute()方法有什么不同
submit()方法和execute()方法都是用于向线程池提交任务的方法,但是它们有一些不同之处:
-
返回值不同:submit()方法返回一个Future对象,可以用来获取任务的执行结果或取消任务;而execute()方法没有返回值。
-
异常处理不同:submit()方法会捕获任务执行过程中的异常,并将其封装到Future对象中,通过调用Future对象的get()方法可以获取到异常信息;而execute()方法不会捕获任务执行过程中的异常,如果任务发生异常,线程池会将异常抛出到控制台。
-
可接收的参数类型不同:submit()方法可以接收Callable对象、Runnable对象或Runnable实现类的对象作为参数;而execute()方法只能接收Runnable对象或Runnable实现类的对象作为参数。
实例
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;public class ScheduledThreadPoolExample {public static void main(String[] args) {// 创建ScheduledThreadPool,指定线程池中的线程数为3ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);// 定义一个Runnable任务Runnable task = new Runnable() {@Overridepublic void run() {System.out.println("Task executed at " + System.currentTimeMillis());}};// 使用scheduledExecutorService调度任务,在延迟1秒后开始执行任务,并且每隔3秒重复执行scheduledExecutorService.scheduleAtFixedRate(task, 1, 3, TimeUnit.SECONDS);// 主线程休眠10秒,以便观察任务的执行try {Thread.sleep(10000);} catch (InterruptedException e) {e.printStackTrace();}// 关闭ScheduledThreadPoolscheduledExecutorService.shutdown();}
}
在这个示例中,我们首先创建了一个ScheduledThreadPool,指定线程池中的线程数为3。
然后,我们定义了一个Runnable任务,该任务将在执行时打印当前时间。
接下来,我们使用scheduledExecutorService.scheduleAtFixedRate()方法来调度任务。这个方法接受四个参数:任务、延迟时间、重复间隔和时间单位。在这个例子中,我们将任务延迟1秒后开始执行,并且每隔3秒重复执行一次。
最后,我们让主线程休眠10秒,以便观察任务的执行。然后,我们调用scheduledExecutorService.shutdown()方法来关闭ScheduledThreadPool。
总结
线程池的主要优点如下:
- 重用线程:线程池可以重用已经创建的线程,避免了频繁地创建和销毁线程的开销。
- 控制并发数量:线程池可以限制并发线程的数量,防止过多的线程导致系统资源的浪费或者负载过重。
- 提高响应速度:线程池可以预先创建一组线程,当任务到达时可以立即执行,而不需要等待线程的创建过程,从而提高系统响应速度。
- 提供线程管理和监控功能:线程池可以提供一些管理和监控的方法,用于管理和监控线程的状态和运行情况。
使用线程池时需要注意以下几点:
- 线程池大小的选择:线程池的大小需要根据系统的负载和资源情况进行选择,太小会导致线程不足,任务无法及时处理,太大会导致资源浪费和负载过重。
- 线程池的关闭:在程序结束时需要正确地关闭线程池,释放资源,避免资源泄漏。
- 任务的提交方式:任务的提交可以采用不同的方式,如同步提交、异步提交等,需要根据实际需要进行选择。
- 错误处理机制:线程池需要有相应的错误处理机制,处理任务执行过程中的异常情况。