写在前面:内容主要为黄岳钊老师视频分享课的学习笔记。
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(); } AsyncTaskandroid特有的轻量级异步任务类,它可以在线程池中执行后台任务,然后把执行进度和结果传递给主线程中更新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被取消了,马上退出
HandlerThreadHandlerThread实际上是一个带有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); IntentServiceIntentService是一种特殊的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