• 多线程知识汇总


    IntentService 
    多线程的应用在Android  开发中是非常常见的,常用方法主要有:
    • 集成Thread类
    • 实现Runnable接口
    • AsyncTask
    • Handler
    • HandlerThread
    • IntentService
    IntentService
    定义: Android 里的一个封装类,继承四大组件之一 service
    作用:处理异步请求 & 实现多线程
    使用场景:线程任务 按照顺序,在后台执行。
            最常见的场景,离线下载,但是不符合多个数据同时请求的场景,所有任务都在同一个Thread looper 里执行。
    特别注意
    1. 若启动IntentService多次,那么每个耗时操作则以队列的方式在 IntentService 的 onHandleIntent回调方法中依次执行,执行完自动结束。
    问题1:IntentService 如何单独开启1个新的工作线程
    @Override
    public void onCreate() {
        super.onCreate();
        
        // 1. 通过实例化HandlerThread新建线程 & 启动;故 使用IntentService时,不需额外新建线程
        // HandlerThread继承自Thread,内部封装了 Looper
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        thread.start();
      
        // 2. 获得工作线程的 Looper & 维护自己的工作队列
        mServiceLooper = thread.getLooper();
        // 3. 新建mServiceHandler & 绑定上述获得Looper
        // 新建的Handler 属于工作线程 ->>分析1
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }
       /**
         * 分析1:ServiceHandler源码分析
         **/
         private final class ServiceHandler extends Handler {
             // 构造函数
             public ServiceHandler(Looper looper) {
             super(looper);
           }
            // IntentService的handleMessage()把接收的消息交给onHandleIntent()处理
            @Override
             public void handleMessage(Message msg) {
      
              // onHandleIntent 方法在工作线程中执行
              // onHandleIntent() = 抽象方法,使用时需重写 ->>分析2
              onHandleIntent((Intent)msg.obj);
              // 执行完调用 stopSelf() 结束服务
              stopSelf(msg.arg1);
        }
    }
       /**
         * 分析2: onHandleIntent()源码分析
         * onHandleIntent() = 抽象方法,使用时需重写
         **/
          @WorkerThread
          protected abstract void onHandleIntent(Intent intent);
    问题2 IntentService 如何通过onStartCommand() 将intent传递给服务, 依次插入到工作队列中的。
    /**
      * onStartCommand()源码分析
      * onHandleIntent() = 抽象方法,使用时需重写
      **/
      public int onStartCommand(Intent intent, int flags, int startId) {
        // 调用onStart()->>分析1
        onStart(intent, startId);
        return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
    }
    /**
      * 分析1:onStart(intent, startId)
      **/
      public void onStart(Intent intent, int startId) {
        // 1. 获得ServiceHandler消息的引用
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        // 2. 把 Intent参数 包装到 message 的 obj 发送消息中,
        //这里的Intent  = 启动服务时startService(Intent) 里传入的 Intent
        msg.obj = intent;
        // 3. 发送消息,即 添加到消息队列里
        mServiceHandler.sendMessage(msg);
    }
    源码总结:
    IntentService 本质 是  service + HanderThread
    1. 通过handlerThread单独开启一个工作线程,IntentService
    2. 创建一个内部Handler:ServiceHandler
    3. 绑定serviceHandler 与 IntentService
    4. 通过onStartCommand() 传递intent到ServiceHandler,依次插入Intent到工作队列中。并且逐个发送到给HanderIntent
    5. 通过onHandlerIntent ()依次处理所有Intent对象所对应的任务。
    线程 关键字: Synchronized 
    它是java中一个关键字
    被他修饰的方法可以保证同一时刻最多只有一个线程执行此方法,其他线程必须等待当前线程执行完该方法/代码块后才能执行该方法。
    保证线程安全,解决多线程中的并发同步问题 实现的是阻塞型并发
    原理: 
    • 依赖JVM实现同步
    • 底层通过一个监视器对象 monitor 完成,wait()、notify()等方法也依赖于  这个监视器对象 monitor  
    monitor 监视器锁的本质,依赖于底层操作系统的互斥锁 实现。
    其他控制并发/ 现成同步的方法
    Lock       ReentrantLock
    CAS   compre and Swap   乐观锁
    // CAS的操作参数
    内存位置(A)
    预期原值(B)
    预期新值(C)
    // 使用CAS解决并发的原理:
    // 1. 首先比较A、B,若相等,则更新A中的值为C、返回True;若不相等,则返回false;
    // 2. 通过死循环,以不断尝试尝试更新的方式实现并发
    // 伪代码如下
    public boolean compareAndSwap(long memoryA, int oldB, int newC){
        if(memoryA.get() == oldB){
            memoryA.set(newC);
            return true;
        }
        return false;
    }
    优点: 耗费资源少,相对于synchronized  ,省去了挂起线程,恢复线程的开销,但是迟迟得不到更新,死循环对CPU资源也是一种浪费
    考点:线程与进程的区别
    假设:进程 = 桌子,单线程 = 1个人吃饭
    • 单进程、单线程:一个人在一个桌子上吃饭
    • 单进程、多线程:多个人在同一个桌子上一起吃饭
    • 多进程、单线程:多个人每个人在自己的桌子上吃饭
    一个进程可以拥有多个线程  ,多个线程可以共享他们隶属的同意进程的系统资源

    ThreadLocal

    // 1. 直接创建对象
    private ThreadLocal myThreadLocal = new ThreadLocal()
    // 2. 创建泛型对象
    private ThreadLocal myThreadLocal = new ThreadLocal();
    // 3. 创建泛型对象 & 初始化值
    // 指定泛型的好处:不需要每次对使用get()方法返回的值作强制类型转换
    private ThreadLocal myThreadLocal = new ThreadLocal() {
        @Override
        protected String initialValue() {
            return "This is the initial value";
        }
    };
    // 特别注意:
    // 1. ThreadLocal实例 = 类中的private、static字段
    // 2. 只需实例化对象一次 & 不需知道它是被哪个线程实例化
    // 3. 每个线程都保持 对其线程局部变量副本 的隐式引用
    // 4. 线程消失后,其线程局部实例的所有副本都会被垃圾回收(除非存在对这些副本的其他引用)
    // 5. 虽然所有的线程都能访问到这个ThreadLocal实例,但是每个线程只能访问到自己通过调用ThreadLocal的set()设置的值
    // 即 哪怕2个不同的线程在同一个`ThreadLocal`对象上设置了不同的值,他们仍然无法访问到对方的值
    // 1. 设置值:set()
    // 需要传入一个Object类型的参数
    myThreadLocal.set("初始值”);
    // 2. 读取ThreadLocal变量中的值:get()
    // 返回一个Object对象
    String threadLocalValue = (String) myThreadLocal.get();
    实现原理:
    TheradLocal 类中有一个map(ThreadLocalMap) :  用于存储每个线程 设置的存储在 ThreadLocal 变量的值
    1、ThreadLocalMap的键 key = 当前ThreadLocal 实例、 值value = 该线程设置存储在ThreadLocal变量的值
    2、该key 是ThreadLocal对象的弱引用,当要抛弃掉 ThreadLocal对象时,垃圾收集器会忽略该key 的引用二清理掉ThreadLoacl对象。
    // ThreadLocal的源码
    public class ThreadLocal {
        ...
      /**
        * 设置ThreadLocal变量引用的值
        *  ThreadLocal变量引用 指向 ThreadLocalMap对象,即设置ThreadLocalMap的值 = 该线程设置的存储在ThreadLocal变量的值
        *  ThreadLocalMap的键Key = 当前ThreadLocal实例
        *  ThreadLocalMap的值 = 该线程设置的存储在ThreadLocal变量的值
        **/  
        public void set(T value) {
          
            // 1. 获得当前线程
            Thread t = Thread.currentThread();
            // 2. 获取该线程的ThreadLocalMap对象 ->>分析1
            ThreadLocalMap map = getMap(t);
            // 3. 若该线程的ThreadLocalMap对象已存在,则替换该Map里的值;否则创建1个ThreadLocalMap对象
            if (map != null)
                map.set(this, value);// 替换
            else
                createMap(t, value);// 创建->>分析2
        }
      /**
        * 获取ThreadLocal变量里的值
        * 由于ThreadLocal变量引用 指向 ThreadLocalMap对象,即获取ThreadLocalMap对象的值 = 该线程设置的存储在ThreadLocal变量的值
        **/
        public T get() {
            // 1. 获得当前线程
            Thread t = Thread.currentThread();
            // 2. 获取该线程的ThreadLocalMap对象
            ThreadLocalMap map = getMap(t);
            // 3. 若该线程的ThreadLocalMap对象已存在,则直接获取该Map里的值;否则则通过初始化函数创建1个ThreadLocalMap对象
            if (map != null) {
                ThreadLocalMap.Entry e = map.getEntry(this);
                if (e != null)
                    return (T)e.value; // 直接获取值
            }
            return setInitialValue(); // 初始化
        }
      /**
        * 初始化ThreadLocal的值
        **/
        private T setInitialValue() {
            T value = initialValue();
            // 1. 获得当前线程
            Thread t = Thread.currentThread();
            // 2. 获取该线程的ThreadLocalMap对象
            ThreadLocalMap map = getMap(t);
             // 3. 若该线程的ThreadLocalMap对象已存在,则直接替换该值;否则则创建
            if (map != null)
                map.set(this, value); // 替换
            else
                createMap(t, value); // 创建->>分析2
            return value;
        }
      /**
        * 分析1:获取当前线程的threadLocals变量引用
        **/
        ThreadLocalMap getMap(Thread t) {
            return t.threadLocals;
        }
      /**
        * 分析2:创建当前线程的ThreadLocalMap对象
        **/
        void createMap(Thread t, T firstValue) {
        // 新创建1个ThreadLocalMap对象 放入到 Thread类的threadLocals变量引用中:
            // a. ThreadLocalMap的键Key = 当前ThreadLocal实例
            // b. ThreadLocalMap的值 = 该线程设置的存储在ThreadLocal变量的值
            t.threadLocals = new ThreadLocalMap(this, firstValue);
            // 即 threadLocals变量 属于 Thread类中 ->> 分析3
        }
      
        ...
    }
      /**
        * 分析3:Thread类 源码分析
        **/
        public class Thread implements Runnable {
           ...
           ThreadLocal.ThreadLocalMap threadLocals = null;
           // 即 Thread类持有threadLocals变量
           // 线程类实例化后,每个线程对象拥有独立的threadLocals变量变量
           // threadLocals变量在 ThreadLocal对象中 通过set() 或 get()进行操作
           ...
    }
    问题: ThreadLoacl 如何做到现成安全的
    • 每个线程拥有自己独立的Threadlocals变量,也就是ThreadLocalMap
    • 每当线程访问ThreadLocals变量时,访问的都是各自线程自己的 ThreadLocalMap变量(键值对)
    • ThreadLocalMap变量的键key = 唯一 = ThreadLocal 实例
    问题:TheadLocal 与同步机制的区别
    线程池 ThreadPool
    定义: 一块缓存了一定线程数量的区域
    作用:复用线程  管理线程(统一分配,监控,  控制线程池最大并发数)
    优点:降低因线程的创建 销毁所带来的性能开销 - 可以重用缓存在线程池的线程。提高线程响应速度,执行效率。提高线程管力度
    线程池有六个核心参数
    // 创建线程池对象如下
    // 通过 构造方法 配置核心参数
       Executor executor = new ThreadPoolExecutor(
                                                  CORE_POOL_SIZE,
                                                  MAXIMUM_POOL_SIZE,
                                                  KEEP_ALIVE,
                                                  TimeUnit.SECONDS,
                                                  sPoolWorkQueue,
                                                  sThreadFactory
                                                   );
    // 构造函数源码分析
        public ThreadPoolExecutor (int corePoolSize,
                                   int maximumPoolSize,
                                   long keepAliveTime,
                                   TimeUnit unit,
                                   BlockingQueue,
                                   ThreadFactory threadFactory )
    ——————————————————————————————————————————————————————————————————————————————
    // 1. 创建线程池
       // 创建时,通过配置线程池的参数,从而实现自己所需的线程池
       Executor threadPool = new ThreadPoolExecutor(
                                                  CORE_POOL_SIZE,
                                                  MAXIMUM_POOL_SIZE,
                                                  KEEP_ALIVE,
                                                  TimeUnit.SECONDS,
                                                  sPoolWorkQueue,
                                                  sThreadFactory
                                                  );
        // 注:在Java中,已内置4种常见线程池,下面会详细说明
    // 2. 向线程池提交任务:execute()
        // 说明:传入 Runnable对象
           threadPool.execute(new Runnable() {
                @Override
                public void run() {
                    ... // 线程执行任务
                }
            });
    // 3. 关闭线程池shutdown()
      threadPool.shutdown();
      
      // 关闭线程的原理
      // a. 遍历线程池中的所有工作线程
      // b. 逐个调用线程的interrupt()中断线程(注:无法响应中断的任务可能永远无法终止)
      // 也可调用shutdownNow()关闭线程:threadPool.shutdownNow()
      // 二者区别:
      // shutdown:设置 线程池的状态 为 SHUTDOWN,然后中断所有没有正在执行任务的线程
      // shutdownNow:设置 线程池的状态 为 STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表
      // 使用建议:一般调用shutdown()关闭线程池;若任务不一定要执行完,则调用shutdownNow()
    根据不同参数的不同配置,java中最常见的线程池有四种
    • 定长线程池(FixedThreadPool)
    • 定时线程池(ScheduledThreadPool )
    • 可缓存线程池(CachedThreadPool)
    • 单线程化线程池(SingleThreadExecutor)
  • 相关阅读:
    Cannot read properties of null bug解决方法
    LabVIEW报错“仪器IO助手未正确安装”
    远程服务器的Docker环境遇到问题,无法调试
    vue中异步数据获取(确保数据被获取)
    MMKV源码解读与理解
    activiti-spring-app-process 源码分析
    【文本挖掘】业绩预告原因·其一
    【JVM笔记】HotSpot为何解释器与编译器共存
    网络设计与网络设备配置,网络设计需要哪些设备
    vue安装electron事项
  • 原文地址:https://blog.csdn.net/qq_30974087/article/details/133066675