美文网首页
关于Android多线程的理解

关于Android多线程的理解

作者: wayDevelop | 来源:发表于2019-05-08 16:01 被阅读0次

    扩展文章
    非主线程中能不能直接new Handler()
    Android 异步消息处理机制 让你深入理解 Looper、Handler、Message三者关系
    JAVA中线程同步的方法
    关于AsyncTask、HandlerThread的理解
    关于Service,IntentService的理解

    扩展知识

    • 线程和进程有什么区别?
      一个进程是一个独立(self contained)的运行环境,它可以被看作一个程序或者一个应用。而线程是在进程中执行的一个任务。线程是进程的子集,一个进程可以有很多线程,每条线程并行执行不同的任务。不同的进程使用不同的内存空间,而所有的线程共享一片相同的内存空间。别把它和栈内存搞混,每个线程都拥有单独的栈内存用来存储本地数据。

    • 多线程编程的好处是什么?
      多个线程被并发的执行以提高程序的效率,提高CPU和内存利用率

    • 什么是线程调度器(Thread Scheduler)和时间分片(Time Slicing)?
      线程调度器是一个操作系统服务,它负责为Runnable状态的线程分配CPU时间。一旦我们创建一个线程并启动它,它的执行便依赖于线程调度器的实现。时间分片是指将可用的CPU时间分配给可用的Runnable线程的过程。分配CPU时间可以基于线程优先级或者线程等待的时间。线程调度并不受到Java虚拟机控制,所以由应用程序来控制它是更好的选择(也就是说不要让你的程序依赖于线程的优先级)。

    • 什么是ThreadLocal?

    ThreadLocal用于创建线程的本地变量,我们知道一个对象的所有线程会共享它的全局变量,所以这些变量不是线程安全的,我们可以使用同步技术。但是当我们不想使用同步的时候,我们可以选择ThreadLocal变量。
    ThreadLocal是数据的隔离,但是并非数据的复制,而是在每一个线程中创建一个新的数据对象,然后每一个线程使用的是不一样的。
    每个线程都会拥有他们自己的Thread变量,它们可以使用get()\set()方法去获取他们的默认值或者在线程内部改变他们的值。ThreadLocal实例通常是希望它们同线程状态关联起来是private static属性。

    • 什么是线程池
      一个线程池管理了一组工作线程,同时它还包括了一个用于放置等待执行的任务的队列。
    • 创建线程
      JAVA多线程实现方式主要有三种:继承Thread类、实现Runnable接口、使用ExecutorService、Callable、Future实现有返回结果的多线程。其中前两种方式线程执行完后都没有返回值,只有最后一种是带返回值的。
    1、继承Thread类实现多线程

    继承Thread类的方法尽管被我列为一种多线程实现方式,但Thread本质上也是实现了Runnable接口的一个实例,它代表一个线程的实例,并且,启动线程的唯一方法就是通过Thread类的start()实例方法。start()方法是一个native方法,它将启动一个新线程,并执行run()方法。这种方式实现多线程很简单,通过自己的类直接extend Thread,并复写run()方法,就可以启动新线程并执行自己定义的run()方法。例如:

    public class MyThread extends Thread {
      public void run() {
       System.out.println("MyThread.run()");
      }
    }
    在合适的地方启动线程如下:
    MyThread myThread1 = new MyThread();
    MyThread myThread2 = new MyThread();
    myThread1.start();
    myThread2.start();
    

    2、实现Runnable接口方式实现多线程

    如果自己的类已经extends另一个类,就无法直接extends Thread,此时,必须实现一个Runnable接口,如下:

    public class MyThread extends OtherClass implements Runnable {
      public void run() {
       System.out.println("MyThread.run()");
      }
    }
    

    为了启动MyThread,需要首先实例化一个Thread,并传入自己的MyThread实例:

    MyThread myThread = new MyThread();
    Thread thread = new Thread(myThread);
    thread.start();
    
    事实上,当传入一个Runnable target参数给Thread后,Thread的run()方法就会调用target.run(),参考JDK源代码:
    public void run() {
      if (target != null) {
       target.run();
      }
    }
    

    3、使用ExecutorService、Callable、Future实现有返回结果的多线程。

    执行Callable任务后,可以获取一个Future的对象,在该对象上调用get就可以获取到Callable任务返回的Object了,再结合线程池接口ExecutorService就可以实现传说中有返回结果的多线程了。下面提供了一个完整的有返回结果的多线程测试例子
    注意:get方法是阻塞的,即:线程无返回结果,get方法会一直等待

      Log.w("TAG", "----程序开始运行----");
                    Date date1 = new Date();
                    int taskSize = 2;
                    // 创建一个线程池
                    ExecutorService pool = Executors.newFixedThreadPool(taskSize);
                    // 创建多个有返回值的任务
                    List<Future> list = new ArrayList<Future>();
                    for (int i = 0; i < taskSize; i++) {
                        Callable c = new MyCallable(i + " ");
                        // 执行任务并获取Future对象
                        Future f = pool.submit(c);
                        // System.out.println(">>>" + f.get().toString());
                        list.add(f);
                    }
    
                    // 关闭线程池
                    pool.shutdown();
    
                    // 获取所有并发任务的运行结果
                    for (Future f : list) {
                        // 从Future对象上获取任务的返回值,并输出到控制台
                        try {
                            Log.w("TAG", ">----123-->>" + f.get().toString());
                        } catch (ExecutionException e) {
                            e.printStackTrace();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
    
                    Date date2 = new Date();
                    Log.w("TAG", "----程序结束运行----,程序运行时间【" + (date2.getTime() - date1.getTime()) + "毫秒】");
                }
    
    
    public class MyCallable implements Callable<Object> {
        private String taskNum;
    
        public MyCallable(String taskNum) {
            this.taskNum = taskNum;
        }
    
        public Object call() throws Exception {
            Log.w("TAG", ">call---->>" + taskNum + "任务启动");
            Date dateTmp1 = new Date();
            Thread.sleep(3000);
            Date dateTmp2 = new Date();
            long time = dateTmp2.getTime() - dateTmp1.getTime();
            Log.w("TAG", ">>>" + taskNum + "任务终止");
            return taskNum + "任务返回运行结果,当前任务时间【" + time + "毫秒】";
        }
    
    }
    
    

    执行结果,两个任务中止后,将结果返回

     W/TAG: ----程序开始运行----
     W/TAG: >call---->>0 任务启动
     W/TAG: >call---->>1 任务启动
     W/TAG: >>>0 任务终止
     W/TAG: >----123-->>0 任务返回运行结果,当前任务时间【3006毫秒】
     W/TAG: >>>1 任务终止
     W/TAG: >----123-->>1 任务返回运行结果,当前任务时间【3005毫秒】
     W/TAG: ----程序结束运行----,程序运行时间【3029毫秒】
    

    关于线程池

    ExecutorService与生命周期

    ExecutorService扩展了Executor并添加了一些生命周期管理的方法。一个Executor的生命周期有三种状态,运行 ,关闭 ,终止 。Executor创建时处于运行状态。当调用ExecutorService.shutdown()后,处于关闭状态,isShutdown()方法返回true。这时,不应该再想Executor中添加任务,所有已添加的任务执行完毕后,Executor处于终止状态,isTerminated()返回true。
    如果Executor处于关闭状态,往Executor提交任务会抛出unchecked exception RejectedExecutionException。

    Java通过Executors提供四种线程池,分别为:

    • CachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。在线程空闲60秒后终止线程。
      ②最大线程数Integer.MAX_VALUE,最多65535个线程
      ③超时时间60s
    • ScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
    • FixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
    • SingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
      ②最大线程数Integer.MAX_VALUE,最多65535个线程
      他就是线程数量为1的FixedThreadPool,如果向SingleThreadExecutor提交多个任务,那么这些任务会排队,每个任务都会在下个任务开始之前就结束,所有任务都用一个线程,并且按照提交的顺序执行。
      ①一个核心线程
       具体的注释可以点击进去参考源码,
        ExecutorService pool1 = Executors.newCachedThreadPool();
        ExecutorService pool2 = Executors.newFixedThreadPool(3);
        ExecutorService pool3 = Executors.newScheduledThreadPool(3);
        ExecutorService pool4 = Executors.newSingleThreadExecutor();
    

    Executors类用于管理Thread对象,简化并发过程,我们可以看到FixedThreadPool的创建过程:

     public static ExecutorService newFixedThreadPool(int nThreads) {
            return new ThreadPoolExecutor(nThreads, nThreads,
                                          0L, TimeUnit.MILLISECONDS,
                                          new LinkedBlockingQueue<Runnable>());
        }
    

    显然这四种线程池方法都是返回实现了ExecutorService接口的ThreadPoolExecutor。
    Executor是Java中的概念,是一个接口,真正的线程池实现是ThreadPoolExecutor。它提供了一系列的参数来配置不同的线程池。当然我们也可以直接用ThreadPoolExecutor来自定义更灵活的线程池。

     public ThreadPoolExecutor(
                 int corePoolSize,//核心线程数
                int maximumPoolSize,//最大线程数
                long keepAliveTime, //非核心线程的超时时间
                TimeUnit unit,//单位
                BlockingQueue<Runnable> workQueue,//任务队列 
                ThreadFactory threadFactory//线程工厂
                RejectedExecutionHandler handler) 
    
    • corePoolSize
      核心线程数,核心线程池默认会在线程池中一直存活,无论它是不是闲置。如果将ThreadPoolExecute的allowCoreThreadTimeOut设置为true,那么闲置的核心线程会执行超时策略,这个超时策略的时间间隔是由keepAliveTime所指定的。
    • maximumPoolSize
      线程池所能容纳的最大线程数,当活动的线程数达到这个数值后,后续的任务会被阻塞。
    • keepAliveTime
      非核心线程池的超时时长,非核心线程池闲置的时间超过这个时间就会被回收。当ThreadPoolExecute的allowCoreThreadTimeOut设置为true时,它同样也作用于核心线程。
    • unit
      用于指定keepAliveTime的单位。
    • workQueue
      线程池中的任务队列,通过线程池的execute方法提交的Runnable对象会储存在这个参数中。
    • threadFactory
      线程工厂,为线程池提供创建新线程的功能,ThreadFactory是一个接口,他只有一个方法:
    public interface ThreadFactory {
        Thread newThread(Runnable var1);
    }
    

    除了这些参数外还有个很不常用的参数RejectedExecutionHandler handler。当线程池无法执行新任务时(任务队列满了或者无法成功执行)会调用handler的rejectExecutionException。

    执行步骤

    Android多线程通信机制

    • Android线程间通信方式之AsyncTask机制
    • Handler机制;
    • runOnUiThread方法,用Activity对象的runOnUiThread方法更新,在子线程中通过runOnUiThread()方法更新UI
    • View.post(Runnable r) 、

    相关文章

      网友评论

          本文标题:关于Android多线程的理解

          本文链接:https://www.haomeiwen.com/subject/lpxeoqtx.html