线程池与Android的日日夜夜

20人阅读 评论(0) 收藏 举报
分类:

线程池与Android的日日夜夜

假如你Java中研究到了线程池的话,一般来说,你已经对线程的原理颇有研究了,或者说,你意识到了线程的某些瓶颈或者缺点。你说,要有光,所以,天降线程池。

1.jpg

正儿八经的说,如果你为每一个请求创建一个新的线程,这在性能上影响是巨大的,因为线程对象的创建销毁需要Java虚拟机频繁的GC,假如说,一个请求所用的时间比创建销毁线程对象时间还短的话,那么时间将会大程度浪费在虚拟机的GC上,系统性能降低。

2.jpg

所以啊,线程池主要就是复用线程对象,就跟上面所说,解决线程对象频繁创建和销毁的问题,内部可以抽象成一个“池”,线程对象放在里头,需要用的时候就拿出来用,不用了就泡着,泡坏了或者不要了就清掉。也正因为如此,线程池可以用来处理高并发的访问请求

目录

  1. 先从最基本的线程的3种用法说起
  2. 一个最基本的线程池用例
  3. 分析各种参数:线程池创建的ThreadPoolExecutor类
  4. 常见阻塞队列及使用场景
  5. 比较Executors中3种线程池的区别和使用情景
  6. 对比线程和线程池的优缺点,各种使用场景及其区别
  7. 其他:并发集合框架
  8. 默认Executors生成线程池和自传参数进构造方法ThreadPoolExecutor创建线程池的利弊
  9. 分析实际应用,如OkHttp中的线程池,如AsyncTask中的线程池,RxJava中的线程池

一 先从最基本的线程开始

先重新了解一下,创建线程的三种方法:
  1. 继承Thread类创建线程
  2. 实现Runnable创建线程
  3. 实现Callable接口 、使用Future类接收返回值

(1)继承Thread类,重写父类run方法

public class MyThread extends Thread {
    @Override
    public void run() {
        super.run();
        System.out.println("biubiubiu");
    }

    public static void main(String[] arg){
        MyThread myThread = new MyThread();
        myThread.start();
    }
}

(2)实现Runnable接口,实现接口的run方法

public class MyRunnable implements Runnable {

    public static void main(String[] arg) {
        MyRunnable myRunnable = new MyRunnable();
        Thread thread = new Thread(myRunnable);
        thread.start();
    }

    @Override
    public void run() {
        System.out.println("光头强和熊大熊二");
    }
}

当然,我们最常用的是匿名的内部Runnable类

public class MyRunnable {

    public static void main(String[] arg) {

        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("光头强的斧头");
            }
        });
        thread.start();
    }
}

(3)实现Callable接口,使用Future来接收返回值(接收可选)

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class MyCallable implements Callable<String> {

    public static void main(String[] arg) {

        MyCallable myCallable = new MyCallable();
        FutureTask<String> futureTask = new FutureTask<>(myCallable);
        new Thread(futureTask).start();
        try {
            System.out.println(futureTask.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }

    @Override
    public String call() throws Exception {


        return "猪猪侠";
    }
}

二、一个最基本的线程池用例

先放一个基本的线程池,这里构造的是核心线程为2,最大线程数为5,有界阻塞数列为5的线程池

import java.util.concurrent.*;

public class MyDemo {

    public static void main(String[] arg) {
        ExecutorService executorService = new ThreadPoolExecutor(2, 5, 60, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(5));

        for (int i = 0; i < 13; i++) {
            int finalI = i;
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("当前顺序是:" + finalI + ",线程名字" + Thread.currentThread().getName());
                }
            });
        }
    }
}

console如下

Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task MyDemo$1@135fbaa4 rejected from java.util.concurrent.ThreadPoolExecutor@45ee12a7[Running, pool size = 5, active threads = 5, queued tasks = 5, completed tasks = 0]
    at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)
    at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
    at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)
    at MyDemo.main(MyDemo.java:10)
当前顺序是:0,线程名字pool-1-thread-1
当前顺序是:2,线程名字pool-1-thread-1
当前顺序是:3,线程名字pool-1-thread-1
当前顺序是:4,线程名字pool-1-thread-1
当前顺序是:5,线程名字pool-1-thread-1
当前顺序是:6,线程名字pool-1-thread-1
当前顺序是:1,线程名字pool-1-thread-2
当前顺序是:7,线程名字pool-1-thread-3
当前顺序是:8,线程名字pool-1-thread-4
当前顺序是:9,线程名字pool-1-thread-5

先看这里的console,这里打印了RejectedExecutionException异常,还有打印了10个线程执行方法体里头的信息。后面的线程名字有6个1。其他的都是单独的2,3,4,5号,这里说明线程在线程池中得到了复用。

三、线程池创建的ThreadPoolExecutor类

ThreadPoolExecutor类有4个构造方法,其中的三个构造方法最终会调用参数最多的(7个)的构造方法

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

    ..

构造方法各个参数:
1. corePoolSize:线程池中的核心线程数,一般情况设置为CPU核心数
2. maximumPoolSize:线程池的线程数量最大值,非核心线程数=最大值-核心线程数
3. keepAliveTime:非核心线程闲置时候的超时回收时间,要是想多任务(该任务轻量执行内容/块)下线程的利用率,可以增大这个超时时间
4. unit:上面这个参数的单位,有分秒毫秒等等
5. workQueue:线程池的任务队列。新建的线程数超过核心线程时,线程加入任务队列进 行等待或者分发。常用的有ArrayBlockingQueueLinkedBlockingQueueSynchronousQueue
6. threadFactory:线程池的线程工厂。常用来设置每个线程的名字。默认名字是 pool-1-thread-1,一般默认为Executors.defaultThreadFactory()即可,当然,ThreadPoolExecutor类的构造方法最终都是传入DefaultThreadFactory
7. RejectedExecutionHandler:饱和策略。当任务队列和线程池都达到最大值时的处理策略。默认是无法处理新的任务的AbortPolicy。比如上面第二节console输出的是AbortPolicy策略。那是因为创建的最大线程数是5,任务队列是5,那么线程池中会存在10个线程,而我创建了13个线程的同时超过了10个线程,接着就会抛出这个RejectedExecutionHandler异常

1. 线程池的处理过程

拿第二节创建的线程池来举例,核心线程是2,最大线程数是5(说明非核心线程数为3)。任务队列是ArrayBlockingQueue(特点是它用数组实现,元素排序规则是先进先出,默认不保证线程池按照阻塞的先后顺序访问队列),数量为5个,超时为6秒,其他都为默认。

那么,其实内部是这样的——–>看图:

  1. 核心线程未饱和
    核心线程未饱和.gif
    当只有1核心线程时,这时新建的任务会直接添加为核心线程

  1. 核心线程饱和队列未饱和
    核心线程饱和队列未饱和.gif
    当核心线程已满,任务队列未饱和时,这时新建的任务会添加到工作队列

  1. 核心线程饱和队列饱和非核心线程未饱和
    核心线程饱和队列饱和非核心线程未饱和.gif
    当核心线程已满,任务队列已饱和,非核心线程未饱和时,新建的任务会添加为非核心线程。

  1. 核心线程饱和队列饱和非核心线程饱和
    核心线程饱和队列饱和非核心线程饱和.gif

当线程池线程已达最大值,队列也已饱和,这时新建任务会执行饱和策略


总结起来,其实就是:

线程池执行流程.png


这里很懵逼的是阻塞队列,事实上不是每个阻塞队列都像ArrayBlockingQueue如此,下节将分析常用的阻塞队列

四、常见阻塞队列及使用场景

阻塞队列使用方法大同小异,只要了解他的内部结构构成,以及由其结构影响的各种特性即可,具体测试及用法可看
BlockingQueue(阻塞队列)详解
深入理解阻塞队列(二)——ArrayBlockingQueue源码分析
深入理解阻塞队列(三)——LinkedBlockingQueue源码分析

常用的几种阻塞队列如下:
- ArrayBlockingQueue:有界阻塞队列,它用数组实现,元素排序规则是先进先出,默认不保证线程池按照阻塞的先后顺序访问队列,一般构造方法会指定元素数量,和是否公平顺序按照阻塞顺序访问队列,通常用在需要生产者和消费者顺序的操作队列中的数据,以降低吞吐量的时候,比如
- LinkedBlockingQueue:有界阻塞队列,链表实现,与ArrayBlockingQueue区别不同,它是并行的操作队列中的数据,这也决定了它能用于高并发,巨大吞吐量的情况,需要注意的是,LinkedBlockingQueue的容量记得要指定哦,不然太大了加入生产者的速度大于消费者,那么队列阻塞可能不会阻塞,因为内存会炸。
- SyschronousQueue:不存储元素的异步队列,有个特点,插入操作的完成要等待另一个线程的对应移除操作,适合那种立即处理且耗时较少的任务。

五、比较Executors中3种线程池的区别和使用情景

其实不止3种(有6种),这里只分析其中常用的4种,其他的大同小异

  1. FixedThreadPool:固定线程数的线程池,特点在核心线程和线程最大数量相等,意味着只有核心线程,keepAliveTime时间为0说明多余线程马上停止,队列它用的是new LinkedBlockingQueue<Runnable>(),这里源码点进去看发现指定队列容量为无穷大。总结的说,就是线程池大小固定,任务队列无界
/**
     * Creates a thread pool that reuses a fixed number of threads
     * operating off a shared unbounded queue.  At any point, at most
     * {@code nThreads} threads will be active processing tasks.
     * If additional tasks are submitted when all threads are active,
     * they will wait in the queue until a thread is available.
     * If any thread terminates due to a failure during execution
     * prior to shutdown, a new one will take its place if needed to
     * execute subsequent tasks.  The threads in the pool will exist
     * until it is explicitly {@link ExecutorService#shutdown shutdown}.
     *
     * @param nThreads the number of threads in the pool
     * @return the newly created thread pool
     * @throws IllegalArgumentException if {@code nThreads <= 0}
     */
    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
  • 应用场景:保证所有任务都会被执行,永远不拒绝新任务。但是假如任务时间无限长的时候会出现由于队列数量过大引起的内存问题。
    1. CacheThreadPool:核心线程为0,线程最大值为无穷大,说明
      非核心线程数是无穷大的,空闲线程等待新任务的时间是60s。这里阻塞队列用的是new SynchronousQueue<Runnable>()说明每个插入和移除操作要同步进行。总结的说,就是线程池无心大,等待长度为1(因为阻塞队列的原因)
    /**
     * Creates a thread pool that creates new threads as needed, but
     * will reuse previously constructed threads when they are
     * available.  These pools will typically improve the performance
     * of programs that execute many short-lived asynchronous tasks.
     * Calls to {@code execute} will reuse previously constructed
     * threads if available. If no existing thread is available, a new
     * thread will be created and added to the pool. Threads that have
     * not been used for sixty seconds are terminated and removed from
     * the cache. Thus, a pool that remains idle for long enough will
     * not consume any resources. Note that pools with similar
     * properties but different details (for example, timeout parameters)
     * may be created using {@link ThreadPoolExecutor} constructors.
     *
     * @return the newly created thread pool
     */
    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
  • 应用场景:适合大量的需要立即处理并且耗时较少的的任务
    1. SingleThreadPool:核心线程和最大线程数都为0,也就是说SingleThreadPool只有一个核心线程,后面的等待时间队列都和FixedThreadPool一样。总结的说,就是线程池大小固定为1,任务队列无界
/**
     * Creates an Executor that uses a single worker thread operating
     * off an unbounded queue. (Note however that if this single
     * thread terminates due to a failure during execution prior to
     * shutdown, a new one will take its place if needed to execute
     * subsequent tasks.)  Tasks are guaranteed to execute
     * sequentially, and no more than one task will be active at any
     * given time. Unlike the otherwise equivalent
     * {@code newFixedThreadPool(1)} the returned executor is
     * guaranteed not to be reconfigurable to use additional threads.
     *
     * @return the newly created single-threaded Executor
     */
    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
  • 应用场景:它的特性保证所有了所有的任务在一个线程中按顺序运行。所以它适用于在逻辑上需要单线程处理任务的场景。由于阻塞队列无限大,同样可能会出现FixedThreadPool的耗时过长时产生的内存问题。

2018-04-16 06:05AM
更新中…

查看评论

写给我爱的人

过去的日日夜夜,是否早已云淡风轻地成为你生命中过了景的琐碎?在记忆在那个永不在回来的夏季里,你我之间所有的“辉煌”尽皆寂灭!我的泪水在心里汇成一泻千里的河。那种酸楚,那种痛彻身心的无奈就是到了今天还能...
  • 0011411
  • 0011411
  • 2005-06-27 14:13:00
  • 2934

线程池完整类

  • 2013年03月20日 12:25
  • 4KB
  • 下载

Android开发笔记之线程池的原理以及实现

声明: 转载与[http://blog.csdn.net/hsuxu/article/details/8985931]1.线程池的简介简介 1. 多线程技术主要解决处理器单元内多个线程执行的问题,...
  • u012416955
  • u012416955
  • 2016-08-04 19:43:57
  • 1399

Android 多线程 线程池原理 封装线程池

我自己理解看来。线程池顾名思义就是一个容器的意思,需要注意的是,每一个线程都是需要CPU分配资源去执行的。如果由于总是new Thread()开启一个线程,那么就会大量的消耗CPU的资源,导致Andr...
  • xiangyunwan
  • xiangyunwan
  • 2017-05-19 17:59:25
  • 2260

Android.线程池的原理和线程池管理类的使用

##线程池的原理 线程池使用来管理线程的,之所以称为池,是因为其可以管理多条线程,所以需要用一个集合来管理线程,然后线程池是有大小的,当一个线程池管理的线程数目为计算机的cup数*2+1个的时候,...
  • suncold123
  • suncold123
  • 2016-08-26 21:08:17
  • 803

Android开发——Android中常见的4种线程池(保证你能看懂并理解)

0.前言使用线程池可以给我们带来很多好处,首先通过线程池中线程的重用,减少创建和销毁线程的性能开销。其次,能控制线程池中的并发数,否则会因为大量的线程争夺CPU资源造成阻塞。最后,线程池能够对线程进行...
  • SEU_Calvin
  • SEU_Calvin
  • 2016-09-03 10:44:43
  • 37606

Android线程池(一)简单使用

Android线程池hreadPoolExecutor是什么相当于一个容器,容纳的是Thread或者Runable为什么要使用ThreadPoolExecutor1、每一个线程都是需要CUP去分配的,...
  • iromkoear
  • iromkoear
  • 2017-03-22 23:54:15
  • 764

Android开发中线程池的使用Demo

  • 2016年08月20日 20:43
  • 20.45MB
  • 下载

深入理解在Android中线程池的使用

前言(1)本文共花费2周零3天的凌晨时光,这段时间收获很多.(2)从整理文章,作者从线程-->阻塞队列-->二进制-->线程池的内部机制,一路走来,本来是想写一篇为AsyncTask做铺垫的文章,没想...
  • l540675759
  • l540675759
  • 2017-03-29 11:39:04
  • 9841

Android开发之线程池使用总结

线程池算是Android开发中非常常用的一个东西了,只要涉及到线程的地方,大多数情况下都会涉及到线程池。Android开发中线程池的使用和Java中线程池的使用基本一致。那么今天我想来总结一下Andr...
  • u012702547
  • u012702547
  • 2016-08-20 20:49:25
  • 27445
    个人资料
    等级:
    访问量: 273
    积分: 216
    排名: 34万+
    文章存档
    最新评论