ThreadPoolExecutor

阿里Java开发手册明确java开发使用 ThreadPoolExecutor 的方式创建线程池

    3. 【强制】线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。
        说明:使用线程池的好处是减少在创建和销毁线程上所花的时间以及系统资源的开销,解决资源不足的问题。
        如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者 “过度切换”的问题。
    4. 【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,
        这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
        说明: Executors 返回的线程池对象的弊端如下:
        1) FixedThreadPool 和 SingleThreadPool :
            允许的请求队列长度为 Integer.MAX_VALUE ,可能会堆积大量的请求,从而导致 OOM 。
        2) CachedThreadPool 和 ScheduledThreadPool :
            允许的创建线程数量为 Integer.MAX_VALUE ,可能会创建大量的线程,从而导致 OOM 。

java五种常见线程池

    threadPool = Executors.newCachedThreadPool();//有缓冲的线程池,线程数 JVM 控制
    threadPool = Executors.newFixedThreadPool(3);//固定大小的线程池
    threadPool = Executors.newScheduledThreadPool(2);//定时任务线程数
    threadPool = Executors.newSingleThreadExecutor();//单线程的线程池,只有一个线程在工作
    threadPool = new ThreadPoolExecutor(...);//默认线程池,可控制参数比较多   

ThreadPoolExecutor创建线程池

//五个参数的构造函数,少了ThreadFactory 和 拒绝策略,使用默认的
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue)
 
//六个参数的构造函数,少了拒绝策略,即使用默认的
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory)
 
//六个参数的构造函数,少了threadFactory,即使用默认的
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          RejectedExecutionHandler handler)
 
//七个参数的构造函数,全
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)

corePoolSize - 线程池大小,即核心线程的最大数量

默认线程池创建之后,线程池中的线程数为0,当任务过来就会创建一个核心线程去执行,当线程数小于核心线程数时,即使有空闲线程,线程池也会优先创建新线程处理。直到核心线程数达到corePoolSize 之后,就会被到达的任务放在缓存队列中。(注意是到达的任务)。
核心线程会一直存活,即使没有任务需要执行
如果执行了线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有核心线程。

maximumPoolSize - 线程池的最大线程数,即核心线程+非核心线程的最大数量。
keepAliveTime - 针对非核心线程,当非核心线程的空闲时间超过keepAliveTime,则会被销毁
unit - keepAliveTime 的时间单位。
workQueue - 用来储存等待执行任务的队列。
threadFactory - 线程工厂。
handler - 拒绝策略。
(本人拙见:核心线程:体制员工 ,非核心线程:合同工)


流程


  • 如果当前运行的线程少于corePoolSize,则创建新线程来执行任务(注意这一个步骤需要获取全局锁)。
  • 如果运行的线程等于或多于corePoolSize,则将任务加入BlockingQueue。
  • 如果无法将任务加入BlockingQueue(队列已满),则创建新的线程来处理任务(注意这一个步骤需要获取全局锁)。
  • 如果创建的新线程将使当前运行的线程超出maximumPoolSize,任务将被执行饱和(拒绝)策略。ThreadPoolExecutor 采用上述的设计思路,是为执行execute()方法时,尽可能避免获取全局锁(一个严重的可伸缩瓶颈)。在ThreadPoolExecutor完成预热之后,几乎所有的execute()方法调用都是在执行步骤2,而步骤2不需要获取全局锁。

workQueue(用于保存等待被执行的任务的阻塞队列)

  • ArrayBlockingQueue :一个由数组结构组成的有界阻塞队列,按FIFO排序任务。
new ArrayBlockingQueue<Runnable>(100)
  • LinkedBlockingQueue : 一个由链表结构组成的有界阻塞队列,按FIFO排序任务,吞吐量通常要高于ArrayBlockingQueue。
new LinkedBlockingQueue<>();
  • SynchronousQueue: 一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常高于LinkedBlockingQueue。
  • PriorityBlockingQueue :一个支持优先级排序的无界阻塞队列。

threadFactory (线程工厂)
自定义ThreadFactory可以实现自定义线程的名称、组以及优先级等信息,甚至可以任性的将线程设置为守护线程。总之,自定义ThreadFactory可以更加自由的设置线程池中所有线程的状态。

class MyTreadFactory implements ThreadFactory {
        private final AtomicInteger mThreadNum = new AtomicInteger(1);
        @Override
        public Thread newThread(Runnable r) {
            Thread t = new Thread(r, "my-thread-" + mThreadNum.getAndIncrement());
            System.out.println(t.getName() + " has been created");
            return t;
        }
    }

handler (处理并发量大的情况下,拒绝策略对程序健壮性非常有用)
RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy ();

  • AbortPolicy (默认): 丢弃任务并抛出RejectedExecutionException异常。
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    throw new RejectedExecutionException("Task " + r.toString() +
        " rejected from " +e.toString());
} 
  • DiscardPolicy:也是丢弃任务,但是不抛出异常。
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}
  • DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    if (!e.isShutdown()) {
        e.getQueue().poll();
        e.execute(r);
    }
} 
  • CallerRunsPolicy:由调用线程处理该任务
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    if (!e.isShutdown()) {
        r.run();
    }
}

如果你要自定义,添加写日志记录啥的

public static class MyIgnorePolicy implements RejectedExecutionHandler {
        @Override
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            doLog(r, e);
        }
        private void doLog(Runnable r, ThreadPoolExecutor e) {
            // 可做日志记录等
            System.err.println( r.toString() + " rejected");
//          System.out.println("completedTaskCount: " + e.getCompletedTaskCount());
        }
    }

线程池的关闭
ThreadPoolExecutor提供了两个方法,用于线程池的关闭,分别是shutdown()和shutdownNow(),其中:

  • shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务

  • shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务


  • 1、RUNNING
    状态说明:线程池处于RUNNING状态,能够接收新任务,以及对已添加的任务进行处理。
    状态切换:线程池的初始化状态是RUNNING。换句话说,线程池一旦被创建,就处于RUNNING状态,并且线程池中的任务数为0。

  • 2、SHUTDOWN
    状态说明:线程池处于SHUTDOWN状态,不接收新任务,能够处理已经添加的任务。
    状态切换:调用shutdown()方法时,线程池由RUNNING -> SHUTDOWN。

  • 3、STOP
    状态说明:线程池处于STOP状态,不接收新任务,不处理已提交的任务,并且会中断正在处理的任务。
    状态切换:调用线程池中的shutdownNow()方法时,线程池由(RUNNING or SHUTDOWN) -> STOP。

  • 4、TIDYING
    状态说明:当所有的任务已经停止,ctl记录“任务数量”为0,线程池会变为TIDYING状态。当线程池处于TIDYING状态时,会执行钩子函数 terminated()。terminated()在ThreadPoolExecutor类中是空, 的,若用户想在线程池变为TIDYING时,进行相应处理,可以通过重载 terminated()函数来实现。
    状态切换:当线程池在SHUTDOWN状态下,阻塞队列为空并且线程池中执行任务也为空时,就会由SHUTDOWN -> TIDYING。当线程池在STOP状态下,线程池中执行的任务为空时,就会由STOP-> TIDYING。

  • 5、TERMINATED
    状态说明:线程池线程池彻底停止,线程池处于TERMINATED状态,
    状态切换:线程池处于TIDYING状态时,执行完terminated()之后, 就会由TIDYING->TERMINATED。


submit()和execute()的区别
任务分两类:一类是实现了Runnable接口的类,一类是实现了Callable接口的类。两者都可以被ExecutorService执行,它们的区别是:

  • execute(Runnable x) 没有返回值。可以执行任务,但无法判断任务是否成功完成。——实现Runnable接口的任务

  • submit(Runnable x) 返回一个future。可以用这个future来判断任务是否成功完成。——实现Callable接口和Runnable接口的任务,如果为Runnable,则get得到的为null:

  • 使用submit方法还有一个特点就是,他的异常可以在主线程中catch到。而使用execute方法执行任务是捕捉不到异常的。

future.get()方法是会有阻塞性,在调用submit提交任务之后,future.get()一直等到任务执行完毕,拿到了返回的返回值,主线程才会继续运行。


设置合理的线程池大小
任务一般可分为:CPU密集型、IO密集型、混合型,对于不同类型的任务需要分配不同大小的线程池。

  • CPU密集型任务
    尽量使用较小的线程池,一般为CPU核心数+1。
    因为CPU密集型任务使得CPU使用率很高,若开过多的线程数,只能增加上下文切换的次数,因此会带来额外的开销。
    IO密集型任务
    可以使用稍大的线程池,一般为2*CPU核心数。
  • IO密集型任务CPU使用率并不高,因此可以让CPU在等待IO的时候去处理别的任务,充分利用CPU时间。
  • 混合型任务
    可以将任务分成IO密集型和CPU密集型任务,然后分别用不同的线程池去处理。
    只要分完之后两个任务的执行时间相差不大,那么就会比串行执行来的高效。
    因为如果划分之后两个任务执行时间相差甚远,那么先执行完的任务就要等后执行完的任务,最终的时间仍然取决于后执行完的任务,而且还要加上任务拆分与合并的开销,得不偿失。


public class ThreadTest {
    //自定义ThreadFactory 
    private static class NameTreadFactory implements ThreadFactory {
        private final AtomicInteger mThreadNum = new AtomicInteger(1);
        @Override
        public Thread newThread(Runnable r) {
            Thread t = new Thread(r, "my-thread-" + mThreadNum.getAndIncrement());
            System.out.println(t.getName() + " has been created");
            return t;
        }
    }
    //自定义拒绝策略
    private static class MyIgnorePolicy implements RejectedExecutionHandler {
        @Override
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            doLog(r, e);
        }
        private void doLog(Runnable r, ThreadPoolExecutor e) {
            // 日志记录
            System.err.println( r.toString() + " rejected");
        }
    }

    private final static ThreadPoolExecutor executor = new ThreadPoolExecutor
            (10, 30, 2000, TimeUnit.MILLISECONDS,
                    new ArrayBlockingQueue<Runnable>(100),new NameTreadFactory(),new MyIgnorePolicy());

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //execute
        executor.execute(()->{
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("execute,无返回值");
        });
        //submit
        Future<String> future = executor.submit(()->{
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "submit,有返回值Future<String>";
        });
        System.out.println(future.get());
        System.out.println("结束");
    }
}

打印结果

my-thread-1 has been created
my-thread-2 has been created
execute,无返回值
submit,有返回值Future<String>
结束
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269