Java线程池使用

Android开发过程线程的使用很常见,最常见的用法应该是如下所示new一个线程。

    private class ReadThread extends Thread {
        @Override
        public void run() {
            super.run();
        }
    }
    new Thread().start();

这样使用确实很简单方便直观。

但如果线程的数量很多的话,这样会出现大量的线程创建和销毁,不仅浪费大量的内存,还会降低系统的运行效率。

为了能够复用线程,避免大量的内存浪费,我们可以使用线程池来创建和使用线程。

一、线程池ThreadPoolExecutor基本使用。

首先声明一个线程池对象,如下图可以看到有四个构造方法。不过追究到底发现其它三个方法都是第四个方法的实现,所以我们直接看第四个构造方法就行了。


构造方法

构造方法如下:

 public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

从上面代码可以看到,构造方法中做了一些非空的判断,和初始化赋值。下面来看一下各个参数的含义:

  • corePoolSize: 核心线程的大小。默认情况下,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,则不再创建,会把到达的任务放到缓存队列当中。除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,初始化时候会直接创建corePoolSize个线程。
  • maximumPoolSize :最大线程数,表示线程池中最多可以创建多少个线程
  • keepAliveTime:当线程数大于corePoolSize时,线程空闲后,保持存活的时间。如果线程数不超过corePoolSize,则keepAliveTime不起作用。除非调用了allowCoreThreadTimeOut(boolean)方法。
  • unit: keepAliveTime 的时间单位
  • workQueue: 缓冲队列,用来存储等待执行的任务。有以下几种:
    • LinkedBlockingQueue
    • SynchronousQueue
    • DelayedWorkQueue
  • threadFactory:线程工厂,负责创建工厂
  • handler:饱和策略,当到达线程数上限或工作队列已满时的拒绝处理逻辑。有以下四种策略。


    饱和策略
    • AbortPolicy:不处理,直接抛出异常。
    • CallerRunsPolicy:将任务分给调用线程来执行
    • DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务
    • DiscardPolicy:直接丢弃,不处理

分析完构造参数的含义,现在我们开始声明一个线程池,并且简单实用一下吧。

    public static int corePoolSize = 3;
    public static int maxPoolSize = 5;
    public static long keepAliveTime = 60;
    private static int mCount = 0;

    public static void main(String[] args) {

        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize, maxPoolSize, keepAliveTime, TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());

        for (int i = 0; i < 20; i++) {
            threadPoolExecutor.execute(mRunnable);
        }
    }
    
    private static Runnable mRunnable = new Runnable() {
        @Override
        public void run() {
            mCount++;
            System.out.println("name:"+Thread.currentThread().getName() + "    count = " + mCount);
        }
    };

输入结果:

name:pool-1-thread-1    count = 1
name:pool-1-thread-2    count = 2
name:pool-1-thread-1    count = 3
name:pool-1-thread-2    count = 4
name:pool-1-thread-1    count = 5
name:pool-1-thread-2    count = 6
name:pool-1-thread-1    count = 7
name:pool-1-thread-2    count = 8
name:pool-1-thread-1    count = 9
name:pool-1-thread-2    count = 10
name:pool-1-thread-1    count = 11
name:pool-1-thread-2    count = 12
name:pool-1-thread-1    count = 13
name:pool-1-thread-2    count = 14
name:pool-1-thread-1    count = 15
name:pool-1-thread-2    count = 16
name:pool-1-thread-1    count = 17
name:pool-1-thread-2    count = 18
name:pool-1-thread-1    count = 19
name:pool-1-thread-3    count = 20

从输出结果可以看出,执行20次线程任务,使用了3个线程完成任务。不需要创建20个线程,大大节约了内存。

为了方便我们使用线程池,Java JDK提供了4中简单的创建方式。

二、线程池的四种创建方式

  • Executors.newCachedThreadPool();
  • Executors.newFixedThreadPool();
  • Executors.newSingleThreadExecutor();
  • Executors.newScheduledThreadPool();

1、newCachedThreadPool()

创建一个带缓存的线程池,如果有缓存线程则复用缓存线程,没有则创建新的线程。

构造方法如下:

    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
    
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }
  • 核心线程数corePoolSize = 0,
  • 最大线程数为 maximumPoolSize 为 int 的最大值,相当于无限大,
  • 保活时间keepAliveTime = 60秒
  • 阻塞队列workQueue使用的是SynchronousQueue 同步队列
  • defaultHandler是一个AbortPolicy

使用:

    public static void main(String[] args) throws InterruptedException {
        ExecutorService service = Executors.newCachedThreadPool();
        for (int i = 0; i < 20; i++) {
            if (i < 10)
                Thread.sleep(500);
            service.execute(mRunnable);
        }
    }

当 i 小于10的时候,每次循环让线程sleep 500ms。执行效果如下:

name:pool-1-thread-1    count = 1
name:pool-1-thread-1    count = 2
name:pool-1-thread-1    count = 3
name:pool-1-thread-1    count = 4
name:pool-1-thread-1    count = 5
name:pool-1-thread-1    count = 6
name:pool-1-thread-1    count = 7
name:pool-1-thread-1    count = 8
name:pool-1-thread-1    count = 9
name:pool-1-thread-1    count = 10
name:pool-1-thread-1    count = 11
name:pool-1-thread-2    count = 12
name:pool-1-thread-1    count = 13
name:pool-1-thread-2    count = 14
name:pool-1-thread-3    count = 15
name:pool-1-thread-1    count = 17
name:pool-1-thread-2    count = 16
name:pool-1-thread-4    count = 18
name:pool-1-thread-5    count = 19
name:pool-1-thread-6    count = 20

可以看到,前面10个循环因为加了500毫秒的暂停让线程执行完成,所以再次执行则会复用已经创建好的空闲线程,并没有重新创建新的线程。后面的循环则一直创建了新的线程,因为没有空闲线程。

使用场景:

因为newCachedThreadPool方式创建的线程池核心线程数为0,最大线程数几乎无穷大。所以newCachedThreadPool适合处理需要创建大量耗时任务的请求。

2、newFixedThreadPool()

创建指定长度的线程池,线程数量超过指定长度则会在队列中等待执行。

构造方法

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

从构造方法可以看出,核心线程数corePoolSize 和最大线程数maximumPoolSize是相等的。并且保活时间是0ms。使用的是LinkedBlockingQueue缓冲队列。

使用:

    public static void main(String[] args) throws InterruptedException {
        ExecutorService service = Executors.newFixedThreadPool(3);
        for (int i = 0; i < 20; i++) {
            if (i<10)
                Thread.sleep(500);
            service.execute(mRunnable);
        }
    }

运行结果:

name:pool-1-thread-1    count = 1
name:pool-1-thread-2    count = 2
name:pool-1-thread-3    count = 3
name:pool-1-thread-1    count = 4
name:pool-1-thread-2    count = 5
name:pool-1-thread-3    count = 6
name:pool-1-thread-1    count = 7
name:pool-1-thread-2    count = 8
name:pool-1-thread-3    count = 9
name:pool-1-thread-1    count = 10
name:pool-1-thread-1    count = 12
name:pool-1-thread-3    count = 13
name:pool-1-thread-2    count = 12
name:pool-1-thread-3    count = 15
name:pool-1-thread-1    count = 14
name:pool-1-thread-1    count = 18
name:pool-1-thread-1    count = 19
name:pool-1-thread-1    count = 20
name:pool-1-thread-3    count = 17
name:pool-1-thread-2    count = 16

我们指定线程数为3,前10个循环让线程sleep 500ms,可以看出newFixedThreadPool会创建新的线程来执行任务。

使用场景:

控制线程中最大并发数,超过最大并发时,等待执行

3、newSingleThreadExecutor

创建一个线程的线程池,线程池中只有一个线程,超过一个则等待执行

构造方法:

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

从构造方法可以看见 核心线程数corePoolSize和最大线程数maximumPoolSize都为1。使用FinalizableDelegatedExecutorService()方法保证线程的唯一性

使用:

    public static void main(String[] args) throws InterruptedException {
        ExecutorService service = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 20; i++) {
            service.execute(mRunnable);
        }
    }

运行结果:

name:pool-1-thread-1    count = 1
name:pool-1-thread-1    count = 2
name:pool-1-thread-1    count = 3
name:pool-1-thread-1    count = 4
name:pool-1-thread-1    count = 5
name:pool-1-thread-1    count = 6
name:pool-1-thread-1    count = 7
name:pool-1-thread-1    count = 8
name:pool-1-thread-1    count = 9
name:pool-1-thread-1    count = 10
name:pool-1-thread-1    count = 11
name:pool-1-thread-1    count = 12
name:pool-1-thread-1    count = 13
name:pool-1-thread-1    count = 14
name:pool-1-thread-1    count = 15
name:pool-1-thread-1    count = 16
name:pool-1-thread-1    count = 17
name:pool-1-thread-1    count = 18
name:pool-1-thread-1    count = 19
name:pool-1-thread-1    count = 20

可以看到所有的任务都是一个线程执行的,因为就只允许创建一个线程。

使用场景:

需要频繁创建线程对象,可以使用newSingleThreadExecutor,避免内存浪费。按顺序执行

4、newScheduledThreadPool

创建一个定长线程池,支持定时及周期性任务

构造方法

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

可以看出核心线程数由参数指定,最大线程数是 int 的最大值,使用 DelayedWorkQueue延迟队列

使用:

    public static void main(String[] args) {
        ExecutorService service = Executors.newScheduledThreadPool(3);
        for (int i = 0; i < 20; i++) {
            service.execute(mRunnable);
        }
    }

执行结果:

name:pool-1-thread-1    count = 1
name:pool-1-thread-2    count = 2
name:pool-1-thread-2    count = 3
name:pool-1-thread-1    count = 4
name:pool-1-thread-2    count = 5
name:pool-1-thread-2    count = 6
name:pool-1-thread-3    count = 7
name:pool-1-thread-2    count = 8
name:pool-1-thread-1    count = 10
name:pool-1-thread-2    count = 11
name:pool-1-thread-3    count = 9
name:pool-1-thread-2    count = 13
name:pool-1-thread-1    count = 12
name:pool-1-thread-2    count = 15
name:pool-1-thread-3    count = 14
name:pool-1-thread-2    count = 17
name:pool-1-thread-1    count = 16
name:pool-1-thread-2    count = 19
name:pool-1-thread-3    count = 18
name:pool-1-thread-1    count = 20

支持延时和周期使用:

    public static void main(String[] args) {
    
        ScheduledExecutorService service = Executors.newScheduledThreadPool(3);
        service.scheduleAtFixedRate(mRunnable, 2, 1, TimeUnit.SECONDS);
    }

延迟2秒后,每隔1秒执行一次任务。执行结果

name:pool-1-thread-1    count = 1
name:pool-1-thread-1    count = 2
name:pool-1-thread-2    count = 3
name:pool-1-thread-2    count = 4
name:pool-1-thread-2    count = 5
name:pool-1-thread-2    count = 6
name:pool-1-thread-2    count = 7
name:pool-1-thread-1    count = 8
name:pool-1-thread-1    count = 9
...

使用场景:

计时任务

参考:https://www.cnblogs.com/exe19/p/5359885.html

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

推荐阅读更多精彩内容

  • 线程池在实际开发中应用相当广泛,下面来科普一下。 线程池的优点 1. 减少资源消耗,通过复用线程减少线程的创建与销...
    程序员kyle阅读 642评论 0 1
  • 第一部分 来看一下线程池的框架图,如下: 1、Executor任务提交接口与Executors工具类 Execut...
    压抑的内心阅读 4,231评论 1 24
  • 在Java1.5中提供了一个非常高效实用的多线程包:java.util.concurrent,提供了大量高级工具,...
    jackcooper阅读 28,418评论 8 23
  • 大千世界聚散有缘。请以一颗虔诚的心,尊敬的心善待你的左邻右舍。不仰慕他们的豪富,不鄙视他们的贫穷。 贫...
    佛灯熠熠阅读 786评论 4 18
  • 前言 最近公司有AR开发的需求,需要使用手机扫描图片显示出不同的模型,同时模型要有旋转、缩放的功能,提供的框架是V...
    GrapeX阅读 3,191评论 1 6