android线程管理总结

一 前言:

在android中,UI主线程并非线程安全的,所有UI相关的操作均需在UI主线程中完成。在默认情况下,开发者创建的Service、Activity、Broadcast均运行在UI主线程中,但将一些耗时操作,如网络下载、大文件读写、加解密计算、数据库操作等,也放在UI线程中执行,往往会阻塞UI线程,造成ANR异常,因此,在Android应用开发中,使用工作线程(子线程)来执行这些耗时操作就应运而生了。

二 线程的三种启动方式:

1.通过继承Thread类,并改写run方法来实现一个线程

public class MyThread extends Thread{
        public static final String TAG = "MyThread:";
        //改写run方法
        @Override
        public void run() {
            for(int i = 0; i < 50; i ++){
                Log.i(TAG,Thread.currentThread().getName() + "i = "+ i);
            }
        }
    }

启动线程

new MyThread().start();

2.创建一个Runnable对象

public class MyRunnable implements Runnable{
        public static final String TAG = "MyRunnable:";
        @Override
        public void run() {
            for(int i = 0; i < 10; i ++){
                Log.e(TAG,Thread.currentThread().getName() + "i = "+ i);
            }
        }
    }

启动线程

new Thread(new MyRunnable()).start();

3.通过Handler启动线程

public class MainActivity extends AppCompatActivity {

    public static final String TAG = "android thread:";
    private Handler mHandler = new Handler();
    private int sum = 0;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mHandler.postDelayed(mRunnable,0);//给自己发送消息,执行一次任务,不延时
    }

    /**
     * 任务执行操作
     */
    private Runnable mRunnable = new Runnable() {
        @Override
        public void run() {
            Log.e(TAG, Thread.currentThread().getName() + " " + sum);
            sum ++;
            mHandler.postDelayed(mRunnable,2000);//每两秒给自己发送消息,执行一次任务
        }
    };

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mHandler.removeCallbacks(mRunnable);//activity销毁时,把线程任务移除销毁
    }
}

上述三种方式是android开启子线程的常用方法,虽然很好的解决了阻塞UI线程(主线程)的问题,但每次开启一个线程都是需要CPU分配资源去执行的。如果总是每次需要时去开启一个子线程,那么就会大量的消耗CPU的资源,导致Android运行变慢,甚至OOM(out of memory),那么问题来了,是否有一个很好的解决方式,既可以解决不阻塞主线程,又不是很耗费CPU资源的方案呢?

三 线程池:

基于以上问题,android/java引入了线程池的概念

Android中的线程池的概念来源于Java中的Executor,Executor是一个接口,真正的线程池的实现为ThreadPoolExecutor,ThreadPoolExecutor提供了一系列参数来配置线程池,通过不同的参数可以创建不同的线程池。

线程池的优点
1.复用线程池中的线程,避免因为线程的创建和销毁所带来的性能开销。
2.能够有效的控制线程池的最大并发数,避免大量的线程之间因互相抢占系统资源而导致的阻塞现象。
3.能够对线程进行简单的管理,并提供定时执行以及指定间隔循环执行等功能。

线程池的结构图

Executor只是一个接口,它是Java/Android线程池框架的基础,它将任务的提交与任务的执行分离开来。
ExecutorService继承自Executor,有两个关键类实现了ExecutorService接口:ThreadPoolExecutor和ScheduledThreadPoolExecutor。
(1).ThreadPoolExecutor 是线程池的核心实现类,用来执行被提交的任务。
(2).ScheduledThreadPoolExecutor 也是一个实现类,可以在给定的延迟后运行命令,或者定期执行命令。它比Timer更灵活,功能更强大。

android 内部通过ThreadPoolExecutor、ScheduledThreadPoolExecutor创建出四种不同的线程池,分别为:

  • newCachedThreadPool 创建一个可缓存线程池,线程池的最大长度无限制,但如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
  • newFixedThreadPool 创建一个定长线程池(线程个数为 nThreads),可控制线程最大并发数,超出的线程会在队列中等待。
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>(),
                                      threadFactory);
    }
  • newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
  • newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
public static ScheduledExecutorService newScheduledThreadPool(
            int corePoolSize, ThreadFactory threadFactory) {
        return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
    }

一般需求通过以上四种线程池基本上可以解决,但如果有特殊需求,也可以自定义线程池,来做统一的管理。通过对比源码我们发现,前三个线程池的构造方法最终都调用了ThreadPoolExecutor的构造方法,最后一个调用了ScheduledThreadPoolExecutor的构造方法,今天我们就来分析下这第一个方法的具体含义:

ThreadPoolExecutor

ThreadPoolExecutor一共有四种构造函数,为了更全面的了解,我们看一下参数最多的那一个:

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

该线程池中核心线程数最大值
线程池新建线程的时候,如果当前线程总数小于corePoolSize,则新建的是核心线程,如果超过corePoolSize,则新建的是非核心线程。核心线程默认情况下会一直存活在线程池中,即使这个核心线程啥也不干(闲置状态)。而如果指定ThreadPoolExecutorallowCoreThreadTimeOut这个属性为true,那么核心线程如果不干活(闲置状态)的话,超过一定时间(时长由keepAliveTime决定),就会被销毁掉。

  • maximumPoolSize

该线程池中线程总数最大值
线程总数 = 核心线程数 + 非核心线程数。

  • keepAliveTime

该线程池中非核心线程闲置超时时长
一个非核心线程,如果不干活(闲置状态)的时长超过这个参数所设定的时长,就会被销毁掉,如果设置allowCoreThreadTimeOut = true,则会作用于核心线程

  • unit

keepAliveTime的单位,TimeUnit是一个枚举类型,其包括:
NANOSECONDS : 1微毫秒 = 1微秒 / 1000
MICROSECONDS : 1微秒 = 1毫秒 / 1000
MILLISECONDS : 1毫秒 = 1秒 /1000
SECONDS : 秒
MINUTES : 分
HOURS : 小时
DAYS : 天

  • workQueue

线程池中的任务队列,通过线程池execute方法提交的Runnable对象会存储在这个参数中。当所有的核心线程都在干活时,新添加的任务会被添加到这个队列中等待处理,如果队列满了,则新建非核心线程执行任务。这个任务队列是BlockQueue类型,属于阻塞队列,就是当队列为空的时候,此时取出任务的操作会被阻塞,等待任务加入队列中不为空的时候,才能进行取出操作,而在满队列的时候,添加操作同样被阻塞。

  • threadFactory

线程工厂,为线程池提供创建新线程的功能。ThreadFactory是一个接口,它只有一个方法,newThread(Runnable r),用来创建线程。

  • handler

一般用来抛出异常,比如上面提到的两个错误发生了,就会由这个handler抛出异常,用默认的就行

基于上面ThreadPoolExecutor的构造函数的理解,我们就可以自己自定义一个线程管理类,来对多线程进行处理:

分装

/**
 * 线程池管理,管理整个项目中的所有线程
 * Created by Administrator on 2018/2/8.
 */

public class ThreadPoolManager {
    private static ThreadPoolManager mIntance;
    private int corePoolSize;//核心线程池的数量,同时能够执行的线程数量
    private int maximumPoolSize;//最大线程池数量,表示当缓冲队列满的时候能继续容纳的等待任务的数量
    private long keepAliveTime = 1;//存活时间
    private TimeUnit unit = TimeUnit.HOURS;//小时
    private ThreadPoolExecutor executor;
    public static ThreadPoolManager getInstance(){
        if(mIntance == null){
            mIntance = new ThreadPoolManager();
        }
        return mIntance;
    }

    private ThreadPoolManager() {
        //corePoolSize赋值:当前设备可用处理器核心数*2 + 1
        corePoolSize = Runtime.getRuntime().availableProcessors()*2+1;
        maximumPoolSize = corePoolSize; //最大线程池数量数量与核心数设置为一致
        executor = new ThreadPoolExecutor(
                corePoolSize, //当某个核心任务执行完毕,会依次从缓冲队列中取出等待任务
                maximumPoolSize, //先corePoolSize,然后new LinkedBlockingQueue<Runnable>(),然后maximumPoolSize,但是它的数量是包含了corePoolSize的
                keepAliveTime, //表示的是maximumPoolSize当中等待任务的存活时间
                unit,
                new LinkedBlockingQueue<Runnable>(), //缓冲队列,用于存放等待任务,Linked的先进先出
                Executors.defaultThreadFactory(), //创建线程的工厂,默认
                new ThreadPoolExecutor.AbortPolicy() //用来对超出maximumPoolSize的任务的处理策略
        );
    }

    /**
     * 执行任务
     */
    public void execute(Runnable runnable){
        if(runnable==null){
            return;
        }
        executor.execute(runnable);
    }
    /**
     * 从线程池中移除任务
     */
    public void remove(Runnable runnable){
        if(runnable==null){
            return;
        }
        executor.remove(runnable);
    }
}

调用

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        /**
         * 模拟多任务同时执行
         */
        for(int i = 0;i < 8;i ++){
            ThreadPoolManager.getInstance().execute(new DoTask(i));
        }
    }

    /**
     * 模拟单个任务的执行
     */
    class DoTask implements Runnable{
        private int no;
        public DoTask(int no){
            this.no = no;
            Log.i("task","task-->"+no+"等待中...");
        }
        @Override
        public void run() {
            Log.i("task","task-->"+no+"开始执行...");
            SystemClock.sleep(1000);//模拟延时执行的时间
            Log.e("task","task-->"+no+"已经结束...");
        }
    }
}

打印结果

最后的话

这是我第一次写技术文档,有错误的地方还望指正,希望能和大家一起交流提高。以后有空也会定期写一些技术总结,最后,还要感谢以下几位博主,文中主要内容也是从以下几篇文中总结出来然后加上自己的理解和实践最终成型的。

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

推荐阅读更多精彩内容