首页 > 代码库 > Android中使用多线程的各种姿势
Android中使用多线程的各种姿势
写在前面:内容主要为黄岳钊老师视频分享课的学习笔记。
1)为什么需要多线程处理?
解决耗时任务
文件IO、联网请求、数据库操作、RPC提高并发能力
同一时间处理更多事情防止ANR
InputDispatching Timeout:输入事件分发超时5s(触摸或按键)
Service Timeout:服务20s内未执行完
BroadcastQueue Timeout:前台广播10s内未执行完
ContentProvider Timeout:内容提供者执行超时避免掉帧
要达到每秒60帧,每帧必须16ms处理完
2)使用多线程的几种姿势
- Thread
① new Thread,重载run方法;
② 实现Runable接口,作为参数传给Thread。
public static void main(String[] args){
//Android中 UI线程的Id恒定为1
System.out.println("UI Thread Id : "+Thread.currentThread().getId());
new Thread(){
@Override
public void run() {
// TODO: 2016/12/2
System.out.println("run in thread "+Thread.currentThread().getId());
}
}.start();
Runnable runnable = new Runnable() {
@Override
public void run() {
// TODO: 2016/12/2
System.out.println("run in thread "+Thread.currentThread().getId());
}
};
new Thread(runnable).start();
}
- AsyncTask
android特有的轻量级异步任务类,它可以在线程池中执行后台任务,然后把执行进度和结果传递给主线程中更新UI。但不适合特别耗时的后台任务。
(android特有,为便于更新UI,由google推出)
public class MyTask extends AsyncTask<String,Integer,String> {
String TAG = "haha";
@Override
protected void onPreExecute() {
// TODO: 2016/12/2
Log.d(TAG,"onPreExecute run in thread "+Thread.currentThread().getId());
}
//只有这个方法在后台执行,其他方法都在UI线程
@Override
protected String doInBackground(String... params) {
// TODO: 2016/12/2
Log.d(TAG,"doInBackground run in thread "+Thread.currentThread().getId());
//传入的参数和execute中传入参数相同
Log.d(TAG,"input param "+params[0]);
//此方法将回调onProgressUpdate 用于进度更新
publishProgress(5);
//此return的信息将传递到onPostExecute
return "task done!";
}
@Override
protected void onProgressUpdate(Integer... values) {
// TODO: 2016/12/2
Log.d(TAG,"onProgressUpdate run in thread "+Thread.currentThread().getId());
Log.d(TAG,"update progress "+values[0]);
}
@Override
protected void onPostExecute(String s) {
// TODO: 2016/12/2
Log.d(TAG,"onPostExecute run in thread "+Thread.currentThread().getId());
Log.d(TAG,"onPostExecute input "+s);
}
}
分析:AsyncTask只有doInBackground 在子线程中执行,其他都是在UI线程。
使用注意点:
1)想象一种情况:在一个Activity页面,如果发起了AsyncTask任务,然后页面离开/销毁了,此时如果doInBackground没执行完,会有什么问题?
首先,AsyncTask白白消耗资源,结果已经用不上了,因为UI也不在;2)那么如何优雅的终止AsyncTask呢?
鉴于以上的问题,一般我们要在Activity onDestory的时候cancel掉AsyncTask任务。3)关于cancel()方法
cancel()方法并非是直接停止Asynctask后台线程,而是发送一个停止线程的状态位,
因而需要在doInBackground 不断的检查此状态位。
如:if(isCancelled()) return null; // Task被取消了,马上退出
- HandlerThread
HandlerThread实际上是一个带有Looper的Thread,从而可向子线程传递消息。
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//HandlerThread 其实就是一个带有Looper的Thread
HandlerThread handlerThread = new HandlerThread("handler-thread");
handlerThread.start();
Log.d(TAG,"handlerthread id :"+handlerThread.getId());
//用handlerThread的looper生成handler 用于向子线程中发送消息、runnable 等
Handler handler = new Handler(handlerThread.getLooper()){
@Override
public void handleMessage(Message msg) {
Log.d(TAG,"handleMessage received in thread "+Thread.currentThread().getId());
Log.d(TAG,"received message is "+msg.obj);
}
};//必须先start thread
Runnable runnable = new Runnable() {
@Override
public void run() {
Log.d(TAG,"this runnable run in thread "+Thread.currentThread().getId());
}
};
//使runnable在子线程(即handlerThread线程执行)
handler.post(runnable);
//此消息发送至handlerThread线程
Message message = new Message();
message.obj = "test message";
handler.sendMessage(message);
}
- ExecutorService
首先来看下java.util.concurrent包下关于并发编程的几个重要类和接口:Executor,Executors、ExecutorService。
Executor(执行器):
如下,是一个接口,提供了execute()方法,用于执行任务,而不用显式的创建线程(使用其内部的线程池来完成操作)。
其最常用的子接口ExecutorService,扩展了shutdown()、submit()等方法,用于管理终止任务。
ExecutorService:
shutdown:完成已提交的任务后(调用后不能再提交新任务,且可以等待已提交任务执行完),关闭ExecutorService以回收资源。
submit:返回Future对象,用于取消执行或等待完成。
invokeAll:用于批量执行。
Executors:
主要用于提供线程池相关的操作,返回Executor执行器。
再来看几种不同的线程池:FixedThreadPool、CachedThreadPool、ScheduledThreadPool、SingleThreadPool。
FixedThreadPool:
通过Executors.newFixedThreadPool()创建,是一种线程数量固定的线程池。线程并不会被回收,除非线程池被关闭。当所有线程处于活动状态,新任务会被阻塞,直至有线程可用。
适于线程数固定。
CachedThreadPool:
通过Executors.newCachedThreadPool()创建,线程数量不定。只有非核心线程,最大线程数为Integer.MAX_VALUE。
空闲线程有超时机制,时长为60s,超过60s闲置线程会被回收。
当没有空闲线程时,对新任务会创建新线程,从而保证所有任务立即被执行。
适于执行大量且耗时较短的任务,线程数按需创建,用完回收。
Runnable runnable2 = new Runnable() {
@Override
public void run() {
Log.d(TAG,"runnable2 run in thread "+Thread.currentThread().getId());
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
Log.d(TAG,"runnable2 is interrupted");
}
Log.d(TAG,"任务完成!");
}
};
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(runnable2);
executorService.execute(runnable2);
try {
//休眠10s 前面的任务都执行完 且线程都没有被回收,新添加的任务将使用现有的空闲线程
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//将复用前面的两个空闲线程
executorService.execute(runnable2);
executorService.execute(runnable2);
try {
//休眠70s 空闲线程被回收,新添加的任务将启动新线程
TimeUnit.SECONDS.sleep(70);
} catch (InterruptedException e) {
e.printStackTrace();
}
//所有的空闲线程已空闲60s而被回收 因而新添加的任务将创建新线程
executorService.execute(runnable2);
executorService.shutdown();
ScheduledThreadPool:
通过Executors.newScheduledThreadPool()创建,核心数量固定且非核心线程数量无限制,当非核心线程闲置时立即被回收。
适于执行定时任务和固定周期的重复任务。
Runnable runnable2 = new Runnable() {
@Override
public void run() {
Log.d(TAG,"runnable2 run in thread "+Thread.currentThread().getId());
try {
//睡眠1min 为了便于观察结果
TimeUnit.SECONDS.sleep(60);
} catch (InterruptedException e) {
e.printStackTrace();
Log.d(TAG,"runnable2 is interrupted");
}
Log.d(TAG,"任务完成!");
}
};
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
scheduledExecutorService.schedule(runnable2,1,TimeUnit.SECONDS);
scheduledExecutorService.schedule(runnable2,1,TimeUnit.SECONDS);
//创建的时候核心线程数为2 因此任务数目大于2时会等待前面的任务执行完才会开始执行新任务
scheduledExecutorService.schedule(runnable2,1,TimeUnit.SECONDS);
scheduledExecutorService.shutdown();
(注意观察任务完成的时间和线程id)
SingleThreadPool:
通过 Executors.newSingleThreadExecutor()创建,只有一个核心线程,因而所有任务顺序执行,因而不用处理线程同步问题。
Runnable runnable1 = new Runnable() {
@Override
public void run() {
Log.d(TAG,"runnable1 run in thread "+Thread.currentThread().getId());
}
};
Runnable runnable2 = new Runnable() {
@Override
public void run() {
Log.d(TAG,"runnable2 run in thread "+Thread.currentThread().getId());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
Log.d(TAG,"runnable2 is interrupted");
}
}
};
ExecutorService singleThreadTool = Executors.newSingleThreadExecutor();
singleThreadTool.execute(runnable1);
singleThreadTool.execute(runnable1);
Future future = singleThreadTool.submit(runnable2);
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//停止runnable2
future.cancel(true);
//关闭线程池
singleThreadTool.shutdown();
//此任务不会被执行
singleThreadTool.submit(runnable1);
- IntentService
IntentService是一种特殊的Service,继承自Service且是一个抽象类。其子类必须实现onHandleIntent方法。
IntentService底层通过HandlerThread实现。onHandleIntent方法在后台运行。
由于IntentService是服务,不容易被系统杀死,适于执行高优先级的后台任务。
3)使用多线程的公共问题
- 生命周期
运行时间长,不受UI生命周期限制,引用外部UI类、Activity容易导致内存泄漏。 - UI操作
- 通常不能直接操作UI,需要配合Handler。
- 线程同步问题
4)适用场景
- Thread
默认优先级与创建时所在线程相同,适合单次耗时任务,执行完自己会结束线程。 HandlerThread
默认优先级与UI线程同,使用过多会导致UI卡顿;
不使用时需手动调用HandlerThread.quit()退出。AsyncTask
本质是对ThreadPoolExecutor和FutureTask的封装,默认为后台线程优先级。
提供了取消、进度通知、修改UI的能力,但不易使用。
创建数量超过128个,会抛出异常。
兼容性:1.6到2.x并发执行,3.0起串行执行。4.1之前首次使用必须要在UI线程创建。ExecutorService
优先级为默认优先级,不使用时需调用shutdown回收线程池。IntentService
不依赖UI,适于后台运行,执行完任务自动退出。
5)推荐的多线程使用方式
普通场景,要求高并发
Executors.newFixedThreadPool()高复用
Executors.newCachedThreadPool()低延迟需要顺序执行
使用HandlerThread- 需要后台执行
IntentService
Android中使用多线程的各种姿势