为什么您可能需要 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 上的已处理请求 #0 线程
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 上的已处理请求 #0 线程
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)方法。


ExecutorService executorService = Executors.newSingleThreadExecutor(new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                return new Thread(r, "MyThread");
            }
        });
                    
我们覆盖创建新线程的方法,将线程名称传递给构造函数。

ExecutorService executorService = Executors.newSingleThreadExecutor(new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread thread = new Thread(r, "MyThread");
                thread.setPriority(Thread.MAX_PRIORITY);
                return thread;
            }
        });
                    
我们更改了创建线程的名称和优先级。

所以我们看到我们有 2 个重载的Executors.newSingleThreadExecutor方法。一个没有参数,第二个带有ThreadFactory参数。

使用ThreadFactory,您可以根据需要配置创建的线程,例如,通过设置优先级、使用线程子类、向线程添加 UncaughtExceptionHandler