【Android+多线程】异步 多线程 知识总结:基础概念 / 多种方式 / 实现方法 / 源码分析

1 基本概念

1.1 线程

定义:一个基本的CPU执行单元 & 程序执行流的最小单元

  1. 比进程更小的可独立运行的基本单位,可理解为:轻量级进程
  2. 组成:线程ID + 程序计数器 + 寄存器集合 + 堆栈
  3. 注:线程自己不拥有系统资源,与其他线程共享进程所拥有的全部资源。

作用:减少程序在并发执行时所付出的时空开销,提高操作系统的并发性能。
状态
在这里插入图片描述
分类

  1. 守护线程
    定义:守护用户线程的线程,即在程序运行时为其他线程提供一种通用服务
    常见:如 垃圾回收线程
    设置方式:
//设置该线程为守护线程
thread.setDaemon(true)
  1. 非守护线程(用户线程)
    主要包括:主线程 & 子线程。

    a. 主线程(UI线程)
    定义:Android系统在程序启动时会自动启动一条主线程
    作用:处理四大组件与用户进行交互的事情(如UI、界面交互相关)
    注:因为用户随时会与界面发生交互,因此主线程任何时候都必须保持很高的响应速度,所以主线程不允许进行耗时操作,否则会出现ANR

    b. 子线程(工作线程)
    定义:手动创建的线程
    作用:耗时的操作(网络请求、I/O操作等)

  2. 守护线程 与 非守护线程的区别
    区别:虚拟机是否已退出
    当所有用户线程结束时,因为没有守护的必要,所以守护线程也会终止,虚拟机也同样退出;
    反过来,只要任何用户线程还在运行,守护线程就不会终止,虚拟机就不会退出

优先级:
线程优先级分为10个级别,分别用Thread类常量表示。

// 如:
Thread.MIN_PRIORITY // 优先级1
Thread.MAX_PRIORITY // 优先级10通过方法setPriority(int grade)进行优先级设置
默认线程优先级是5,即 Thread.NORM_PRIORITY

多线程:
多个线程同时进行,即多个任务同时进行

其实,计算机任何特定时刻只能执行一个任务;
多线程只是一种错觉:只是因为JVM快速调度资源来轮换线程,使得线程不断轮流执行,所以看起来好像在同时执行多个任务而已

Android官方声明:在多线程编程时有两大原则:

  1. 不要阻塞UI线程(即主线程):单线程会导致主线程阻塞,然后出现ANR错误:主线程被阻塞超过5s则会出现错误
  2. 不要在UI线程之外更新UI组件

所以,我们需要多线程(1个主线程+n个工作线程)来解决上述两个问题:

  1. 将耗时任务放在工作线程中进行
    对应原则:不要阻塞UI线程(即主线程),即当我们有耗时的任务,如果在UI线程中执行,那就会阻塞UI线程了,必须要抛到工作线程中去执行;

  2. 将更新UI组件放在主线程中进行
    对应原则:不要在UI线程之外访问UI组件,即更新UI组件时,一定得在UI线程里执行,故需要在工作线程中执行的任务结果返回到UI线程中去更新组件

即:

  • 将耗时任务从主线程抛到工作线程中进行
  • 将更新UI组件任务从工作线程抛到主线程中进行

Android中多线程的实现方式:

  • 基础使用:继承Thread类、实现Runnable接口、Handler
  • 符合使用:AsyncTask、HandlerThread、IntentService
  • 高级使用:线程池ThreadPool

线程调度:

  • 当系统存在大量线程时,系统会通过时间片轮转的方式调度线程,因此线程不可能做到绝对的并发

  • 处于就绪状态(Runnable)的线程都会进入到线程队列中等待CPU资源

  • 同一时刻在线程队列中可能有很多个

  • 在采用时间片的系统中,每个线程都有机会获得CPU的资源以便进行自身的线程操作;当线程使用CPU资源的时间到后,即时线程没有完成自己的全部操作,JVM也会中断当前线程的执行,把CPU资源的使用权切换给下一个队列中等待的线程。

  • 被中断的线程将等待CPU资源的下一次轮回,然后从中断处继续执行

调度优先级
Java虚拟机(JVM)中的线程调度器负责管理线程,并根据以下规则进行调度:

  • 根据线程优先级(高-低),将CPU资源分配给各线程
  • 具备相同优先级的线程以轮流的方式获取CPU资源

示例
存在A、B、C、D四个线程,其中:A和B的优先级高于C和D(A、B同级,C、D同级)
那么JVM将先以轮流的方式调度A、B,直到A、B线程死亡,再以轮流的方式调度C、D

线程同步

  • 定义:当线程A使用同步方法A时,其他线程必须等到线程A使用完同步方法A后才能使用
  • 同步方法用关键字 Synchronized 进行修饰
public synchronized void Sb_Android(){}

线程联合

定义:线程A在占有CPU资源期间,通过调用join()方法中断自身线程执行,然后运行联合它的线程B,直到线程B执行完毕后线程A再重新排队等待CPU资源,这个过程称为线程A联合线程B

线程A联合线程B,即在线程A的执行操作里定义:

B.join();完整的例子:
public class ThreadJoinExample {public static void main(String[] args) {Thread threadA = new Thread(() -> {System.out.println("线程 A 开始执行");Thread threadB = new Thread(() -> {System.out.println("线程 B 开始执行");try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("线程 B 执行完毕");});threadB.start();try {threadB.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("线程 A 继续执行");});threadA.start();}
}

1.2 进程

定义
是进程实体的运行过程 & 系统进行资源分配和调度的一个独立单位

作用
使多个程序可 并发执行,以提高系统的资源利用率和吞吐量

状态
在这里插入图片描述

在这里插入图片描述
进程和线程区别
在这里插入图片描述
单/多进程、单/多线程的区别
假设:进程 = 桌子,单线程 = 1个人吃饭

  • 单进程、单线程:一个人在一个桌子上吃饭
  • 单进程、多线程:多个人在同一个桌子上一起吃饭
  • 多进程、单线程:多个人每个人在自己的桌子上吃饭

2 实现多线程的方式

2.1 继承Thread类

// 步骤1:创建线程类 (继承自Thread类)class MyThread extends Thread{// 步骤2:复写run(),内容 = 定义线程行为@Overridepublic void run(){... // 定义的线程行为}
}// 步骤3:创建线程对象,即 实例化线程类MyThread mt=new MyThread(“线程名称”);// 步骤4:通过 线程对象 控制线程的状态,如 运行、睡眠、挂起  / 停止
// 此处采用 start()开启线程mt.start();

简便使用:匿名类
很多情况下,开发者会选择一种更加方便的方法去创建线程:匿名类

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

举个例子

  • 应用场景:创建两个线程-实现两个不同的耗时任务
  • 实例说明:实现2个窗口同时卖火车票;每个窗口卖100张,但卖票速度不同:窗口1是1s/张,窗口2是3s/张

main_activity.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:paddingBottom="@dimen/activity_vertical_margin"android:paddingLeft="@dimen/activity_horizontal_margin"android:paddingRight="@dimen/activity_horizontal_margin"android:paddingTop="@dimen/activity_vertical_margin"tools:context="com.example.carson_ho.demoforthread_2.MainActivity">//设置一个按钮以启动卖票<Buttonandroid:id="@+id/button"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="点击开始卖票" />
</RelativeLayout>

MainActivity.java

package com.example.carson_ho.demoforthread_2;import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;public class MainActivity extends AppCompatActivity {//主布局中定义了一个按钮用以启动线程Button button;//步骤1:创建线程类,继承自Thread类//因为这里需要有两个操作:一个窗口卖票速度是1s/张,一个窗口是3s/张//所以需要创建两个Thread的子类//第一个Thread子类实现一个窗口卖票速度是1s/张private class MyThread1 extends Thread{private int ticket = 100;//一个窗口有100张票private String name; //窗口名, 也即是线程的名字public MyThread1(String name){this.name=name;}//在run方法里复写需要进行的操作:卖票速度是1s/张@Overridepublic void run(){while (ticket>0){ticket--;System.out.println(name + "卖掉了1张票,剩余票数为:"+ticket);try {Thread.sleep(1000);//卖票速度是1s一张} catch (InterruptedException e) {e.printStackTrace();}}}}//第二个Thread子类实现一个窗口卖票速度是3s/张private class MyThread2 extends Thread{private int ticket = 100;//一个窗口有100张票private String name; //窗口名, 也即是线程的名字public MyThread2(String name){this.name=name;}//在run方法里复写需要进行的操作:卖票速度是3s/张@Overridepublic void run(){while (ticket>0){ticket--;System.out.println(name + "卖掉了1张票,剩余票数为:"+ticket);try {Thread.sleep(3000);//卖票速度是1s一张} catch (InterruptedException e) {e.printStackTrace();}}}}@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);//Button按下时会开启一个新线程执行卖票button = (Button) findViewById(R.id.button);button.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {//步骤2:创建线程类的实例//创建二个线程,模拟二个窗口卖票MyThread1 mt1 = new MyThread1("窗口1");MyThread2 mt2 = new MyThread2("窗口2");//步骤3:调用start()方法开启线程//启动二个线程,也即是窗口,开始卖票mt1.start();mt2.start();}});}
}

2.2 实现Runnable接口

Runnable接口 比 继承Thread 好在哪?

  1. 适合资源共享:Runnable的代码可被多个线程Thread实例共享,适合多个线程处理同一资源的情况
  2. 灵活:一个类可以继承多个接口,避免 继承Thread类方式 导致的单继承局限性

特别注意

  • Java中真正能创建新线程的只有Thread类对象
  • 通过实现Runnable的方式,最终还是通过Thread类对象来创建线程

所以对于 实现了Runnable接口的类,称为 线程辅助类;
Thread类才是真正的线程类

具体用法

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

简便使用:匿名类

    // 步骤1:通过匿名类 直接 创建线程辅助对象,即 实例化 线程辅助类Runnable mt = new Runnable() {// 步骤2:复写run(),定义线程行为@Overridepublic void run() {}};// 步骤3:创建线程对象,即 实例化线程类;线程类 = Thread类;Thread mt1 = new Thread(mt, "窗口1");// 步骤4:通过 线程对象 控制线程的状态,如 运行、睡眠、挂起  / 停止mt1.start();

举个例子

应用场景:创建两个线程-实现两个不同的耗时任务
实例说明:实现2个窗口同时卖火车票;每个窗口卖100张,但卖票速度不同:窗口1是1s/张,窗口2是3s/张

main_activity.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:paddingBottom="@dimen/activity_vertical_margin"android:paddingLeft="@dimen/activity_horizontal_margin"android:paddingRight="@dimen/activity_horizontal_margin"android:paddingTop="@dimen/activity_vertical_margin"tools:context="com.example.carson_ho.demoforrunnable2.MainActivity">//设置按钮用以启动线程<Buttonandroid:id="@+id/button"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="点击开始卖票" />
</RelativeLayout>

MainActivity.java

package com.example.carson_ho.demoforrunnable2;import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;public class MainActivity extends AppCompatActivity {//主布局中定义了一个按钮用以启动线程Button button;//步骤1:创建线程类,实现Runnable接口//由于需要实现两个不同的操作:卖票速度1s/张和3s/张//所以需要创建两个线程类并实现Runnable接口//第一个线程类:实现卖票速度1s/张操作private class MyThread1 implements Runnable{private int ticket = 100;//一个窗口有100张票//在run方法里复写需要进行的操作:卖票速度1s/张@Overridepublic void run(){while (ticket>0){ticket--;System.out.println(Thread.currentThread().getName() + "卖掉了1张票,剩余票数为:"+ticket);try {Thread.sleep(1000);//卖票速度是1s一张} catch (InterruptedException e) {e.printStackTrace();}}}}//第二个线程类:实现卖票速度3s/张操作private class MyThread2 implements Runnable{private int ticket = 100;//一个窗口有100张票//在run方法里复写需要进行的操作:卖票速度3s/张@Overridepublic void run(){while (ticket>0){ticket--;System.out.println(Thread.currentThread().getName() + "卖掉了1张票,剩余票数为:"+ticket);try {Thread.sleep(3000);//卖票速度是3s一张} catch (InterruptedException e) {e.printStackTrace();}}}}@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);//Button按下时会开启一个新线程执行卖票button = (Button) findViewById(R.id.button);button.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {//步骤2:创建线程类的实例//分别实例化两个线程子类MyThread1 mt1 = new MyThread1();MyThread2 mt2 = new MyThread2();//创建二个线程,模拟二个窗口卖票Thread mt11 = new Thread(mt1, "窗口1");//卖票速度1s/张Thread mt22 = new Thread(mt2, "窗口2");//卖票速度3s/张//步骤3:调用start()方法开启线程//启动二个线程,也即是窗口,开始卖票mt11.start();mt22.start();}});}
}

再举个例子

应用场景:创建两个线程-实现一个耗时任务
实例说明:实现2个窗口同时卖火车票;两个窗口一共卖100张,卖票速度均为1s/张

main_activity.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:paddingBottom="@dimen/activity_vertical_margin"android:paddingLeft="@dimen/activity_horizontal_margin"android:paddingRight="@dimen/activity_horizontal_margin"android:paddingTop="@dimen/activity_vertical_margin"tools:context="com.example.carson_ho.demoforrunnable3.MainActivity">
//设置按钮用以启动线程<Buttonandroid:id="@+id/button"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="点击开始卖票" />
</RelativeLayout>

MainActivity.java

package com.example.carson_ho.demoforrunnable3;import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;public class MainActivity extends AppCompatActivity {//主布局中定义了一个按钮用以启动线程Button button;//步骤1:创建线程类,实现Runnable接口private class MyThread1 implements Runnable{private int ticket = 100;//两个窗口一共要卖100张票//在run方法里复写需要进行的操作:卖票速度1s/张@Overridepublic void run(){while (ticket>0){ticket--;System.out.println(Thread.currentThread().getName() + "卖掉了1张票,剩余票数为:"+ticket);try {Thread.sleep(1000);//卖票速度是1s一张} catch (InterruptedException e) {e.printStackTrace();}}}}@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);//Button按下时会开启一个新线程执行卖票button = (Button) findViewById(R.id.button);button.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {//步骤2:创建线程类的实例//因为是两个窗口共卖100张票,即共用资源//所以只实例化一个实现了Runnable接口的类MyThread1 mt = new MyThread1();//因为要创建二个线程,模拟二个窗口卖票Thread mt11 = new Thread(mt, "窗口1");Thread mt12 = new Thread(mt, "窗口2");//步骤3:调用start()方法开启线程//启动二个线程,也即是窗口,开始卖票mt11.start();mt12.start();}});}
}

二者比较

继承Thread类 和 实现Runnable接口对比
在这里插入图片描述

2.3 AsyncTask

定义:
是Android封装好的轻量级异步类。
是抽象类,使用时要实现子类

优点:

  1. 方便实现异步通信:不需使用 “任务线程+Handler” 的复杂组合
  2. 节省资源:内部采用线程池的缓存现场+复用线程,避免频繁创建/销毁线程带来的系统资源开销

2.3.1 使用步骤

AsyncTask的使用步骤:

  1. 创建 AsyncTask 子类 & 根据需求实现核心方法
  2. 创建 AsyncTask子类的实例对象(即 任务实例)
  3. 手动调用execute()从而执行异步线程任务
/*** 步骤1:创建AsyncTask子类* 注: *   a. 继承AsyncTask类*   b. 为3个泛型参数指定类型;若不使用,可用java.lang.Void类型代替*   c. 根据需求,在AsyncTask子类内实现核心方法*/private class MyTask extends AsyncTask<Params, Progress, Result> {....// 方法1:onPreExecute()// 作用:执行 线程任务前的操作// 注:根据需求复写@Overrideprotected void onPreExecute() {...}// 方法2:doInBackground()// 作用:接收输入参数、执行任务中的耗时操作、返回 线程任务执行的结果// 注:必须复写,从而自定义线程任务@Overrideprotected String doInBackground(String... params) {...// 自定义的线程任务// 可调用publishProgress()显示进度, 之后将执行onProgressUpdate()publishProgress(count);}// 方法3:onProgressUpdate()// 作用:在主线程 显示线程任务执行的进度// 注:根据需求复写@Overrideprotected void onProgressUpdate(Integer... progresses) {...}// 方法4:onPostExecute()// 作用:接收线程任务执行结果、将执行结果显示到UI组件// 注:必须复写,从而自定义UI操作@Overrideprotected void onPostExecute(String result) {...// UI操作}// 方法5:onCancelled()// 作用:将异步任务设置为:取消状态@Overrideprotected void onCancelled() {...}}/*** 步骤2:创建AsyncTask子类的实例对象(即 任务实例)* 注:AsyncTask子类的实例必须在UI线程中创建*/MyTask mTask = new MyTask();/*** 步骤3:手动调用execute(Params... params) 从而执行异步线程任务* 注:*    a. 必须在UI线程中调用*    b. 同一个AsyncTask实例对象只能执行1次,若执行第2次将会抛出异常*    c. 执行任务中,系统会自动调用AsyncTask的一系列方法:onPreExecute() 、doInBackground()、onProgressUpdate() 、onPostExecute() *    d. 不能手动调用上述方法*/mTask.execute()

举个例子

  1. 点击按钮 则 开启线程执行线程任务
  2. 显示后台加载进度
  3. 加载完毕后更新UI组件
  4. 期间若点击取消按钮,则取消加载

主布局文件:activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:gravity="center"tools:context="com.example.carson_ho.handler_learning.MainActivity"><Buttonandroid:layout_centerInParent="true"android:id="@+id/button"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="点我加载"/><TextViewandroid:id="@+id/text"android:layout_below="@+id/button"android:layout_centerInParent="true"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="还没开始加载!" /><ProgressBarandroid:layout_below="@+id/text"android:id="@+id/progress_bar"android:layout_width="fill_parent"android:layout_height="wrap_content"android:progress="0"android:max="100"style="?android:attr/progressBarStyleHorizontal"/><Buttonandroid:layout_below="@+id/progress_bar"android:layout_centerInParent="true"android:id="@+id/cancel"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="cancel"/>
</RelativeLayout>

主逻辑代码文件:MainActivity.java

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()// 作用:执行 线程任务前的操作@Overrideprotected void onPreExecute() {text.setText("加载中");// 执行前显示提示}// 方法2:doInBackground()// 作用:接收输入参数、执行任务中的耗时操作、返回 线程任务执行的结果// 此处通过计算从而模拟“加载进度”的情况@Overrideprotected 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()// 作用:在主线程 显示线程任务执行的进度@Overrideprotected void onProgressUpdate(Integer... progresses) {progressBar.setProgress(progresses[0]);text.setText("loading..." + progresses[0] + "%");}// 方法4:onPostExecute()// 作用:接收线程任务执行结果、将执行结果显示到UI组件@Overrideprotected void onPostExecute(String result) {// 执行完毕后,则更新UItext.setText("加载完毕");}// 方法5:onCancelled()// 作用:将异步任务设置为:取消状态@Overrideprotected void onCancelled() {text.setText("已取消");progressBar.setProgress(0);}}@Overrideprotected 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() {@Overridepublic 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() {@Overridepublic void onClick(View v) {// 取消一个正在执行的任务,onCancelled方法将会被调用mTask.cancel(true);}});}
}

使用时的注意点

1. 关于 生命周期

  • 结论
    AsyncTask不与任何组件绑定生命周期
  • 使用建议
    在Activity 或 Fragment中使用 AsyncTask时,最好在Activity 或 Fragment的onDestory()调用 cancel(boolean)

2. 关于 内存泄漏

  • 结论
    若AsyncTask被声明为Activity的非静态内部类,当Activity需销毁时,会因AsyncTask保留对Activity的引用 而导致Activity无法被回收,最终引起内存泄露
  • 使用建议
    AsyncTask应被声明为Activity的静态内部类

3. 线程任务执行结果 丢失

  • 结论
    当Activity重新创建时(屏幕旋转 / Activity被意外销毁时后恢复),之前运行的AsyncTask(非静态的内部类)持有的之前Activity引用已无效,故复写的onPostExecute()将不生效,即无法更新UI操作
  • 使用建议
    在Activity恢复时的对应方法 重启 任务线程

2.3.2 工作原理

AsyncTask的实现原理 = 线程池 + Handler

其中:线程池用于线程调度、复用 & 执行任务;Handler 用于异步通信

其内部封装了2个线程池 + 1个Handler,具体介绍如下:
在这里插入图片描述
核心方法:
在这里插入图片描述
方法执行顺序:
在这里插入图片描述

2.3.3 源码分析

根据AsyncTask的使用步骤分析:

  1. 创建 AsyncTask 子类 & 根据需求实现核心方法
  2. 创建 AsyncTask子类的实例对象(即 任务实例)
  3. 手动调用execute()从而执行异步线程任务
/*** 步骤1:创建AsyncTask子类* 注: *   a. 继承AsyncTask类*   b. 为3个泛型参数指定类型;若不使用,可用java.lang.Void类型代替*   c. 根据需求,在AsyncTask子类内实现核心方法*/private class MyTask extends AsyncTask<Params, Progress, Result> {....// 方法1:onPreExecute()// 作用:执行 线程任务前的操作// 注:根据需求复写@Overrideprotected void onPreExecute() {...}// 方法2:doInBackground()// 作用:接收输入参数、执行任务中的耗时操作、返回 线程任务执行的结果// 注:必须复写,从而自定义线程任务@Overrideprotected String doInBackground(String... params) {...// 自定义的线程任务// 可调用publishProgress()显示进度, 之后将执行onProgressUpdate()publishProgress(count);}// 方法3:onProgressUpdate()// 作用:在主线程 显示线程任务执行的进度// 注:根据需求复写@Overrideprotected void onProgressUpdate(Integer... progresses) {...}// 方法4:onPostExecute()// 作用:接收线程任务执行结果、将执行结果显示到UI组件// 注:必须复写,从而自定义UI操作@Overrideprotected void onPostExecute(String result) {...// UI操作}// 方法5:onCancelled()// 作用:将异步任务设置为:取消状态@Overrideprotected void onCancelled() {...}}/*** 步骤2:创建AsyncTask子类的实例对象(即 任务实例)* 注:AsyncTask子类的实例必须在UI线程中创建*/MyTask mTask = new MyTask();/*** 步骤3:手动调用execute(Params... params) 从而执行异步线程任务* 注:*    a. 必须在UI线程中调用*    b. 同一个AsyncTask实例对象只能执行1次,若执行第2次将会抛出异常*    c. 执行任务中,系统会自动调用AsyncTask的一系列方法:onPreExecute() 、doInBackground()、onProgressUpdate() 、onPostExecute() *    d. 不能手动调用上述方法*/mTask.execute()

步骤1:创建AsyncTask子类
在该步骤中,只需知道 “该类中复写的方法将在后续源码中调用” 即可

步骤2:创建AsyncTask子类的实例对象(即 任务实例)

/*** 具体使用*/MyTask mTask = new MyTask();/*** 源码分析:AsyncTask的构造函数*/public AsyncTask() {// 1. 初始化WorkerRunnable变量 = 一个可存储参数的Callable对象 ->>分析1mWorker = 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 ->>分析2mFuture = new FutureTask<Result>(mWorker) {// done()简介:FutureTask内的Callable执行完后的调用方法// 作用:复查任务的调用、将未被调用的任务的结果通过InternalHandler传递到UI线程@Overrideprotected void done() {try {// 在执行完任务后检查,将没被调用的Result也一并发出 ->>分析3postResultIfNotInvoked(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) {//若 发生异常,则将发出nullpostResultIfNotInvoked(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);}}

总结:

  • 创建了1个WorkerRunnable类 的实例对象 & 复写了call()方法
  • 创建了1个FutureTask类 的实例对象 & 复写了 done()方法

步骤3:手动调用execute(Params… params)

/*** 具体使用*/mTask.execute()/*** 源码分析:AsyncTask的execute()*/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)的对象// ->>分析2exec.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();}}// 分析3protected synchronized void scheduleNext() {// 1. 取出队列头部任务if ((mActive = mTasks.poll()) != null) {// 2. 执行取出的队列头部任务// 即 调用执行任务线程池类(THREAD_POOL_EXECUTOR)->>继续往下看THREAD_POOL_EXECUTOR.execute(mActive);}}}

总结:

  • 执行任务前,通过 任务队列 线程池类(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+1private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;//设置线程池空闲线程存活时间30sprivate 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 最大容量为128private static final BlockingQueue<Runnable> sPoolWorkQueue =new LinkedBlockingQueue<Runnable>(128);// 步骤2: 根据参数配置执行任务线程池,即 THREAD_POOL_EXECUTORpublic static final Executor THREAD_POOL_EXECUTOR;static {ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,sPoolWorkQueue, sThreadFactory);// 设置核心线程池的 超时时间也为30sthreadPoolExecutor.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 ->>分析1postResult(result); }return result;}};.....// 省略}
/*** 分析1:postResult(result)*/private Result postResult(Result result) {@SuppressWarnings("unchecked")// 创建Handler对象 ->> 源自InternalHandler类—>>分析2Message 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()必须在主线程使用}@Overridepublic 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]); ->>分析3break;// 若收到的消息 = 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的状态变更为:FINISHEDmStatus = 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更新操作

在这里插入图片描述

2.4 HandlerThread

定义:一个Android 已封装好的轻量级异步类
作用:

  • 实现多线程
  • 实现工作线程 & 主线程(UI线程)之间的通信

优点:
方便实现异步通信,即不需使用 “任务线程(如继承Thread类) + Handler”的复杂组合

HandlerThread本质上是通过继承Thread类和封装Handler类的使用,从而使得创建新线程和与其他线程进行通信变得更加方便易用

工作原理:
内部原理 = Thread类 + Handler类机制

  • 通过继承Thread类,快速地创建1个带有Looper对象的新工作线程
  • 通过封装Handler类,快速创建Handler & 与其他线程进行通信

2.4.1 使用步骤

HandlerThread的使用步骤分为5步

// 步骤1:创建HandlerThread实例对象
// 传入参数 = 线程名字,作用 = 标记该线程HandlerThread mHandlerThread = new HandlerThread("handlerThread");// 步骤2:启动线程mHandlerThread.start();// 步骤3:创建工作线程Handler & 复写handleMessage()
// 作用:关联HandlerThread的Looper对象、实现消息处理操作 & 与其他线程进行通信
// 注:消息处理操作(HandlerMessage())的执行线程 = mHandlerThread所创建的工作线程中执行Handler workHandler = new Handler( handlerThread.getLooper() ) {@Overridepublic 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();

举个例子

  1. 点击按钮实现延迟操作
  2. 最终更新UI组件

主布局文件:activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:gravity="center"tools:context="com.example.carson_ho.handler_learning.MainActivity"><TextViewandroid:id="@+id/text1"android:layout_centerInParent="true"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="测试结果" /><Buttonandroid:id="@+id/button1"android:layout_centerInParent="true"android:layout_below="@+id/text1"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="点击延迟1s + 显示我爱学习"/><Buttonandroid:id="@+id/button2"android:layout_centerInParent="true"android:layout_below="@+id/button1"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="点击延迟3s + 显示我不爱学习"/><Buttonandroid:id="@+id/button3"android:layout_centerInParent="true"android:layout_below="@+id/button2"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="结束线程的消息循环"/>
</RelativeLayout>

主代码文件:MainActivity.java

public class MainActivity extends AppCompatActivity {Handler mainHandler,workHandler;HandlerThread mHandlerThread;TextView text;Button button1,button2,button3;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);// 显示文本text = (TextView) findViewById(R.id.text1);// 创建与主线程关联的HandlermainHandler = 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){// 消息1case 1:try {//延时操作Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}// 通过主线程Handler.post方法进行在主线程的UI更新操作mainHandler.post(new Runnable() {@Overridepublic void run () {text.setText("我爱学习");}});break;// 消息2case 2:try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}mainHandler.post(new Runnable() {@Overridepublic void run () {text.setText("我不喜欢学习");}});break;default:break;}}};/*** 步骤4:使用工作线程Handler向工作线程的消息队列发送消息* 在工作线程中,当消息循环时取出对应消息 & 在工作线程执行相关操作*/// 点击Button1button1 = (Button) findViewById(R.id.button1);button1.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {// 通过sendMessage()发送// a. 定义要发送的消息Message msg = Message.obtain();msg.what = 1; //消息的标识msg.obj = "A"; // 消息的存放// b. 通过Handler发送消息到其绑定的消息队列workHandler.sendMessage(msg);}});// 点击Button2button2 = (Button) findViewById(R.id.button2);button2.setOnClickListener(new View.OnClickListener() {@Overridepublic 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() {@Overridepublic void onClick(View v) {mHandlerThread.quit();}});}
}

注意事项

1. 内存泄露
在上面的例子中,出现了严重的警告:

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

即造成了严重的内存泄漏,需要使用静态内部类+弱引用+退出Activity时清空MessageQueue 解决。

2. 连续发送消息

  • 当你连续点击3下时,发现并无按照最新点击的按钮操作显示,而是按顺序的一个个显示出来
  • 原因:使用HandlerThread时只是开了一个工作线程,当你点击了n下后,只是将n个消息发送到消息队列MessageQueue里排队,等候派发消息给Handler再进行对应的操作

2.4.2 源码分析

HandlerThread的内部原理 = Thread类 + Handler类机制,即:

  • 通过继承Thread类,快速地创建1个带有Looper对象的新工作线程
  • 通过封装Handler类,快速创建Handler & 与其他线程进行通信

按HandlerThread的使用步骤介绍:

// 步骤1:创建HandlerThread实例对象
// 传入参数 = 线程名字,作用 = 标记该线程HandlerThread mHandlerThread = new HandlerThread("handlerThread");// 步骤2:启动线程mHandlerThread.start();// 步骤3:创建工作线程Handler & 复写handleMessage()
// 作用:关联HandlerThread的Looper对象、实现消息处理操作 & 与其他线程进行通信
// 注:消息处理操作(HandlerMessage())的执行线程 = mHandlerThread所创建的工作线程中执行Handler workHandler = new Handler( handlerThread.getLooper() ) {@Overridepublic 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();

步骤1:创建HandlerThread的实例对象

/*** 具体使用* 传入参数 = 线程名字,作用 = 标记该线程*/ HandlerThread mHandlerThread = new HandlerThread("handlerThread");/*** 源码分析:HandlerThread类的构造方法*/ public class HandlerThread extends Thread {// 继承自Thread类int mPriority; // 线程优先级int mTid = -1; // 当前线程idLooper 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;}...}

总结

  • HandlerThread类继承自Thread类
  • 创建HandlerThread类对象 = 创建Thread类对象 + 设置线程优先级 = 新开1个工作线程 + 设置线程优先级

步骤2:启动线程

/*** 具体使用*/ mHandlerThread.start();/*** 源码分析:此处调用的是父类(Thread类)的start(),最终回调HandlerThread的run()*/ @Overridepublic void run() {// 1. 获得当前线程的idmTid = 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() {}

总结

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

步骤3:创建工作线程Handler & 复写handleMessage()

/*** 具体使用* 作用:将 Handler 关联 HandlerThread 的 Looper 对象、实现消息处理操作 & 与其他线程进行通信* 注:消息处理操作(HandlerMessage())的执行线程 = mHandlerThread所创建的工作线程中执行*/ Handler workHandler = new Handler( handlerThread.getLooper() ) {@Overridepublic boolean handleMessage(Message msg) {...//消息处理return true;}});/*** 源码分析:handlerThread.getLooper()* 作用:获得当前HandlerThread线程中的Looper对象*/ public Looper getLooper() {// 若线程不是存活的,则直接返回nullif (!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;}

总结

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

步骤4:使用工作线程Handler向工作线程的消息队列发送消息

/*** 具体使用* 作用:在工作线程中,当消息循环时取出对应消息 & 在工作线程执行相关操作* 注:消息处理操作(HandlerMessage())的执行线程 = mHandlerThread所创建的工作线程中执行*/ // a. 定义要发送的消息Message msg = Message.obtain();msg.what = 2; //消息的标识msg.obj = "B"; // 消息的存放// b. 通过Handler发送消息到其绑定的消息队列workHandler.sendMessage(msg);/*** 源码分析:workHandler.sendMessage(msg)* 此处的源码即Handler的源码,故不作过多描述*/ 

步骤5:结束线程,即停止线程的消息循环

/*** 具体使用*/ mHandlerThread.quit();/*** 源码分析: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(安全)会调用该方法 ->>分析3} else {removeAllMessagesLocked(); // 方式2(不安全)会调用该方法 ->>分析2}// 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);}}
}

总结
在这里插入图片描述

2.5 IntentService

IntentService 知识总结:应用场景 / 使用步骤 / 源码分析

2.6 线程池ThreadPool

定义:
1块 缓存了一定数量线程 的区域

作用:

  • 复用线程
  • 管理线程:统一分配、调优、监控,控制线程池的最大并发数

优点:

  1. 降低 因线程的创建&销毁带来的性能开销:重用缓存在线程池中的线程
  2. 提高 线程响应速度 & 执行效率:
    重用线程 = 不需要创建线程 即可马上执行
    管理线程 = 优化线程的执行顺序,避免大量线程因互相抢占系统资源而导致阻塞现象
  3. 提高 对线程的管理度

传统多线程方式(继承Thread类 & 实现Runnable接口)的问题:

  1. 每次 新建/销毁 线程对象 消耗资源、响应速度慢
  2. 线程缺乏统一管理,容易阻塞

核心参数
在这里插入图片描述
上述6个参数的配置 决定了 线程池的功能,具体设置时机 = 创建 线程池类对象时 传入

  1. ThreadPoolExecutor类 = 线程池的真正实现类
  2. 开发者可根据不同需求 配置核心参数,从而实现自定义线程池
// 创建线程池对象如下
// 通过 构造方法 配置核心参数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 )

注:Java 里已内置4种常用的线程池(即 已经配置好核心参数),下面会详细说明

内部运行逻辑
在这里插入图片描述

2.6.1 使用流程

// 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() {@Overridepublic void run() {... // 线程执行任务}});// 3. 关闭线程池shutdown() threadPool.shutdown();// 关闭线程的原理// a. 遍历线程池中的所有工作线程// b. 逐个调用线程的interrupt()中断线程(注:无法响应中断的任务可能永远无法终止)// 也可调用shutdownNow()关闭线程:threadPool.shutdownNow()// 二者区别:// shutdown:设置 线程池的状态 为 SHUTDOWN,然后中断所有没有正在执行任务的线程// shutdownNow:设置 线程池的状态 为 STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表// 使用建议:一般调用shutdown()关闭线程池;若任务不一定要执行完,则调用shutdownNow()

2.6.2 常见的4类功能线程池

根据参数的不同配置,Java中最常见的线程池有4类:

  • 定长线程池(FixedThreadPool)
  • 定时线程池(ScheduledThreadPool )
  • 可缓存线程池(CachedThreadPool)
  • 单线程化线程池(SingleThreadExecutor)

即 对于上述4类线程池,Java已根据 应用场景 配置好核心参数

1. 定长线程池(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();

2. 定时线程池(ScheduledThreadPool )

  • 特点:核心线程数量固定、非核心线程数量无限制(闲置时马上回收)
  • 应用场景:执行定时 / 周期性 任务
  • 使用:通过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();

3. 可缓存线程池(CachedThreadPool)

  • 特点:只有非核心线程、线程数量不固定(可无限大)、灵活回收空闲线程(具备超时机制,全部回收时几乎不占系统资源)、新建线程(无线程可用时)
  • 任何线程任务到来都会立刻执行,不需要等待
  • 应用场景:执行大量、耗时少的线程任务
  • 使用:通过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. 单线程化线程池(SingleThreadExecutor)

  • 特点:只有一个核心线程(保证所有任务按照指定顺序在一个线程中执行,不需要处理线程同步的问题)
  • 应用场景:不适合并发,但可用于引起IO阻塞 & 影响UI线程响应的操作,如数据库操作,文件操作等
  • 使用:通过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.7 消息传递机制 Handler

Handler 知识总结:应用场景 / 使用方式 / 工作原理 / 源码分析 / 内存泄漏

2.8 线程变量 ThreadLocal

定义:线程的局部变量
作用:为 每个线程 提供1个特定空间(即该变量),以保存该线程独享的资源
应用场景:隔离线程 & 防止线程间的数据资源共享
特别注意:

  1. 每个线程都可独立改变自己空间内的资源(设置、存储的值),不会和其他线程的资源冲突
  2. 1个ThreadLocal变量只能被同一个线程读写。若2个线程 同时执行1段含有1个ThreadLocal变量引用的代码,他们也无法访问到对方的ThreadLocal变量

2.8.1 使用流程

主要是创建ThreadLocal变量 & 访问ThreadLocal变量

1. 创建ThreadLocal变量

共有3种方式,具体如下

// 1. 直接创建对象
private ThreadLocal myThreadLocal = new ThreadLocal()// 2. 创建泛型对象
private ThreadLocal myThreadLocal = new ThreadLocal<String>();// 3. 创建泛型对象 & 初始化值
// 指定泛型的好处:不需要每次对使用get()方法返回的值作强制类型转换
private ThreadLocal myThreadLocal = new ThreadLocal<String>() {@Overrideprotected String initialValue() {return "This is the initial value";}
};// 特别注意:
// 1. ThreadLocal实例 = 类中的private、static字段
// 2. 只需实例化对象一次 & 不需知道它是被哪个线程实例化
// 3. 每个线程都保持 对其线程局部变量副本 的隐式引用
// 4. 线程消失后,其线程局部实例的所有副本都会被垃圾回收(除非存在对这些副本的其他引用)
// 5. 虽然所有的线程都能访问到这个ThreadLocal实例,但是每个线程只能访问到自己通过调用ThreadLocal的set()设置的值// 即 哪怕2个不同的线程在同一个`ThreadLocal`对象上设置了不同的值,他们仍然无法访问到对方的值

2. 访问ThreadLocal变量

// 1. 设置值:set()
// 需要传入一个Object类型的参数
myThreadLocal.set("初始值”);// 2. 读取ThreadLocal变量中的值:get()
// 返回一个Object对象
String threadLocalValue = (String) myThreadLocal.get();

2.8.2 具体使用

 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>(){@Overrideprotected String initialValue() {return "初始化值";}};@Overridepublic 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值 & 分别获取,结果并未互相干扰

2.8.3 实现原理

  • 核心原理:ThreadLocal类中有1个Map(称:ThreadLocalMap):用于存储每个线程 & 该线程设置的存储在ThreadLocal变量的值
  1. ThreadLocalMap的 键Key = 当前ThreadLocal实例、值value = 该线程设置的存储在ThreadLocal变量的值
  2. 该key是ThreadLocal对象的弱引用;当要抛弃掉ThreadLocal对象时,垃圾收集器会忽略该key的引用而清理掉ThreadLocal对象
  • 关于如何设置 & 获取 ThreadLocal变量里的值,具体请看下面的源码分析

// ThreadLocal的源码public class ThreadLocal<T> {.../** * 设置ThreadLocal变量引用的值*  ThreadLocal变量引用 指向 ThreadLocalMap对象,即设置ThreadLocalMap的值 = 该线程设置的存储在ThreadLocal变量的值*  ThreadLocalMap的 键Key = 当前ThreadLocal实例*  ThreadLocalMap的 值Value = 该线程设置的存储在ThreadLocal变量的值**/  public void set(T value) {// 1. 获得当前线程Thread t = Thread.currentThread();// 2. 获取该线程的ThreadLocalMap对象 ->>分析1ThreadLocalMap map = getMap(t);// 3. 若该线程的ThreadLocalMap对象已存在,则替换该Map里的值;否则创建1个ThreadLocalMap对象if (map != null)map.set(this, value);// 替换elsecreateMap(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); // 替换elsecreateMap(t, value); // 创建->>分析2return 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的值Value = 该线程设置的存储在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()进行操作...
}

2.8.4 补充说明

1. ThreadLocal如何做到线程安全

  • 每个线程拥有自己独立的ThreadLocals变量(指向ThreadLocalMap对象 )
  • 每当线程 访问 ThreadLocals变量时,访问的都是各自线程自己的ThreadLocalMap变量(键 - 值)
  • ThreadLocalMap变量的键 key = 唯一 = 当前ThreadLocal实例

上述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>(){@Overrideprotected String initialValue() {return "初始化值";}};@Overridepublic 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值 & 分别获取,结果并未互相干扰

2. 与同步机制的区别
在这里插入图片描述

2.9 线程同步关键字 Synchronized

定义:Synchronized 是 Java中的1个关键字

作用:保证同一时刻最多只有1个线程执行 被Synchronized修饰的方法 / 代码

其他线程 必须等待当前线程执行完该方法 / 代码块后才能执行该方法 / 代码块

应用场景保证线程安全,解决多线程中的并发同步问题(实现的是阻塞型并发),具体场景如下:

  1. 修饰 实例方法 / 代码块时,(同步)保护的是同一个对象方法的调用 & 当前实例对象
  2. 修饰 静态方法 / 代码块时,(同步)保护的是 静态方法的调用 & class 类对象

2.9.1 原理

  1. 依赖 JVM 实现同步
  2. 底层通过一个监视器对象(monitor)完成, wait()、notify() 等方法也依赖于 monitor 对象

监视器锁(monitor)的本质 依赖于 底层操作系统的互斥锁(Mutex Lock)实现

2.9.2 具体使用

使用规则

  1. 锁对象设置
    a. 修饰代码块时,需1个reference对象 作为锁的对象
    b. 修饰实例方法时,默认的锁对象 = 当前对象
    c. 修饰类方法(静态)时,默认的锁对象 = 当前类的Class对象
  2. 根据锁对象不同,一把锁同时最多只能被一个线程持有
    a. 若目标锁已被当前线程持有,其他线程只能阻塞等待当前线程释放目标锁
    b. 若当前线程已持有目标锁,其他线程仍然可以调用目标类中未被synchronized修饰的方法
  3. 当对象获取多个锁时,必须以相反的顺序释放 & 在与所有锁被获取时相同的词法范围内释放所有的锁
    a. 若线程进入由线程已拥有的监控器保护的synchronized块,就允许线程继续进行
    b. 只有线程退出他进入的监控器保护的第一个synchronized块时,才释放锁
  4. 特别注意:
    a. Java类中,实例对象会有多个,但只有1个Class对象
    b. 静态方法 & 实例方法上的锁默认不一样:若同步则需要制定两把锁。静态方法加锁,能和所有其他静态方法加锁的进行互斥,直接属于类,效果同xx.class锁

锁的类型 & 等级
由于Synchronized 会修饰 代码块、类的实例方法 & 静态方法,故分为不同锁的类型
在这里插入图片描述
之间的区别
在这里插入图片描述
使用方式

/*** 对象锁*/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(); } } }

注意

Synchronized修饰方法时存在缺陷:若修饰1个大的方法,将会大大影响效率

  • 示例
    若使用Synchronized关键字修饰 线程类的run(),由于run()在线程的整个生命期内一直在运行,因此将导致它对本类任何Synchronized方法的调用都永远不会成功

  • 解决方案
    使用 Synchronized关键字声明代码块

该解决方案灵活性高:可针对任意代码块 & 任意指定上锁的对象

代码如下

synchronized(syncObject) { // 访问或修改被锁保护的共享状态 // 上述方法 必须 获得对象 syncObject(类实例或类)的锁
}

2.9.3 特点

  1. 保证原子性、可见性、有序性
    释放锁时,所有写入都会写回内存;获得锁后,都会从内存读取最新数据
    在这里插入图片描述
  2. 可重入性
    对同一个线程,在获得锁后,在调用其他需同样锁的代码时可直接调用
    原理:记录锁的持有线程 & 持有数量
    (1)调用synchronized代码时检查对象是否已经被锁,是则检查是否被当前线程锁定,若是则计数+1,不是则加入等待队列
    (2)释放时计数-1,直到为0 释放锁
  3. 重量级
    底层是通过一个监视器对象monitor完成,wait() notify()等方法也依赖monitor对象
    监视器锁的本质依赖于底层操作系统的互斥锁实现。操作系统实现线程切换需要从用户态转换到内核态,切换过程长,所以synchronized效率低 & 重量级

2.9.4 其他控制并发 / 线程同步方式

1. Lock、ReentrantLock

  • 简介
    在这里插入图片描述
  • 区别
    在这里插入图片描述
    2. CAS
    Compare And Swap,即 比较 并 交换,是一种解决并发操作的乐观锁

synchronized锁住的代码块:同一时刻只能由一个线程访问,属于悲观锁

原理:

// 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资源也是一种浪费

具体实现方式:

  • 使用CAS有个“先检查后执行”的操作
  • 而这种操作在Java中是典型的不安全的操作,所以 CAS在实际中是由C++通过调用CPU指令实现的
  • 具体过程
// 1. CAS在Java中的体现为Unsafe类
// 2. Unsafe类会通过C++直接获取到属性的内存地址
// 3. 接下来CAS由C++的Atomic::cmpxchg系列方法实现

典型应用:AtomicInteger

对 i++ 与 i–,通过compareAndSet & 一个死循环实现

而compareAndSet函数内部 = 通过jni操作CAS指令。直到CAS操作成功跳出循环

   private volatile int value; /** * Gets the current value. * * @return the current value */ public final int get() { return value; } /** * Atomically increments by one the current value. * * @return the previous value */ public final int getAndIncrement() { for (;;) { int current = get(); int next = current + 1; if (compareAndSet(current, next)) return current; } } /** * Atomically decrements by one the current value. * * @return the previous value */ public final int getAndDecrement() { for (;;) { int current = get(); int next = current - 1; if (compareAndSet(current, next)) return current; } }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/479046.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

NLP论文速读(剑桥大学出品)|分解和利用专家模型中的偏好进行改进视觉模型的可信度

论文速读|Decompose and Leverage Preferences from Expert Models for Improving Trustworthiness of MLLMs 论文信息&#xff1a; 简介&#xff1a; 本文探讨的背景是多模态大型语言模型&#xff08;MLLMs&#xff09;&#xff0c;这类模型通过结合视觉特征和文本空间来增强语…

CentOS8.5.2111(7)完整的Apache综合实验

一、实验目标 1.掌握Linux系统中Apache服务器的安装与配置&#xff1b; 2.掌握个人主页、虚拟目录、基于用户和主机的访问控制及虚拟主机的实现方法。 二、实验要求 练习使用linux系统下WEB服务器的配置方法。 三、实验背景 重庆工程学院为筹备“重庆工程大学”特申请了c…

零基础3分钟快速掌握 ——Linux【终端操作】及【常用指令】Ubuntu

1.为啥使用Linux做嵌入式开发 能广泛支持硬件 内核比较高效稳定 原码开放、软件丰富 能够完善网络通信与文件管理机制 优秀的开发工具 2.什么是Ubuntu 是一个以桌面应用为主的Linux的操作系统&#xff0c; 内核是Linux操作系统&#xff0c; 具有Ubuntu特色的可视…

JVM类加载和垃圾回收算法详解

文章目录 JVM一、JVM运行流程1. JVM执行流程 二、JVM运行时数据区1. 程序计数器&#xff08;线程私有&#xff09;2. 虚拟机栈 &#xff08;线程私有&#xff09;3. 本地方法栈&#xff08;线程私有&#xff09;4. 堆&#xff08;线程共享&#xff09;5. 元空间&#xff08;线程…

iOS 17.4 Not Installed

0x00 系统警告 没有安装 17.4 的模拟器&#xff0c;任何操作都无法进行&#xff01; 点击 OK 去下载&#xff0c;完成之后&#xff0c;依旧是原样&#xff01; 0x01 解决办法 1、先去官网下载对应的模拟器&#xff1a; https://developer.apple.com/download/all/?q17.4 …

day04 企业级Linux安装及远程连接知识实践

1. 使用传统的网卡命名方式 在启动虚拟机时&#xff0c;按tab键进入编辑模式 添加命令&#xff1a; net.ifnames0 biosdevname0 这样linux系统会使用传统的网卡命名&#xff0c;例如eth0、eth1…… 2. 快照 做系统关键操作时&#xff0c;一定要使用快照(先将系统关机) 3.…

人体特定吸收率 (SAR) 分布建模

ANSYS HFSS 提供了一种建模 SAR 分布的方法&#xff01; 2020 年对我们所有人来说都是充满挑战的一年&#xff0c;由于 COVID 19 限制和居家隔离&#xff0c;许多工程师不得不推迟开发时间表。ANSYS HFSS 为所有工程师提供了一种在家安全工作的好方法。隔离期间&#xff0c;您…

.NET9 - Swagger平替Scalar详解(四)

书接上回&#xff0c;上一章介绍了Swagger代替品Scalar&#xff0c;在使用中遇到不少问题&#xff0c;今天单独分享一下之前Swagger中常用的功能如何在Scalar中使用。 下面我们将围绕文档版本说明、接口分类、接口描述、参数描述、枚举类型、文件上传、JWT认证等方面详细讲解。…

计算(a+b)/c的值

计算&#xff08;ab&#xff09;/c的值 C语言代码C语言代码Java语言代码Python语言代码 &#x1f490;The Begin&#x1f490;点点关注&#xff0c;收藏不迷路&#x1f490; 给定3个整数a、b、c&#xff0c;计算表达式(ab)/c的值&#xff0c;/是整除运算。 输入 输入仅一行&…

PICO 获取设备号 SN码

Unity版本 2020.3.42f1c1PICO SDK版本PICO Unity Integration SDK-3.0.5-20241105Pico设备pico 4ultra 注意 此api暂时只测试企业版本 pico 4ultra 代码 using Unity.XR.PICO.TOBSupport;private void Awake() {bool result PXR_Enterprise.InitEnterpriseService();Debug.L…

【大数据技术基础】 课程 第8章 数据仓库Hive的安装和使用 大数据基础编程、实验和案例教程(第2版)

第8章 数据仓库Hive的安装和使用 8.1 Hive的安装 8.1.1 下载安装文件 访问Hive官网&#xff08;http://www.apache.org/dyn/closer.cgi/hive/&#xff09;下载安装文件apache-hive-3.1.2-bin.tar.gz 下载完安装文件以后&#xff0c;需要对文件进行解压。按照Linux系统使用的…

[STM32]从零开始的STM32 FreeRTOS移植教程

一、前言 如果能看到这个教程的话&#xff0c;说明大家已经学习嵌入式有一段时间了。还记得嵌入式在大多数时候指的是什么吗&#xff1f;是的&#xff0c;我们所说的学习嵌入式大部分时候都是在学习嵌入式操作系统。从简单的一些任务状态机再到复杂一些的RTOS&#xff0c;再到最…

DAY133权限提升-Windows权限提升篇溢出漏洞土豆家族通杀全系补丁对比EXP筛选

知识点 1、Web到Win-系统提权-土豆家族 2、Web到Win-系统提权-人工操作 章节点&#xff1a; 1、Web权限提升及转移 2、系统权限提升及转移 3、宿主权限提升及转移 4、域控权限提升及转移 Windows提权&#xff1a; 1、内核溢出漏洞提权 2、数据库类型提权 3、第三方软件…

web day03 Maven基础 Junit

目录 Maven坐标&#xff1a; 依赖排除&#xff1a; 依赖范围&#xff1a; Maven生命周期&#xff1a; 单元测试&#xff1a; Junit入门&#xff1a; 断言&#xff1a; Junit中的常见注解&#xff1a; 概念&#xff1a;Maven 是一款用于管理和构建 Java项目的工具&#…

day18 结构体

有参宏和函数的区别 1.展开时机&#xff1a;有参宏而言&#xff0c;在预处理阶段展开&#xff0c;而函数在调用时才展开 2.内存使用&#xff1a;有参宏而言&#xff0c;占用的是所在函数的空间&#xff0c;而函数在调用时会单独开辟空间 3.效率上&#xff1a;有参宏的效率比…

44.扫雷第二部分、放置随机的雷,扫雷,炸死或成功 C语言

按照教程打完了。好几个bug都是自己打出来的。比如统计周围8个格子时&#xff0c;有一个各自加号填成了减号。我还以为平移了&#xff0c;一会显示是0一会显示是2。结果单纯的打错了。debug的时候断点放在scanf后面会顺畅一些。中间多放一些变量名方便监视。以及mine要多显示&a…

docker 通过Dockerfile自定义的镜像部署Springboot项目

一、镜像结构介绍&#xff1a; 镜像&#xff1a;层&#xff08;Layer&#xff09;添加安装包、依赖、配置等&#xff0c;每一次操作都形成新的一层&#xff1b;基础镜像&#xff08;BaseImage&#xff09;应用依赖的系统函数库、环境、配置、文件等&#xff1b;入口&#xff0…

全网最早Towards Generalizable Multi-Object Tracking—通用跟踪器的点跟踪CVPR2024

Towards Generalizable Multi-Object Tracking—迈向可推广的多目标跟踪 原标题&#xff1a;Towards Generalizable Multi-Object Tracking 论文链接&#xff1a;https://arxiv.org/pdf/2406.00429 代码链接&#xff1a;https://github.com/qinzheng2000/GeneralTrack.git 作者…

MyBatis框架-动态SQL-XML中的常用标签+特殊字符在XML中的显示

一、if标签、where标签、trim标签、choose标签、set标签、foreach标签 1、问题引入&#xff1a;where关键字和and关键字在动态SQL里面应该如何添加&#xff1f; &#xff08;1&#xff09;if标签&#xff1a; test属性的值是判断条件 if标签里面的内容是条件成立时添加到SQ…

探秘嵌入式位运算:基础与高级技巧

目录 一、位运算基础知识 1.1. 位运算符 1.1.1. 与运算&#xff08;&&#xff09; 1.1.2. 或运算&#xff08;|&#xff09; 1.1.3. 异或运算&#xff08;^&#xff09; 1.1.4. 取反运算&#xff08;~&#xff09; 1.1.5. 双重按位取反运算符&#xff08;~~&#xf…