java并发编程之线程池

前言

本文介绍几种java常用的线程池如:FixedThreadPool,ScheduledThreadPool,CachedThreadPool等线程池,并分析介绍Executor框架,做到“知其然”:会用线程池,正确使用线程池。并且“知其所以然”:了解背后的基本原理。

转载请指明原处:
http://blogs.xzchain.cn

1.Executor

Executor是java SE5的java.util.concurrent包下的执行器,用于管理Thread对象,它帮程序员简化了并发编程。与客户端直接执行任务不同,Executor作为客户端和任务执行之间的中介,将任务的提交和任务的执行策略解耦开来,Executor允许我们管理异步任务的执行。

Executor的实现思想基于生产者-消费者模型,提交任务的线程相当于生产者,执行任务的工作线程相当于消费者,这里所说的任务即我们实现Runnable接口的类。

Executor关键类图如下:
这里写图片描述
根据这个类图,详细分析一下其中的门路:

  • Executor:该接口定义了一个 void execute(Runnable command)方法,用来接受任务。
  • ExecutorService:继承了Executor并对其进行了丰富的拓展,提供了任务生命周期管理相关的方法,如shutdown(),shutdownNow()等方法,并提供了追踪一个或者多个异步任务执行状况并返回Future的方法,如submit(),invokeAll()方法
  • AbstractExecutorService:ExecutorService的默认实现类,该类是一个抽象类。
  • ThreadPoolExecutor:继承了AbstractExecutorService并实现了其方法,可以通过Executors提供的静态工厂方法创建线程池并返回ExecutorService实例
  • ScheduledExecutorService:提供定时调度任务的接口
  • ScheduledThreadPoolExecutor:ScheduledExecutorService的实现类,提供可定时调度任务的线程池。

2.ThreadPoolExecutor

根据上面的类图,我们详细看下ThreadPoolExecutor提供的构造方法:

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
      //略 ……
    }

参数介绍:

     * @param corePoolSize the number of threads to keep in the pool, even
     *        if they are idle, unless {@code allowCoreThreadTimeOut} is set
     * @param maximumPoolSize the maximum number of threads to allow in the
     *        pool
     * @param keepAliveTime when the number of threads is greater than
     *        the core, this is the maximum time that excess idle threads
     *        will wait for new tasks before terminating.
     * @param unit the time unit for the {@code keepAliveTime} argument
     * @param workQueue the queue to use for holding tasks before they are
     *        executed.  This queue will hold only the {@code Runnable}
     *        tasks submitted by the {@code execute} method.
     * @param threadFactory the factory to use when the executor
     *        creates a new thread
     * @param handler the handler to use when execution is blocked
     *        because the thread bounds and queue capacities are reached

参数说明:

  • corePoolSize:核心线程数/基本线程数,没有任务执行时线程池的大小(在创建ThreadPoolExecutor时,线程不会立即启动,有任务提交时才会启动)
  • maximumPoolSize:最大线程数,允许的创建的线程数
  • keepAliveTime:线程存活时间,当某个线程的空闲时间超过了存活时间,将被标记为可回收的,如果此时线程池的大小超过了corePoolSize,线程将被终止。
  • TimeUnit:keepAliveTime的单位,可选毫秒,秒等
  • workQueue:保存任务的阻塞队列,主要有三种:有界队列,无界队列和同步移交队列(Synchronous Handoff)。下面将详细说明
  • threadFactory(可选):在Executor创建新线程的时候使用,默认使用defaultThreadFactory创建线程
  • handler:定义“饱和策略”,这里的饱和根据参数说明是指线程池容量(workQueue也满了)满了的时候,会使用饱和策略进行任务的拒绝。默认的策略是“Abort”即中止策略,该策略抛出RejectExecutorException异常,其他的策略有“抛弃(Discard)、抛弃最旧(Discard-Oldest)”等。

很多人有疑问这些参数的含义到底是什么,网上的解释也五花八门,这里我通俗的解释一下,当提交一个任务的时候,会先检查当前执行的线程数,如果当前执行的线程数小于基本线程数(corePoolSize),则直接创建线程执行任务,且这个线程属于core线程。如果当前执行的线程数大于等于基本线程数,该任务会被放到阻塞队列(workQueue)中,在阻塞队列里的任务会复用core线程,即阻塞队列里的任务会等待core线程提取执行。而当阻塞队列是有界队列时,在阻塞队列满了的时候,会创建新的线程来执行任务,这些新的线程是非core线程,满足keepAliveTime的时候会被销毁,而已经进入队列里的任务会继续由已有的全部线程来执行。超过最大线程时(maximumPoolSize),会使用我们定义的“饱和策略”来处理。

这里面其实牵扯了另一个问题,即如何实现线程复用,简单来说就是线程在执行任务时,执行完后会去队列里面take新的任务,而take方法是阻塞的,因此线程并不会被销毁,只会不停的执行任务,没有任务时要么根据我们的逻辑销毁要么阻塞等待任务。

3.Executors

有时候使用ThreadPoolExecutor自己创建线程池时由于不清楚如何设置线程池大小,存活时间等问题会导致资源浪费,而ThreadPoolExecutor是一个灵活的、稳定的线程池,允许各种定制。Executors提供了一系列的静态工程方法帮我们创建各种线程池。

newFixedThreadPool

newFixedThreadPool创建可重用且线程数量固定的线程池,当线程池所有线程都在执行任务时,新的任务会在阻塞队列中等待,当某个线程因为异常而结束,线程池会创建新的线程进行补充。

public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

newFixedThreadPool使用基于FIFO(先进先出)策略的LinkedBlockingQueue作为阻塞队列。

newSingleThreadPool

newSingleThreadPool使用单线程的Executor,其中corePoolSize和maximumPoolSize都为1,固定线程池大小为1。

public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

newScheduledThreadPool

newScheduledThreadPool创建可以延迟执行或者定时执行任务的线程池,使用延时工作队列DelayedWorkQueue。

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }


public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
    }

newCachedThreadPool

newCachedThreadPool是一个可缓存的线程池,线程池中的线程如果60s内未被使用将被移除。使用同步队列SynchronousQueue。

    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

关于同步队列SynchronousQueue

回想一下,newSingleThreadPool,newFixedThreadPool默认使用无界队列LinkedBlockingQueue,当任务执行速度远远低于新任务到来的速度时,该队列将无限增加,如果我们使用有界队列例如ArrayBlockingQueue,当队列填满后则需要完善的饱和策略,这里需要根据需求选取折中之道。
这里就引出了SynchronousQueue,对于非当大的或者无界的线程池,使用SynchronousQueue可以避免排队,它可以讲任务直接从生产中交给工作者线程,SynchronousQueue本质上不是一个队列,而是一种同步移交机制,要将一个任务放入SynchronousQueue时必须要有一个线程在等待接受,如果没有线程在等待且当前线程数大于最大值,将启用饱和策略拒绝任务。

总结:newSingleThreadPool使用单线程的Executor,newFixedThreadPool设置线程池的基本大小和最大大小为指定的值,而且创建的线程池不会超时。newCachedThreadPool将线程池的最大大小设置为Integer.MAX_VALUE(即2的31次方减1),基本大小为0,超时时间为1分钟,该线程池可以无限拓展,且需求降低时会自动收缩。newScheduledThreadPool用于执行定时任务的线程池。其他形式的线程池大家可参考其他资料。

关于线程池大小的问题

线程池的大小取决于提交的任务的类型和系统的性能。

  • 如果线程池过大,大量线程将在相对有限的cpu和内存资源上竞争,将导致内存使用量过高,耗尽资源
  • 如果线程池太小,空想的处理器资源无法有效利用,降低了吞吐率。

为了正确设置线程池大小需要考虑系统的CPU数,内存容量,任务类型(是计算密集型还是io密集型或是二者皆可),任务是否需要稀缺资源(如jdbc连接)

对于计算密集型任务,在拥有N个处理器的系统上,最佳线程池的大小为N+1。这个额外的线程保证了突发情况下CPU时钟周期不会浪费。对于包含IO操作或者其他阻塞操作的任务,由于线程不会一直执行,线程池需要更大。
线程池的大小设置最优公式如下:
参数定义:
N = number of CPUs (cpu数量)
U = target CPU utilization,0<= U <= 1 (cpu利用率)
W/C = ratio of wait time to compute time (任务等待时间和计算时间的比值)
那么最优线程池大小计算公式为:
S = N*U*(1+W/C)
其中cpu数可以通过Runtime.getRuntime().availableProcessors()获得

CPU周期只是影响线程池大小的一个主要参数,其他的因素也很重要,需要综合实践。

参考《thinking in java》、《java并发编程实战》

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
SQLAlchemy 是一个 SQL 工具包和对象关系映射(ORM)库,用于 Python 编程语言。它提供了一个高级的 SQL 工具和对象关系映射工具,允许开发者以 Python 类和对象的形式操作数据库,而无需编写大量的 SQL 语句。SQLAlchemy 建立在 DBAPI 之上,支持多种数据库后端,如 SQLite, MySQL, PostgreSQL 等。 SQLAlchemy 的核心功能: 对象关系映射(ORM): SQLAlchemy 允许开发者使用 Python 类来表示数据库表,使用类的实例表示表中的行。 开发者可以定义类之间的关系(如一对多、多对多),SQLAlchemy 会自动处理这些关系在数据库中的映射。 通过 ORM,开发者可以像操作 Python 对象一样操作数据库,这大大简化了数据库操作的复杂性。 表达式语言: SQLAlchemy 提供了一个丰富的 SQL 表达式语言,允许开发者以 Python 表达式的方式编写复杂的 SQL 查询。 表达式语言提供了对 SQL 语句的灵活控制,同时保持了代码的可读性和可维护性。 数据库引擎和连接池: SQLAlchemy 支持多种数据库后端,并且为每种后端提供了对应的数据库引擎。 它还提供了连接池管理功能,以优化数据库连接的创建、使用和释放。 会话管理: SQLAlchemy 使用会话(Session)来管理对象的持久化状态。 会话提供了一个工作单元(unit of work)和身份映射(identity map)的概念,使得对象的状态管理和查询更加高效。 事件系统: SQLAlchemy 提供了一个事件系统,允许开发者在 ORM 的各个生命周期阶段插入自定义的钩子函数。 这使得开发者可以在对象加载、修改、删除等操作时执行额外的逻辑。
SQLAlchemy 是一个 SQL 工具包和对象关系映射(ORM)库,用于 Python 编程语言。它提供了一个高级的 SQL 工具和对象关系映射工具,允许开发者以 Python 类和对象的形式操作数据库,而无需编写大量的 SQL 语句。SQLAlchemy 建立在 DBAPI 之上,支持多种数据库后端,如 SQLite, MySQL, PostgreSQL 等。 SQLAlchemy 的核心功能: 对象关系映射(ORM): SQLAlchemy 允许开发者使用 Python 类来表示数据库表,使用类的实例表示表中的行。 开发者可以定义类之间的关系(如一对多、多对多),SQLAlchemy 会自动处理这些关系在数据库中的映射。 通过 ORM,开发者可以像操作 Python 对象一样操作数据库,这大大简化了数据库操作的复杂性。 表达式语言: SQLAlchemy 提供了一个丰富的 SQL 表达式语言,允许开发者以 Python 表达式的方式编写复杂的 SQL 查询。 表达式语言提供了对 SQL 语句的灵活控制,同时保持了代码的可读性和可维护性。 数据库引擎和连接池: SQLAlchemy 支持多种数据库后端,并且为每种后端提供了对应的数据库引擎。 它还提供了连接池管理功能,以优化数据库连接的创建、使用和释放。 会话管理: SQLAlchemy 使用会话(Session)来管理对象的持久化状态。 会话提供了一个工作单元(unit of work)和身份映射(identity map)的概念,使得对象的状态管理和查询更加高效。 事件系统: SQLAlchemy 提供了一个事件系统,允许开发者在 ORM 的各个生命周期阶段插入自定义的钩子函数。 这使得开发者可以在对象加载、修改、删除等操作时执行额外的逻辑。
GeoPandas是一个开源的Python库,旨在简化地理空间数据的处理和分析。它结合了Pandas和Shapely的能力,为Python用户提供了一个强大而灵活的工具来处理地理空间数据。以下是关于GeoPandas的详细介绍: 一、GeoPandas的基本概念 1. 定义 GeoPandas是建立在Pandas和Shapely之上的一个Python库,用于处理和分析地理空间数据。 它扩展了Pandas的DataFrame和Series数据结构,允许在其中存储和操作地理空间几何图形。 2. 核心数据结构 GeoDataFrame:GeoPandas的核心数据结构,是Pandas DataFrame的扩展。它包含一个或多个列,其中至少一列是几何列(geometry column),用于存储地理空间几何图形(如点、线、多边形等)。 GeoSeries:GeoPandas中的另一个重要数据结构,类似于Pandas的Series,但用于存储几何图形序列。 二、GeoPandas的功能特性 1. 读取和写入多种地理空间数据格式 GeoPandas支持读取和写入多种常见的地理空间数据格式,包括Shapefile、GeoJSON、PostGIS、KML等。这使得用户可以轻松地从各种数据源中加载地理空间数据,并将处理后的数据保存为所需的格式。 2. 地理空间几何图形的创建、编辑和分析 GeoPandas允许用户创建、编辑和分析地理空间几何图形,包括点、线、多边形等。它提供了丰富的空间操作函数,如缓冲区分析、交集、并集、差集等,使得用户可以方便地进行地理空间数据分析。 3. 数据可视化 GeoPandas内置了数据可视化功能,可以绘制地理空间数据的地图。用户可以使用matplotlib等库来进一步定制地图的样式和布局。 4. 空间连接和空间索引 GeoPandas支持空间连接操作,可以将两个GeoDataFrame按照空间关系(如相交、包含等)进行连接。此外,它还支持空间索引,可以提高地理空间数据查询的效率。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值