Android 多线程和线程池

Android 多线程的可以归纳为两种情况:
1、将任务从工作线程抛到主线程;
2、将任务从主线程抛到工作线程;

一、将任务从工作线程抛到主线程

1、Handler#sendXXXMessage 方法

sendXXXMessage 方法共有七个:

public final boolean sendMessage(Message msg)
public final boolean sendMessageDelayed(Message msg, long delayMillis)
public boolean sendMessageAtTime(Message msg, long uptimeMillis)
public final boolean sendEmptyMessage(int what)
public final boolean sendEmptyMessageDelayed(int what, long delayMillis)
public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis)
public final boolean sendMessageAtFrontOfQueue(Message msg)

2、Handler#postRunnable(Runnable)方法

postXXX 系列方法有四个

public final boolean post(Runnable r)
public final boolean postDelayed(Runnable r, long delayMillis)
public final boolean postAtTime(Runnable r, long uptimeMillis)
public final boolean postAtTime(Runnable r, Object token, long uptimeMillis)
public final boolean postAtFrontOfQueue(Runnable r)

其实内部都是调用 getPostMessage 把 Runnable 封装成 Message 对象的 callback 属性,然后调用 sendXXXMessage 系列方法

3、Activity.runOnUIThread(Runnable)方法

public final void runOnUiThread(Runnable action) {
        if (Thread.currentThread() != mUiThread) {
            mHandler.post(action);
        } else {
            action.run();
        }
    }

如果 Activity 在 UI 线程,直接运行该 Runnable 对象的 run 方法,如果不在 UI 线程,通过 Activity 持有的 Handle 对象调用 post 方法

4、View.post(Runnable)方法

public boolean post(Runnable action) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            return attachInfo.mHandler.post(action);
        }

        // Postpone the runnable until we know on which thread it needs to run.
        // Assume that the runnable will be successfully placed after attach.
        getRunQueue().post(action);
        return true;
    }

5、AyscTask

@MainThread
protected void onPreExecute() {}
@MainThread
protected void onPostExecute(Result result) {}
@MainThread
protected void onProgressUpdate(Progress... values) {}

二、将任务从主线程抛到工作线程

1、Thread,Runnable

继承 Thread 或者实现 Runnable 接口

2、AyscTask

@WorkerThread
protected abstract Result doInBackground(Params... params);

3、HandlerThread

该类继承自 Thread,在普通的线程中是没有 Looper 对象的,在该线程的 run 方法中调用 Looper.prepare()Looper.loop() 开启消息循环,这样就允许在 HandlerThread 中创建 Handler 了。

HandleThread#run

一般的使用步骤是:
创建一个 HandlerThread 对象并调用 HandlerThraed#start() 方法启动线程,然后调用 HandlerThread#getLooper() 获取 Looper 对象作为参数创建 Handler 对象

4、IntentService

该类继承自 Service 类,在 onCreate 方法中开启了一个 HandlerThread ,并把该线程的 Looper 对象作为参数创建一个 Handler 对象:


IntentService#onCreate

内部定义了 Handler 的子类 ServiceHandler,在 handleMessage 方法中回调 onHandleIntent 方法,所以在使用 IntentService 时在 onHandleIntent 方法中处理耗时操作


ServiceHandler

三、线程池

什么时候使用线程池
  • 单个任务处理时间比较短
  • 需要处理的任务数量很大
使用线程池的优点:
  • 重用线程池中的线程,避免线程的创建和销毁带来的性能开销。
  • 可以有效控制线程池中的最大并发数,避免大量线程之间互相抢占系统资源导致阻塞;
  • 能够对线程进行简单的管理并提供定时执行、间隔执行等功能。

线程池相关类

  • Executor :Java 中线程池的顶级接口;
  • ExecutorService:真正的线程池接口;
  • ScheduledExecutorService:和Timer/TimerTask类似,解决那些需要任务重复执行的问题;
  • ThreadPoolExecutor:ExecutorService 的默认实现;
  • ScheduledThreadPoolExecutor:继承 ThreadPoolExecutor 的 ScheduledExecutorService 接口实现,周期性任务调度的类实现;
  • Executors:创建一些常见的线程池;

ThreadPoolExecutor

public ThreadPoolExecutor(int corePoolSize,//核心池的大小
                              int maximumPoolSize,//线程池最大线程数
                              long keepAliveTime,//保持时间
                              TimeUnit unit,//时间单位
                              BlockingQueue<Runnable> workQueue,//任务队列
                              ThreadFactory threadFactory,//线程工厂
                              RejectedExecutionHandler handler) //异常的捕捉器
}

相关参数:

  • corePoolSize:核心线程数。提交任务时,如果线程池内的线程数小于 corePoolSize,无论有没有空闲线程都会创建几个新的线程,如果调用了 ThreadPoolExecutor#prestartCoreThread() 线程池会提前创建并开启所有核心线程;
  • maximumPoolSize:最大线程数。在提交任务时,如果任务队列已满,并且线程池内的线程数小于最大线程数,则线程池会创建新的线程;
  • keepAliveTime:线程池维护线程所允许的空闲时间。一般情况下用于非核心线程,只有 ThreadPoolExecutor#allowCoreTheadTimeOut() 设为 true 时才作用于核心线程;
  • unit:超时时间的单位。是一个枚举值,TimeUnit.Days、TimeUnit.HOURS、TimeUnit.MINUTES、TiemUnit.SECONDS、TimeUnit.MILLISECONDS 等;
  • workQueue:等待队列,提交任务时,如果线程池内的线程数大于等于核心线程数,那么会把该任务封装成一个 Worker 对象添加到等待队列;
    该参数是 BlockingQueue 接口的实现类,常见的 BlockingQueue 类有:
  • ArrayBlockingQueue:基于数组的阻塞队列实现,在 ArrayBlockingQueue 内部,维护了一个定长数组,以便缓存队列中的数据对象,这是一个常用的阻塞队列,除了一个定长数组外,
    ArrayBlockingQueue 内部还保存着两个整形变量,分别标识着队列的头部和尾部在数组中的位置;
  • LinkedBlockingQueue:基于链表的阻塞队列,同ArrayListBlockingQueue类似,其内部也维持着一个数据缓冲队列(该队列由一个链表构成);
  • DelayQueue:DelayQueue 中的元素只有当其指定的延迟时间到了,才能够从队列中获取到该元素。DelayQueue 是一个没有大小限制的队列,因此往队列中插入数据的操作(生产者)永远不会被阻塞,而只有获取数据的操作(消费者)才会被阻塞。
  • PriorityBlockingQueue:基于优先级的阻塞队列(优先级的判断通过构造函数传入的 Compator 对象来决定),但需要注意的是
    PriorityBlockingQueue 并不会阻塞数据生产者,而只会在没有可消费的数据时,阻塞数据的消费者。因此使用的时候要特别注意,生产者生产数据的速度绝对不能快于消费者消费数据的速度,否则时间一长,会最终耗尽所有的可用堆内存空间。
  • SynchronousQueue: 一种无缓冲的等待队列;
  • threadFactory:ThreadFactory 类型的变量,用于创建新线程;
  • handler:RejectedExecutionHandler 类型的变量,表示线程池的饱和策略。如果阻塞队列满了并且没有空闲的线程,这时如果继续提交任务,就需要采取一种策略处理该任务。线程池提供了4种策略:
  • AbortPolicy:直接抛出异常,这是默认策略;
  • CallerRunsPolicy:用调用者所在的线程来执行任务;
  • DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;
  • DiscardPolicy:直接丢弃任务;

线程池策略

(1)当 currentSize < corePoolSize 时,直接启动一个核心线程并执行任务。
(2)当 currentSize >= corePoolSize、并且 workQueue 未满时,添加进来的任务会被安排到 workQueue 中等待执行。
(3)当 workQueue 已满,但是 currentSize < maximumPoolSize 时,会立即开启一个非核心线程来执行任务。
(4)当 currentSize >= corePoolSize、workQueue 已满、并且
currentSize > maximumPoolSize 时,调用 handler 默认抛出
RejectExecutionExpection 异常。

主要方法
public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
 
        int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        else if (!addWorker(command, false))
            reject(command);
    }

工作线程数小于 核心线程数,调用 addWorker 方法创建一个新的线程
线程池中的每一个线程被封装成一个 Worker 对象,ThreadPool 维护的其实就是一组 Worker 对象。

public void execute(Runnable command)//提交任务
public void shutdown()//正在执行任务的线程执行完后关闭
public List<Runnable> shutdownNow()//立即关闭大部分线程

Executors

提供了一些静态方法,帮助我们方便的生成一些常用的线程池:
1)newSingleThreadExecutor

newSingleThreadExecutor

创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
2)newFixedThreadPool

newFixedThreadPool

创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
3)newCachedThreadPool

newCachedThreadPool

创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,
那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
4)newScheduledThreadPool

newScheduledThreadPool

创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。
ScheduledThreadPoolExecutor 有一系列的 scheduleXXX 方法:

public ScheduledFuture<?> schedule(Runnable command,long delay,TimeUnit unit)
public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay,TimeUnit unit) 
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnit unit)
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay,TimeUnit unit)

execute 方法和 submit 方法内部都是调用了 schedule 方法

public void execute(Runnable command) {
        schedule(command, 0, NANOSECONDS);
    }
public Future<?> submit(Runnable task) {
        return schedule(task, 0, NANOSECONDS);
    }

public <T> Future<T> submit(Runnable task, T result) {
        return schedule(Executors.callable(task, result), 0, NANOSECONDS);
    }

public <T> Future<T> submit(Callable<T> task) {
        return schedule(task, 0, NANOSECONDS);
    }

线程池管理类

定义一个线程池管理类

public class ThreadManager {

    private ThreadPoolProxy longPool;
    private ThreadPoolProxy shortPool;

    private ThreadManager() {

    }

    private static ThreadManager instance = new ThreadManager();

    public static ThreadManager getInstance() {
        return instance;
    }

    public ThreadPoolProxy createLongThreadPool() {
        if (longPool == null) {
            longPool = new ThreadPoolProxy(5, 5, 5000);
        }
        return longPool;
    }

    public ThreadPoolProxy createShortThreadPool() {
        if (shortPool == null) {
            shortPool = new ThreadPoolProxy(3, 3, 5000);
        }
        return shortPool;
    }


    public class ThreadPoolProxy {
        private ThreadPoolExecutor executor;
        private int corePoolSize;
        private int maximumPoolSize;
        private long time;

        public ThreadPoolProxy(int corePoolSize, int maximumPoolSize, long time) {
            this.corePoolSize = corePoolSize;
            this.maximumPoolSize = maximumPoolSize;
            this.time = time;
        }

        public void execute(Runnable runnable) {
            if (executor == null) {
                executor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, time, TimeUnit.MILLISECONDS,
                        new LinkedBlockingQueue<Runnable>(10));
            }
            executor.execute(runnable);
        }

        public void cancel(Runnable runnable) {
            if (executor != null && !executor.isShutdown() && !executor.isTerminated())
                executor.remove(runnable);
        }
    }
}

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

推荐阅读更多精彩内容