上图是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>());
}
上述三种方式,其实都不建议。使用线程池应该根据使用的场景,合理的安排核心线程和非核心线程。
作者:东土也
链接:https://juejin.cn/post/7145002669155811364
来源:稀土掘金