Java线程基础-线程和线程池的介绍(主要是Android部分)
第一次写博客,想想还是有点小激动。由于是新手文中可能有错误,欢迎各位批评指正。如果对您产生了什么误导,先说一声抱歉。
1、 线程概念
- 线程是操作系统中的最小的调度单元
- 同时线程又是一种受限制的系统资源,即线程不可能无限制的产生
- 线程的创建和销毁都有消耗系统的资源
- 系统存在大量的线程时,系统会通过时间片轮转的方式调度每一个线程,因此线程不可能做到绝对的并行。除非线程数小于CPU的核心数
- 如果在一个进程当中,频繁的创建和销毁线程,这显然不是高效的做法,正确的做法是:使用线程池,下文会介绍常用的线程池
2 、Android中常用线程的种类
- Android 线程分类:主线程(不能进行耗时操作),子线程(又称作工作线程)
2.1、Thread(这个是最常用的就不介绍了)
2.2、AsyncTask异步请求框架
他的底层用到了线程池,他底层封装了线程池和Handler,主要为了方便开发者在子线程中更新UI,但是并不适合进行特别耗时的后台任务,对于特别耗时的后台任务来说,建议使用线程池
轻量级的异步任务类
三个泛型参数(Params 、Progress、Result) Params表示参数的类型 Progress表示后台任务执行的速度类型 Result 表示返回结果的类型
四个核心方法
1) onPreExcute 主线程中执行,异步任务开始前被执行,可以做一些初始化工作
2) doInBackground(Params… params) 在此方法中可以通过PublishProgress 方法来更新任务进度 会调用下面的方法
3) onProgressUpdate(Progress… values) 主线程中执行 后台任务 发生改变的时候调用
4)onPostExecute(Result result) 主线程中执行result 是后台任务的返回值
5)onCancel() 方法取消异步任务限制
- 这个类必须在主线程中加载
- execute必须在主线程中执行
- 不在在程序中调用那四个方法
- 只执行一次,只能调用一个execute方法
AsyncTask内部有一个队列ArrayDeque<>,保证其串行的机制,同时AsyncTask内部线程实现的是callable接口,下一篇将
分析AsyncTask的源码
2.3、IntentService
由于服务不能进行耗时操作,所以系统对其封装来执行后台任务
- 是一个服务,系统对其封装来执行后台任务,
- 是一个特殊的额服务,它继承了Service并且它是一个抽象类,因此必须创建子类才能使用,他可以执行后台的耗时任务,人去完毕他就会停止,因为它是服务所以,它的优先级比单纯的线程要高很多,比较适合执行一些高优先级的任务,因为它不易被后台杀死。
2.4、HandlerThread
- 继承了Thread,它是一种使用Handler的Thread 它的实现很简单就是在Run方法中通过Looper.prepare()来创建消息队列,并且通过,Lopper.loop()来开启消息循环,这样实际使用中就允许在HandlerThread 中创建Handler HandlerThread的Run方法如下
- 主要代码
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
3、线程池的介绍(这里只做简单的介绍和说明)
3.1 线程池的3大优点
- 重用线程池中的线程,避免因为创建和销毁线程带来的开销
- 能有效的控制线程池的最大开销数,避免大量的线程之间因相互抢占资源而导致的阻塞现象
- 能够对线程进行简单的管理,并提供定时执行以及指定间隔循环执行等功能
3.2 ThreadPoolExecutor
ThreadPoolExecutor 继承自AbstractExecutorService类,而AbstractExecutorService类实现了ExecutorService接口。ExecutorService接口继承了Executor接口,Executor接口里面定义了一个线程池执行的方法 * void execute(Runnable var1); *,有兴趣的同学可以去看看源码。
3.2.1 ThreadPoolExecutor的构造方法如下
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {}
参数详解
- CorePoolSize 线程池的核心线程数,默认的情况下 核心线程会在线程池里面一直存活。即使他们处于闲置状态,如果将ThreadPoolExecutor的allowCoreThreadTimeOut的属性设置为true 那么闲置的核心线程在等待新任务开始时会有超时策略,这个时间由KeepALiveTime所指定,当等待的时间超过KeepAliveTime指定的时间后 核心线程将会被终止
- maxmunmPoolSize 线程池所能容纳的最大线程数,活动线程达到这个数值后,后续的新任务将会被阻塞
- keepAliveTime 非核心线程闲置时候 的超时时长,超过这个时长,非核心线程就会被回收,当ThreadPoolExecutor的allowCoreThreadTimeOut的属性设置为true 时,他同样可以用于核心线程
- unit 用于指定keepAliveTime 参数的时间单位 是一个枚举 常用的有秒 毫秒 分钟等
- workQueue 线程池中队列 通过线程池的execute方法提交的Runnable对象会存储在这个参数中
- threadFactory 线程工厂 ,为线程提供创建新的线程功能,是一个接口 只有一个方法Thread newThread(Runnable r)
执行时候遵循的规则
- 1、如果线程池中的线程数量未达到核心线程的数量,那么会直接启动一个核心线程
- 2、如果线程池中的线程数量已经达到或者超过核心线程的数量,那么任务会被插入到任务队列中排队执行
- 3、如果步骤2中无法将任务插入到任务队列中,这往往是由于任务队列已满,这个时候如果线程数量未达到线程池规定的最大值,那么将会立即启动一个非核心的线程来执行任务。
- 4、如果步骤3中线程的数量已经达到线程池规定的最大值,那么久拒绝执行任务ThreadPoolExecutor就会调用 RejectedExecutionHandler 的rejectedExecution方法来通知调用者
3.3 线程池的分类
Java工程师为我们提供了一个工厂类Executors,这个类用的是简单工厂模式,方便我们创建线程池。这个类中的设计模式比较简单但是很常用,不了解的同学可以去了解一下。下面介绍几种常见的线程池;
3.3.1 线程池的分类
- FixedThreadPool
创建的方法
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
通过Executors的newFIxedThreadPool方法来创建,他是一种线程数量固定的线程池,当线程处于空闲状态的时候,他们并不会被回收,除非是关闭。所有的线程都处于活动状态时候,新任务会处于等大的状态,知道有线程空闲出来,有这个线程池只有核心的线程 并且不会被回收,这就意味着可以更加迅速的响应外界的请求,创建如下 ,没有超时机制,任务队列的大小限制
- CacheThreadPool
创建的方法
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
通过Executor 的newCacheThreadPool方法创建,他是一种线程数量不固定的线程池,只有非核心线程,并且最大的线程数是Integer.MAX_VALUE,由于Integer.MAX_VALUE是一个很大的数,想大于线程可以无限多个,当线程池中的线程都处于活动状态时候,线程池会创建新的线程来处理新任务,否则就会利用空闲的线程来处理新任务,线程池中的空闲线程都有超时机制,这个时间不超过60秒 超过这个事件将会被回收,和上面的FixedThreadPool不同的是 CacheThreadPool相当于一个空集合这会导致任何的任务都不会立即执行。因为在这种场景下面SynchronousQueue 是无法插入任务的,SynchronousQueue是一个非常特殊的队列很多情况下把他简单的理解为无法存储元素的队列 由于它在实际中较少使用,这里就不讨论了,CacheThreadPool的特性来看 这类线程比较适合执行大量的耗时较少的任务,当这个线程池都处于闲置状态时候,所有线程都会停止,这个时候几乎不占用资源
- ScheduledThreadPool
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
- 通过Executor 的new ScheduledThreadPool方法创建核心线程数量是固定的,非核心数量是不固定的,非核心闲置会被回收
- SingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
- 通过Executor 的new SingleThreadExecutor 方法创建,这类线程池内部只有一个核心线程,它确保所有的任务都在同一个线程中按顺序执行,SingleThreadExecutor的意义是在于所有的外界任务到一个线程中这使得在这些任务之间不需要同步问题
总结:
ExecutorService ex = Executors.newFixedThreadPool(1);
ex.execute(new Runnable(){...});
ex.shutdown();
创建一个核心线程为1的线程池。将任务传入执行。执行结束要调用shutdown();方法。
下一篇将为您带来Async的源码解析,敬请关注。