Android学习笔记(4)-Android 线程部分总结

本来准备今天写okhttp的,发现要好好整理一下源码才能讲清楚原理,所以今天就先复习一下Android多线程的相关知识吧

创建线程的几种方式

  • 继承Thread类
  • 实现Runnable接口
  • 实现Callable接口
    线程创建我们使用的比较多的是继承Thread或实现Runnable接口,实现Callable接口的方式比较少,这里就简单举例说明一下Thread和Runnable的使用

Thread的使用

  Thread thread = new Thread(){
            @Override
            public void run(){
                Log.d(TAG,"thread run()");
            }
        };
        thread.start();

Runnable接口使用

new Runnable(){
            @Override
            public void run(){
                Log.d(TAG,"Runnable run()");
            }
        }.run();

先总结一下线程的几种状态

  • 创建状态(new)
  • 就绪状态(Runnable)
  • 运行状态(Running)
  • 阻塞状态(Blocked)
  • 死亡状态(Dead)
    几个重要方法
  • sleep()
    当前线程会暂时交出cpu的控制权,但不会释放锁,时间到达后继续运行
  • wait() ,notify/notifyAll()
    wait()和notify()、notifyAll()方法的调用都必须在synchronized修饰的方法或者代码块中调用,使用过程中必须获得对象锁,否则会抛出java.lang.IllegalMonitorStateException的异常。
    wait()方法会阻塞线程,让它交出对象锁
    notify()会随机挑选一个阻塞队列中的线程将其改为就绪状态
    notifyAll()会将阻塞队列中的所有线程都改为就绪状态。
  • join()
    当有A,B两个线程,且在A中调用了B线程的join方法时,表示只有当B线程执行完毕时A才能继续执行,(如果真要实现这种串行业务,直接使用HandlerThread更好)。
  • yield()方法
    yield()让当前正在运行的线程回到可运行状态,以允许具有相同优先级的其他线程获得运行的机会。(不会释放锁)
  • interrupt()和isInterrupted()/interrupted()。
    thread.interrupt(),当thread对象变为中断状态,interrupt()并不能中断在运行中的线程,它只能改变中断状态而已。(划重点,是状态,不是实际中断了)
    thread.interrupted(),判断当前线程对象的状态是否为中断状态,内部实现是调用的当前线程的isInterrupted(),并且会重置当前线程的中断状态。
    thread.isInterrupted(),判断当前线程对象的状态是否为中断状态,不会重置当前线程的中断状态。

接下来开始总结多线程的使用,Android的SDK给我们提供了四种多线程的使用方式。

多线程的使用方式

  • HandlerThread
  • IntentService 可以自动关闭的服务
  • AsyncTask 异步任务
  • ThreadPoolExecutor 线程池

HandlerThread 使用

因为之前的文章里有总结过,我们这里就简单总结一下

public void startHandlerThread(){
        final HandlerThread myHandlerThread = new HandlerThread("myHandlerThread");
        myHandlerThread.start();
        workHandler = new Handler((myHandlerThread.getLooper())){
            @Override
            public void handleMessage(Message message){
                super.handleMessage(message);
                switch (message.what){
                    case 0:
                        Log.d(TAG, "收到来自主线程的消息.."+Thread.currentThread().getName());
                        break;
                    case 1:
                        Log.d(TAG, "收到来自子线程的消息.."+Thread.currentThread().getName());
                        break;
                }
            }
        };
        workHandler.sendEmptyMessage(0);
        new Runnable(){
            @Override
            public void run() {
                workHandler.sendEmptyMessage(1);
            }
        }.run();

    }

因为Handler本质上是一个单一的线程,只是通过消息的传递来实现串行任务和线程的复用,达到节省开销的目的,使用时记得使用完毕后关闭该线程中的looper循环。

IntentService 使用

IntentService也总结过,这里也就简单总结一下

使用

首先需要写一个类继承自IntentService,并在onHandleIntent()方法中去实现我们想要在这个Service中的业务.

public class MyIntentService extends IntentService {

    public MyIntentService() {
        super("MyIntentService");
    }

    @Override
    protected void onHandleIntent(@Nullable Intent intent) {
        LogUtils.e("MyIntentService" , "开始执行IntentService");
    }
}

定义好类以后我们还需要在AndroidMainfest.xml application标签中声明一下这个Service

<service android:name=".MyIntentService"></service>

接着我们只需要在使用时调用

Intent intent = new Intent(this,MyIntentService.class);
        startService(intent);

原理就是IntentService开启了一个类型是HandlerThread的线程,它在每次执行完onNewIntent()方法中的业务代码以后,会自动调用stopSelf()方法关闭这个Service,达到用完即关的目的,其目的也是为了节省资源 ,提高资源利用率。

AsyncTask 使用

首先我们需要创建一个异步任务类继承自AsyncTask。

 private class MyAsyncTask extends AsyncTask<Integer,Integer,String>{

        @Override
        protected void onPreExecute() {
            //第一个执行方法
            Log.d(TAG,"onPreExecute thread:"+Thread.currentThread().getName());
            super.onPreExecute();
        }
        @Override
        protected String doInBackground(Integer... params) {
            Log.d(TAG,"doInBackground thread:"+Thread.currentThread().getName());
            //第二个执行方法,onPreExecute()执行完后执行
            for(int i=0;i<=100;i++){
                publishProgress(i);
                try {
                    Thread.sleep(params[0]);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            return "执行完毕";
        }
        @Override
        protected void onProgressUpdate(Integer... progress) {
            Log.d(TAG,"onProgressUpdate thread:"+Thread.currentThread().getName());
            progressTv.setText(progress[0]+"%");
            super.onProgressUpdate(progress);
        }
        @Override
        protected void onPostExecute(String result) {
            Log.d(TAG,"onPostExecute thread:"+Thread.currentThread().getName());

            //doInBackground返回时触发,换句话说,就是doInBackground执行完后触发
            //这里的result就是上面doInBackground执行后的返回值,所以这里是"执行完毕"
            setTitle(result);
            super.onPostExecute(result);
        }
 }

这里我们先看创建Async时的三个参数的含义,从左到右分别是参数(例子里是线程休息时间),进度(publishProgress用到),返回值类型,这样看有点难理解,我们再仔细看一下各个方法就明白了.但我们调用execute()方法开启异步任务时,打印结果如下:

03-02 14:55:02.326 12437-12437/? D/ThreadDemoActivity: onPreExecute thread:main
03-02 14:55:02.327 12437-12481/? D/ThreadDemoActivity: doInBackground thread:AsyncTask #1
03-02 14:55:02.359 12437-12437/? D/ThreadDemoActivity: onProgressUpdate thread:main
...
03-02 14:55:12.860 12437-12437/? D/ThreadDemoActivity: onPostExecute thread:main
  • onPreExecute()方法,在主线程中运行,做一些初始操作
  • doInBackground()方法,在子线程中运行,做一些耗时操作,比如网络请求,调用publishProgress()方法可反馈进度,同时触发onProgressUpdate()方法更新进度,当执行完毕时触发onPostExecute()方法,反馈执行结果。
  • onProgressUpdate(),在主线程中运行,做UI更新操作
  • onPostExecute(),在主线程中运行,做UI更新操作
    所以异步任务中只有doInBackground()方法是运行在子线程中的,其他都是运行在主线程中的。

ThreadPoolExecutor 使用

    public void threadPoolTest(){
          final int CORE_POOLSIZE = 2;
          final int MAXIMUM_POOLSIZE = 4;
          final int WORKQUEUE_SIZE = 10;
          final ArrayBlockingQueue queue = new ArrayBlockingQueue<Runnable>(WORKQUEUE_SIZE);
          ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(CORE_POOLSIZE, MAXIMUM_POOLSIZE, 30, TimeUnit.SECONDS, queue);
          for (int i = 0; i < 5; i++) {
            int index = i;
            final int finalI = i;
            threadPoolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    Log.d(TAG,"index==" + finalI + "  workQueue的长度  " + queue.size());
                }
            });
        }
    }

打印结果如下

03-02 15:22:17.479 14789-14829/com.txVideo.demo D/ThreadDemoActivity: index==0  workQueue的长度  3
03-02 15:22:17.479 14789-14829/com.txVideo.demo D/ThreadDemoActivity: index==2  workQueue的长度  2
03-02 15:22:17.479 14789-14829/com.txVideo.demo D/ThreadDemoActivity: index==3  workQueue的长度  1
03-02 15:22:17.480 14789-14829/com.txVideo.demo D/ThreadDemoActivity: index==4  workQueue的长度  0
03-02 15:22:17.480 14789-14830/com.txVideo.demo D/ThreadDemoActivity: index==1  workQueue的长度  0

我们首先介绍一下ThreadPoolExecutor类的几个重要参数的含义,这是线程池构造参数最多的一个构造方法。

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

  • corePoolSize 最大核心线程数,就是允许最大有几个线程同时运行
  • maximumPoolSize 最大线程池大小
  • keepAliveTime 线程池中,超过corePoolSize 数目的非核心线程的保活时间。
  • unit 保活时间的单位
  • workQueue 保持任务的队列,也就是缓存队列
  • threadFactory 线程工厂,用来创建线程
  • handler 饱和策略,用来处理队列满或无法处理是时的场景。
    所以使用时,我们只需要根据参数创建好线程池,在添加线程时调用execute()方法添加线程即可。
    根据构造方法,我们可以直接使用Android提供的四种线程池,通过Executors创建
  • Executors.newFixedThreadPool()
    创建一个定长的线程池,每提交一个任务就创建一个线程,直到达到池的最大长度,这时线程池会保持长度不再变化
   public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
  • Executors.newCachedThreadPool()
    创建一个可缓存的线程池,如果当前线程池的长度超过了处理的需要时,它可以灵活的回收空闲的线程,当需要增加时,它可以灵活的添加新的线程,而几乎不会对池的长度作任何限制
 public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
  1. Executors.newScheduledThreadPool()
    创建一个定长的线程池,而且支持定时的以及周期性的任务执行,类似于Timer
  public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE,
              DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
              new DelayedWorkQueue());
    }
  1. Executors.newSingleThreadExecutor()
    创建一个单线程化的executor,它只创建唯一的worker线程来执行任务
   public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

所以这四种线程池其实也是根据设置不同参数的线程池来构建的,我们在实际开发时可以根据需求来自己构建线程池或直接使用这四种线程池。多线程编程一直是安卓的重要一部分,所以实际开发中多总结才能更好的正确使用它。
今天的Android 线程部分总结就先总结到这里啦,觉得有帮助记得点个赞~

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容