计算机系统应用教程网站

网站首页 > 技术文章 正文

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

btikc 2024-09-02 16:41:56 技术文章 9 ℃ 0 评论

上图是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
来源:稀土掘金

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表