Android的线程池了解一下?

众所周知,Android中的主线程就是我们俗称的UI线程,我们只能在主线程中操作UI,并且我们会避免在主线程中进行耗时操作,这样会造成主线程阻塞引起ANR,所以我们会把耗时操作(例如流的读写,下载文件等)放入我们新开辟的线程中进行。
我不由得汪汪大笑..啊不,哈哈大笑,这还不简单?请把我尊贵的Filco键盘给我,秀操作的时候到了,于是我敲下了这段大家都熟知的代码~

new Thread(new Runnable() {
            @Override
            public void run() {
                //你可以在这里尽情的做你爱做的事(操作UI除外)
            }
        }).start();

咱们这么写确实可以达到我们想要的目的,但是这么写是有弊端的,且听我慢慢BB出来:

首先我们这么写意味着我们使用了匿名内部类,那就代表着我们没办法重用,也就是说我们这里新建的这个线程只能完成我们这里所指定的任务然后被销毁回收,这样对程序性能也有影响。我们下次还想进行耗时操作的时候又得开启新线程,这意味着我们不断的在创建新线程对象和销毁它,假如我们现在有1000个任务要在子线程中执行,难道我们要循环创建1000次?1000个线程那得占用很大的系统资源,搞不好会OOM哦~

好了,那么我们这里就可以引入线程池的概念了。

什么是线程池?

顾名思义线程池就是一个池子里全是线程,啊对不起对不起...再也不敢了,这段去掉。

线程池是指在初始化一个多线程应用程序过程中创建一个线程集合,然后在需要执行新的任务时重用这些线程而不是新建一个线程。线程池中线程的数量通常完全取决于可用内存数量和应用程序的需求。然而,增加可用线程数量是可能的。线程池中的每个线程都有被分配一个任务,一旦任务已经完成了,线程回到池子中并等待下一次分配任务。

看了这段你懂了吗?我们可以这么理解,有这么一个集合,里边全是线程,不同类型的线程池会有不同的工作模式而已。

接下来我们来介绍下常用的4个线程池。

四种常用的线程池

fixedThreadPool

使用线程池中的fixedThreadPool,这种线程池的特点是在你创建这个线程池时会指定一个最大线程数,
每提交一个任务就会新创建一个线程直到达到最大线程数,如果已经达到了最大线程数的话就会将新提交的任务缓存进线程池队列中等待空闲线程
注意:当所有的任务都完成即线程空闲时,这里的所有线程并不会自己释放,会占用一定的系统资源,除非这整个线程池被关闭(即fixedThreadPool.shutdown();)

先来搞个例子

ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);//这里指定最大线程数为3
        for (int i = 0; i < 10; i++) {
            final int index = i;
            fixedThreadPool.execute(new Runnable() {
                public void run() {
                    try {
                        Log.e("test", "线程编号:" + Thread.currentThread().getId() + "打印数字" + index);
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }

运行结果

08-01 14:07:13.751 21958-22058/com.example.android_1.testdemo E/test: 线程编号:3958打印数字1
08-01 14:07:13.751 21958-22059/com.example.android_1.testdemo E/test: 线程编号:3959打印数字2
08-01 14:07:13.751 21958-22057/com.example.android_1.testdemo E/test: 线程编号:3957打印数字0
08-01 14:07:15.753 21958-22059/com.example.android_1.testdemo E/test: 线程编号:3959打印数字4
08-01 14:07:15.753 21958-22058/com.example.android_1.testdemo E/test: 线程编号:3958打印数字3
08-01 14:07:15.754 21958-22057/com.example.android_1.testdemo E/test: 线程编号:3957打印数字5
08-01 14:07:17.757 21958-22059/com.example.android_1.testdemo E/test: 线程编号:3959打印数字7
08-01 14:07:17.757 21958-22057/com.example.android_1.testdemo E/test: 线程编号:3957打印数字6
08-01 14:07:17.757 21958-22058/com.example.android_1.testdemo E/test: 线程编号:3958打印数字8
08-01 14:07:19.758 21958-22057/com.example.android_1.testdemo E/test: 线程编号:3957打印数字9

看是不是至始至終都只有3个线程?

cachedThreadPool

ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
cachedThreadPool.execute(new Runnable() {
            @Override
            public void run() {
                
            }
        });

这种线程池会根据需要,在线程可用时,重用之前构造好的池中线程。这个线程池在执行 大量短生命周期的异步任务时(many short-lived asynchronous task),可以显著提高程序性能。调用 execute 时,可以重用之前已构造的可用线程,如果不存在可用线程,那么会重新创建一个新的线程并将其加入到线程池中。如果线程超过 60 秒还未被使用,就会被中止并从缓存中移除。因此,线程池在长时间空闲后不会消耗任何资源。这个线程中是没有限制线程数量的(其实还是有限制的,只不过数量为Interger. MAX_VALUE)

singleThreadExecutor

这种线程池会使用单个工作线程来执行一个无边界的队列。(注意,如果单个线程在执行过程中因为某些错误中止,新的线程会替代它执行后续线程)。它可以保证认为是按顺序执行的,任何时候都不会有多于一个的任务处于活动状态。和 newFixedThreadPool(1) 的区别在于,如果线程遇到错误中止,它是无法使用替代线程的。

通俗的来讲你可以认为你所提交的任务就是在排队,按顺序来,我们敲个例子验证一下~

ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
        Runnable runnable0=new Runnable() {
            @Override
            public void run() {
                try {
                    Log.e("test", "runnable0,线程ID"+Thread.currentThread().getId());
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        Runnable runnable1=new Runnable() {
            @Override
            public void run() {
                try {
                    Log.e("test", "runnable1,线程ID"+Thread.currentThread().getId());
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        Runnable runnable2=new Runnable() {
            @Override
            public void run() {
                try {
                    Log.e("test", "runnable2,线程ID"+Thread.currentThread().getId());
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };

        singleThreadExecutor.execute(runnable0);
        singleThreadExecutor.execute(runnable1);
        singleThreadExecutor.execute(runnable2);
        //特意设置了时间差异

看看运行结果~

08-01 16:24:42.131 575-1082/com.example.android_1.testdemo E/test: runnable0,线程ID4124
08-01 16:24:45.134 575-1082/com.example.android_1.testdemo E/test: runnable1,线程ID4124
08-01 16:24:47.135 575-1082/com.example.android_1.testdemo E/test: runnable2,线程ID4124

可以看到是我们的预期结果~

newScheduledThreadPool

这种线程池也可以指定池中存在的最大线程数,而且这种线程池能够延时以及轮询方式执行任务,这种线程池有这几种核心方法,下面我们来一一看一番~

schedule(Runnable command, long delay, TimeUnit unit)

我们直接看代码以及注释

//延时处理Runnable任务
//参数:schedule(需要执行的任务Runnable,延时时间长度,延时时间单位)
//例如下面这段代码的意思就是延时3秒执行Runnable任务

        scheduledExecutorService.schedule(new Runnable() {
            @Override
            public void run() {

            }
        }, 3, TimeUnit.SECONDS);
schedule(Callable<V> callable, long delay, TimeUnit unit)

这个和上边那个差不多,只不过一个是Runnable任务,一个是Callable任务
//延时处理Callable任务
//参数:schedule(需要执行的任务Runnable,延时时间长度,延时时间单位)
//例如下面这段代码的意思就是延时3秒执行Callable任务

scheduledExecutorService.schedule(new Callable<Object>() {
            @Override
            public Object call() {
                return null;
            }
        }, 3, TimeUnit.SECONDS);
scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)

这个方法就是类似于轮询,先来看一下参数的意思
scheduleAtFixedRate(需要执行的Runnable任务,第一次执行任务所需要延迟的时间, 每一次开始执行任务后与下一次执行任务的延迟时间, 延时时间单位)

像下面这段代码的意思就是延时3秒执行第一次任务,从任务开始的时候计时,过了5秒再执行下一次任务。

ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);

        String date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(System.currentTimeMillis()));
        Log.e("test", "开始执行:" + date);
        scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                    String date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(System.currentTimeMillis()));
                    Log.e("test", date);
            }
        }, 3, 5, TimeUnit.SECONDS);

我们来看下运行结果

08-02 11:40:50.880 22738-22738/com.example.android_1.testdemo E/test: 开始执行:2018-08-02 11:40:50
08-02 11:40:53.887 22738-22986/com.example.android_1.testdemo E/test: 2018-08-02 11:40:53
08-02 11:40:58.894 22738-22986/com.example.android_1.testdemo E/test: 2018-08-02 11:40:58
08-02 11:41:03.889 22738-22991/com.example.android_1.testdemo E/test: 2018-08-02 11:41:03
08-02 11:41:08.891 22738-22986/com.example.android_1.testdemo E/test: 2018-08-02 11:41:08

从结果我们可以看出,确实如我们所料,但是还有另外一种情况,如果我要执行的任务所需要的时间大于间隔时间怎么办?这个方法的做法是等前一个任务执行完了再继续下一个任务,也就是说,时间间隔会与我们指定的时间不一样,我们写段代码验证一下~

ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);
        //

        String date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(System.currentTimeMillis()));
        Log.e("test", "开始执行:" + date);
        scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                try {
                    String date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(System.currentTimeMillis()));
                    Log.e("test", date);
                    Thread.sleep(6000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, 3, 5, TimeUnit.SECONDS);

这段代码我每个任务需要6s的执行时间,大于5秒的间隔时间,来看看结果如何吧~

08-02 11:44:55.473 23286-23286/com.example.android_1.testdemo E/test: 开始执行:2018-08-02 11:44:55
08-02 11:44:58.480 23286-23422/com.example.android_1.testdemo E/test: 2018-08-02 11:44:58
08-02 11:45:04.499 23286-23422/com.example.android_1.testdemo E/test: 2018-08-02 11:45:04
08-02 11:45:10.505 23286-23435/com.example.android_1.testdemo E/test: 2018-08-02 11:45:10
08-02 11:45:16.516 23286-23422/com.example.android_1.testdemo E/test: 2018-08-02 11:45:16

从结果我们可以看出...时间间隔变成了6秒,那是因为我们指定的5秒间隔过后,系统发现上一个任务还没执行完成,所以等上一个执行完成了再继续执行下一个任务,所以变成了6秒~
但是如果发现已经执行完了就会按我们规定的时间间隔来。

scheduleWithFixedDelay`(Runnable command, long initialDelay, long period, TimeUnit unit)

这个方法的参数意义和上一个是一样的,不同之处在于这个方法是每次任务执行结束之后再去计算延时,而不是每次开始就计时,我们来写段代码验证一下~


ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);
        //

        String date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(System.currentTimeMillis()));
        Log.e("test", "开始执行:" + date);
        scheduledExecutorService.scheduleWithFixedDelay(new Runnable() {
            @Override
            public void run() {
                try {
                    String date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(System.currentTimeMillis()));
                    Log.e("test", date);
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, 3, 5, TimeUnit.SECONDS);

看一下运行结果~

08-02 11:48:40.409 23741-23741/com.example.android_1.testdemo E/test: 开始执行:2018-08-02 11:48:40
08-02 11:48:43.416 23741-23862/com.example.android_1.testdemo E/test: 2018-08-02 11:48:43
08-02 11:48:51.430 23741-23862/com.example.android_1.testdemo E/test: 2018-08-02 11:48:51
08-02 11:48:59.444 23741-23865/com.example.android_1.testdemo E/test: 2018-08-02 11:48:59
08-02 11:49:07.450 23741-23862/com.example.android_1.testdemo E/test: 2018-08-02 11:49:07
08-02 11:49:15.456 23741-23873/com.example.android_1.testdemo E/test: 2018-08-02 11:49:15

看,是不是间隔时间变成了任务执行时间+指定间隔时间啦?这就和我们想的完全一样~

这里小结一下:
scheduleAtFixedRate ,是以上一个任务开始的时间计时,period(即第三个参数)时间过去后,检测上一个任务是否执行完毕,如果上一个任务执行完毕,则当前任务立即执行,如果上一个任务没有执行完毕,则需要等上一个任务执行完毕后立即执行。
scheduleWithFixedDelay,是以上一个任务结束时开始计时,period(即第三个参数)时间过去后,立即执行。

最后

这就是我们比较常用的四大线程池,我们平时开发的时候可以根据应用场景不同而选择不同类型的线程池来使用嘻嘻

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

推荐阅读更多精彩内容