复习

作者: 往事一块六毛八 | 来源:发表于2021-03-25 12:11 被阅读0次

    一:ThreadLocal

    • 作用:为每个线程提供一个特定空间(即该变量),以保证该线程独享的资源
    • 应用场景:隔离线程&防止线程间数据资源共享
    • 注意:
      1:使每个线程都可以独立的改变自己空间的资源(即存储的值)而不会和其他线程的资源冲突
      2:一个变量只能被同一个线程读、写。若两个线程同时执行一段含有一个Threadlocal变量引用的代码,它们也无法访问到对方的ThreadLocal变量

    具体使用

     public class ThreadLocalTest {
    
            // 测试代码
            public static void main(String[] args){
                // 新开2个线程用于设置 & 获取 ThreadLoacl的值
                MyRunnable runnable = new MyRunnable();
                new Thread(runnable, "线程1").start();
                new Thread(runnable, "线程2").start();
            }
    
            // 线程类
            public static class MyRunnable implements Runnable {
    
                // 创建ThreadLocal & 初始化
                private ThreadLocal<String> threadLocal = new ThreadLocal<String>(){
                    @Override
                    protected String initialValue() {
                        return "初始化值";
                    }
                };
    
                @Override
                public void run() {
    
                    // 运行线程时,分别设置 & 获取 ThreadLoacl的值
                    String name = Thread.currentThread().getName();
                    threadLocal.set(name + "的threadLocal"); // 设置值 = 线程名
                    try {
                        Thread.sleep(1000);//为了判断线程1到底是不是取了线程2里面的值
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(name + ":" + threadLocal.get());
                }
            }
        }
    输出结果:
    线程1:线程1的threadLocal
    线程2:线程2的threadLocal
    
    // 从上述结果看出,在2个线程分别设置ThreadLocal值 & 分别获取,结果并未互相干扰
    

    原理

    
    // ThreadLocal的源码
    
    public class ThreadLocal<T> {
    
        ...
    
      /** 
        * 设置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()进行操作
    
           ...
    }
    

    核心原理:
    存值:获取当前线程的ThreadLocalMap对象,key是当前ThreadLocal实例,value是该线程设置的存储在ThreadLocal变量的值
    取值:根据当前线程获取区ThreadLocalMap对象,然后存储的值。

    实际开发中应用的例子

    主线程跟子线程通过spliteHelper获取数据的问题。

    二:Handler机制

    应用:

    子线程中需要更新UI的消息传递到UI线程中,从而实现子线程对UI的更新处理,最终实现异步消息的处理,从而避免线程操作不安全的问题

    相关概念

    • Message:线程之间通讯的数据单元
    • MessageQueue:消息存储的队列,是一种数据结构(先进先出)
    • Handler:
      1:主线程与子线程的通信媒介(将消息添加到消息队列)
      2:线程消息的主要处理者(处理循环器Looper分派过来的消息)
    • Looper:消息队列与处理者的通信媒介
      1:消息获取:循环取出消息队列的消息
      2:消息分发:将取出的消息发送给对应的处理者

    使用方式

    • Handler.sendMessage()
    • Handler.post()
      二者区别:
      1:不需外部创建消息对象,而是内部根据传入的Runnable对象 封装消息对象
      2:回调的消息处理方法是:复写Runnable对象的run()

    源码分析

    image.png image.png
    image.png image.png image.png

    消息的阻塞跟唤醒

    阻塞和延时,主要是next()中nativePollOnce(ptr, nextPollTimeoutMillis)调用naive方法操作管道,由nextPollTimeoutMillis决定是否需要阻塞nextPollTimeoutMillis为0的时候表示不阻塞,为-1的时候表示一直阻塞直到被唤醒,其他时间表示延时。

    简单理解阻塞和唤醒
    就是在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce()方法里,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。
    这里采用的epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。 所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。 从阻塞到唤醒,消息切换

    三:线程池

    • 定义
      一块缓存了一定线程数量的区域
    • 作用
      1:复用线程
      2:管理线程(统一分配,调优和监控;控制线程池的最大并发数)
    • 优点
      1:降低因线程的创建/销毁所带来的性能开销
      2:提高线程响应速度/执行效率
      3:提高对线程的管理

    工作原理

    • corePoolSize:核心线程数
    • maximumPoolSize:线程池所能容纳的最大线程数
    • keepAliveTime:非核心线程闲置超时时长
    • TimeUnit unit:指定keepAliveTime参数的时间单位
    • workQueue:任务队列
    • threadFactory:线程工程
      作用:为线程池创建新线程

    ThreadPoolExecutor类 = 线程池的真正实现类

    // 创建线程池对象如下
    // 通过 构造方法 配置核心参数
       Executor executor = new ThreadPoolExecutor( 
                                                  CORE_POOL_SIZE,
                                                  MAXIMUM_POOL_SIZE,
                                                  KEEP_ALIVE,
                                                  TimeUnit.SECONDS, 
                                                  sPoolWorkQueue,
                                                  sThreadFactory 
                                                   );
    

    执行原理

    image.png

    使用流程

    // 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()
    

    常见的4类功能线程池

    • 定长线程池(FixedThreadPool)

    通过Executors的newFixedThreadPool方法来创建。它是一种线程数量固定的线程池,当线程处于空闲状态时,它们并不会被收回,除非线程池关闭了。当所有的线程都处于活动状态时,新任务都会处于等待状态,直到有线程空闲出来。

    // 1. 创建定长线程池对象 & 设置线程池线程数量固定为3
    ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
    
    // 2. 创建好Runnable类线程对象 & 需执行的任务
    Runnable task =new Runnable(){
      public void run(){
        System.out.println("执行任务啦");
         }
        };
            
    // 3. 向线程池提交任务:execute()
    fixedThreadPool.execute(task);
            
    // 4. 关闭线程池
    fixedThreadPool.shutdown();
    
    • 定时线程池(ScheduledThreadPool )

    通过Executors的newScheduledThreadPool方法来创建。它的核心线程数量是固定的,而非核心线程数量是没有限制的,并且当核心线程闲置时会被立即收回。ScheduledThreadPoll这类线程主要用于执行定时任务和具有固定周期的重复任务

    // 1. 创建 定时线程池对象 & 设置线程池线程数量固定为5
    ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
    
    // 2. 创建好Runnable类线程对象 & 需执行的任务
    Runnable task =new Runnable(){
           public void run(){
                  System.out.println("执行任务啦");
              }
        };
    // 3. 向线程池提交任务:schedule()
    scheduledThreadPool.schedule(task, 1, TimeUnit.SECONDS); // 延迟1s后执行任务
    scheduledThreadPool.scheduleAtFixedRate(task,10,1000,TimeUnit.MILLISECONDS);// 延迟10ms后、每隔1000ms执行任务
    
    // 4. 关闭线程池
    scheduledThreadPool.shutdown();
    
    • 可缓存线程池(CachedThreadPool)

    通过Executors的newCachedThreadPool方法来创建。它是一种线程数量不定的线程池,它只有非核心线程,并且最大线程数为Integer.MAX_VALUE。由于Integer.MAX_VALUE是一个很大的数,实际上就相当于最大线程数可以任意大。当线程池中的线程都处于活动状态时,线程池会创建新的线程来处理新任务,否则就会利用空闲的线程来处理新的任务。线程池中的空闲线程都有超时机制,这个超时时长为60秒,超过60秒闲置线程就会被回收。和FixedThreadPool不同的是,CachedThreadPool的任务队列其实相当于一个空集合,这将导致任何任务都会立即被执行,因为这种情况下SynchronousQueue是无法插入任务的。SynchronousQueue是一个非常特殊的队列,很多情况下可以理解为一个无法存储元素的队列(实际中很少使用)。从CachedThreadPool的特性来看这类线程池比较适合执行大量的耗时较少的任务。当整个线程池都处于闲置状态时,线程池中的线程都会超时而被停止,这个时候CachedThreadPool之中是没有任何线程的,它几乎不占用任何系统资源的。

    // 1. 创建可缓存线程池对象
    ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
    
    // 2. 创建好Runnable类线程对象 & 需执行的任务
    Runnable task =new Runnable(){
      public void run(){
            System.out.println("执行任务啦");
                }
        };
    
    // 3. 向线程池提交任务:execute()
    cachedThreadPool.execute(task);
    
    // 4. 关闭线程池
    cachedThreadPool.shutdown();
    
    //当执行第二个任务时第一个任务已经完成
    //那么会复用执行第一个任务的线程,而不用每次新建线程。
    
    • 单线程化线程池(SingleThreadExecutor)

    通过Executors的newSingleThreadExecutor方法来创建。这类线程池内部只有一个核心线程,它确保所有的任务都在同一个线程中顺序执行。SingleThreadExecutor的意义在于统一所有的外界任务到一个线程中,这使得在这些任务之间不需要处理线程同步的问题

    // 1. 创建单线程化线程池
    ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
    
    // 2. 创建好Runnable类线程对象 & 需执行的任务
    Runnable task =new Runnable(){
      public void run(){
            System.out.println("执行任务啦");
                }
        };
    
    // 3. 向线程池提交任务:execute()
    singleThreadExecutor.execute(task);
    
    // 4. 关闭线程池
    singleThreadExecutor.shutdown();
    
    

    几种线程池对比

    image.png

    四:asyncTask

    • 定义:一个Android已经封装好的轻量级异步类
    • 作用:
      1:实现多线程:在工作线程中执行耗时任务
      2:异步通信,消息传递

    类跟方法的介绍

    public abstract class AsyncTask<Params, Progress, Result> { 
     ... 
    }
    
    // 类中参数为3种泛型类型
    // 整体作用:控制AsyncTask子类执行线程任务时各个阶段的返回类型
    // 具体说明:
        // a. Params:开始异步任务执行时传入的参数类型,对应excute()中传递的参数
        // b. Progress:异步任务执行过程中,返回下载进度值的类型
        // c. Result:异步任务执行完成后,返回的结果类型,与doInBackground()的返回值类型保持一致
    // 注:
        // a. 使用时并不是所有类型都被使用
        // b. 若无被使用,可用java.lang.Void类型代替
        // c. 若有不同业务,需额外再写1个AsyncTask的子类
    }
    

    核心方法

    image.png

    方法执行顺序


    image.png

    案例

    public class MainActivity extends AppCompatActivity {
    
        // 线程变量
        MyTask mTask;
    
        // 主布局中的UI组件
        Button button,cancel; // 加载、取消按钮
        TextView text; // 更新的UI组件
        ProgressBar progressBar; // 进度条
        
        /**
         * 步骤1:创建AsyncTask子类
         * 注:
         *   a. 继承AsyncTask类
         *   b. 为3个泛型参数指定类型;若不使用,可用java.lang.Void类型代替
         *      此处指定为:输入参数 = String类型、执行进度 = Integer类型、执行结果 = String类型
         *   c. 根据需求,在AsyncTask子类内实现核心方法
         */
        private class MyTask extends AsyncTask<String, Integer, String> {
    
            // 方法1:onPreExecute()
            // 作用:执行 线程任务前的操作
            @Override
            protected void onPreExecute() {
                text.setText("加载中");
                // 执行前显示提示
            }
    
    
            // 方法2:doInBackground()
            // 作用:接收输入参数、执行任务中的耗时操作、返回 线程任务执行的结果
            // 此处通过计算从而模拟“加载进度”的情况
            @Override
            protected String doInBackground(String... params) {
    
                try {
                    int count = 0;
                    int length = 1;
                    while (count<99) {
    
                        count += length;
                        // 可调用publishProgress()显示进度, 之后将执行onProgressUpdate()
                        publishProgress(count);
                        // 模拟耗时任务
                        Thread.sleep(50);
                    }
                }catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
                return null;
            }
    
            // 方法3:onProgressUpdate()
            // 作用:在主线程 显示线程任务执行的进度
            @Override
            protected void onProgressUpdate(Integer... progresses) {
    
                progressBar.setProgress(progresses[0]);
                text.setText("loading..." + progresses[0] + "%");
    
            }
    
            // 方法4:onPostExecute()
            // 作用:接收线程任务执行结果、将执行结果显示到UI组件
            @Override
            protected void onPostExecute(String result) {
                // 执行完毕后,则更新UI
                text.setText("加载完毕");
            }
    
            // 方法5:onCancelled()
            // 作用:将异步任务设置为:取消状态
            @Override
            protected void onCancelled() {
    
                text.setText("已取消");
                progressBar.setProgress(0);
    
            }
        }
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
            // 绑定UI组件
            setContentView(R.layout.activity_main);
    
            button = (Button) findViewById(R.id.button);
            cancel = (Button) findViewById(R.id.cancel);
            text = (TextView) findViewById(R.id.text);
            progressBar = (ProgressBar) findViewById(R.id.progress_bar);
    
            /**
             * 步骤2:创建AsyncTask子类的实例对象(即 任务实例)
             * 注:AsyncTask子类的实例必须在UI线程中创建
             */
            mTask = new MyTask();
    
            // 加载按钮按按下时,则启动AsyncTask
            // 任务完成后更新TextView的文本
            button.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
    
                    /**
                     * 步骤3:手动调用execute(Params... params) 从而执行异步线程任务
                     * 注:
                     *    a. 必须在UI线程中调用
                     *    b. 同一个AsyncTask实例对象只能执行1次,若执行第2次将会抛出异常
                     *    c. 执行任务中,系统会自动调用AsyncTask的一系列方法:onPreExecute() 、doInBackground()、onProgressUpdate() 、onPostExecute()
                     *    d. 不能手动调用上述方法
                     */
                    mTask.execute();
                }
            });
    
            cancel = (Button) findViewById(R.id.cancel);
            cancel.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    // 取消一个正在执行的任务,onCancelled方法将会被调用
                    mTask.cancel(true);
                }
            });
    
        }
    
    }
    

    源码解析

    主要是调用线程池执行耗时任务,然后用handler跟新UI的

    五 Synchronized关键字

    • 作用:
      保证同一时刻最多只有一个线程执行被Synchronized修饰的方法/代码(即其他线程 必须等待当前线程执行完该方法 / 代码块后才能执行该方法 / 代码块)

    • 应用场景
      1:修饰实例方法
      2:修饰代码块
      3:修饰 静态方法

    • 锁对象设置
      1:修饰代码块时,需1个reference对象作为锁的对象
      2:修饰实例方法时,默认的锁对象=当前实例对象
      3:修饰静态方法时,默认的锁对象=当前类的Class对象

    • 注意
      1:Java类中,实例对象会有多个,但只有1个Class对象(即类的不同实例共享该类的Class对象)
      2:静态方法/实利方法上的锁默认不一样

      • 若同步则需要制定两把锁
      • 静态方法加锁能和其他静态方法加锁的进行互斥
    • 锁的分类

    1:对象锁
    含synchronized方法/代码块的实利对象
    2:方法锁
    使用synchronized修饰的方法
    3:类锁
    使用synchronized修饰静态方法/代码块

    image.png

    六:volatile

    https://blog.csdn.net/xdzhouxin/article/details/81192344

    七:MVVM架构

    image.png

    2:MVX之间的区别


    image.png
    • mvp具体思路:
      model层拿到数据,交给present,present层与view层相互通信,通过接口的方式进行数据的回调。
    • mvvm具体思路:
      model层拿到数据,viewmodel持有model的引用获取到model层的数据,view层通过livedate感知数据,在通过databinding绑定数据。

    相关文章

      网友评论

          本文标题:复习

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