浅谈java线程池(上)

从多线程开始

计算机发展初期,还没有操作系统,它们始终运行一个程序,这个程序访问所有资源。这样运行方式使得人们不能很好的利用计算机资源。

操作系统的发展使得程序可以同时运行,也促进了线程的发展。

多线程的优点

合理利用多线程,可以让程序运行得更快,能够充分利用计算机的资源,可以简单的处理异步事件,也可以构造友好的用户界面。

多线程的风险

并不是所有多线程都能够提高程序性能,并发的风险主要体现在:

  • 上下文切换:CPU资源是非常宝贵的,CPU花费大量资源在对用户线程的切换上不值得。
  • 死锁:多个线程对同一资源的互相争用,陷入死循环。
  • 资源限制:计算机资源是有限的,内存,IO,带宽。资源的限制也会让多线程陷入麻烦。

因此,使用多线程编程也需要引入一些特定的框架和方法,以规避风险。

java中的线程池

线程池就是运用场景最多的并发框架,几乎所有用到并发或异步执行的程序都可以用到线程池。合理使用线程池能够带来:

  • 降低资源消耗。重复利用已经创建的线程减少创建销毁线程的消耗。
  • 提高响应速度。免去创建线程消耗的时间。
  • 方便管理线程。统一调配线程,避免线程无限制耗费资源。

Executor框架

Executor是一个用户级别的调度器,它用于将用户任务映射到固定数量的线程上,操作系统内核将这些线程映射到处理器上。Executor框架的作用就是将用户任务与操作系统内核隔离。这种两级调用模型图如下:
在这里插入图片描述
下面详细介绍一下Executor框架中的重要接口和类。
先看一下类与接口关系图
在这里插入图片描述

Executor接口

Executor是一个根接口,它只定义了execute方法,参数Runnable

public interface Executor {
    void execute(Runnable command);
}
ExecutorService接口

ExecutorService 接口对 Executor 接口进行了扩展,提供了终止,关闭线程池方法。重载了执行任务的方法,提供了一个返回为 Future 的泛形对象,它能够获取任务的执行结果。

线程池都需要实现这个接口,所以通常定义为线程池的通用接口。

Future接口

提供取消任务,获取任务执行结果方法,它可以这样用

public void TestFuture {
    ExecutorService threadPool = Executors.newCachedThreadPool();
    
    Future<String> future = threadPool.submit(new Callable<String>() {
      @Override
      public String call() throws Exception {
        return "hello world";
      }});
    
    try {
      // 获取任务执行结果
      String result = future.get();
    } catch (InterruptedException e) {
      // 中断异常
    } catch (ExecutionException e) {
      // 执行异常
    }
}
FutureTask类

Future 的实现类是FutureTask。FutureTask 也实现了Runnable接口,所以可以将 FutureTask 理解为一个具有返回值的 Thread。

public void TestFutureTask {
    Future<String> futureTask = new FutureTask<>(new Callable<String>() {
      @Override
      public String call() throws Exception {
        return "hello world";
      }});
    
    try {
      String result = futureTask.get();
    } catch (InterruptedException e) {
      // 中断异常
    } catch (ExecutionException e) {
      // 执行异常
    }
}
Executors类

Executors是一个工具类,它可以作为一个线程池工厂创建一些内置的线程池,这些线程池通常在特定场景下使用,不过在实际应用中通常都不会使用这个工具创建线程池,而是使用ThreadPoolExecutor 自己去自定义一个满足自己业务需要的线程池。

以下是 Executors 内置线程池的特性

ExecutorService线程池
newCachedThreadPool()可缓存的线程池,该线程池中没有核心线程,当有需要时创建线程来执行任务,没有需要时回收线程,几乎没有设置线程数量上限。适用于耗时少,任务量大的情况。
newFixedThreadPool(int i)固定长度线程池,针对一些很稳定很固定的正规并发线程。
newScheduledThreadPool(int i)调度型线程池,可按设定时间执行,也可周期执行的任务
newSingleThreadExecutor()单例线程,用于顺序执行的任务
newWorkStealingPool(int i)返回ForJoinPool线程池,指定并行级别执行线程任务
ThreadPoolExecutor类

这是 Executor 框架里最重要的实现类了,先看它的使用方法。

public void TestThreadPoolExecutor {
    final ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
        coreThreadNum, 
        maxThreadNum, 
        liveTime,
        TimeUnit.SECONDS,
        new LinkedBlockingQueue<Runnable>(queueSize), 
        new ThreadPoolExecutor.DiscardPolicy());
    
    // 优雅关闭线程池    
    Runtime.getRuntime().addShutdownHook(new Thread(){
      public void run(){
        try {
          logger.warn("shutdown common threadPoolExecutor");
          threadPool.shutdown();
        } catch (Throwable e) {
          logger.error(e.getMessage(), e);
        }
      }
    });
}

解释一下构造函数的各个参数

  • coreThreadNum:线程池中所保存的核心线程数,包括空闲线程。
  • maxThreadNum:池中允许的最大线程数。
  • liveTime:线程最大空闲时间。
  • TimeUnit.SECONDS:空闲时间单位。
  • LinkedBlockingQueue:任务执行前保存任务的队列,仅保存由execute方法提交的Runnable任务。
  • policy:饱和策略

饱和策略有四种

  1. AbortPolicy (终止执行):默认策略, Executor会抛出一个RejectedExecutionException运行异常到调用者线程来完成终止。
  2. CallerRunsPolicy(调用者线程来运行任务):这种策略会由调用execute方法的线程自身来执行任务,它提供了一个简单的反馈机制并能降低新任务的提交频率。
  3. DiscardPolicy (丢弃策略):不处理,直接丢弃提交的任务。
  4. DiscardOldestPolicy (丢弃队列里最近的一个任务):如果Executor还未shutdown的话,则丢弃工作队列的最近的一个任务,然后执行当前任务。

小结

这篇文章重点介绍一下java的 Executor 框架,以及使用方法。
下篇文章将通过分析 Excutor 框架中重要实现的源码,来理解java设计者对线程池的设计思路。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值