注册

Android常用多线程解析(一)线程的使用

image.png 上图是Android中多线程实现的主要方式,和线程的控制流程。

1.最基础的方式就是在需要的时候new一个Thread,但是这种方式不利于线程的管理,容易引起内存泄漏。 试想一下,你在Activity中new一个Thread去处理耗时任务,并且在任务结束后通过Handler切换到UI线程上去操作UI。这时候你的Activity已经被销毁,因为Thread还在运行,所以他并不会被销毁,此外Thread中还持有Handler的引用,这时候必将会引发内存泄漏和crash。

newThread:可复写Thread#run方法,也可以传递Runnable对象 缺点:缺乏统一管理,线程无法复用,线程间会引起竞争,可能占用过多系统资源导致死机或oom。

Thread的两种写法
class ThreadRunable : Thread() {
override fun run() {
Thread.sleep(10000)
}
}

fun testThread(){
Thread{
Thread.sleep(10000)
}.start()
ThreadRunable().start()
}

2.在Android中我们也会使用AsyncTask来构建自己的异步任务。但是在Android中所有的AsyncTask都是共用一个核心线程数为1的线程池,也就是说如果你多次调用AsyncTask.execute方法后,你的任务需要等到前面的任务完成后才会执行。Android已经不建议使用AsyncTask了

@Deprecated
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();

private static class SerialExecutor implements Executor {
final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
Runnable mActive;

public synchronized void execute(final Runnable r) {
mTasks.offer(new Runnable() {
public void run() {
try {
r.run();
} finally {
scheduleNext();
}
}
});
if (mActive == null) {
scheduleNext();
}
}

protected synchronized void scheduleNext() {
if ((mActive = mTasks.poll()) != null) {
THREAD_POOL_EXECUTOR.execute(mActive);
}
}
}


使用AsyncTask的方式有以下几种

2.1.继承AsyncTask<String, Int, String>()并且复写他的三个方法

class MyAsyncTask : AsyncTask<String, Int, String>(){
//线程池中调用该方法,异步任务的代码运行在这个方法中,参数代表运行异步任务传递的参数,通过AsyncTask.execute方法传递
override fun doInBackground(vararg params: String?): String {
Log.e(TAG, Thread.currentThread().name)
for(progress in 0..100){
//传递任务进度
publishProgress(progress)
}
return "success"
}
//运行在UI线程上 参数代表异步任务传递过来的进度
override fun onProgressUpdate(vararg values: Int?) {
Log.e(TAG, "progress ${values}, Thread ${Thread.currentThread().name}")
}

//异步任务结束,运行在UI线程上 参数代表异步任务运行的结果
override fun onPostExecute(result: String?) {
Log.e(TAG, "result ${result}, Thread ${Thread.currentThread().name}")
}

}
MyAsyncTask().execute("123")

2.2.直接调用execute方法传递Runable对象

for(i in 0..10){
AsyncTask.execute(Runnable {
Log.e(TAG, "for invoke: ${Thread.currentThread().name} time ${System.currentTimeMillis()}" )
Thread.sleep(10000)
})
}

2.3.直接向线程池添加任务

/**
* 并发执行任务
*/

for(i in 0..10){
AsyncTask.THREAD_POOL_EXECUTOR.execute( Runnable {
Log.e(TAG, "for invoke: ${Thread.currentThread().name} time ${System.currentTimeMillis()}" )
})
}

2.4.其中第三种是并行执行,使用是AsyncTask内部的线程池

@Deprecated
public static final Executor THREAD_POOL_EXECUTOR;

static {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(), sThreadFactory);
threadPoolExecutor.setRejectedExecutionHandler(sRunOnSerialPolicy);
THREAD_POOL_EXECUTOR = threadPoolExecutor;
}

3.使用HandlerThread

HandlerThread在内部维护一个loop遍历子线程的消息,允许你向子线程中发送任务

使用方法如下,我们需要先构建HandlerThread实例,并调用start方法,启动内部的loop。

之后需要创建Handler并且将HandlerThread的loop传递进去,在内部实现handleMessage方法处理任务。

fun handlerThread(){
val handlerThread = HandlerThread("Handler__Thread")
handlerThread.start()
//传递的loop是ThreadHandler
val handler = object : Handler(handlerThread.looper){
override fun handleMessage(msg: Message) {
Log.e(TAG, "handlerThread ${Thread.currentThread().name}")
}
}
handler.sendEmptyMessage(1)
}

4.使用IntentService执行完任务后自动销毁,适用于一次性任务(已经被弃用)推荐使用workManager。


/**
* 任务执行完成后自动销毁,适用于一次性任务
*/

class MyIntentService : IntentService("MyIntentService"){
override fun onHandleIntent(intent: Intent?) {
Log.e("MyIntentService", "Thread ${Thread.currentThread().name}")
}

}

5.使用线程池。

线程池是我们最常用的控制线程的方式,他可以集中管理你的线程,复用线程,避免过多开辟新线程造成的内存不足。 线程池的详细解析将在下一章中描述

public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler
)

上面是线程池的构造函数

corePoolSize是线程池的核心线程数,这些线程不会被回收。

maximumPoolSize是线程池最大线程数,线程池分为核心线程和非核心线程,两者之和为maximumPoolSize。非核心线程将会在任务执行完后一段时间被释放。

keepAliveTime非核心线程存在的时间。

unit 非核心线程存在的时间单位

workQueue任务队列,在核心线程都在处理任务的时候会将任务存放在任务队列中。只有当任务队列存放满后,才会启动非核心线程。

threadFactory构建线程的工程,一般会在其中处理一些线程启动前的操作。

handler拒绝策略,线程池被关闭的时候(调用shutdonw),线程池线程数等于maximumPoolSize,任务队列已满的时候会被调用。

主要方法

void execute(Runnable run)//提交任务,交由线程池调度

void shutdown()//关闭线程池,等待任务执行完成

void shutdownNow()//关闭线程池,不等待任务执行完成

int getTaskCount()//返回线程池找中所有任务的数量  (已完成的任务+阻塞队列中的任务)

int getCompletedTaskCount()//返回线程池中已执行完成的任务数量  (已完成的任务)

int getPoolSize()//返回线程池中已创建线程数量

int getActiveCount()//返回当前正在运行的线程数量

void terminated() 线程池终止时执行的策略

线程池还有几种内置的方式。

5.1.newFixedThreadPool 创建固定线程数的线程池。核心线程数和最大核心线程数相等为入参。

这种方式创建的线程池,因为使用的是无界LinkedBlockingQueue队列,不加控制的话会引起内存溢出

创建固定线程数的线程池
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}

5.2.newSingleThreadExecutor 创建一个核心线程数和最大线程数为1的线程池,使用的也是LinkedBlockingQueue。 也会引发内存问题

public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}

5.3.newCachedThreadPool 创建一个无核心线程,最大线程数无限大的线程池。因为使用的是SynchronousQueue队列,不会存储任务,每提交一个任务就会创建一个新的线程使用。当任务足够多的情况下也会引起内存溢出。

public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}

上述三种方式,其实都不建议。使用线程池应该根据使用的场景,合理的安排核心线程和非核心线程。

0 个评论

要回复文章请先登录注册