为什么您可能需要 1 个线程的 ExecutorService?
您可以使用Executors.newSingleThreadExecutor方法创建一个ExecutorService,它带有一个包含单个线程的池。pool的逻辑如下:
- 该服务一次只执行一项任务。
- 如果我们提交N个任务执行,所有N个任务都会被单线程一个接一个地执行。
- 如果线程被中断,将创建一个新线程来执行任何剩余的任务。
让我们想象一下我们的程序需要以下功能的情况:
我们需要在 30 秒内处理用户请求,但单位时间内不超过一个请求。
我们创建一个任务类来处理用户请求:
class Task implements Runnable {
private final int taskNumber;
public Task(int taskNumber) {
this.taskNumber = taskNumber;
}
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException ignored) {
}
System.out.printf("Processed request #%d on thread id=%d\\n", taskNumber, Thread.currentThread().getId());
}
}
该类对处理传入请求的行为进行建模并显示其编号。
接下来,在main方法中,我们为 1 个线程创建一个ExecutorService,我们将使用它来顺序处理传入的请求。由于任务条件规定“30秒以内”,我们加上30秒等待,然后强行停止ExecutorService。
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newSingleThreadExecutor();
for (int i = 0; i < 1_000; i++) {
executorService.execute(new Task(i));
}
executorService.awaitTermination(30, TimeUnit.SECONDS);
executorService.shutdownNow();
}
启动程序后,控制台显示有关请求处理的消息:
ID = 16 上的已处理请求 #1 线程
ID = 16 上的已处理请求 #2
...
线程 ID = 16 上的已处理请求 #29
处理请求 30 秒后,executorService调用shutdownNow()方法,该方法停止当前任务(正在执行的任务)并取消所有挂起的任务。之后,程序成功结束。
但一切并不总是那么完美,因为我们的程序很容易出现这样的情况,即我们池中唯一的线程拾取的任务之一工作不正确,甚至终止我们的线程。我们可以模拟这种情况来弄清楚executorService在这种情况下如何与单个线程一起工作。
为此,在执行其中一项任务时,我们使用不安全且过时的Thread.currentThread().stop()方法终止我们的线程。我们这样做是为了模拟其中一个任务终止线程的情况。
我们将更改Task类中的运行方法:
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException ignored) {
}
if (taskNumber == 5) {
Thread.currentThread().stop();
}
System.out.printf("Processed request #%d on thread id=%d\\n", taskNumber, Thread.currentThread().getId());
}
我们将中断任务#5。
让我们看看线程在任务 #5 结束时被中断的输出是什么样子的:
id=16 上的已处理
请求 #1 线程 id=16 上的已
处理请求 #2 线程 id=16 上的已处理请求 #3 线程 id=16
上的已处理请求 #4
已处理请求 #6线程 id=17
线程 id=17 上的已处理请求 #7
…
线程 id=17 上已处理的请求 #29
我们看到线程在任务 5 结束时被中断后,任务开始在标识符为 17 的线程中执行,尽管它们之前是在标识符为 16 的线程上执行的。并且因为我们的池有一个单线程,这只能说明一件事:executorService用一个新的线程替换了停止的线程并继续执行任务。
因此,当我们想要顺序处理任务并且一次只处理一个任务时,我们应该使用带有单线程池的 newSingleThreadExecutor,并且我们想要继续处理队列中的任务而不管前一个任务是否完成(例如,一个我们的任务杀死了线程)。
线程工厂
说到创建和重新创建线程,我们不能不提线程工厂.
A线程工厂是一个按需创建新线程的对象。
我们可以创建自己的线程创建工厂并将其实例传递给Executors.newSingleThreadExecutor(ThreadFactory threadFactory)方法。
|
我们覆盖创建新线程的方法,将线程名称传递给构造函数。 |
|
我们更改了创建线程的名称和优先级。 |
所以我们看到我们有 2 个重载的Executors.newSingleThreadExecutor方法。一个没有参数,第二个带有ThreadFactory参数。
使用ThreadFactory,您可以根据需要配置创建的线程,例如,通过设置优先级、使用线程子类、向线程添加 UncaughtExceptionHandler等。
GO TO FULL VERSION