temp

android线程管理总结

2018-02-08  本文已影响84人  牧头码尾

一 前言:

在android中,UI主线程并非线程安全的,所有UI相关的操作均需在UI主线程中完成。在默认情况下,开发者创建的Service、Activity、Broadcast均运行在UI主线程中,但将一些耗时操作,如网络下载、大文件读写、加解密计算、数据库操作等,也放在UI线程中执行,往往会阻塞UI线程,造成ANR异常,因此,在Android应用开发中,使用工作线程(子线程)来执行这些耗时操作就应运而生了。

二 线程的三种启动方式:

1.通过继承Thread类,并改写run方法来实现一个线程

public class MyThread extends Thread{
        public static final String TAG = "MyThread:";
        //改写run方法
        @Override
        public void run() {
            for(int i = 0; i < 50; i ++){
                Log.i(TAG,Thread.currentThread().getName() + "i = "+ i);
            }
        }
    }

启动线程

new MyThread().start();

2.创建一个Runnable对象

public class MyRunnable implements Runnable{
        public static final String TAG = "MyRunnable:";
        @Override
        public void run() {
            for(int i = 0; i < 10; i ++){
                Log.e(TAG,Thread.currentThread().getName() + "i = "+ i);
            }
        }
    }

启动线程

new Thread(new MyRunnable()).start();

3.通过Handler启动线程

public class MainActivity extends AppCompatActivity {

    public static final String TAG = "android thread:";
    private Handler mHandler = new Handler();
    private int sum = 0;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mHandler.postDelayed(mRunnable,0);//给自己发送消息,执行一次任务,不延时
    }

    /**
     * 任务执行操作
     */
    private Runnable mRunnable = new Runnable() {
        @Override
        public void run() {
            Log.e(TAG, Thread.currentThread().getName() + " " + sum);
            sum ++;
            mHandler.postDelayed(mRunnable,2000);//每两秒给自己发送消息,执行一次任务
        }
    };

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mHandler.removeCallbacks(mRunnable);//activity销毁时,把线程任务移除销毁
    }
}

上述三种方式是android开启子线程的常用方法,虽然很好的解决了阻塞UI线程(主线程)的问题,但每次开启一个线程都是需要CPU分配资源去执行的。如果总是每次需要时去开启一个子线程,那么就会大量的消耗CPU的资源,导致Android运行变慢,甚至OOM(out of memory),那么问题来了,是否有一个很好的解决方式,既可以解决不阻塞主线程,又不是很耗费CPU资源的方案呢?

三 线程池:

基于以上问题,android/java引入了线程池的概念

Android中的线程池的概念来源于Java中的Executor,Executor是一个接口,真正的线程池的实现为ThreadPoolExecutor,ThreadPoolExecutor提供了一系列参数来配置线程池,通过不同的参数可以创建不同的线程池。

线程池的优点
1.复用线程池中的线程,避免因为线程的创建和销毁所带来的性能开销。
2.能够有效的控制线程池的最大并发数,避免大量的线程之间因互相抢占系统资源而导致的阻塞现象。
3.能够对线程进行简单的管理,并提供定时执行以及指定间隔循环执行等功能。

线程池的结构图

Executor只是一个接口,它是Java/Android线程池框架的基础,它将任务的提交与任务的执行分离开来。
ExecutorService继承自Executor,有两个关键类实现了ExecutorService接口:ThreadPoolExecutor和ScheduledThreadPoolExecutor。
(1).ThreadPoolExecutor 是线程池的核心实现类,用来执行被提交的任务。
(2).ScheduledThreadPoolExecutor 也是一个实现类,可以在给定的延迟后运行命令,或者定期执行命令。它比Timer更灵活,功能更强大。

android 内部通过ThreadPoolExecutor、ScheduledThreadPoolExecutor创建出四种不同的线程池,分别为:

public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>(),
                                      threadFactory);
    }
public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
public static ScheduledExecutorService newScheduledThreadPool(
            int corePoolSize, ThreadFactory threadFactory) {
        return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
    }

一般需求通过以上四种线程池基本上可以解决,但如果有特殊需求,也可以自定义线程池,来做统一的管理。通过对比源码我们发现,前三个线程池的构造方法最终都调用了ThreadPoolExecutor的构造方法,最后一个调用了ScheduledThreadPoolExecutor的构造方法,今天我们就来分析下这第一个方法的具体含义:

ThreadPoolExecutor

ThreadPoolExecutor一共有四种构造函数,为了更全面的了解,我们看一下参数最多的那一个:

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

该线程池中核心线程数最大值
线程池新建线程的时候,如果当前线程总数小于corePoolSize,则新建的是核心线程,如果超过corePoolSize,则新建的是非核心线程。核心线程默认情况下会一直存活在线程池中,即使这个核心线程啥也不干(闲置状态)。而如果指定ThreadPoolExecutorallowCoreThreadTimeOut这个属性为true,那么核心线程如果不干活(闲置状态)的话,超过一定时间(时长由keepAliveTime决定),就会被销毁掉。

该线程池中线程总数最大值
线程总数 = 核心线程数 + 非核心线程数。

该线程池中非核心线程闲置超时时长
一个非核心线程,如果不干活(闲置状态)的时长超过这个参数所设定的时长,就会被销毁掉,如果设置allowCoreThreadTimeOut = true,则会作用于核心线程

keepAliveTime的单位,TimeUnit是一个枚举类型,其包括:
NANOSECONDS : 1微毫秒 = 1微秒 / 1000
MICROSECONDS : 1微秒 = 1毫秒 / 1000
MILLISECONDS : 1毫秒 = 1秒 /1000
SECONDS : 秒
MINUTES : 分
HOURS : 小时
DAYS : 天

线程池中的任务队列,通过线程池execute方法提交的Runnable对象会存储在这个参数中。当所有的核心线程都在干活时,新添加的任务会被添加到这个队列中等待处理,如果队列满了,则新建非核心线程执行任务。这个任务队列是BlockQueue类型,属于阻塞队列,就是当队列为空的时候,此时取出任务的操作会被阻塞,等待任务加入队列中不为空的时候,才能进行取出操作,而在满队列的时候,添加操作同样被阻塞。

线程工厂,为线程池提供创建新线程的功能。ThreadFactory是一个接口,它只有一个方法,newThread(Runnable r),用来创建线程。

一般用来抛出异常,比如上面提到的两个错误发生了,就会由这个handler抛出异常,用默认的就行

基于上面ThreadPoolExecutor的构造函数的理解,我们就可以自己自定义一个线程管理类,来对多线程进行处理:

分装

/**
 * 线程池管理,管理整个项目中的所有线程
 * Created by Administrator on 2018/2/8.
 */

public class ThreadPoolManager {
    private static ThreadPoolManager mIntance;
    private int corePoolSize;//核心线程池的数量,同时能够执行的线程数量
    private int maximumPoolSize;//最大线程池数量,表示当缓冲队列满的时候能继续容纳的等待任务的数量
    private long keepAliveTime = 1;//存活时间
    private TimeUnit unit = TimeUnit.HOURS;//小时
    private ThreadPoolExecutor executor;
    public static ThreadPoolManager getInstance(){
        if(mIntance == null){
            mIntance = new ThreadPoolManager();
        }
        return mIntance;
    }

    private ThreadPoolManager() {
        //corePoolSize赋值:当前设备可用处理器核心数*2 + 1
        corePoolSize = Runtime.getRuntime().availableProcessors()*2+1;
        maximumPoolSize = corePoolSize; //最大线程池数量数量与核心数设置为一致
        executor = new ThreadPoolExecutor(
                corePoolSize, //当某个核心任务执行完毕,会依次从缓冲队列中取出等待任务
                maximumPoolSize, //先corePoolSize,然后new LinkedBlockingQueue<Runnable>(),然后maximumPoolSize,但是它的数量是包含了corePoolSize的
                keepAliveTime, //表示的是maximumPoolSize当中等待任务的存活时间
                unit,
                new LinkedBlockingQueue<Runnable>(), //缓冲队列,用于存放等待任务,Linked的先进先出
                Executors.defaultThreadFactory(), //创建线程的工厂,默认
                new ThreadPoolExecutor.AbortPolicy() //用来对超出maximumPoolSize的任务的处理策略
        );
    }

    /**
     * 执行任务
     */
    public void execute(Runnable runnable){
        if(runnable==null){
            return;
        }
        executor.execute(runnable);
    }
    /**
     * 从线程池中移除任务
     */
    public void remove(Runnable runnable){
        if(runnable==null){
            return;
        }
        executor.remove(runnable);
    }
}

调用

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        /**
         * 模拟多任务同时执行
         */
        for(int i = 0;i < 8;i ++){
            ThreadPoolManager.getInstance().execute(new DoTask(i));
        }
    }

    /**
     * 模拟单个任务的执行
     */
    class DoTask implements Runnable{
        private int no;
        public DoTask(int no){
            this.no = no;
            Log.i("task","task-->"+no+"等待中...");
        }
        @Override
        public void run() {
            Log.i("task","task-->"+no+"开始执行...");
            SystemClock.sleep(1000);//模拟延时执行的时间
            Log.e("task","task-->"+no+"已经结束...");
        }
    }
}

打印结果

最后的话

这是我第一次写技术文档,有错误的地方还望指正,希望能和大家一起交流提高。以后有空也会定期写一些技术总结,最后,还要感谢以下几位博主,文中主要内容也是从以下几篇文中总结出来然后加上自己的理解和实践最终成型的。

上一篇下一篇

猜你喜欢

热点阅读