[Java基础系列] 第十章 进程间的通信 之 Java/Android多线程开发(二)

132 阅读37分钟

(二)Android 多线程开发

在这里插入图片描述

2.1)基础使用

1、继承Thread类

(1)简介 Thread类是Java中实现多线程的具体类,封装了所需线程操作。在Android开发中用于实现多线程。 注:线程对象&运行线程区别 线程对象是运行线程的实体,用来控制线程行为的唯一方式。线程对象通过线程类实例化创建,负责控制线程的状态,如:运行、睡眠、挂起/停止。 优点 实现简单:只要继承Thread类&复写run()即可实现多线程操作 缺点 局限性大:必须集成Thread类(Java规定单继承,即集成Thread类后不可继承其他类) 不适合资源共享:一个线程=一个实例对象,相对独立无法资源共享 消耗资源:Thread线程=一次性消费品&一个耗时任务。执行完一个耗时操作后,线程会被自动销毁,如果有100个耗时任务则必须开100个线程。多次创建&销毁线程,耗费系统资源 (2)使用 2.1)使用步骤 在这里插入图片描述 2.2)常规使用

// 步骤1:创建线程类 (继承自Thread类)
   class MyThread extends Thread{

// 步骤2:复写run(),内容 = 定义线程行为
    @Override
    public void run(){
    ... // 定义的线程行为
    }
}

// 步骤3:创建线程对象,即 实例化线程类
  MyThread mt=new MyThread(“线程名称”);

// 步骤4:通过 线程对象 控制线程的状态,如 运行、睡眠、挂起  / 停止
// 此处采用 start()开启线程
  mt.start();

2.3)匿名类使用

// 步骤1:采用匿名类,直接 创建 线程类的实例
 new Thread("线程名称") {
                 // 步骤2:复写run(),内容 = 定义线程行为
                    @Override
                    public void run() {       
                  // 步骤3:通过 线程对象 控制线程的状态,如 运行、睡眠、挂起  / 停止   
                      }.start();

2.4)常规&匿名类使用区别 在这里插入图片描述

2、实现Runnable接口

(1)简介 一个与多线程相关的抽象接口,仅定义1个方法=run(),在Android开发中用于实现多线程 适合资源共享:Runnable可被多个线程(Thread实例)共享,适合多线程处理同一资源的情况 灵活:一个类可以继承多个接口,避免集成THread类导致的单继承局限性

补充:Java进阶知识——接口与继承区别 (1)关键字:接口interface,继承extends (2)定义:接口:对功能的描述,继承:具体描述一种类 (3)结构:接口只能定义全局常量、抽象方法,继承可以定义属性方法、常量、变量等等 (4)接口可以实现"多继承",继承只能"单继承" (5)实现接口的类一定要实现接口的抽象方法,继承的类可以调用、重载父类的任意方法

(2)使用 2.1)使用步骤 在这里插入图片描述 注: Java中真正能创建新线程的只有Thread类对象 通过实现Runnable的方式,最终还是通过Thread类对象来创建线程 所以对于 实现了Runnable接口的类,称为 线程辅助类;Thread类才是真正的线程类 2.2)常规使用

// 步骤1:创建线程辅助类,实现Runnable接口
 class MyThread implements Runnable{
    ....
    @Override
// 步骤2:复写run(),定义线程行为
    public void run(){

    }
}

// 步骤3:创建线程辅助对象,即 实例化 线程辅助类
  MyThread mt=new MyThread();

// 步骤4:创建线程对象,即 实例化线程类;线程类 = Thread类;
// 创建时通过Thread类的构造函数传入线程辅助类对象
// 原因:Runnable接口并没有任何对线程的支持,我们必须创建线程类(Thread类)的实例,从Thread类的一个实例内部运行
  Thread td=new Thread(mt);

// 步骤5:通过 线程对象 控制线程的状态,如 运行、睡眠、挂起  / 停止
// 当调用start()方法时,线程对象会自动回调线程辅助类对象的run(),从而实现线程操作
  td.start();

2.3)匿名类使用

    // 步骤1:通过匿名类 直接 创建线程辅助对象,即 实例化 线程辅助类
    Runnable mt = new Runnable() {
                    // 步骤2:复写run(),定义线程行为
                    @Override
                    public void run() {
                    }
                };

                // 步骤3:创建线程对象,即 实例化线程类;线程类 = Thread类;
                Thread mt1 = new Thread(mt, "窗口1");
           
                // 步骤4:通过 线程对象 控制线程的状态,如 运行、睡眠、挂起  / 停止
                mt1.start();

(3)继承Thread类&实现Runnable接口对比 在这里插入图片描述 Android中Thread/Runnable方法使用了java原生的Thread/Runnable的线程形态,详细用法可参考上文Java多线程开发。

3、Handler

见陈小云Android学习之旅:第十章 进程间的通信 之 Handler机制(二)

2.2)复合使用

1、AsyncTask

(1)简介 一个Android已封装好的轻量级异步类,属于抽象类,使用时需要实现子类。用于 实现多线程,如在工作线程中执行耗时任务 异步通信、消息传递,如实现工作线程&主线程(UI线程)之间的通信,即:将工作线程的执行结果传递给主线程,从而在主线程中指向相关UI操作(保证线程安全)

public abstract class AsyncTask<Params, Progress, Result> { 
 ... 
 }

不需使用"任务线程(如Thread类)+Handler"复杂组合,方便实现异步通信 采用线程池的缓存线程+复用线程,避免频繁创建&销毁线程所带来的系统资源开销 (2)类定义 AsyncTask类属于抽象类,即使用时需 实现子类

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的子类
}

(3)核心方法 AsyncTask 核心 & 常用的方法如下: 在这里插入图片描述 方法执行顺序如下 在这里插入图片描述 (4)使用步骤 步骤1:创建AsyncTask子类 a. 继承AsyncTask类 b. 为3个泛型参数指定类型;若不使用,可用java.lang.Void类型代替 c. 根据需求,在AsyncTask子类内实现核心方法

  private class MyTask extends AsyncTask<Params, Progress, Result> {

        ....

      // 方法1:onPreExecute()
      // 作用:执行 线程任务前的操作
      // 注:根据需求复写
      @Override
      protected void onPreExecute() {
           ...
        }

      // 方法2:doInBackground()
      // 作用:接收输入参数、执行任务中的耗时操作、返回 线程任务执行的结果
      // 注:必须复写,从而自定义线程任务
      @Override
      protected String doInBackground(String... params) {

            ...// 自定义的线程任务

            // 可调用publishProgress()显示进度, 之后将执行onProgressUpdate()
             publishProgress(count);
              
         }

      // 方法3:onProgressUpdate()
      // 作用:在主线程 显示线程任务执行的进度
      // 注:根据需求复写
      @Override
      protected void onProgressUpdate(Integer... progresses) {
            ...

        }

      // 方法4:onPostExecute()
      // 作用:接收线程任务执行结果、将执行结果显示到UI组件
      // 注:必须复写,从而自定义UI操作
      @Override
      protected void onPostExecute(String result) {

         ...// UI操作

        }

      // 方法5:onCancelled()
      // 作用:将异步任务设置为:取消状态
      @Override
        protected void onCancelled() {
        ...
        }
  }

步骤2:创建Async子类的实例对象(任务实例) AsyncTask子类的实例必须在UI线程中创建

  MyTask mTask = new MyTask();

步骤3:手动调用execute()从而执行异步线程任务 a.必须在UI线程中调用 b.同一个AsyncTask实例对象只能执行1次,若执行第2次会抛出异常 c.执行任务中,系统会系统会自动调用AsyncTask的一系列方法:onPreExecute() 、doInBackground()、onProgressUpdate() 、onPostExecute() d. 不能手动调用上述方法

mTask.execute();

(5)实例分析 5.1)实例需求 点击按钮 则 开启线程执行线程任务 显示后台加载进度 加载完毕后更新UI组件 期间若点击取消按钮,则取消加载 5.2)代码实现

    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);

        }
    }

(6)问题&解决 6.1)关于生命周期 问题:AsyncTask不与任何组件绑定生命周期 解决:在Activity 或 Fragment中使用 AsyncTask时,最好在Activity 或 Fragment的onDestory()调用 cancel(boolean); 6.2)关于内存泄露 问题:若AsyncTask被声明为Activity的非静态内部类,当Activity需销毁时,会因AsyncTask保留对Activity的引用 而导致Activity无法被回收,最终引起内存泄露 解决:AsyncTask应被声明为Activity的静态内部类 线程任务执行结果丢失 问题:当Activity重新创建时(屏幕旋转 / Activity被意外销毁时后恢复),之前运行的AsyncTask(非静态的内部类)持有的之前Activity引用已无效,故复写的onPostExecute()将不生效,即无法更新UI操作 解决:在Activity恢复时的对应方法 重启 任务线程 (7)源码分析 7.1)原理介绍 AsyncTask的实现原理 = 线程池 + Handler 其中:线程池用于线程调度、复用 & 执行任务;Handler 用于异步通信 其内部封装了2个线程池 + 1个Handler,具体介绍如下: 在这里插入图片描述 7.2)源码分析 根据AsyncTask使用步骤讲解 步骤1:创建AsbncTask子类 该类复写的方法在后续源码中调用 步骤2:创建AsyncTask子类的实例对象(任务实例) 1、具体使用

  MyTask mTask = new MyTask();

2、源码分析:AsyncTask的构造函数

/**
  * 源码分析:AsyncTask的构造函数
  */
  public AsyncTask() {
        // 1. 初始化WorkerRunnable变量 = 一个可存储参数的Callable对象 ->>分析1
        mWorker = new WorkerRunnable<Params, Result>() {
            // 在任务执行线程池中回调:THREAD_POOL_EXECUTOR.execute()
            // 下面会详细讲解
            public Result call() throws Exception {

                // 添加线程的调用标识
                mTaskInvoked.set(true); 

                Result result = null;
                try {
                    // 设置线程的优先级
                    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                    
                    // 执行异步操作 = 耗时操作
                    // 即 我们使用过程中复写的耗时任务
                    result = doInBackground(mParams);

                    Binder.flushPendingCommands();
                } catch (Throwable tr) {
                    
                    mCancelled.set(true);// 若运行异常,设置取消的标志
                    throw tr;
                } finally {
                    
                    // 把异步操作执行的结果发送到主线程
                    // 从而更新UI,下面会详细讲解
                    postResult(result); 
                }
                return result;
            }
        };

        // 2. 初始化FutureTask变量 = 1个FutureTask ->>分析2
        mFuture = new FutureTask<Result>(mWorker) {

            // done()简介:FutureTask内的Callable执行完后的调用方法
            // 作用:复查任务的调用、将未被调用的任务的结果通过InternalHandler传递到UI线程
            @Override
            protected void done() {
                try {

                    // 在执行完任务后检查,将没被调用的Result也一并发出 ->>分析3
                    postResultIfNotInvoked(get());

                } catch (InterruptedException e) {
                    android.util.Log.w(LOG_TAG, e);
                } catch (ExecutionException e) {
                    throw new RuntimeException("An error occurred while executing doInBackground()",
                            e.getCause());
                } catch (CancellationException e) {

                    //若 发生异常,则将发出null
                    postResultIfNotInvoked(null);
                }
            }
        };
    }

/**
  * 分析1:WorkerRunnable类的构造函数
  */
  private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
        // 此处的Callable也是任务;
        // 与Runnable的区别:Callable<T>存在返回值 = 其泛型
        Params[] mParams;
    }

/**
  * 分析2:FutureTask类的构造函数
  * 定义:1个包装任务的包装类
  * 注:内部包含Callable<T> 、增加了一些状态标识 & 操作Callable<T>的接口
  */
  public FutureTask(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;      
    }
    // 回到调用原处

/**
  * 分析3:postResultIfNotInvoked()
  */
  private void postResultIfNotInvoked()(Result result) {
        // 取得任务标记
        final boolean wasTaskInvoked = mTaskInvoked.get();

        // 若任务无被执行,将未被调用的任务的结果通过InternalHandler传递到UI线程
        if (!wasTaskInvoked) {
            postResult(result);
        }
    }

3、总结 创建了1个WorkerRunnable类 的实例对象 & 复写了call()方法 创建了1个FutureTask类 的实例对象 & 复写了 done() 步骤3:手动调用execute(Params...params) 1、具体使用

mTask.execute();

2、源码分析

public final AsyncTask<Params, Progress, Result> execute(Params... params) {

        return executeOnExecutor(sDefaultExecutor, params);
        // ->>分析1

    }

 /**
  * 分析1:executeOnExecutor(sDefaultExecutor, params)
  * 参数说明:sDefaultExecutor = 任务队列 线程池类(SerialExecutor)的对象
  */
  public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,Params... params) {

        // 1. 判断 AsyncTask 当前的执行状态
        // PENDING = 初始化状态
        if (mStatus != Status.PENDING) {
            switch (mStatus) {
                case RUNNING:
                    throw new IllegalStateException("Cannot execute task:"
                            + " the task is already running.");
                case FINISHED:
                    throw new IllegalStateException("Cannot execute task:"
                            + " the task has already been executed "
                            + "(a task can be executed only once)");
            }
        }

        // 2. 将AsyncTask状态设置为RUNNING状态
        mStatus = Status.RUNNING;

        // 3. 主线程初始化工作
        onPreExecute();

        // 4. 添加参数到任务中
        mWorker.mParams = params;

        // 5. 执行任务
        // 此处的exec = sDefaultExecutor = 任务队列 线程池类(SerialExecutor)的对象
        // ->>分析2
        exec.execute(mFuture);
        return this;
    }

/**
  * 分析2:exec.execute(mFuture)
  * 说明:属于任务队列 线程池类(SerialExecutor)的方法
  */
  private static class SerialExecutor implements Executor {
        // SerialExecutor = 静态内部类
        // 即 是所有实例化的AsyncTask对象公有的

        // SerialExecutor 内部维持了1个双向队列;
        // 容量根据元素数量调节
        final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
        Runnable mActive;

        // execute()被同步锁synchronized修饰
        // 即说明:通过锁使得该队列保证AsyncTask中的任务是串行执行的
        // 即 多个任务需1个个加到该队列中;然后 执行完队列头部的再执行下一个,以此类推
        public synchronized void execute(final Runnable r) {
            // 将实例化后的FutureTask类 的实例对象传入
            // 即相当于:向队列中加入一个新的任务
            mTasks.offer(new Runnable() {
                public void run() {
                    try {
                        r.run();
                    } finally {
                        scheduleNext();->>分析3
                    }
                }
            });
            // 若当前无任务执行,则去队列中取出1个执行
            if (mActive == null) {
                scheduleNext();
            }
        }
        // 分析3
        protected synchronized void scheduleNext() {
            // 1. 取出队列头部任务
            if ((mActive = mTasks.poll()) != null) {

                // 2. 执行取出的队列头部任务
                // 即 调用执行任务线程池类(THREAD_POOL_EXECUTOR)->>继续往下看
                THREAD_POOL_EXECUTOR.execute(mActive);
                
            }
        }
    }

3、总结: 执行任务前,通过 任务队列 线程池类(SerialExecutor)将任务按顺序放入到队列中; 通过同步锁 修饰execute()从而保证AsyncTask中的任务是串行执行的,之后的线程任务执行是 通过任务线程池类(THREAD_POOL_EXECUTOR) 进行的。 继续往下分析:THREAD_POOL_EXECUTOR.execute()

/**
  * 源码分析:THREAD_POOL_EXECUTOR.execute()
  * 说明:
  *     a. THREAD_POOL_EXECUTOR实际上是1个已配置好的可执行并行任务的线程池
  *     b. 调用THREAD_POOL_EXECUTOR.execute()实际上是调用线程池的execute()去执行具体耗时任务
  *     c. 而该耗时任务则是步骤2中初始化WorkerRunnable实例对象时复写的call()
  * 注:下面先看任务执行线程池的线程配置过程,看完后请回到步骤2中的源码分析call()
  */

    // 步骤1:参数设置
        //获得当前CPU的核心数
        private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
        //设置线程池的核心线程数2-4之间,但是取决于CPU核数
        private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
        //设置线程池的最大线程数为 CPU核数*2+1
        private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
        //设置线程池空闲线程存活时间30s
        private static final int KEEP_ALIVE_SECONDS = 30;

        //初始化线程工厂
        private static final ThreadFactory sThreadFactory = new ThreadFactory() {
            private final AtomicInteger mCount = new AtomicInteger(1);

            public Thread newThread(Runnable r) {
                return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
            }
        };

        //初始化存储任务的队列为LinkedBlockingQueue 最大容量为128
        private static final BlockingQueue<Runnable> sPoolWorkQueue =
                new LinkedBlockingQueue<Runnable>(128);

    // 步骤2: 根据参数配置执行任务线程池,即 THREAD_POOL_EXECUTOR
    public static final Executor THREAD_POOL_EXECUTOR;

    static {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
                sPoolWorkQueue, sThreadFactory);
        // 设置核心线程池的 超时时间也为30s
        threadPoolExecutor.allowCoreThreadTimeOut(true);
        THREAD_POOL_EXECUTOR = threadPoolExecutor;
    }

    // 请回到步骤2中的源码分析call()

至此,我们回到步骤2中的源码分析call()

/**
  * 步骤2的源码分析:AsyncTask的构造函数
  */
    public AsyncTask() {
        // 1. 初始化WorkerRunnable变量 = 一个可存储参数的Callable对象
        mWorker = new WorkerRunnable<Params, Result>() {

            public Result call() throws Exception {

                // 添加线程的调用标识
                mTaskInvoked.set(true); 

                Result result = null;
                try {
                    // 设置线程的优先级
                    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                    
                    // 执行异步操作 = 耗时操作
                    // 即 我们使用过程中复写的耗时任务
                    result = doInBackground(mParams);

                    Binder.flushPendingCommands();
                } catch (Throwable tr) {
                    
                    mCancelled.set(true);// 若运行异常,设置取消的标志
                    throw tr;
                } finally {
                    
                    // 把异步操作执行的结果发送到主线程
                    // 从而更新UI ->>分析1
                    postResult(result); 
                }
                return result;
            }
        };

        .....// 省略
    }
/**
  * 分析1:postResult(result)
  */
   private Result postResult(Result result) {

        @SuppressWarnings("unchecked")

        // 创建Handler对象 ->> 源自InternalHandler类—>>分析2
        Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
                new AsyncTaskResult<Result>(this, result));
        // 发送消息到Handler中
        message.sendToTarget();
        return result;

    }


/**
  * 分析2:InternalHandler类
  */
    private static class InternalHandler extends Handler {

        // 构造函数
        public InternalHandler() {
            super(Looper.getMainLooper());
            // 获取的是主线程的Looper()
            // 故 AsyncTask的实例创建 & execute()必须在主线程使用
        }

        @Override
        public void handleMessage(Message msg) {

            AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;

            switch (msg.what) {
                // 若收到的消息 = MESSAGE_POST_RESULT
                // 则通过finish() 将结果通过Handler传递到主线程
                case MESSAGE_POST_RESULT:
                    result.mTask.finish(result.mData[0]); ->>分析3
                    break;

                // 若收到的消息 = MESSAGE_POST_PROGRESS
                // 则回调onProgressUpdate()通知主线程更新进度的操作
                case MESSAGE_POST_PROGRESS:
                    result.mTask.onProgressUpdate(result.mData);
                    break;
            }
        }
    }
/**
  * 分析3:result.mTask.finish(result.mData[0])
  */
  private void finish(Result result) {
        // 先判断是否调用了Cancelled()
            // 1. 若调用了则执行我们复写的onCancelled()
            // 即 取消任务时的操作
            if (isCancelled()) {
                onCancelled(result);
            } else {

            // 2. 若无调用Cancelled(),则执行我们复写的onPostExecute(result)
            // 即更新UI操作
                onPostExecute(result);
            }
            // 注:不管AsyncTask是否被取消,都会将AsyncTask的状态变更为:FINISHED
            mStatus = Status.FINISHED;
        }

总结 任务线程池类(THREAD_POOL_EXECUTOR)实际上是1个已配置好的可执行并行任务的线程池 调用THREAD_POOL_EXECUTOR.execute()实际上是调用线程池的execute()去执行具体耗时任务 而该耗时任务则是步骤2中初始化 WorkerRunnable实例对象时复写的call()内容 在call()方法里,先调用 我们复写的doInBackground(mParams)执行耗时操作 再调用postResult(result), 通过 InternalHandler 类 将任务消息传递到主线程;根据消息标识(MESSAGE_POST_RESULT)判断,最终通过finish()调用我们复写的onPostExecute(result),从而实现UI更新操作 7.3)Async源码总结 在这里插入图片描述

2、HandlerThread

(1)介绍 HandlerThread是一个Android已封装好的轻量级异步类,用于实现多线程(在工作线程中执行耗时任务)及异步通信、消息传递(工作线程&主线程之间通信)从而保证线程安全 HandlerThread本质上是通过继承Thread类和封装Handler类的使用,从而使得创建新线程和与其他线程进行通信变得更加方便易用(不需要使用"任务线程(如继承Thread类)+Handler"复杂组合) (2)使用

步骤1:创建HandlerThread实例对象
//传入参数 = 线程名字,作用 = 标记该线程
HandlerThread mHandlerThread = new HandlerThread("handlerThread");
步骤2:启动线程
mHandlerThread.start()
步骤3:创建工作线程Handler&复写handleMessage()
// 作用:关联HandlerThread的Looper对象、实现消息处理操作 & 与其他线程进行通信
// 注:消息处理操作(HandlerMessage())的执行线程 = mHandlerThread所创建的工作线程中执行
  Handler workHandler = new Handler( handlerThread.getLooper() ) {
            @Override
            public boolean handleMessage(Message msg) {
                ...//消息处理
                return true;
            }
        });
步骤4:使用工作线程Handler向工作线程的消息队列发送消息
// 在工作线程中,当消息循环时取出对应消息 & 在工作线程执行相关操作
  // a. 定义要发送的消息
  Message msg = Message.obtain();
  msg.what = 2; //消息的标识
  msg.obj = "B"; // 消息的存放
  // b. 通过Handler发送消息到其绑定的消息队列
  workHandler.sendMessage(msg);
步骤5:结束线程,即停止线程的消息循环
  mHandlerThread.quit();

(3)实例

public class MainActivity extends AppCompatActivity {

    Handler mainHandler,workHandler;
    HandlerThread mHandlerThread;
    TextView text;
    Button button1,button2,button3;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 显示文本
        text = (TextView) findViewById(R.id.text1);

        // 创建与主线程关联的Handler
        mainHandler = new Handler();

        /**
          * 步骤1:创建HandlerThread实例对象
          * 传入参数 = 线程名字,作用 = 标记该线程
          */
        mHandlerThread = new HandlerThread("handlerThread");

        /**
         * 步骤2:启动线程
         */
        mHandlerThread.start();

        /**
         * 步骤3:创建工作线程Handler & 复写handleMessage()
         * 作用:关联HandlerThread的Looper对象、实现消息处理操作 & 与其他线程进行通信
         * 注:消息处理操作(HandlerMessage())的执行线程 = mHandlerThread所创建的工作线程中执行
         */

        workHandler = new Handler(mHandlerThread.getLooper()){
            @Override
            // 消息处理的操作
            public void handleMessage(Message msg)
            {
                //设置了两种消息处理操作,通过msg来进行识别
                switch(msg.what){
                    // 消息1
                    case 1:
                        try {
                            //延时操作
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        // 通过主线程Handler.post方法进行在主线程的UI更新操作
                        mainHandler.post(new Runnable() {
                            @Override
                            public void run () {
                                text.setText("我爱学习");
                            }
                        });
                        break;

                    // 消息2
                    case 2:
                        try {
                            Thread.sleep(3000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        mainHandler.post(new Runnable() {
                            @Override
                            public void run () {
                                text.setText("我不喜欢学习");
                            }
                        });
                        break;
                    default:
                        break;
                }
            }
        };

        /**
         * 步骤4:使用工作线程Handler向工作线程的消息队列发送消息
         * 在工作线程中,当消息循环时取出对应消息 & 在工作线程执行相关操作
         */
        // 点击Button1
        button1 = (Button) findViewById(R.id.button1);
        button1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                // 通过sendMessage()发送
                // a. 定义要发送的消息
                Message msg = Message.obtain();
                msg.what = 1; //消息的标识
                msg.obj = "A"; // 消息的存放
                // b. 通过Handler发送消息到其绑定的消息队列
                workHandler.sendMessage(msg);
            }
        });

        // 点击Button2
        button2 = (Button) findViewById(R.id.button2);
        button2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                // 通过sendMessage()发送
                // a. 定义要发送的消息
                Message msg = Message.obtain();
                msg.what = 2; //消息的标识
                msg.obj = "B"; // 消息的存放
                // b. 通过Handler发送消息到其绑定的消息队列
                workHandler.sendMessage(msg);
            }
        });

        // 点击Button3
        // 作用:退出消息循环
        button3 = (Button) findViewById(R.id.button3);
        button3.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mHandlerThread.quit();
            }
        });

    }
    
}

(4)源码解析 4.1)工作原理 内部原理 = Thread类 + Handler类机制 (1)通过继承Thread类,快速创建1个带有Looper对象的新工作线程 (2)通过封装Handler类,快速创建Handler&与其他线程进行通信 4.2)源码解析 步骤1:创建HandlerThread的实例对象 1、具体使用

HandlerThread mHandlerThread = new HandlerThread("handlerThread");

2、源码解析

 public class HandlerThread extends Thread {
    // 继承自Thread类
        
        int mPriority; // 线程优先级
        int mTid = -1; // 当前线程id
        Looper mLooper; // 当前线程持有的Looper对象

       // HandlerThread类有2个构造方法
       // 区别在于:设置当前线程的优先级参数,即可自定义设置 or 使用默认优先级

            // 方式1. 默认优先级
            public HandlerThread(String name) {
                // 通过调用父类默认的方法创建线程
                super(name);
                mPriority = Process.THREAD_PRIORITY_DEFAULT;
            }
          
            // 方法2. 自定义设置优先级
            public HandlerThread(String name, int priority) {
                super(name);
                mPriority = priority;
            }
            ...
     }

3、总结 HandlerThread类继承自Thread类 创建HandlerThread类对象 = 创建Thread类对象 + 设置线程优先级 = 新开1个工作线程 + 设置线程优先级 步骤2:启动线程 1、具体使用

mHandlerThread.start();

2、源码解析

/**
  * 源码分析:此处调用的是父类(Thread类)的start(),最终回调HandlerThread的run()
  */ 
  @Override
    public void run() {
        // 1. 获得当前线程的id
        mTid = Process.myTid();

        // 2. 创建1个Looper对象 & MessageQueue对象
        Looper.prepare();

        // 3. 通过持有锁机制来获得当前线程的Looper对象
        synchronized (this) {
            mLooper = Looper.myLooper();
           
            // 发出通知:当前线程已经创建mLooper对象成功
            // 此处主要是通知getLooper()中的wait()
            notifyAll();
            
            // 此处使用持有锁机制 + notifyAll() 是为了保证后面获得Looper对象前就已创建好Looper对象
        }

        // 4. 设置当前线程的优先级
        Process.setThreadPriority(mPriority);

        // 5. 在线程循环前做一些准备工作 ->>分析1
        // 该方法实现体是空的,子类可实现 / 不实现该方法
        onLooperPrepared();

        // 6. 进行消息循环,即不断从MessageQueue中取消息 & 派发消息
        Looper.loop();

        mTid = -1;
    }
}

/**
  * 分析1:onLooperPrepared();
  * 说明:该方法实现体是空的,子类可实现 / 不实现该方法
  */ 
    protected void onLooperPrepared() {

    }

3、总结 1、为当前工作线程(即步骤1创建的线程)创建1个Looper对象 & MessageQueue对象 2、通过持有锁机制来获得当前线程的Looper对象 3、发出通知:当前线程已经创建mLooper对象成功 4、工作线程进行消息循环,即不断从MessageQueue中取消息 & 派发消息 步骤3:创建工作线程Handler & 复写handleMessage() 1、具体使用

Handler workHandler = new Handler( handlerThread.getLooper() ) {
            @Override
            public boolean handleMessage(Message msg) {
                ...//消息处理
                return true;
            }
        });

2、源码解析

/**
  * 源码分析:handlerThread.getLooper()
  * 作用:获得当前HandlerThread线程中的Looper对象
  */ 
    public Looper getLooper() {
        // 若线程不是存活的,则直接返回null
        if (!isAlive()) {
            return null;
        } 
        // 若当前线程存活,再判断线程的成员变量mLooper是否为null
        // 直到线程创建完Looper对象后才能获得Looper对象,若Looper对象未创建成功,则阻塞
        synchronized (this) {
  
      
            while (isAlive() && mLooper == null) {
                try {
                    // 此处会调用wait方法去等待
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
        // 上述步骤run()使用 持有锁机制 + notifyAll()  获得Looper对象后
        // 则通知当前线程的wait()结束等待 & 跳出循环
        // 最终getLooper()返回的是在run()中创建的mLooper对象
        return mLooper;
    }

3、总结 在获得HandlerThread工作线程的Looper对象时存在一个同步的问题:只有当线程创建成功 & 其对应的Looper对象也创建成功后才能获得Looper的值,才能将创建的Handler 与 工作线程的Looper对象绑定,从而将Handler绑定工作线程 解决方案:即保证同步的解决方案 = 同步锁、wait() 和 notifyAll(),即 在run()中成功创建Looper对象后,立即调用notifyAll()通知 getLooper()中的wait()结束等待 & 返回run()中成功创建的Looper对象,使得Handler与该Looper对象绑定 步骤4:使用工作线程Handler向工作线程的消息队列发送消息 1、具体使用

 // a. 定义要发送的消息
  Message msg = Message.obtain();
  msg.what = 2; //消息的标识
  msg.obj = "B"; // 消息的存放
  // b. 通过Handler发送消息到其绑定的消息队列
  workHandler.sendMessage(msg);

2、源码解析 源码分析:workHandler.sendMessage(msg) 此处的源码即Handler的源码,故不作过多描述 步骤5:结束线程,即停止线程的消息循环 1、具体使用

  mHandlerThread.quit();

2、源码解析

/**
  * 源码分析:mHandlerThread.quit()
  * 说明:
  *     a. 该方法属于HandlerThread类
  *     b. HandlerThread有2种让当前线程退出消息循环的方法:quit() 、quitSafely()
  */ 
    
  // 方式1:quit() 
  // 特点:效率高,但线程不安全
  public boolean quit() {
        Looper looper = getLooper();
        if (looper != null) {
            looper.quit(); 
            return true;
        }
        return false;
    }

  // 方式2:quitSafely()
  // 特点:效率低,但线程安全
  public boolean quitSafely() {
        Looper looper = getLooper();
        if (looper != null) {
            looper.quitSafely();
            return true;
        }
        return false;
    }

  // 注:上述2个方法最终都会调用MessageQueue.quit(boolean safe)->>分析1

/**
  * 分析1:MessageQueue.quit(boolean safe)
  */ 
    void quit(boolean safe) {
        if (!mQuitAllowed) {
            throw new IllegalStateException("Main thread not allowed to quit.");
        }
        synchronized (this) {
            if (mQuitting) {
                return;
            }
            mQuitting = true;

            
            if (safe) {
                removeAllFutureMessagesLocked(); // 方式1(不安全)会调用该方法 ->>分析2
            } else {
                removeAllMessagesLocked(); // 方式2(安全)会调用该方法 ->>分析3
            }
            // We can assume mPtr != 0 because mQuitting was previously false.
            nativeWake(mPtr);
        }
    }
/**
  * 分析2:removeAllMessagesLocked()
  * 原理:遍历Message链表、移除所有信息的回调 & 重置为null
  */ 
  private void removeAllMessagesLocked() {
    Message p = mMessages;
    while (p != null) {
        Message n = p.next;
        p.recycleUnchecked();
        p = n;
    }
    mMessages = null;
}
/**
  * 分析3:removeAllFutureMessagesLocked() 
  * 原理:先判断当前消息队列是否正在处理消息
  *      a. 若不是,则类似分析2移除消息
  *      b. 若是,则等待该消息处理处理完毕再使用分析2中的方式移除消息退出循环
  * 结论:退出方法安全与否(quitSafe() 或 quit()),在于该方法移除消息、退出循环时是否在意当前队列是否正在处理消息
  */ 
  private void removeAllFutureMessagesLocked() {

    final long now = SystemClock.uptimeMillis();
    Message p = mMessages;

    if (p != null) {
        // 判断当前消息队列是否正在处理消息
        // a. 若不是,则直接移除所有回调
        if (p.when > now) {
            removeAllMessagesLocked();
        } else {
        // b. 若是正在处理,则等待该消息处理处理完毕再退出该循环
            Message n;
            for (;;) {
                n = p.next;
                if (n == null) {
                    return;
                }
                if (n.when > now) {
                    break;
                }
                p = n;
            }
            p.next = null;
            do {
                p = n;
                n = p.next;
                p.recycleUnchecked();
            } while (n != null);
        }
    }
}

4.3)总结 在这里插入图片描述 (5)问题&解决 5.1)内存泄露 1、问题

In Android, Handler classes should be static or leaks might occur.

2、原因 Handler导致内存泄露:当Handler消息队列 还有未处理的消息 / 正在处理消息时,存在引用关系: “未被处理 / 正处理的消息 -> Handler实例 -> 外部类” 若出现 Handler的生命周期 > 外部类的生命周期 时(即 Handler消息队列 还有未处理的消息 / 正在处理消息 而 外部类需销毁时),将使得外部类无法被垃圾回收器(GC)回收,从而造成 内存泄露 3、解决 将Handler子类设置为静态内部类+使用weakReference弱引用持有Activity实例 5.2)连续发送消息 1、问题 当你连续点击3下时,发现并无按照最新点击的按钮操作显示,而是按顺序的一个个显示出来 2、原因 使用HandlerThread时只是开了一个工作线程,当你点击了n下后,只是将n个消息发送到消息队列MessageQueue里排队,等候派发消息给Handler再进行对应的操作

3、IntentService

(1)介绍 Android里的一个封装类,继承四大组件之一Service,用于处理异步请求&实现多线程。线程任务需按顺序、在后台执行。适用于离线下载,不符合多个数据同时请求的场景(所有任务都在同一个Thread looper里执行) (2)使用 步骤1:定义 IntentService的子类 传入线程名称、复写onHandleIntent()方法

public class myIntentService extends IntentService {

  /** 
    * 在构造函数中传入线程名字
    **/  
    public myIntentService() {
        // 调用父类的构造函数
        // 参数 = 工作线程的名字
        super("myIntentService");
    }

   /** 
     * 复写onHandleIntent()方法
     * 根据 Intent实现 耗时任务 操作
     **/  
    @Override
    protected void onHandleIntent(Intent intent) {

        // 根据 Intent的不同,进行不同的事务处理
        String taskName = intent.getExtras().getString("taskName");
        switch (taskName) {
            case "task1":
                Log.i("myIntentService", "do task1");
                break;
            case "task2":
                Log.i("myIntentService", "do task2");
                break;
            default:
                break;
        }
    }

    @Override
    public void onCreate() {
        Log.i("myIntentService", "onCreate");
        super.onCreate();
    }
   /** 
     * 复写onStartCommand()方法
     * 默认实现 = 将请求的Intent添加到工作队列里
     **/  
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i("myIntentService", "onStartCommand");
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        Log.i("myIntentService", "onDestroy");
        super.onDestroy();
    }
}

步骤2:在Manifest.xml中注册服务

<service android:name=".myIntentService">
            <intent-filter >
                <action android:name="cn.scu.finch"/>
            </intent-filter>
        </service>

步骤3:在Activity中开启Service服务

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

            // 同一服务只会开启1个工作线程
            // 在onHandleIntent()函数里,依次处理传入的Intent请求
            // 将请求通过Bundle对象传入到Intent,再传入到服务里

            // 请求1
            Intent i = new Intent("cn.scu.finch");
            Bundle bundle = new Bundle();
            bundle.putString("taskName", "task1");
            i.putExtras(bundle);
            startService(i);

            // 请求2
            Intent i2 = new Intent("cn.scu.finch");
            Bundle bundle2 = new Bundle();
            bundle2.putString("taskName", "task2");
            i2.putExtras(bundle2);
            startService(i2);

            startService(i);  //多次启动
        }
    }

(3)源码解析 3.1)工作原理 在这里插入图片描述 若启动IntentService 多次,那么 每个耗时操作 则 以队列的方式 在 IntentService的 onHandleIntent回调方法中依次执行,执行完自动结束 3.2)源码解析 问题1:IntentService如何单独开启1个新的工作线程 IntentService源码中的 onCreate()方法

@Override
public void onCreate() {
    super.onCreate();
    
    // 1. 通过实例化andlerThread新建线程 & 启动;故 使用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);
}

3.3)源码总结 IntentService本质 = Handler + HandlerThread: 1、通过HandlerThread 单独开启1个工作线程:IntentService 2、创建1个内部 Handler :ServiceHandler 3、绑定 ServiceHandler 与 IntentService 4、通过 onStartCommand() 传递服务intent 到ServiceHandler 、依次插入Intent到工作队列中 & 逐个发送给 onHandleIntent() 5、通过onHandleIntent() 依次处理所有Intent对象所对应的任务 因此我们通过复写onHandleIntent() & 在里面 根据Intent的不同进行不同线程操作 即可 3.4)注意事项 注意事项1:工作任务队列 = 顺序执行 即 若一个任务正在IntentService中执行,此时你再发送1个新的任务请求,这个新的任务会一直等待直到前面一个任务执行完毕后才开始执行 原因: 1、由于onCreate()只会调用一次 = 只会创建1个工作线程; 2、当多次调用 startService(Intent)时(即 onStartCommand()也会调用多次),其实不会创建新的工作线程,只是把消息加入消息队列中 & 等待执行。 3、所以,多次启动 IntentService 会按顺序执行事件 若服务停止,则会清除消息队列中的消息,后续的事件不执行 注意事项2:不建议通过 bindService() 启动 IntentService 原因:

// 在IntentService中,onBind()`默认返回null
@Override
public IBinder onBind(Intent intent) {
    return null;
}

采用 bindService()启动 IntentService的生命周期如下:

onCreate() ->> onBind() ->> onunbind()->> onDestory()

即,并不会调用onStart() 或 onStartcommand(),故不会将消息发送到消息队列,那么onHandleIntent()将不会回调,即无法实现多线程的操作 此时,你应该使用Service,而不是IntentService (4)对比 4.1)与Service对比 在这里插入图片描述 4.2)与其他线程对比 在这里插入图片描述

2.3)高级使用

线程池(ThreadPool)

(1)介绍 线程池是一块缓存了一定线程数量的区域,用于复用线程和管理线程(如1、统一分配、调优&监控2、控制线程池的最大并发数) 降低因线程创建&销毁带来的性能开销(重用缓存在线程池的线程) 提高线程响应速度&执行效率:1、重用线程 = 不需创建线程,即可马上执行2、管理线程 = 优化线程执行顺序(避免大量线程间因互相抢占系统资源而到只阻塞现象) 提高对线程的管理度 注:传统多线程方式(集成Thread类 & 实现Runnable接口)的问题 1、每次新建/销毁线程对象消耗资源、响应速度慢 2、线程缺乏统一管理,容易出现阻塞情况 (2)工作原理 2.1核心参数 在这里插入图片描述 上述6个参数的配置 决定了 线程池的功能,具体设置时机 = 创建 线程池类对象时 传入 ThreadPoolExecutor类 = 线程池的真正实现类 开发者可根据不同需求 配置核心参数,从而实现自定义线程池

// 创建线程池对象如下
// 通过 构造方法 配置核心参数
   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<Runnable workQueue>,
                               ThreadFactory threadFactory )

2.2内部原理逻辑 在这里插入图片描述 (3)使用流程

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

作者:Carson_Ho
链接:https://www.jianshu.com/p/0e4a5e70bf0e
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

(4)常见的4类功能线程池 4.1定长线程池(FixedThreadPool) 1、特点 只有核心线程 & 不会被回收、线程数量固定、任务队列无大小限制(超出的线程任务会在队列中等待) 2、应用场景 控制线程最大并发数 3、具体使用 通过 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();

4.2定时线程池(ScheduledThreadPool) 1、特点 核心线程数量固定、非核心线程数量无限制(闲置时马上回收) 2、应用场景 执行定时 / 周期性 任务 3、具体使用 通过Executors.newScheduledThreadPool()创建

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

4.3可缓存线程池(CachedThreadPool) 1、特点 只有非核心线程、线程数量不固定(可无限大)、灵活回收空闲线程(具备超时机制,全部回收时几乎不占系统资源)、新建线程(无线程可用时) 任何线程任务到来都会立刻执行,不需要等待 2、应用场景 执行大量、耗时少的线程任务 3、具体使用 通过Executors.newCachedThreadPool()创建

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

//当执行第二个任务时第一个任务已经完成
//那么会复用执行第一个任务的线程,而不用每次新建线程。

4.4单线程化线程池(SingleThreadExecutor) 1、特点 只有一个核心线程(保证所有任务按照指定顺序在一个线程中执行,不需要处理线程同步的问题) 2、应用场景 不适合并发但可能引起IO阻塞性及影响UI线程响应的操作,如数据库操作,文件操作等 3、使用 通过Executors.newSingleThreadExecutor()创建

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

2.4)对比

在这里插入图片描述

2.5)其他

1、Synchronized

一.设计目的 (1)JDK为何要设计锁 即同一时刻最多只有1个线程执行被Synchronized修饰的方法/代码,其他线程必须等待当前线程执行完该方法/代码块后才能执行该方法/代码块 (2)应用场景 多线程编程中,有可能会出现多个线程同时访问一个共享、可变资源(临界资源)的情况,这种资源可能是:对象、变量、文件等。由于线程执行的过程是不可控的,所以需要采用同步机制来协同对对象可变状态的访问 加锁目的:序列化访问临界资源,即同一时刻只能有一个线程访问临界资源(同步互斥访问) 1、修饰 实例方法 / 代码块时,(同步)保护的是同一个对象方法的调用 & 当前实例对象 2、修饰 静态方法 / 代码块时,(同步)保护的是 静态方法的调用 & class 类对象 二.设计原理 (1)加锁对象 1、同步实例方法,锁是当前实例对象 2、同步类方法,锁是当前对象 3、同步代码块,锁是括号里的对象 (2)加锁原理 1、依赖 JVM 实现同步 2、底层通过一个监视器对象(monitor)完成, wait()、notify() 等方法也依赖于 monitor 对象 3、监视器锁(monitor)的本质 依赖于 底层操作系统的互斥锁(Mutex Lock)实现

(object){
//monitorenter进入同步块
//业务逻辑
//monitorexit退出同步块
}

(3)JVM加锁过程 在这里插入图片描述 三.具体使用 Synchronized 用于 修饰 代码块、类的实例方法 & 静态方法 (1)使用规则 在这里插入图片描述 (2)锁的类型&等级 1、类型 Synchronized会修饰代码块、类的实例方法&静态方法 在这里插入图片描述 2、区别 在这里插入图片描述 (3)使用方式

/**
 * 对象锁
 */
    public class Test{ 
    // 对象锁:形式1(方法锁) 
    public synchronized void Method1(){ 
        System.out.println("我是对象锁也是方法锁"); 
        try{ 
            Thread.sleep(500); 
        } catch (InterruptedException e){ 
            e.printStackTrace(); 
        } 
 
    } 
 
    // 对象锁:形式2(代码块形式) 
    public void Method2(){ 
        synchronized (this){ 
            System.out.println("我是对象锁"); 
            try{ 
                Thread.sleep(500); 
            } catch (InterruptedException e){ 
                e.printStackTrace(); 
            } 
        } 
 
    } 
 }

/**
 * 方法锁(即对象锁中的形式1)
 */
    public synchronized void Method1(){ 
        System.out.println("我是对象锁也是方法锁"); 
        try{ 
            Thread.sleep(500); 
        } catch (InterruptedException e){ 
            e.printStackTrace(); 
        } 
 
    } 

/**
 * 类锁
 */
public class Test{ 
   // 类锁:形式1 :锁静态方法
    public static synchronized void Method1(){ 
        System.out.println("我是类锁一号"); 
        try{ 
            Thread.sleep(500); 
        } catch (InterruptedException e){ 
            e.printStackTrace(); 
        } 
 
    } 
 
    // 类锁:形式2 :锁静态代码块
    public void Method2(){ 
        synchronized (Test.class){ 
            System.out.println("我是类锁二号"); 
            try{ 
                Thread.sleep(500); 
            } catch (InterruptedException e){ 
                e.printStackTrace(); 
            } 
 
        } 
 
    } 
}

四.特点 在这里插入图片描述

2、ThreadLocal

(1)简介 ThreadLocal是线程的局部变量,用于为每个线程提供1个特定空间(即该变量),以保存该线程所独享的资源。适用于隔离线程&放置线程间数据资源共享的场景。 注: a.使每个线程可独立地改变自己空间内的资源(设置、存储的值)而不会和其他线程资源冲突 b.1个变量只能被同一个进程读、写,若第2个线程同时执行1段含有1个ThreadLocal变量引用的代码,它们也无法访问到对方的ThreadLocal变量 (2)使用流程 2.1创建ThreadLocal变量

// 1. 直接创建对象
private ThreadLocal myThreadLocal = new ThreadLocal()

// 2. 创建泛型对象
private ThreadLocal myThreadLocal = new ThreadLocal<String>();

// 3. 创建泛型对象 & 初始化值
// 指定泛型的好处:不需要每次对使用get()方法返回的值作强制类型转换
private ThreadLocal myThreadLocal = new ThreadLocal<String>() {
    @Override
    protected String initialValue() {
        return "This is the initial value";
    }
};

// 特别注意:
// 1. ThreadLocal实例 = 类中的private、static字段
// 2. 只需实例化对象一次 & 不需知道它是被哪个线程实例化
// 3. 每个线程都保持 对其线程局部变量副本 的隐式引用
// 4. 线程消失后,其线程局部实例的所有副本都会被垃圾回收(除非存在对这些副本的其他引用)
// 5. 虽然所有的线程都能访问到这个ThreadLocal实例,但是每个线程只能访问到自己通过调用ThreadLocal的set()设置的值
 // 即 哪怕2个不同的线程在同一个`ThreadLocal`对象上设置了不同的值,他们仍然无法访问到对方的值

2.2访问ThreadLocal变量

// 1. 设置值:set()
// 需要传入一个Object类型的参数
myThreadLocal.set("初始值”);

// 2. 读取ThreadLocal变量中的值:get()
// 返回一个Object对象
String threadLocalValue = (String) myThreadLocal.get();

(3)具体使用

 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);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(name + ":" + threadLocal.get());
            }
        }
    }

测试结果

线程1:线程1的threadLocal 线程2:线程2的threadLocal

从上述结果看出,在2个线程分别设置ThreadLocal值 & 分别获取,结果并未互相干扰 (4)实现原理 4.1)核心原理 ThreadLocal类中有1个Map(称:ThreadLocalMap):用于存储每个线程 & 该线程设置的存储在ThreadLocal变量的值 1、ThreadLocalMap的键Key = 当前ThreadLocal实例、值value = 该线程设置的存储在ThreadLocal变量的值 2、该key是 ThreadLocal对象的弱引用;当要抛弃掉ThreadLocal对象时,垃圾收集器会忽略该 key的引用而清理掉ThreadLocal对象 4.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()进行操作

       ...
}

(5)补充 5.1)ThreadLocal如何做到线程安全 1、每个线程拥有自己独立的ThreadLocals变量(指向ThreadLocalMap对象 ) 2、每当线程 访问 ThreadLocals变量时,访问的都是各自线程自己的ThreadLocalMap变量(键 - 值) 3、ThreadLocalMap变量的键 key = 唯一 = 当前ThreadLocal实例 5.2)与同步机制的区别 在这里插入图片描述 #(三)Java/Android 多线程开发联系/区别