复习

作者: 往事一块六毛八 | 来源:发表于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绑定数据。

相关文章

  • 复习,复习,复习

    复习,复习,复习 老师让我今天就进行职业规划师初级证书的考试,我…… 总而言之,我给推到明天了。今天的文就是我的课...

  • 复习复习再复习

    安顿老师点评了我们的作业,同时告诉我们“很多人说,课程里学了很多知识,但是写的时候还是感觉憋不出来,下手...

  • 复习复习再复习

    复习复习再复习,默写默写再默写 合同的效力 一、合同的定义 合同的效力又称合同的法律效力,是指已经成立的合同对合同...

  • 复习,复习,再复习!

    “日清-周结-月考”循环学习法 日清,就是当天的学习任务当天完成,做到“三不”——“不等”、“不靠”、“不拖”,严...

  • 复习复习再复习

    距离考试只剩下不到十个小时了,回顾我这两天的复习,感觉什么都复习了,又感觉什么都没复习,可能一切都是心理作用。 在...

  • 复习复习

    距离期末考试还有一天半的时间,距离回家还有两天的时间,然而我还没有复习完,小组的VRP 还没弄完,一直在修改,还有...

  • 复习复习

    这个学期快结束了,到校也快一个月了,复习的号角声也应该吹响。 大早上看着图书馆门前一个个背着书包的身影,就...

  • 复习!复习!

    2018年过去了,我们迎来了2019年,当然,期末考试自然也向我们走来,瞧!最近我们大家都在认真复习着。每天早上我...

  • 复习复习

    函数,单射,双射,满射,数列的极限,函数极限,保号性,唯一性,有界性,无穷大无穷小,极限存在准则,有限个无穷小的和...

  • 复习复习

    连着三周的实习周全都结束了,现在可以说是静下心来好好复习不再有任何杂念了,在这最后一周的实习周中我们是见识到了克...

网友评论

      本文标题:复习

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