线程池与Android的日日夜夜

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

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

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

目录

  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 {@Overridepublic 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();}@Overridepublic void run() {System.out.println("光头强和熊大熊二");}
}

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

public class MyRunnable {public static void main(String[] arg) {Thread thread = new Thread(new Runnable() {@Overridepublic 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();}}@Overridepublic 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() {@Overridepublic 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. 核心线程未饱和

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

  1. 核心线程饱和队列未饱和

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

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

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

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

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


总结起来,其实就是:


这里很懵逼的是阻塞队列,事实上不是每个阻塞队列都像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的耗时过长时产生的内存问题。

六、对比线程和线程池的优缺点,各种使用场景及其区别

我们知道使用线程池可以大大的提高系统的性能,提高程序任务的执行效率,在线程池中,每一个工作线程都能得到重复利用,执行多个任务,减少对象新建回收的次数。

线程缺点:

1、每次新建线程都需要新建对象
2、没有定时执行,定期执行,中断
3、不能统一管理,有些时候会发生线程之间对资源的竞争,一定情况下就会内存泄漏。

线程池缺点:

1、一旦加入到线程池中就没有办法让它停止,除非任务执行完毕自动停止;

2、一个进程共享一个线程池;

3、要执行的任务不能有返回值(当然,线程中要执行的方法也是不能有返回值,如果确实需要返回值必须采用其它技巧来解决);

4、在线程池中所有任务的优先级都是一样的,无法设置任务的优先级;

5、不太适合需要长期执行的任务(比如在Windows服务中执行),也不适合大的任务;

6、不能为线程设置稳定的关联标识,比如为线程池中执行某个特定任务的线程指定名称或者其它属性。

线程池优点:

1、重用已存在的线程,减少对象创建、回收次数,提高JVM性能
2、通过控制最大线程数,提高系统资源利用率
3、定时执行、定期执行、并发数控制。

线程池使用场景

实际情况下,Web 服务器、数据库服务器、文件服务器或邮件服务器之类的许多服务器应用程序都需要用线程池去处理远程传过来的任务。

当然,如果说,细化的话,上面已分析过3种线程池的使用情况,其他的和他们三大体上分析过程是一样的。具体线程池的参数如何分配,需要根据实际需求情况去确定,重要的当然是分析过程咯哦。

2018-05-10 11:30AM
更新中…

参考

用线程池和不用线程池的区别是什么?

线程池与Android的日日夜夜相关推荐

  1. android线程池断点续传,Android之多线程下载及断点续传

    今天我们来接触一下多线程下载,当然也包括断点续传,我们可以看到 很多下载器,当开通会员的时候下载东西的速度就变得快了许多,这是为什么呢?这就是跟今天讲的多线程有关系了,其实就是多开了几个线程一起下载罢 ...

  2. android网络请求线程池,利用线程池实现Android客户端的http网络数据请求工具类

    该工具类值只实现了HTTP的get方法,参考get方法可轻松实现post.put.delete等方法,下面是get方法的实现 public class SimpleHttpClient { priva ...

  3. Android中常见的4种线程池的理解(转)

    转:https://blog.csdn.net/l540675759/article/details/62230562 转:https://blog.csdn.net/seu_calvin/artic ...

  4. [转]new Thread的弊端及Java四种线程池的使用

    介绍new Thread的弊端及Java四种线程池的使用,对Android同样适用.本文是基础篇,后面会分享下线程池一些高级功能. 1.new Thread的弊端 执行一个异步任务你还只是如下new ...

  5. android线程及线程池

    众所周知,在UI系统中进行一些耗时操作,都会导致卡顿现象,因为一次刷新在16ms,如果当次操作过了这个时间,那么用户就能感觉到明显的卡顿,甚至引起ANR . 对于这种情况,一般都是再起一个线程,进行一 ...

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

    0.前言 转载请注明出处:http://blog.csdn.net/seu_calvin/article/details/52415337 使用线程池可以给我们带来很多好处,首先通过线程池中线程的重用 ...

  7. Android多线程:这是一份全面 详细的线程池(ThreadPool)讲解教程

    前言 对于多线程,大家应该很熟悉.但是,大家了解线程池吗? 今天,我将带大家全部学习关于线程池的所有知识. 目录 1. 简介 2. 工作原理 2.1 核心参数 线程池中有6个核心参数,具体如下 上述6 ...

  8. android自定义线程池工具类,妈妈再也不用担心你不会使用线程池了(ThreadUtils)...

    为什么要用线程池 使用线程池管理线程有如下优点:降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗. 提高响应速度:当任务到达时,任务可以不需要等到线程创建就能立即执行. 提高线程的可 ...

  9. Android 面试题目之 线程池

    [color=red][size=xx-large]记不住密码怎么办?[/size][/color] [url]http://a.app.qq.com/o/simple.jsp?pkgname=com ...

最新文章

  1. hive udf 分组取top1_Hive的经典面试题
  2. 如何在sqlserver数据库表中建立复合主键
  3. LeetCode # Array # Easy # 217. Contains Duplicate
  4. 《TCP/IP详解》笔记----第二章 链路层
  5. 【渝粤题库】陕西师范大学200301几何学作业(高起本)
  6. Linux常用错误码--errno-base.h
  7. http 断点续传,Windows下HTTP方式单线程下载
  8. Nginx官网提供的版本类型
  9. Windows 帐号管理相关操作
  10. jsp内置对象作业3-application用户注册
  11. Bing搜索背景图抓取
  12. 初中生计算机课考试方案,信息技术中考备考方案
  13. python读取excel画折线图_python读取excel数据绘制简单曲线图的完整步骤记录
  14. 在spss中实现变量标准化Z值
  15. 扑克洗牌java_java扑克牌洗牌发牌问题
  16. Tropical Cyclone Intensity Estimation
  17. 内存淘汰策略 删除策略
  18. Efficient and Effective Data Imputation with Influence Functions
  19. 解决Chrome、360自动填充用户名和密码行为带来的困扰
  20. Re:从零开始的鸿蒙开发教程

热门文章

  1. layout_marginBottom的使用(将VIEW放置在布局底部)
  2. linux常用命令全称
  3. 解决React中路由跳转报错:Cannot read property ‘push’ of undefined
  4. ISO 11519-2与ISO 11898-3之间的关系
  5. 为什么996工作制只提互联网公司,其他行业没有吗?
  6. 微信小程序从注册appid到熟悉静态微信特有标签
  7. 智慧综合交通运行监测平台
  8. opencv进行双目标定以及极线校正 python代码
  9. 关于教育类app开发,未来该如何发展?
  10. 简析“正向代理”与“反向代理”