线程池

  1. 我们可以通过ThreadPoolExecutor来创建一个线程池。
    new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime,milliseconds,runnableTaskQueue, handler); 
  2. 通过下图理解参数

    工作线程实现核心方法

    public void run() {
    try {
    Runnable task = firstTask;
    firstTask = null;
    while (task != null || (task = getTask()) != null) {
    runTask(task);
    task = null;
    }
    } finally {
    workerDone(this);
    }
    } 

     

     

    3向线程池提交任务
    可以使用两个方法向线程池提交任务,分别为execute()和submit()方法。execute()方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功。
       submit()方法用于提交需要返回值的任务。线程池会返回一个future类型的对象,通过这个future对象可以判断任务是否执行成功,

    4.关闭线程池 

    可以通过调用线程池的shutdown或shutdownNow方法来关闭线程池。它们的原理是遍历线
    程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程,所以无法响应中断的任务
    可能永远无法终止。但是它们存在一定的区别,shutdownNow首先将线程池的状态设置成
    STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表,而
    shutdown只是将线程池的状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线
    程。 

    5.合理地配置线程池
    要想合理地配置线程池,就必须首先分析任务特性,可以从以下几个角度来分析。
    ·任务的性质:CPU密集型任务、IO密集型任务和混合型任务。
    ·任务的优先级:高、中和低。
    ·任务的执行时间:长、中和短。
    ·任务的依赖性:是否依赖其他系统资源,如数据库连接 

    1.估算线程池最优大小:

    Ncpu = CPU的数量 = Runtime.getRuntime().availableProcessors();

    Ucpu = 目标CPU的使用率, 0 <= Ucpu <= 1;

    W/C = 等待时间与计算时间的比率;

    为了保持处理器达到期望的使用率,最优的线程池大小等于:

    Nthreads = Ncpu * Ucpu * (1+ W/C);

     

    Java线程既是工作元,也是行机制。从JDK 5开始,把工作元与行机制分离开
    来。工作元包括RunnableCallable,而行机制由Executor框架提供。 

     

     

  3. 什么是阻塞队列
    阻塞队列(BlockingQueue)是一个支持两个附加操作的队列。这两个附加的操作支持阻塞
    的插入和移除方法。
    1)支持阻塞的插入方法:意思是当队列满时,队列会阻塞插入元素的线程,直到队列不满。
    2)支持阻塞的移除方法:意思是在队列为空时,获取元素的线程会等待队列变为非空 
  4. JDK 7提供了7个阻塞队列,如下。
    ·ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列。
    ·LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列。
    ·PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。
    ·DelayQueue:一个使用优先级队列实现的无界阻塞队列。
    ·SynchronousQueue:一个不存储元素的阻塞队列。
    ·LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。
    ·LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。
    1.ArrayBlockingQueue
    ArrayBlockingQueue是一个用数组实现的有界阻塞队列。此队列按照先进先出(FIFO)的原
    则对元素进行排序。
    默认情况下不保证线程公平的访问队列,所谓公平访问队列是指阻塞的线程,可以按照
    阻塞的先后顺序访问队列,即先阻塞线程先访问队列。非公平性是对先等待的线程是非公平
    的,当队列可用时,阻塞的线程都可以争夺访问队列的资格,有可能先阻塞的线程最后才访问
    队列。为了保证公平性,通常会降低吞吐量。我们可以使用以下代码创建一个公平的阻塞队
    列。
    ArrayBlockingQueue fairQueue = new ArrayBlockingQueue(1000,true);
    访问者的公平性是使用可重入锁实现的,代码如下。
    public ArrayBlockingQueue(int capacity, boolean fair) {
    if (capacity <= 0)
    throw new IllegalArgumentException();
    this.items = new Object[capacity];
    lock = new ReentrantLock(fair);
    notEmpty = lock.newCondition();
    notFull = lock.newCondition();
    }
    2.LinkedBlockingQueue
    LinkedBlockingQueue是一个用链表实现的有界阻塞队列。此队列的默认和最大长度为
    Integer.MAX_VALUE。此队列按照先进先出的原则对元素进行排序。
    3.PriorityBlockingQueue
    PriorityBlockingQueue是一个支持优先级的无界阻塞队列。默认情况下元素采取自然顺序
    升序排列。也可以自定义类实现compareTo()方法来指定元素排序规则,或者初始化
    PriorityBlockingQueue时,指定构造参数Comparator来对元素进行排序。需要注意的是不能保证
    同优先级元素的顺序。
    4.DelayQueue
    DelayQueue是一个支持延时获取元素的无界阻塞队列。队列使用PriorityQueue来实现。队列中的元素必须实现Delayed接口,在创建元素时可以指定多久才能从队列中获取当前元素。
    只有在延迟期满时才能从队列中提取元素。
    DelayQueue非常有用,可以将DelayQueue运用在以下应用场景。
    ·缓存系统的设计:可以用DelayQueue保存缓存元素的有效期,使用一个线程循环查询
    DelayQueue,一旦能从DelayQueue中获取元素时,表示缓存有效期到了。
    5.SynchronousQueue
    SynchronousQueue是一个不存储元素的阻塞队列。每一个put操作必须等待一个take操作,
    否则不能继续添加元素。
    它支持公平访问队列。默认情况下线程采用非公平性策略访问队列。使用以下构造方法
    可以创建公平性访问的SynchronousQueue,如果设置为true,则等待的线程会采用先进先出的
    顺序访问队列。
    public SynchronousQueue(boolean fair) {
    transferer = fair new TransferQueue() : new TransferStack();
    }
    SynchronousQueue可以看成是一个传球手,负责把生产者线程处理的数据直接传递给消费
    者线程。队列本身并不存储任何元素,非常适合传递性场景。SynchronousQueue的吞吐量高于
    LinkedBlockingQueue和ArrayBlockingQueue。
    6.LinkedTransferQueue
    LinkedTransferQueue是一个由链表结构组成的无界阻塞TransferQueue队列。相对于其他阻
    塞队列,LinkedTransferQueue多了tryTransfer和transfer方法。
    7.LinkedBlockingDeque
    LinkedBlockingDeque是一个由链表结构组成的双向阻塞队列。所谓双向队列指的是可以
    从队列的两端插入和移出元素。双向队列因为多了一个操作队列的入口,在多线程同时入队
    时,也就减少了一半的竞争。相比其他的阻塞队列,LinkedBlockingDeque多了addFirst、
    addLast、offerFirst、offerLast、peekFirst和peekLast等方法,以First单词结尾的方法,表示插入、
    获取(peek)或移除双端队列的第一个元素。以Last单词结尾的方法,表示插入、获取或移除双
    端队列的最后一个元素。另外,插入方法add等同于addLast,移除方法remove等效于
    removeFirst。但是take方法却等同于takeFirst,不知道是不是JDK的bug,使用时还是用带有First
    和Last后缀的方法更清楚。
    在初始化LinkedBlockingDeque时可以设置容量防止其过度膨胀。另外,双向阻塞队列可以
    运用在“工作窃取”模式中 

四大线程池实现

 

  1. FixedThreadPool
    FixedThreadPool被称为可重用固定线程数的线程池。下面是FixedThreadPool的源代码实
    现。
    public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
    0L, TimeUnit.MILLISECONDS,
    new LinkedBlockingQueue<Runnable>());
    }
    FixedThreadPool的corePoolSize和maximumPoolSize都被设置为创建FixedThreadPool时指
    定的参数nThreads。
    当线程池中的线程数大于corePoolSize时,keepAliveTime为多余的空闲线程等待新任务的
    最长时间,超过这个时间后多余的线程将被终止。这里把keepAliveTime设置为0L,意味着多余
    的空闲线程会被立即终止 

  1. 如果当前运行的线程数少于corePoolSize,则创建新线程来执行任务。
    2)在线程池完成预热之后(当前运行的线程数等于corePoolSize),将任务加入
    LinkedBlockingQueue。
    3)线程执行完1中的任务后,会在循环中反复从LinkedBlockingQueue获取任务来执行。
    FixedThreadPool使用无界队列LinkedBlockingQueue作为线程池的工作队列(队列的容量为
    Integer.MAX_VALUE)。使用无界队列作为工作队列会对线程池带来如下影响。
    1)当线程池中的线程数达到corePoolSize后,新任务将在无界队列中等待,因此线程池中
    的线程数不会超过corePoolSize。
    2)由于1,使用无界队列时maximumPoolSize将是一个无效参数。
    3)由于1和2,使用无界队列时keepAliveTime将是一个无效参数。
    4)由于使用无界队列,运行中的FixedThreadPool(未执行方法shutdown()或
    shutdownNow())不会拒绝任务(不会调用RejectedExecutionHandler.rejectedExecution方法)。 

SingleThreadExecutor详解
SingleThreadExecutor是使用单个worker线程的Executor。下面是SingleThreadExecutor的源
代码实现。
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
SingleThreadExecutor的corePoolSize和maximumPoolSize被设置为1。其他参数与
FixedThreadPool相同。SingleThreadExecutor使用无界队列LinkedBlockingQueue作为线程池的工
作队列(队列的容量为Integer.MAX_VALUE)。SingleThreadExecutor使用无界队列作为工作队列
对线程池带来的影响与FixedThreadPool相同,这里就不赘述了。

 

  1. CachedThreadPool
    CachedThreadPool是一个会根据需要创建新线程的线程池。下面是创建CachedThreadPool的源代码。
    public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
    60L, TimeUnit.SECONDS,
    new SynchronousQueue<Runnable>());
    }
    CachedThreadPool的corePoolSize被设置为0,即corePool为空;maximumPoolSize被设置为
    Integer.MAX_VALUE,即maximumPool是无界的。这里把keepAliveTime设置为60L,意味着
    CachedThreadPool中的空闲线程等待新任务的最长时间为60秒,空闲线程超过60秒后将会被
    终止。
    FixedThreadPool和SingleThreadExecutor使用无界队列LinkedBlockingQueue作为线程池的
    工作队列。CachedThreadPool使用没有容量的SynchronousQueue作为线程池的工作队列,但
    CachedThreadPool的maximumPool是无界的。这意味着,如果主线程提交任务的速度高于
    maximumPool中线程处理任务的速度时,CachedThreadPool会不断创建新线程。极端情况下,
    CachedThreadPool会因为创建过多线程而耗尽CPU和内存资源 

 

 

4.ScheduledThreadPoolExecutor的运行机制
ScheduledThreadPoolExecutor的执行示意图(本文基于JDK 6)如图10-8所示。
DelayQueue是一个无界队列,所以ThreadPoolExecutor的maximumPoolSize在ScheduledThreadPoolExecutor中没有什么意义(设置maximumPoolSize的大小没有什么效果)。
ScheduledThreadPoolExecutor的执行主要分为两大部分。
1)当调用ScheduledThreadPoolExecutor的scheduleAtFixedRate()方法或者scheduleWithFixedDelay()方法时,会向ScheduledThreadPoolExecutor的DelayQueue添加一个实现了
RunnableScheduledFutur接口的ScheduledFutureTask。
2)线程池中的线程从DelayQueue中获取ScheduledFutureTask,然后执行任务 

 

阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页