直接使用线程
在Android开发的时候,当我们需要完成一个耗时操作的时候,通常会新建一个子线程出来,例如如下代码
new Thread(new Runnable() { @Override public void run() { //耗时代码 } }).start();
这种方式的线程随处可见,但是这种方式的写法是存在一定问题的,我们知道,在操作系统中,线程是操作系统调度的最小单元,同时线程又不能无限制的产生,并且线程的创建和销毁都会有资源的开销,同时当线程频繁的创建或者销毁的时候,还会让GC频繁的运行,造成程序的卡顿,例如当我们需要网络请求的时候,一定是讲网络请求的代码放到子线程中去运行的,同时如果是ListView中图片的画,采用传统的new
Thread的形式,会在ListView滑动的时候,一下开数十个子线程,程序就会卡顿起来;或者当我们进行下载的时候,通常会指定下载的优先级,优先级高的优先下载,优先级低的会暂停排队,这种需求传统的Thread也是做不到的。那么这就需要用到线程池了。
最后总结一下这种直接使用Thread的缺点
线程池简介
线程池从名字就可以看出,它是用来管理线程的,在线程池中,我们的线程不会被随意的创建出来,它可以缓存一定数量的线程,减少了资源的消耗,同时还可以指定线程的优先级,或者同时需要大量在耗时任务的时候,这些耗时操作是使用FIFO还是LIFO的策略。
Android中的线程池来源于Java,它们主要是通过Executor来派生指定的线程池,使用起来比较方便
总结来说,线程池的有点可以概括为以下三点:
ThreadPoolExecutor
ThreadPoolExecutor是线程池的真正实现,它的构造方法提供了一系列参数来配置线程池,下面是线程池的一个构造方法:
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
我们来看看这几个参数的意义:(以下的所说的任务可以理解为实现Runnable接口的对象)
corePoolSize
它指的是核心线程数,在默认情况下,核心线程会一直存活,即使他们是处于闲置状态的,也就是说在默认状态时,线程池中的核心线程会持续的存在,直至线程池的销毁。而如果配置了allowCoreThreadTimeOut属性为true的话,那么空闲的核心线程也会被销毁了,当它空闲的时间超出了keepAliveTime这个参数规定的时间之后,它就会被销毁掉。
threadPoolExecutor.allowCoreThreadTimeOut(true);
值得注意的是,并不是说只有核心线程才能去执行任务,而是核心线程是最稳定的线程,在默认状态下,它们不会销毁,这样在新的任务需要执行的时候,就会很节省时间,所以核心线程数只需要保证大于0就可以了。
maximumPoolSize
线程池所能容纳的最大线程数,当线程池中的线程达到这个数值,后续的新任务就会被阻塞,无法再添加到线程池中,会被线程池所拒绝,注意最大线程数一定要比核心线程数大的。
KeepAliveTime&TimeUnit
这两个参数用来控制线程的存活时间,默认情况下只会作用于非核心线程,当线程池中的线程处于闲置状态的时间超出了这两个参数所设置的时间之后,线程池就会销毁掉它,
而TimeUnit是时间的单位,它是一个枚举类型,从天到纳秒之间的单位都有的,常用的有MILLISECONDS(毫秒)、SECONDS(秒)和MINUTES(分钟)。值得注意的是,如果配置了线程池的allowCoreThreadTimeOut属性为true的话,那么这两个参数同样会作用于核心线程的。
workQueue
线程池中的任务队列,通过线程池的execute方法来提交的Runnable对象都会存储在这个任务队列中的。
线城池执行任务的规则
当向线程池中提交任务的时候,会满足以下规则:
- 如果线程池中的线程数量没有达到核心线程的数量,那么会直接启动一个核心线程来执行该任务。
- 如果线程池中的线程数量已经达到核心线程数,那么任务就会被插入到任务队列中排队等待执行,当核心线程空闲的时候,就会从任务队列中按照某种规则取出一个任务来执行
- 如果任务队列满了,或者由于其他原因,向线程池提交的任务不能插入到任务队列中的时候,这个时候就会去看线程池中的线程数是否达到线程池的上限,如果没有,就立即开启一个线程并执行。
- 如果线程池中正在工作的线程数已经达到了线程池设置的上限,此时再向线程池中提交任务,线程池就会拒绝执行此任务
关于workQueue我们在接下来会再详细说明的
threadFactory
线程工厂,为线程池创建提供创建新线程的功能。ThreadFactory是一个接口,它只有一个方法
Thread newThread(Runnable r);
RejectedExecutionHandler handler
当线程池无法执行新任务,可能是由于任务队列已满或者其他问题,这时ThreadPoolExecutor就会调用handler的rejectedExecution方法来通知调用者,ThreadPoolExecutor为RejectedExecutionHandler提供了四个可选值:
CallerRunsPolicy
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { if (!e.isShutdown()) { r.run(); } }
使用执行线程池的线程本身来运行该任务,此策略提供简单的反馈控制机制,能减缓新任务的提交速度。
AbortPolicy
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { throw new RejectedExecutionException(); }
这种策略直接抛出异常,丢弃任务
DiscardPolicy
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { }
这种策略也是丢弃任务,不同的是,它并不会抛出异常
DiscardOldestPolicy
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { if (!e.isShutdown()) { e.getQueue().poll(); e.execute(r); } }
抛弃旧的任务,在线程池没有关闭的前提下,首先丢掉缓存在队列中的最早的任务,然后重新尝试运行该任务。并重复此过程,所有使用此策略的时候要额外小心~
以上这些拒绝策略中AbortPolicy是线程池默认的拒绝策略
四类线程池
我们已经对线程池的构造方法中各个参数进行了详细的介绍,我们在来介绍一下Android中最常见的四类具有不同功能的线程池,它们都直接或间接地通过配置ThreadPoolExecutor来实现自己的功能特性,这四类线程池分别是FixedThreadPool、CachedThreadPool、ScheduledThreadPool和SingleThreadExecutor
FixedThreadPool
看看它的构造方法
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
通过它的构造方法可以看出,它是一种线程数量固定的线程池,它的核心线程和最大线程是相等的,即该线程池中的所有线程都是核心线程,所以它也并没有超时机制,而他的任务队列是无边界的任务队列,也就是可以添加无上限的任务,但是都会排队执行
示例:
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3); for (int i = 1; i <= 10; i++) { final int index = i; fixedThreadPool.execute(new Runnable() { @Override public void run() { String threadName = Thread.currentThread().getName(); System.out.println("线程:" + threadName + ",任务:" + index); try { //模拟耗时 Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } }); }
我们创建了一个最大线程和核心线程都是3的一个线程池,然后向其中循环添加10个任务,每一个人只打印当前线程的名字,来看一下效果
可以看到一开始就会执行3个任务,而后面的7个任务都会进入等待状态当核心线程执行完一个之后,就会从队列中按照FIFO的策略取出一个线程进行执行,所以除了前三个任务,剩下的任务是按照顺序执行的
CachedThreadPool
它是通过Executors的newCachedThreadPool方法来创建,实例化方法如下
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
通过它的实例方法可以看出它的核心线程数是0也就是说该线程池并没有核心线程,而它的最大线程数是int类型的的上限,那么我们可以理解为该线程池的最大线程数是没有上限的,也就是说可以无限的创建线程。那么当新任务向线程池中提交的时候,如果有空闲线程,就会把任务放到空闲线程中去,如果没有空闲线程,就会开启一个新的线程来执行此任务,而它的队列SynchronousQueue是一个特殊的队列,在多数情况下,我们可以把它简单的理解为一个无法插入的队列,我们会在之后详细说明的。
代码示例:
ExecutorService cachedThreadPool = Executors.newCachedThreadPool(); for (int i = 1; i <= 10; i++) { final int index = i; try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } cachedThreadPool.execute(new Runnable() { @Override public void run() { String threadName = Thread.currentThread().getName(); System.out.println("线程:" + threadName + ",任务:" + index); try { long time = index * 500; Thread.sleep(time); } catch (InterruptedException e) { e.printStackTrace(); } } }); }
我们这次是在每一次添加的时候都会停止1s的时间,来看看CachedThreadPool的运行情况,并且,每一个任务所执行的时间也不一样,效果如下
可以看出当有存活的空闲线程的时候,任务会放到该空闲线程中去执行例如任务2和任务1就是在同一个线程中去执行的,而如果没有空闲线程的话,新提交的任务就会开启一个新的线程来执行该任务,那么这类线程池比较适合执行大量的耗时少的任务,当线程池处于闲置状态的时候,线程池中的线程都会被销毁,这个时候该线程池几乎是不占用任何系统资源的
ScheduledThreadPool
通过Executors的newScheduledThreadPool方法来创建。实例化代码如下
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) { return new ScheduledThreadPoolExecutor(corePoolSize); }
public ScheduledThreadPoolExecutor(int corePoolSize) { super(corePoolSize, Integer.MAX_VALUE, DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS, new DelayedWorkQueue()); }
可以看出它的核心线程数是固定的,而最大线程数也是int类型的上限,理解为没有限制,它与之前的线程池相区别的就是它的任务队列,DelayedWorkQueue能让任务周期性的执行,也就是说该线程池可以周期性的执行任务。
代码示例:
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3); //延迟2秒后执行该任务 System.out.println("任务开始"); scheduledThreadPool.schedule(new Runnable() { @Override public void run() { String threadName = Thread.currentThread().getName(); System.out.println("任务:" + threadName ); } }, 3, TimeUnit.SECONDS);
我们首先模拟延迟执行任务的代码,在我们提交任务之前先打印一次任务开始,在提交任务的时候,去设置延迟时间为3s,运行一下看看效果
可以看到,我们的程序在执行完开始任务后,经过了3s的时间,才去执行提交的任务,这就是ScheduledThreadPool的延迟功能,而ScheduledThreadPool还可以设置重复不断的执行任务,代码如下
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3); //延迟2秒后执行该任务 System.out.println("任务开始"); //延迟4秒后,每隔1秒执行一次该任务 scheduledThreadPool.scheduleAtFixedRate(new Runnable() { @Override public void run() { System.out.println("执行任务"); } }, 4, 1, TimeUnit.SECONDS);
我们设置了在提交任务时,需要延迟4s才会第一次执行,同时在任务执行完毕后每隔1s又会重复的执行一次该任务,看一下效果
可以看到,我们的线程池实现了该功能,在提交任务的时候,会等待4秒的时间然后开始循环执行任务,每两次执行任务的间隔是1s的时间
SingleThreadExecutor
最后一种线程池,它是只有一个线程的线程池,所有的任务是按照提交顺序排列的,但是它几乎就是凑数的,为什么这么说呢,我们来看一下它的实例化方法:
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); }
通过代码可以看出,这种线程池,就是FixedThreadPool但是实例化方法的参数是1的嘛。
总结:
官方实际上不建议我们使用自己配置的线程池,建议我们使用为我们提供的几种线程池,通常来说,这几种线程池已经可以满足大多数的需求了,使用起来也比较简单
任务队列
通过观察官方为我们提供的几种线程池,我们发现,对于不同类型的线程池来说,决定他们有各种功能的最主要因素就是这个任务队列,而这个任务队列实际上是一个实现了叫BlockingQueue的对象,在这个接口里规定了加入或取出等方法,一共有11个方法,要复写起来非常麻烦,所幸,Java也为我们封装了一些常用的实现类来方便我们的使用,常用的有以下几种
LinkedBlockingQueue:无界的队列
SynchronousQueue:直接提交的队列
DelayedWorkQueue:等待队列
PriorityBlockingQueue:优先级队列
ArrayBlockingQueue:有界的队列
LinkedBlockingQueue&ArrayBlockingQueue
这两个队列很像LinkedList和ArrayList就是一个是用数组实现的,一个使用链表实现的,它们都是FIFO的,而区别是LinkedBlockingQueue可以是没有数量上限的,而根据之间说的任务向现场池中添加的顺序我们知道,如果队列是无上限的话,线程池就不需要非核心线程了,可以看到Java封装好的线程池只要使用这个队列的,它的核心线程数和最大线程数都是一样的。
SynchronousQueue
这个队列会把任务直接提交给线程而不保持它们。在此,如果不存在可用于立即运行任务的线程,则试图把任务加入队列将失败,因此会构造一个新的线程。此策略可以避免在处理可能具有内部依赖性的请求集时出现锁。直接提交通常要求无界 maximumPoolSizes 以避免拒绝新提交的任务。当命令以超过队列所能处理的平均数连续到达时,简单说来,这种队列就是没什么用,走个过场而已,所以使用这个队列的时候,线程池的最大线程数一般是无上限的,也就是int类型的最大值
PriorityBlockingQueue
优先级队列,这种队列在向线程池中提交任务的时候会检测每一个任务的优先级,会先把优先级高的任务扔到线程池中,前几种队列我们都用过了,我们来写一个利用优先级队列的线程池。
首先我们知道我们的任务都是Runnable,我们要想让我们Runnable能够满足优先级队列,那就得让他具有可比性,得让优先级队列知道到底谁的优先级比较高,所以我们自己写一个抽象类来:
public abstract class PriorityRunnable implements Runnable, Comparable<PriorityRunnable> { private int priority; public PriorityRunnable(int priority) { if (priority < 0) { throw new IllegalArgumentException(); } this.priority = priority; } @Override public int compareTo(PriorityRunnable another) { return another.getPriority() - priority; } public int getPriority() { return priority; } }
我们的抽象类最基本的需要实现Runnable接口才能被提交到线程池中,同时我们需要再实现Compareable接口,来告诉队列到底谁大谁小,这里我们自己写了一个int类型的变量代表每一个任务的优先级,然后复写compareTo方法来写我们的比较条件,这里要注意的是,当我们自己去写的时候,不一定非要指定一个int类型的变量,也可以是其他的例如String等的,只要实现了Comparable接口就可以了。接下来我们就可以使用这个任务了,代码如下
ExecutorService priorityThreadPool = new ThreadPoolExecutor(3, 3, 0L, TimeUnit.MILLISECONDS, new PriorityBlockingQueue<Runnable>()); for (int i = 1; i <= 10; i++) { final int priority = i; priorityThreadPool.execute(new PriorityRunnable(priority) { @Override public void run() { String threadName = Thread.currentThread().getName(); System.out.println("线程:" + threadName + ",正在执行优先级为:" + priority + "的任务"); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } }); }
我们创建了一个核心线程为3的使用优先级队列的线程池,向线程池中添加10个优先级不同的任务,来看看效果
当我们运行的时候,前三个任务是没有进入队列的,至接进入到线程池的核心线程开始干活了,之后的7个任务都会进入到优先级队列,通过比较,再进入线程池工作的时候,就会让线程按照我们设定好的优先级顺序执行,优先级高的任务会先执行任务,如果我们让每一个提交的任务都加入一个当前的时间,就可以完成类似LIFO的功能啦~
线程池的其他方法
除了以上功能外,线程池还给我们提供了3个方法,分别是:
beforeExecute() : 任务执行前执行的方法
afterExecute() :任务执行结束后执行的方法
terminated() :线程池关闭后执行的方法
而这三个方法要想使用的话,需要我们自定义一个类来继承自ThreadPoolExecutor,然后复写这三个方法
public class MyThreadPool extends ThreadPoolExecutor { public MyThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) { super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue); } @Override protected void beforeExecute(Thread t, Runnable r) { super.beforeExecute(t, r); //在执行任务之前 } @Override protected void afterExecute(Runnable r, Throwable t) { super.afterExecute(r, t); //在执行任务之后 } @Override protected void terminated() { super.terminated(); //线程池关闭 } }
类似这样,就可以在线程池执行任务之前和之后,都很方便的加上我们的功能
信号量
在使用线程池的时候,任务的提交方式主要是由任务队列决定的,但是任务队列里的方法比较多,复写起来比较麻烦,而且官方也建议我们使用它提供给我们的几种线程池,有时我们的队列需要动态的调整FIFO和LIFO的策略,或者我们提交的策略非常复杂,系统默认的满足不了我们怎么办,通常我们的做法是自己完全重新写一个任务队列,并不实现线程池的接口,也不作为任务队列参数放到线程池中,而我们所有的任务都会先提交到我们自己定义的这个队列中来,然后当线程池空闲的时候再提交到线程池中去。那么问题来了,我们怎么知道线程池中是否有空闲的线程呢?这就要使用Semaphore
信号量了
什么是信号量
那么什么是信号量呢?想像商场的停车场,假设有3个车位,但是一共有6台车想要停在停车场,那么在停车场的门口就会有一个电子显示屏告诉新来的车辆,还有多少的空的车位,而如果停车场已经满了,新来的车要想停在这个商场的停车场就需要在门口等待,知道显示屏显示又有车位的,而信号量就相当于停车场的电子显示屏,它可以确定有多少个位置,如果没有位置了,线程会卡在那一直的等待。
下面用代码实验一下:
//只有一个位置的信号量,相当于停车场只有一个位置 final Semaphore semp = new Semaphore(2); for(int i = 0;i<4;i++){ new Thread(new Runnable() { @Override public void run() { String threadName = Thread.currentThread().getName(); try { //申请信号量 semp.acquire(); System.out.println(threadName+"申请到了信号量"); Thread.sleep(2000); //释放信号量 semp.release(); System.out.println(threadName+"释放了了信号量"); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); }
首先我们定义了一个只有2个位置的信号量,然后循环开启了4个线程,而这4个线程可以理解为几乎是在同一时间开启的,当线程开始的时候会尝试申请信号量,如果申请成功就开始模拟一个耗时操作,在操作完成后,再释放一次信号量,我们来看一看这几个线程的运行情况
可以看到前两个线程成功申请到了信号量,开始执行耗时操作,后两个线程的代码就会停在申请信号量的这一步,直到有新的信号量的放出,代码会继续执行,也就是说必须等到前两个线程完成了任务之后,后面的线程才会继续运行,是不是和2个核心线程的线程池很像啊~
自定义可以FIFO和LIFO的线程池
掌握了信号量的基本使用,我们就可以尝试自定义一个可以动态切换FIFO和LIFO的线程池了,首先看一下效果
可以看到 我们点击FIFO的时候,会从0一直到99,而点击LIFO的时候,线程瞬间就从后向前执行了,非常方便,我们来看看代码,首先是布局文件
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" android:orientation="vertical" tools:context="com.lanou.chenfengyao.myapplication.MainActivity"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal"> <Button android:id="@+id/btn_FIFO" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="FIFO"/> <Button android:id="@+id/btn_LIFO" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="LIFO"/> </LinearLayout> <TextView android:id="@+id/main_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!" /> </LinearLayout>
这个不用多说,就是2个Button来控制线程池的,一个TextView用来显示当前的线程的
接下来是MainActivity:
public class MainActivity extends AppCompatActivity implements View.OnClickListener { private Button FIFOBtn, LIFOBtn; private TextView mainTv; private MyThreadPool myThreadPool; private Handler handler; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); FIFOBtn = (Button) findViewById(R.id.btn_FIFO); LIFOBtn = (Button) findViewById(R.id.btn_LIFO); mainTv = (TextView) findViewById(R.id.main_text); FIFOBtn.setOnClickListener(this); LIFOBtn.setOnClickListener(this); handler = new Handler(new Handler.Callback() { @Override public boolean handleMessage(Message msg) { //设置给TextView; mainTv.setText("任务:"+msg.what); return false; } }); myThreadPool = new MyThreadPool(1); for (int i = 0; i < 100; i++) { final int index = i; myThreadPool.execute(new Runnable() { @Override public void run() { handler.sendEmptyMessage(index); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }); } } @Override public void onClick(View v) { switch (v.getId()) { case R.id.btn_FIFO: myThreadPool.setWay(MyThreadPool.OutWay.FIFO); break; case R.id.btn_LIFO: myThreadPool.setWay(MyThreadPool.OutWay.LIFO); break; } } }
我直接在onCreate方法里创建了我自定义的一个线程池,然后向里面添加了100个任务,而这个线程池的核心线程和最大线程我都设置成了1个,每一个任务也就是把自己任务号通过handler发送给主线程,然后显示到TextView上,在按钮的监听里,调用自定义线程池的setWay方法,把FIFO还是LIFO的信息设置上,那么关键的代码就在我们自定义的线程池里了,我们来看一下:
package com.lanou.chenfengyao.myapplication; import java.util.ArrayList; import java.util.List; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.Semaphore; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; /** * Created by ChenFengYao on 16/5/17. */ public class MyThreadPool extends ThreadPoolExecutor { private volatile Semaphore semaphore; private List<Runnable> runnableList; private LoopThread loopThread; private boolean flag; //两种策略,先进先出和先进后出 enum OutWay { FIFO, LIFO } private OutWay outWay; public MyThreadPool(int corePoolSize) { super(corePoolSize, corePoolSize, 0l, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); semaphore = new Semaphore(corePoolSize); runnableList = new LinkedList<>(); flag = true; outWay = OutWay.FIFO;//默认是先进先出 loopThread = new LoopThread(); loopThread.start(); } //提交任务的方法 @Override public synchronized void execute(Runnable command) { //所有来的任务是提交到我们自己的任务队列中 runnableList.add(command); if (runnableList.size() < 2) { //如果这是队列中的第一个任务,那么就去唤醒轮询线程 synchronized (loopThread) { loopThread.notify(); } } } //设置是FIFO/LIFO public void setWay(OutWay outWay) { this.outWay = outWay; } @Override protected void afterExecute(Runnable r, Throwable t) { super.afterExecute(r, t); //任务完成释放信号量 semaphore.release(); } @Override protected void terminated() { super.terminated(); flag = false;//轮询线程关闭 } class LoopThread extends Thread { @Override public void run() { super.run(); while (flag) { if (runnableList.size() == 0) { try { //如果没有任务,轮询线程就等待 synchronized (this) { wait(); } } catch (InterruptedException e) { e.printStackTrace(); } } else { try { //请求信号量 semaphore.acquire(); int index = runnableList.size(); switch (outWay) { case FIFO: //先进先出 index = 0; break; case LIFO: //先进后出 index = runnableList.size() - 1; break; } //调用父类的添加方法,将任务添加到线程池中 MyThreadPool.super.execute(runnableList.get(index)); runnableList.remove(index); } catch (InterruptedException e) { e.printStackTrace(); } } } } } }
可以看到,这个自定义的线程池的核心线程和最大线程都是一样的,通过构造方法传进来的,这里是1,同时,我们将我们信号量的数值也设置为核心线程数,并且我们在内部有一个List用来存放我们提交的任务,我将线程池父类的execute方法复写了,这里不再向线程池内提交,而是存放到我们自己的RunnableList里。
同时我们开启了一个轮询线程,这个线程的作用就是把RunnableList里的Runnable对象提交到线程池内,首先如果RunnableList里没有任务的话,该线程就wait,一直等到提交任务了再将其唤醒,如果有任务的话,首先尝试拿到一个信号量,也就是看看线程池内是否有空闲的线程可以工作,如果有的话,就根据是FIFO还是LIFO从任务队列中拿一个任务提交给线程池,注意这里需要调用父类execute方法哦,一直重复这样的循环。
那么什么时候释放信号量呢?自然是任务执行完毕的时候啦,我们复写了线程池的afterExecute方法,当线程池内有一个任务完成后,就会回调该方法,在这个方法里,我们释放一次信号量,那么轮询线程就会继续向线程池内提交一个任务
这样,能控制任务的提交方式的线程池就写好啦~
关闭线程池
最后线程池在使用过后,通常是项目整个退出之后,是需要关闭的,关闭线程池有两个方法shutDown和shutDownNow,它们的区别是shutDown会让线程池内的任务完成后关闭,而shutDownNow会放弃掉还没完成的任务立即关闭。
最后
在使用线程池的时候,我们确定线程池核心线程数的时候通常会根据CPU的核心数来确定的,通常会使用CPU核心数+1来定为当前线程池的核心线程数,在Android中的CPU核心数可以通过
Runtime.getRuntime().availableProcessors();
来获得