8种机械键盘轴体对比
本人程序员,要买一个写代码的键盘,请问红轴和茶轴怎么选?
一、前言
Android中线程分为主线程(UI线程)和子线程,主线程主要处理和界面相关的事情,而子线程则用于执行耗时操作。如果在主线程中执行耗时操作,比如网络请求操作,则会报NetworkOnMainThreadException;如果是其他耗时操作,界面卡顿时间超过5秒则会导致程序无法及时响应(ANR)。因此耗时操作必须在子线程去执行。Android提供了五种常用的线程实现方式,分别是:Thread
AsyncTask
HandlerThread
IntentService
ThreadPoolExecutor
二、线程使用
Threadnew Thread(){
@Override
public void run() {
super.run();
// NetWork or DataBase Operation
}
}.start();
这是最简单的创建异步线程的姿势了,但是每当项目中出现这类代码,我都忍不了要把它改掉的冲动。
缺点:创建及销毁线程消耗性能较大;
缺乏统一的管理;
优先级与UI线程一致,抢占资源处于同一起跑线;
匿名内部类默认持有外部类的引用,有内存泄漏的风险;
需要自己处理线程切换。
备注:此种姿势最好不要使用,特定场景下(例如App启动阶段为避免在主线程创建线程池的资源消耗)使用的话务必加上优先级的设置。Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
AysncTask
AsyncTask是Android1.5提供了工具类,它使创建异步任务变得更加简单,同时屏蔽了线程切换。
下面代码是官方文档的示例代码,在doInBackground()方法中处理耗时操作,处理的进度由onProgressUpdate()方法进行回调,耗时操作处理完成之后会调用onPostExecute()方法,在UI线程中执行。
private class DownloadFilesTask extends AsyncTask {
protected Long doInBackground(URL... urls) {
int count = urls.length;
long totalSize = 0;
for (int i = 0; i < count; i++) {
totalSize += Downloader.downloadFile(urls[i]);
publishProgress((int) ((i / (float) count) * 100));
// Escape early if cancel() is called
if (isCancelled()) break;
}
return totalSize;
}
protected void onProgressUpdate(Integer... progress) {
setProgressPercent(progress[0]);
}
protected void onPostExecute(Long result) {
showDialog("Downloaded " + result + " bytes");
}
}
优点:创建异步任务变得更加简单,同时屏蔽了线程切换;
在AsyncTask.java中我们可以看到,异步线程的优先级已经被默认设置成了:THREAD_PRIORITY_BACKGROUND,不会与UI线程抢占资源;
缺点:Api实现版本不一致问题:在Android1.5时AsyncTask的执行是串行的,在Android1.5——3.0之间- AsyncTask是并行的,而到了Android3.0之后AsyncTask的执行又回归到了串行。当然目前我们兼容的最低版本一般都会是最低4.0,那么就不需要对其进行过多的自定义适配,但是一定要注意AsyncTask默认是串行的,用于多线程场景下的话需要调用其重载方法executeOnExecutor()传入自定义的线程池,并且自己处理好同步问题;
匿名内部类默认持有外部类的引用,有内存泄漏的风险。
备注:对于AsyncTask正确的使用姿势,就是区分场景调用不同的执行方法;并且避免出现内存泄漏的问题。
HandlerThread
通过HandlerThread可以创建一个带有looper的线程,引入了Handler、Looper、MessageQueue等概念,可以实现对工作线程的调度。
以下是HandlerThread的使用示例:
HandlerThread handlerThread = new HandlerThread("DataBase Opeartion", Process.THREAD_PRIORITY_BACKGROUND);
handlerThread.start();
Handler handler = new Handler(handlerThread.getLooper()){
@Override
public void handleMessage(Message msg) {
// Do DataBase Opeartion
}
};
优点:串行执行,没有并发带来的问题;
不退出的前提下一直存在,避免线程相关的对象频繁重建和销毁造成的资源消耗。
缺点:串行执行(不同的视角优点也变缺点),并发场景下无能为力;
不指定优先级的情景下默认优先级为THREAD_PRIORITY_DEFAULT,与UI线程同级别。
备注:HandlerThread的正确使用姿势:串行场景,并在构造方法中明确指定优先级。
IntentService
根据官方文档的描述:IntentService是继承于Service并处理异步请求的一个类,在IntentService内有一个工作线程来处理耗时操作,启动IntentService的方式和启动传统Service一样,同时,当任务执行完后,IntentService会自动停止,而不需要我们去手动控制。另外,可以启动IntentService多次,而每一个耗时操作会以工作队列的方式在IntentService的onHandleIntent回调方法中执行,并且,每次只会执行一个耗时操作,依次执行。
实际上IntentService是Service与HandlerThread的组合,内部的工作线程以及调度机制都依赖于HandlerThread。@Override
public void onCreate() {
// TODO: It would be nice to have an option to hold a partial wakelock
// during processing, and to have a static startService(Context, Intent)
// method that would launch the service & hand off a wakelock.
super.onCreate();
HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
thread.start();
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}
@Override
public void onDestroy() {
mServiceLooper.quit();
}
优势:同HandlerThread的优势;
开启服务,进程优先级会提升;
无需手动关闭,执行完之后自动结束。
三、线程池
线程池:基本思想是一种对象池的思想,开辟一块内存空间,里面存放了众多(存活状态)的线程,池中线程执行调度由池管理器来处理。当有线程任务时,从池中取一个,执行完成后线程对象归池,这样可以避免反复创建线程对象所带来的性能开销,节省了系统的资源。
优势:线程的创建和销毁由线程池维护,一个线程在完成任务后并不会立即销毁,而是由后续的任务复用这个线程,从而减少线程的创建和销毁,节约系统的开销;
线程池旨在线程的复用,这就可以节约我们用以往的方式创建线程和销毁所消耗的时间,减少线程频繁调度的开销,从而节约系统资源,提高系统吞吐量;
在执行大量异步任务时提高了性能;
Java内置的一套ExecutorService线程池相关的api,可以更方便的控制线程的最大并发数、线程的定时任务、单线程的顺序执行等。
备注:回到我们上面提的第三个问题:线程池一定会提升效率吗?使用线程池需要特别注意同时并发线程数量的控制。因为CPU只能同时执行固定数量的线程数,一旦同时并发的线程数量超过CPU能够同时执行的阈值,CPU就需要花费精力来判断到底哪些线程的优先级比较高,在不同的线程之间进行调度切换。一旦同时并发的线程数量达到一定的量级,CPU在不同线程之间进行调度的时间就可能过长,反而导致性能严重下降;
每开一个新的线程,都会耗费至少64K以上的内存。线程池中存在了过多的并发数量不仅会影响CPU的调度时间而且会减少可用内存;
线程的优先级具有继承性,在某线程中创建的线程会继承此线程的优先级。那么我们在UI线程中创建了线程池,其中的线程优先级是和UI线程优先级一样的;所以仍然可能出现20个同样优先级的线程平等的和UI线程抢占资源。
对于线程池中线程数量的限制,可以参考AsyncTask中的配置,基于7.0源码,不同版本的实现可能有细微差别
;// We want at least 2 threads and at most 4 threads in the core pool,
// preferring to have 1 less than the CPU count to avoid saturating
// the CPU with background work 核心池数量被限定在2到4之间。
private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
private static final int KEEP_ALIVE_SECONDS = 30;
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
sPoolWorkQueue, sThreadFactory);
四、总结Thread、AsyncTask适合处理单个任务的场景;
HandlerThread适合串行处理多任务的场景;
IntentService适合处理与UI无关的多任务场景;
当需要并行的处理多任务之时,ThreadPoolExecutor是更好的选择,当然也可以使用AsyncTask传入自定义的线程池;
注意线程优先级的设置;
特别注意对不同场景下异步方式的选择。