`

java线程池记录

 
阅读更多

 

 

 

 

上图是线程池的类体的结构图,从中能够看出继承顺序以及一些静态方法等。



首先Executor的execute方法只是执行一个Runnable的任务,当然了从某种角度上将最后的实现类也是在线程中启动此任务的。根据线程池的执行策略最后这个任务可能在新的线程中执行,或者线程池中的某个线程,甚至是调用者线程中执行(相当于直接运行Runnable的run方法)。这点在后面会详细说明。
ExecutorService在Executor的基础上增加了一些方法,其中有两个核心的方法:

  • Future<?> submit(Runnable task)
  • <T> Future<T> submit(Callable<T> task)

这两个方法都是向线程池中提交任务,它们的区别在于Runnable在执行完毕后没有结果,Callable执行完毕后有一个结果。这在多个线程中传递状态和结果是非常有用的。另外他们的相同点在于都返回一个Future对象。Future对象可以阻塞线程直到运行完毕(获取结果,如果有的话),也可以取消任务执行,当然也能够检测任务是否被取消或者是否执行完毕。
在没有Future之前我们检测一个线程是否执行完毕通常使用Thread.join()或者用一个死循环加状态位来描述线程执行完毕。现在有了更好的方法能够阻塞线程,检测任务执行完毕甚至取消执行中或者未开始执行的任务。

ScheduledExecutorService描述的功能和Timer/TimerTask类似,解决那些需要任务重复执行的问题。这包括延迟时间一次性执行、延迟时间周期性执行以及固定延迟时间周期性执行等。当然了继承ExecutorService的ScheduledExecutorService拥有ExecutorService的全部特性。

ThreadPoolExecutor是ExecutorService的默认实现,其中的配置、策略也是比较复杂的。
ScheduledThreadPoolExecutor是继承ThreadPoolExecutor的ScheduledExecutorService接口实现,周期性任务调度的类实现。
这里需要稍微提一下的是CompletionService接口,它是用于描述顺序获取执行结果的一个线程池包装器。它依赖一个具体的线程池调度,但是能够根据任务的执行先后顺序得到执行结果,这在某些情况下可能提高并发效率。

要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,很有可能配置的线程池不是较优的,因此在Executors类里面提供了一些静态工厂,生成一些常用的线程池。

  • newSingleThreadExecutor:创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
  • newFixedThreadPool:创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
  • newCachedThreadPool:创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
  • newScheduledThreadPool:创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。
  • newSingleThreadExecutor:创建一个单线程的线程池。此线程池支持定时以及周期性执行任务的需求。


线程池类为 java.util.concurrent.ThreadPoolExecutor,常用构造方法为: 


ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) 


corePoolSize: 线程池维护线程的最少数量 


maximumPoolSize:线程池维护线程的最大数量 


keepAliveTime: 线程池维护线程所允许的空闲时间 


unit: 线程池维护线程所允许的空闲时间的单位 


workQueue: 线程池所使用的缓冲队列 


handler: 线程池对拒绝任务的处理策略 


一个任务通过 execute(Runnable)方法被添加到线程池,任务就是一个 Runnable类型的对象,任务的执行方法就是 Runnable类型对象的run()方法。 


当一个任务通过execute(Runnable)方法欲添加到线程池时: 


如果此时线程池中的数量小于corePoolSize,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。 


如果此时线程池中的数量等于 corePoolSize,但是缓冲队列 workQueue未满,那么任务被放入缓冲队列。 


如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量小于maximumPoolSize,建新的线程来处理被添加的任务。 


如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量等于maximumPoolSize,那么通过 handler所指定的策略来处理此任务。 


也就是:处理任务的优先级为: 


核心线程corePoolSize、任务队列workQueue、最大线程maximumPoolSize,如果三者都满了,使用handler处理被拒绝的任务。 


当线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止。这样,线程池可以动态的调整池中的线程数。 


unit可选的参数为java.util.concurrent.TimeUnit中的几个静态属性: 


NANOSECONDS、MICROSECONDS、MILLISECONDS、SECONDS。 


workQueue我常用的是:java.util.concurrent.ArrayBlockingQueue 


handler有四个选择: 


ThreadPoolExecutor.AbortPolicy() 


抛出java.util.concurrent.RejectedExecutionException异常 


ThreadPoolExecutor.CallerRunsPolicy() 


重试添加当前的任务,他会自动重复调用execute()方法 


ThreadPoolExecutor.DiscardOldestPolicy() 抛弃旧的任务 


ThreadPoolExecutor.DiscardPolicy() 抛弃当前的任务 


例子:



package demo;

import java.io.Serializable;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class TestThreadPool2
{
    private static int produceTaskSleepTime = 2;
    private static int produceTaskMaxNumber = 10;

    public static void main(String[] args)
    {
        // 构造一个线程池
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 4, 3, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(3),
                new ThreadPoolExecutor.DiscardOldestPolicy());

        for (int i = 1; i <= produceTaskMaxNumber; i++)
        {
            try
            {
                // 产生一个任务,并将其加入到线程池
                String task = "task@ " + i;
                System.out.println("put " + task);
                threadPool.execute(new ThreadPoolTask(task));

                // 便于观察,等待一段时间
                Thread.sleep(produceTaskSleepTime);
            }
            catch (Exception e)
            {
                e.printStackTrace();
            }
        }
    }
}

/**
 * 线程池执行的任务
 */
class ThreadPoolTask implements Runnable, Serializable
{
    private static final long serialVersionUID = 0;
    private static int consumeTaskSleepTime = 2000;
    // 保存任务所需要的数据
    private Object threadPoolTaskData;

    ThreadPoolTask(Object tasks)
    {
        this.threadPoolTaskData = tasks;
    }

    public void run()
    {
        // 处理一个任务,这里的处理方式太简单了,仅仅是一个打印语句
        System.out.println(Thread.currentThread().getName());
        System.out.println("start .." + threadPoolTaskData);

        try
        {
            // //便于观察,等待一段时间
            Thread.sleep(consumeTaskSleepTime);
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
        threadPoolTaskData = null;
    }

    public Object getTask()
    {
        return this.threadPoolTaskData;
    }
}

 


说明: 
1、在这段程序中,一个任务就是一个Runnable类型的对象,也就是一个ThreadPoolTask类型的对象。 
2、一般来说任务除了处理方式外,还需要处理的数据,处理的数据通过构造方法传给任务。 
3、在这段程序中,main()方法相当于一个残忍的领导,他派发出许多任务,丢给一个叫 threadPool的任劳任怨的小组来做。 
这个小组里面队员至少有两个,如果他们两个忙不过来,任务就被放到任务列表里面。 
如果积压的任务过多,多到任务列表都装不下(超过3个)的时候,就雇佣新的队员来帮忙。但是基于成本的考虑,不能雇佣太多的队员,至多只能雇佣 4个。 
如果四个队员都在忙时,再有新的任务,这个小组就处理不了了,任务就会被通过一种策略来处理,我们的处理方式是不停的派发,直到接受这个任务为止(更残忍!呵呵)。 
因为队员工作是需要成本的,如果工作很闲,闲到 3SECONDS都没有新的任务了,那么有的队员就会被解雇了,但是,为了小组的正常运转,即使工作再闲,小组的队员也不能少于两个。 
4、通过调整 produceTaskSleepTime和 consumeTaskSleepTime的大小来实现对派发任务和处理任务的速度的控制,改变这两个值就可以观察不同速率下程序的工作情况。 
5、通过调整4中所指的数据,再加上调整任务丢弃策略,换上其他三种策略,就可以看出不同策略下的不同处理方式。 
6、对于其他的使用方法,参看jdk的帮助,很容易理解和使用。 

分享到:
评论

相关推荐

    java线程池概念.txt

     largestPoolSize只是一个用来起记录作用的变量,用来记录线程池中曾经有过的最大线程数目,跟线程池的容量没有任何关系。 c:添加线程池任务的入口就是execute(); 复制代码 public void execute(Runnable ...

    线程池(Java实现,简易)

    可以直接使用的 任意类实现Runnable接口,则可使用 先构造ThreadPool对象,然后调用其execute方法,将自己的线程作为参数传入即可(注意,不能让你的线程开启) 支持最小线程数 ...支持简单日志记录

    java线程池execute源码-performance:表现

    java线程池execute源码 简介 由于本人工作需要,需要解决一些性能问题,虽然有 Profiler 、Systrace 等工具,但是无法实时监控,多少有些不方便,于是计划写一个能实时监控性能的小工具。经过学习大佬们的文章,最终...

    async-log:Java异步日志记录的最小实现

    异步日志是Java中异步日志记录的最小实现。 原因:提供日志记录实现在多线程环境中效果很好,对性能的影响最小。 不能:该库在设计上非常缺乏功能,以使其保持可维护性和可移植性。用法创建日志存储库所有日志都保...

    【多线程高并发编程】四 java(jdk1.8)五种线程池,你都知道具体的应用场景吗?

    本文已记录到github,形成对应专题。 文章目录前言1.什么是线程池?2.实战2.1通过线程池代码创建线程2.1.Executors源码分析newFixedThreadPoolnewWorkStealingPool(int parallelism)newSingleThreadExecutor()...

    Java多线程–让主线程等待所有子线程执行完毕

     数据量很大百万条记录,因此考虑到要用多线程并发执行,在写的过程中又遇到问题,我想统计所有子进程执行完毕总共的耗时,在第一个子进程创建前记录当前时间用System.currentTimeMillis()在后一个子进程结束后...

    Java基础篇:Executor框架.pdf

    该文档详细记录了Executor框架结构、使用示意图、ThreadPoolExecutor使用示例、线程池原理分析、几种常见线程池(FixedThreadPool、SingleThreadExecutor、CachedThreadPool)的详解以及线程池大小确定等内容

    疯狂JAVA讲义

    第1章 Java概述 1 1.1 Java语言的发展简史 2 1.2 Java的竞争对手及各自优势 4 1.2.1 C#简介和优势 4 1.2.2 Ruby简介和优势 4 1.2.3 Python的简介和优势 5 1.3 Java程序运行机制 5 1.3.1 高级语言的运行机制 6...

    JAVA面试题最全集

    编写代码实现一个线程池 40.描述一下JVM加载class文件的原理机制? 41.试举例说明一个典型的垃圾回收算法? 42.请用java写二叉树算法,实现添加数据形成二叉树功能,并以先序的方式打印出来. 43.请写一个java...

    高级java开发并发问题

    线程池:一个管理线程的池子。 #为什么平时都是使用线程池...3.每个线程获得一个程序计数器,用于记录当前虚拟机正在执行的线程指令地址 4.系统创建一个与Java线程对应的本机线程 5.将与线程相关的描述符添加到JVM内部

    简单Web服务器(Java实现)

    支持HTTP1.1(但是不完善),支持多线程(采用的线程池),支持简单CGI(仅PHP的CGI模块通过测试),支持配置文件和简单的日志记录。要支持PHP的动态脚本的话,需要自行下载PHP的文件到php目录下,并且更改设置,...

    java8集合源码分析-javaInterview:java面试

    2020年深圳java打工仔找工作记录帖,有一段时间的社畜了,所以直奔主题, 找的资料都是面试常用的,整理一下,希望能帮到大家 主要三个部分 1.搜集面试题目 2.java资料 3 找工作日期记录,看从准备工作开始,多久可以找到...

    米哈游笔试题目-Java方向.docx

    单例模式的线程安全日志记录器类:需要设计一个类,保证在多线程环境下只有一个实例存在,并能够正确记录日志信息。 并发安全的阻塞队列类:需要设计一个线程安全的队列类,支持多线程的插入和移除操作,能够正确...

    java8源码-Android_Note:记录平常学习的一点知识!

    [Java并发之线程池(二)之Executors] Java设计模式 Android UI控件篇 Android基础: Android自定义View篇 [自定义View--Canvas] [自定义View--Paint] [自定义View--Matrix] [自定义View--Path] [自定义View--绘制文本]...

    java范例开发大全源代码

     实例201 记录程序执行的时间 351  实例202 程序的退出 352  实例203 获取程序运行环境的信息 353  第4篇 Java高级开发技术  第12章 集合(教学视频:45分钟) 358  12.1 Set 358  实例204 利用...

    java8源码-concurrency:java并发总结

    Executors线程池.md 1 基本概念 1.1 CPU与线程的关系 1.2 线程与进程的区别和关系 1.3 吞吐量 1.4 线程安全 1.5 线程声明周期 1.6 守护线程 1.7 Java内存模型 1.8 可重入 1.9 偏向锁、轻量级锁、重量级锁 1.10 锁的...

    java范例开发大全

    实例242 手术任务(线程池) 462 实例243 模拟人工服务台(线程连接池) 466 13.6 线程应用实例 471 实例244 下雪的村庄 472 实例245 小飞侠 474 实例246 飞流直下 477 实例247 多线程断点续传 479 实例248 滚动的...

Global site tag (gtag.js) - Google Analytics