android机制系列之四 Android+Java多线程编程这一篇就够了

系列目录 https://blog.csdn.net/jzlhll123/article/category/7671581

这里总结了几乎所有的多线程知识和部分原理。融会贯通后对android多线程又有了较深的理解。

  1. Thread 线程:
      线程是CPU调度的基本单位。拥有自己的寄存器和栈。创建只需要一个内核对象和一个堆栈。
      同一个进程内多线程共享资源。
      用户(user)线程&守护(daemon)线程:JVM在没有任何用户线程存在的时候,就自动退出所有的守护线程。
      Process 进程: 程序执行的一个实例。资源分配的独立的地址空间。创建,需要很多虚拟地址空间大量的资源。不能与其他进程共享资源(除非IPC);至少有一个线程()。
  2. Runnable & Thread:
      Runnable是接口;Thread是class;
      Runnable和Thread最大的区别是,如果一个Runnable对象,多次被多个线程执行,就可以共享Runnable内的全局数据。而本质上,Thread就是实现的Runnable。

  3. ThreadPool Executor
      为了控制创建和销毁线程的性能,或者控制同时运行的线程数目,我们会采用线程池。
    java 线程池类图

    例举常用方法:

    //1. 创建固定数量的线程池,而且即使没活干,也不会释放线程。超过数量的任务进来,就会排队
    ExecutorService es = Executors.newFixedThreadPool(int num) 
    //2. 单一线程池
    ExecutorService es = Executors.newSingleThreadExecutor();
    //3. 无线多个线程,不等待,创建可以缓存线程的线程池, 适合执行时间小的任务,内部也会复用线程,闲置后会收回
    ExecutorService es = Executors.newCachedThreadPool();
    es.excute(runnable);
    es.submit();
    //4. 定时线程池
    ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
    scheduledThreadPool.schedule(runnable, 3, TimeUnit.SECONDS); //scheduleAtFixedRate runnable 1, 3, SECONDS
    es.shutdown();
  4. feature,featureTask,Callable:

      Callable接口跟Runnable接口相比,有返回值+抛异常。Callable不能直接用于Thread,Runnable可以;
      Future接口提供了Runnable/Callable的状态查询(是否cancel,完成),阻塞获取结果;
      FutureTask类,则将上述能力全部集中起来。
      比如你Thread传入一个FutureTask的实例task,则可以通过task来查询状态和阻塞结果了。
      在结合Executor的时候,submit(run/call),有返回future,可以通过它来查询状态;而execute(run/call)则没有返回。你也可以传入FutureTask。

    参考 https://blog.csdn.net/w1014074794/article/details/51090462

  5. Timer & TimerTask:
      Timer继承自Thread的子类;TimerTask是实现自Runnable的类。知道了这个,你不难懂如何使用它了。

    • 在特定时间执行任务,只执行一次
      public void schedule(TimerTask task,Date time)
    • 在特定时间之后执行任务,只执行一次
      public void schedule(TimerTask task,long delay)
    • 指定第一次执行的时间,然后按照间隔时间,重复执行
      public void schedule(TimerTask task,Date firstTime,long period)
    • 在特定延迟之后第一次执行,然后按照间隔时间,重复执行
      public void schedule(TimerTask task,long delay,long period)
  6. ThreadLocal:
    线程局部变量,在很多语言中都有语法层面提供。Java是类。如linux上,

       static thread char t_buf[32] = {'\0'};
       extern thread int t_val = 0;

  在java中提供了4个方法,set(T), T get(), remove(), initialValue(),使用 static来定义一个ThreadLocal,解决引用问题。它用来解决一个线程内参数的传递。提供的是一种,线程封闭的技术
  查看Thread源码,Thread其实是实现了Runnable的一个类, 而Thread内部有一个ThreadLocalMap的对象threadLocals(是ThreadLocal的内部类),默认为null,只有当我们有特殊用途(我们定义ThreadLocal)的时候,才会调用Thread.currentThread()拿到当前线程,给这个线程t.threadLocals赋值。
  使用static修饰以后,它就是同一个进程内唯一,每次set就是将一个对象T作为value,ThreadLocal作为key封装成ThreadLocalMap, 设置给当前的Thread。get()拿的时候,就从currentThread拿到map,再从map中根据ThreadLocal作为key,拿出value。
  他的作用是提供了一个线程内的局部变量,用于该线程内,如果该线程终止,这些值都将回收。一般他用在数据库链接,web session等。
  个人理解,在学习ThreadLocal的时候,翻看大神们的帖子,有的人争论,ThreadLocal不是用来解决共享变量问题的,它与多线程的并发问题没有任何关系。又有的人说,它就是为了多线程和线程性能而诞生的。
  两种说法都没有错。
  本质上,它并没有添加什么特殊的东西给到多线程。仅仅是针对Thread的成员变量做文章。
  可以假设,如果没有ThreadLocal,我们需要在Manager处维护一个队列用来存储每一个session,这个队列肯定需要根据Thread来判断(或者userId等,本质上跟Thread也是绑定的)存储的,这个队列也就是一个map。由于他不是Thread的一个成员变量,因此需要考虑更多存入/取出的同步问题。而同步是引起大访问时候性能的瓶颈。
  从这个角度上讲,是线程安全的,节约了一些性能消耗,也间接地解决了共享变量的问题。这个机制给出了一种线程级别的数据共享机制,将一个数据类型绑定在Thread下面,而且生命周期也考虑进去了,内存回收也不用管。并且在不太考虑内存空间的情况下,性能上有优势。
  
7. Loop:

  ThreadLocal的一个很好的实例使用就是Android上的Loop。下面代码基本上就是Looper的基本代码,一个典型的ThreadLocal写法。

public class Looper {
        private static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<>();
        // 1.T get(),2. set(T),3. initialValue(),4. remove() 后2不是必须的 会跟着变量而退出

        public static void prepare() {
            sThreadLocal.set(new Looper());
        }
        public static void loop() {
            Looper looper = myLooper();
            for (;;) {
                //...
            }
        }
        public static Looper myLooper() {
            return sThreadLocal.get();
        }
    }

    prepareMainLoope() //add 1
    MessageQueue //add 2

  除了这个代码,外加一个初始化prepareMainLoope(),这个会在Activity/service初始化的时候,就已经给它初始化一个mainLooper对象;再外加一个 MessageQueue的队列用于loop()里面使用。基本上就是Looper的全部核心代码了。
  Looper在使用的时候,在线程run()的内部prepare()时创建了一个新的Looper对象绑定到这个线程上。然后loop(),就是死循环干活,这个里面可以拿到当前的looper,Looper的成员变量Queue也可以拿到使用。对于当前线程来说,大家只要用Looper.myLooper()就能拿到该线程的looper对象,其他线程是无法接触到的。而且它的释放我们也不用关注了,线程同步问题也不会产生。
  
8. HandlerThread:

扫描二维码关注公众号,回复: 1100353 查看本文章

  HandlerThread其实就是Thread, 在thread run()起来里面,最基本的封装了Looper.prepare()和loop()。使用了HandlerThread+Handler来写代码,我们就基本上不用接触Looper了,不需要关注ThreadLocal在这里面的起到的作用。这就是封装代码的魅力,我们可以把他当做黑盒,某框架或者某机制提供的就是一种稳定易用的技术。但是有追求进一步了解的工程师还是需要去理解Looper, MessageQueue.
  
9. Handler+Thread/HandlerThread:
  使用略,满大街都是。原理:https://blog.csdn.net/jzlhll123/article/details/80396252
  
10. AsyncTask:

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

            @Override
            protected String doInBackground(String... params) { //子线程
                return null;
            }

            @Override
            protected void onPreExecute() { //主线程before doInBackground
            }

            @Override
            protected void onPostExecute(String result) { //主线程 after doInbackground
            }

            @Override
            protected void onProgressUpdate(Integer... values) { //主线程
            }
            //onCancelled onCancelled(result)
        }

  从接口上看,我们可以推测AsyncTask的源代码内部有使用到Handler来封装,将开始,结束和中间状态通过Handler通知到主线程来实现。
  从源码看,AsyncTask内部主要变量与方法:

//变量
THREAD_POOL_EXECUTOR(Executor) //用于给下面一个线程池对象调度使用
sDefaultExecutor(SerialExecutor) //执行我们丢进去的线程,事实上,不论连续多少个线程,每次只有1个(据说老版本有5个)
mWorker(callable) //给下面FutureTask服务
mFeature(FutureTask) //用于查询当前AsyncTask的状态
sHandler(Handler) //getMainHandler()创建出来的,asyncTask建议在主线程里面新建,用来调用那些主线程回调方法
//function
execute(Runnable) //将一个我们的runnable传进去,让sDefaultExecutor处理
execute(...String) //将定义好的参数传进去,则会将DoInBackgound封装到mWorker->mFuture里面去,当调用execute的时候,将mFuture拿给sDefaultExecutor去排队执行

  内部含有一个THREAD_POOL_EXECUTOR线程池(CPU+1/CPU*2 + 1)用来并行运行;内部还有一个sDefaultExecutor是内部类新建的全局变量,用来将每次丢入它的Runnable,添加上串行操作又封装了一次,已保证串行运行。然后将它丢给THREAD_POOL_EXECUTOR去具体的执行,组成了一个复合串行线程池。我们只能接触到sDefault被它控制成串行执行。
  当调用execute的时候,会被标记为Running, 后续的execute(…String)就将抛出crash;而如果已经完成也会抛出完成crash。
  如果只有一个AsyncTask对象,该对象只能执行execute一次(Runnable不算),再次execute会报错;
  如果你new了多个AsyncTask的对象,各自去execute(…string)是没有问题的;因为上面讲到内部的线程池都是static的,保证同一进程中唯一性,所有的AsyncTask共有一个线程池,execute的runnable会排队串行执行完成。
  所以,建议,AsyncTask的使用,作用于那种onCreate()/onResume()就使用下,后续不再使用的,某种一次性的需求,或者任务可以排队等待的,不急的,操作。并且,如果你想多次执行,需要多次new这个AsyncTask对象。 复杂一点的逻辑还是建议使用Handler。
  
11. IntentService:
  IntentService继承自Service,是一个经过包装的轻量级的Service,一般来讲也就不bindSevice()了。用来接收并处理通过Intent传递的异步请求。他的回调方法onHandleIntent(intent)就是用来干异步的活。
  客户端通过调用startService(Intent)启动一个IntentService,内部会创建HandlerThread+Handler,利用一个子线程依次处理顺序过来的请求,处理完成后自动结束Service。如果该service没有结束则是本对象内继续执行,否则就新建一个service,处理完结束。

猜你喜欢

转载自blog.csdn.net/jzlhll123/article/details/80482875