在Java中,Executors是一个线程池的工厂类,它可以创建不同类型的线程池。下面是几种常见的Executors线程池,以及它们的使用区别:
- FixedThreadPool:这种类型的线程池有一个固定的线程数量,一旦线程池中的全部线程都在处理任务,那么后续提交的任务将会等待。如果应用程序需要限制线程数量,以便于限制系统资源的使用总量,适用于大量耗时较长的任务。
- SingleThreadExecutor:只有一个线程的线程池,适用于需要线程按顺序执行的场景。
- CachedThreadPool:这种类型的线程池不需要指定线程数量,它根据需要创建新线程,如果线程池中有空余线程,那么就会使用这些线程,如果没有,就会创建新线程。适用于需要快速响应但并发量不大的任务。
- ScheduledThreadPool:这种类型的线程池可以替代定时器,用于延迟执行或者按周期执行任务,可以通过调用schedule方法或scheduleAtFixedRate以指定的时间周期来执行任务。
总之,不同类型的线程池适用于不同的场景。应该根据具体的应用程序需求来选择合适的线程池类型。
FixedThreadPool 线程池的使用示例
FixedThreadPool 以创建固定数量的线程来执行任务。在使用 FixedThreadPool 时需要注意以下几点:
- 创建线程池时需要指定线程数量,线程数量固定,无法动态调整。
- FixedThreadPool 会将任务提交给空闲线程执行,如果所有线程都在执行任务,新提交的任务会被放入等待队列中。
- 线程池中的线程是一直存在的,如果不手动关闭线程池,它将一直占用系统资源。
使用示例:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class FixedThreadPoolExample {
public static void main(String[] args) {
// 创建一个包含 5 个线程的固定线程池
ExecutorService executor = Executors.newFixedThreadPool(5);
// 提交 10 个任务给线程池执行
for (int i = 0; i < 10; i++) {
Runnable task = new Task(i);
executor.execute(task);
}
// 关闭线程池
executor.shutdown();
}
private static class Task implements Runnable {
private int taskId;
public Task(int taskId) {
this.taskId = taskId;
}
@Override
public void run() {
System.out.println("Task " + taskId + " is running.");
}
}
}
在这个示例中,我们创建了一个包含 5 个线程的 FixedThreadPool,并提交了 10 个任务给线程池执行。由于线程数量固定,所以会先执行前 5 个任务,后面 5 个任务会被放入等待队列中。最后我们调用 executor.shutdown() 方法来关闭线程池。
CachedThreadPool 缓存线程池的使用示例
缓存线程池是指根据需要自动创建线程的线程池,如果线程池中有空闲线程,则会重复使用,如果没有则会自动创建。使用缓存线程池通常适用于需要快速响应的任务。
以下是一个例子,演示如何使用缓存线程池来执行多个任务:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MyTask implements Runnable {
private int taskNum;
public MyTask(int num) {
this.taskNum = num;
}
@Override
public void run() {
System.out.println("正在执行task " + taskNum);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("task " + taskNum + " 执行完毕");
}
}
public class TestThreadPool {
public static void main(String[] args) {
// 创建缓存线程池
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
// 提交任务到线程池
cachedThreadPool.execute(new MyTask(i));
}
// 关闭线程池
cachedThreadPool.shutdown();
System.out.println("所有任务已提交");
}
}
在上面的代码示例中,创建了一个缓存线程池,然后提交了10个任务到线程池中。由于缓存线程池会根据需要自动创建线程,因此在执行这些任务的过程中线程数会动态增长,直到达到系统的最大线程数。任务执行完毕后,关闭线程池。
注意: 在使用线程池时,需要及时关闭线程池以释放资源,否则会导致内存泄漏等问题。可以使用shutdown()
方法来关闭线程池。如果需要让所有任务执行完后再关闭线程池,可以使用awaitTermination()
来等待任务执行完毕。
ScheduledThreadPool 定时线程池的使用示例和注意事项
定时线程池可以用来在指定时间或者周期性地执行任务,它主要的方法是schedule()
和scheduleAtFixedRate()
。以下是一个使用定时线程池的示例,它会在3秒后执行一次任务,并且每隔2秒执行一次任务:
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class TestScheduledThreadPool {
public static void main(String[] args) {
// 创建定时线程池
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(1);
// 延迟3秒后执行任务
scheduledThreadPool.schedule(new Runnable() {
@Override
public void run() {
System.out.println("任务一:延迟3秒后执行。");
}
}, 3, TimeUnit.SECONDS);
// 每隔2秒执行一次任务
scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("任务二:每隔2秒执行一次任务。");
}
}, 0, 2, TimeUnit.SECONDS);
// 关闭线程池
// scheduledThreadPool.shutdown();
}
}
需要注意的一些事项如下:
- 在使用定时线程池时,需要使用
Executors.newScheduledThreadPool()
方法来创建线程池。 -
schedule()
方法用于延迟执行任务,而scheduleAtFixedRate()
方法用于按照指定周期执行任务。 - 在
scheduleAtFixedRate()
方法中,第一个参数为要执行的任务,第二个参数为开始执行的延迟时间,第三个参数为每个任务执行的周期,第四个参数为时间单位。 - 在使用完定时线程池后,需要调用
shutdown()
方法来关闭线程池。
当我们运行上述代码时,可以看到如下输出结果:
任务一:延迟3秒后执行。
任务二:每隔2秒执行一次任务。
任务二:每隔2秒执行一次任务。
任务二:每隔2秒执行一次任务。
可以看到,在3秒后,第一个任务开始执行,而第二个任务会在3秒后开始执行,然后每隔2秒执行一次。如果我们需要中止任务的执行,可以使用ScheduledFuture.cancel()
方法。
SingleThreadExecutor使用及注意事项
SingleThreadExecutor 只会创建一个线程,如果该线程因为异常终止,线程池会自动创建一个新的线程来代替它。
SingleThreadExecutor 适用于需要按照顺序执行任务的场景,因为它保证任务的顺序性。
- SingleThreadExecutor 只有一个线程,如果这个线程出现异常或者挂起,那么整个线程池就会被卡住,无法执行任务。
- 线程池中的线程如果不手动关闭,会一直存在,可能会引发内存泄漏等问题。
- 如果任务量过大,会导致线程阻塞,影响程序性能。
- 线程池中的线程只有一个,如果任务执行时间过长,可能会影响其他任务的执行。
- SingleThreadExecutor 适用于任务量较小的场景,不适用于高并发场景。
- SingleThreadExecutor 执行任务时需要注意异常处理,尽量避免出现未捕获的异常导致线程池崩溃。
下面是一个使用 SingleThreadExecutor 的示例代码:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SingleThreadExecutorExample {
public static void main(String[] args) {
ExecutorService executorService = Executors.newSingleThreadExecutor();
// 提交任务
executorService.submit(() -> {
System.out.println("Task 1 executed by thread: " + Thread.currentThread().getName());
});
executorService.submit(() -> {
System.out.println("Task 2 executed by thread: " + Thread.currentThread().getName());
});
executorService.submit(() -> {
System.out.println("Task 3 executed by thread: " + Thread.currentThread().getName());
});
// 关闭线程池
executorService.shutdown();
}
}
在上面的示例代码中,我们首先通过 Executors.newSingleThreadExecutor()
创建了一个 SingleThreadExecutor 线程池,然后通过 executorService.submit()
方法提交了三个任务。由于 SingleThreadExecutor 只有一个线程,因此这些任务会依次被执行,输出结果类似于:
Task 1 executed by thread: pool-1-thread-1
Task 2 executed by thread: pool-1-thread-1
Task 3 executed by thread: pool-1-thread-1
最后,我们通过 executorService.shutdown()
方法关闭了线程池。需要注意的是,在使用 SingleThreadExecutor 时,如果没有手动关闭线程池,线程池中的线程会一直存在,可能会引发内存泄漏等问题。因此,我们需要在适当的时候手动关闭线程池。