java线程池之Executors

线程池 vs 线程

线程是稀缺资源,如果被无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,合理的使用线程池对线程进行统一分配、调优和监控,有以下好处:

  • 降低资源消耗;
  • 提高响应速度,线程池降低了线程创建和收回的开销;
  • 提高线程的可管理性;

Executors框架

要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,很有可能配置的线程池不是较优的。现在我们来思考这么几个问题?

  • 线程池里面的线程什么时候创建;
  • 线程池大小怎么确定;
  • 线程要定时执行吗
  • 线程的优先级呢.

很复杂有木有? 所以在Executors类里面提供了一些静态工厂,生成一些常用的线程池。先看下这个类的注释:

Factory and utility methods for {@link Executor}, {@link
ExecutorService}, {@link ScheduledExecutorService}, {@link
ThreadFactory}, and {@link Callable} classes defined in this
package.

简单来说就是Executors 实现了对Executor、ExecutorService、ScheduledExecutorService、ThreadFactory、Callable这五个类的工厂方法。

那我们就很好奇了,这五个方法是什么关系呢?和线程池又是什么关系?
先看下类图:
java线程池之Executors_第1张图片
Callable 我们是很熟悉的,就是线程调用的返回。ExecutorService、ScheduledExecutorService都是Executor的实现。那ThreadFactory是啥呢? 继续看注释:

An object that creates new threads on demand. Using thread factories removes hardwiring of calls to new Thread, enabling applications to use special thread subclasses, priorities, etc.

The simplest implementation of this interface is just:
   class SimpleThreadFactory implements ThreadFactory {
    public Thread newThread(Runnable r) {
      return new Thread(r);
    }
  }

就是用来创建线程的工厂类,个性化的定制你的线程。比如你可以用它来记录每个线程的创建时间等信息,以便分析线上问题。
而如果你是做RPC框架研发,需要对线程池做一些封装。比如改变线程池的丢弃策略。线程的等待时间,线程队列大小等。你可以继承ThreadPoolExecutor来实现。小米内部使用的thrift调用框架就是这么实现的。

public class CustomizeThreadPoolExecutor extends ThreadPoolExecutor {
    //个性化定制线程池.
}

那Executors提供了哪些工厂方法呢?

class Executors {
    ExecutorService newFixedThreadPool(...)
    ExecutorService newSingleThreadExecutor(...)
    ScheduledExecutorService newSingleThreadScheduledExecutor()
    ThreadFactory defaultThreadFactory(...)
    Callable callable(...)
    ...
}
 
  

可以看到,返回的类型就是上面注释说的那5个类。下面重点介绍几个常用的来创建线程池。

newSingleThreadExecutor

使用

public static ExecutorService newSingleThreadExecutor()
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory)

创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
这里注意一下java8的lamda表达式的简写:

private static final ExecutorService EXECUTOR_SERVICE = Executors.newSingleThreadExecutor();

EXECUTOR_SERVICE.execute(new Runnable() {
                @Override
                public void run() {
                    doSomeThing();
                }
});

lamda表达式写成:

EXECUTOR_SERVICE.execute(() -> doSomeThing());

进一步的缩写成:

EXECUTOR_SERVICE.execute(this::doSomeThing);

为什么把这个单独拿出来讲,因为简写到这里,你感觉就像是去执行了一个方法,实际上每次调用的时候都new 了一个Runnable的对象。

在笔者做过的线上系统中,有如下地方用到:

  • 来了一个请求之后,要记录每次请求过程中发生了什么。这个是和请求无关的,处理请求的线程可以先返回,然后记录请求的任务放到一个单线程的线程池中。
  • 我们的系统会记录索引,索引创建直接影响到线上服务,所以我们把记录索引,跟踪、记录索引的任务交给了一个新的单线程的线程池来处理。

newFixedThreadPool

public static ExecutorService newFixedThreadPool(int nThreads)

public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory)

创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,在提交新任务,任务将会进入等待队列中等待。如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。

newCachedThreadPool

public static ExecutorService newCachedThreadPool()

public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory)

创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,

那么就会回收部分空闲(60秒处于等待任务到来)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池的最大值是Integer的最大值(2^31-1)。

newScheduledThreadPool

创建一个定长线程池,支持定时及周期性任务执行。
不知道读者还有没有印象,之前讲缓存的时候,缓存定时拉取外部数据,就是使用这种线程池去做的。有兴趣的读者可以移步笔者github。

https://github.com/Acceml/local_cache_manager/blob/master/src/main/java/hello/InterCacheService.java

线程池的使用

上面一节我们讲了如何创建线程池,也就是创建ExecutorService对象,那么线程池以及使用? 我们先看看它提供了一些什么方法:

execute(Runnable)
submit(Runnable)
submit(Callable)

execute(Runnable)

方法 execute(Runnable) 接收壹個 java.lang.Runnable 对象作为参数,并且以异步的方式执行它。如下是壹個使用 ExecutorService 执行 Runnable 的例子:

ExecutorService executorService = Executors.newSingleThreadExecutor();
 
executorService.execute(new Runnable() {
    public void run() {
        System.out.println("Asynchronous task");
    }
});
    
executorService.shutdown();

使用这种方式没有办法获取执行 Runnable 之后的结果,如果你希望获取运行之后的返回值。

submit(Runnable)

方法 submit(Runnable) 同样接收1个 Runnable 的实现作为参数,但是会返回壹1个Future 对象。这個 Future 对象可以用于判断 Runnable 是否结束执行。如下是壹個 ExecutorService 的 submit() 方法的例子:

Future future = executorService.submit(new Runnable() {
    public void run() {
        System.out.println("Asynchronous task");
    }
});
//如果任务结束执行则返回 null
System.out.println("future.get()=" + future.get());

submit(Callable)

方法 submit(Callable) 和方法 submit(Runnable) 比较类似,但是区别则在于它们接收不同的参数类型。Callable 的实例与 Runnable 的实例很类似,但是 Callable 的 call() 方法可以返回结果。方法 Runnable.run() 则不能返回结果。
java线程池之Executors_第2张图片

你可能感兴趣的:(java)