【Android+多线程】异步 多线程 知识总结:基础概念 / 多种方式 / 实现方法 / 源码分析
1 基本概念
1.1 线程
定义:一个基本的CPU执行单元 & 程序执行流的最小单元
- 比进程更小的可独立运行的基本单位,可理解为:轻量级进程
- 组成:线程ID + 程序计数器 + 寄存器集合 + 堆栈
- 注:线程自己不拥有系统资源,与其他线程共享进程所拥有的全部资源。
作用:减少程序在并发执行时所付出的时空开销,提高操作系统的并发性能。
状态:
分类:
- 守护线程
定义:守护用户线程的线程,即在程序运行时为其他线程提供一种通用服务
常见:如 垃圾回收线程
设置方式:
//设置该线程为守护线程
thread.setDaemon(true);
-
非守护线程(用户线程)
主要包括:主线程 & 子线程。a. 主线程(UI线程)
定义:Android系统在程序启动时会自动启动一条主线程
作用:处理四大组件与用户进行交互的事情(如UI、界面交互相关)
注:因为用户随时会与界面发生交互,因此主线程任何时候都必须保持很高的响应速度,所以主线程不允许进行耗时操作,否则会出现ANRb. 子线程(工作线程)
定义:手动创建的线程
作用:耗时的操作(网络请求、I/O操作等) -
守护线程 与 非守护线程的区别
区别:虚拟机是否已退出
当所有用户线程结束时,因为没有守护的必要,所以守护线程也会终止,虚拟机也同样退出;
反过来,只要任何用户线程还在运行,守护线程就不会终止,虚拟机就不会退出
优先级:
线程优先级分为10个级别,分别用Thread类常量表示。
// 如:
Thread.MIN_PRIORITY // 优先级1
Thread.MAX_PRIORITY // 优先级10
通过方法setPriority(int grade)进行优先级设置
默认线程优先级是5,即 Thread.NORM_PRIORITY
多线程:
多个线程同时进行,即多个任务同时进行
其实,计算机任何特定时刻只能执行一个任务;
多线程只是一种错觉:只是因为JVM快速调度资源来轮换线程,使得线程不断轮流执行,所以看起来好像在同时执行多个任务而已
Android官方声明:在多线程编程时有两大原则:
- 不要阻塞UI线程(即主线程):单线程会导致主线程阻塞,然后出现ANR错误:主线程被阻塞超过5s则会出现错误
- 不要在UI线程之外更新UI组件
所以,我们需要多线程(1个主线程+n个工作线程)来解决上述两个问题:
-
将耗时任务放在工作线程中进行
对应原则:不要阻塞UI线程(即主线程),即当我们有耗时的任务,如果在UI线程中执行,那就会阻塞UI线程了,必须要抛到工作线程中去执行; -
将更新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(),内容 = 定义线程行为
@Override
public void run(){
... // 定义的线程行为
}
}
// 步骤3:创建线程对象,即 实例化线程类
MyThread mt=new MyThread(“线程名称”);
// 步骤4:通过 线程对象 控制线程的状态,如 运行、睡眠、挂起 / 停止
// 此处采用 start()开启线程
mt.start();
简便使用:匿名类
很多情况下,开发者会选择一种更加方便的方法去创建线程:匿名类
// 步骤1:采用匿名类,直接 创建 线程类的实例
new Thread("线程名称") {
// 步骤2:复写run(),内容 = 定义线程行为
@Override
public 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">
//设置一个按钮以启动卖票
<Button
android: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/张
@Override
public 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/张
@Override
public void run(){
while (ticket>0){
ticket--;
System.out.println(name + "卖掉了1张票,剩余票数为:"+ticket);
try {
Thread.sleep(3000);//卖票速度是1s一张
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//Button按下时会开启一个新线程执行卖票
button = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public 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 好在哪?
- 适合资源共享:Runnable的代码可被多个线程Thread实例共享,适合多个线程处理同一资源的情况
- 灵活:一个类可以继承多个接口,避免 继承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(),定义线程行为
@Override
public 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">
//设置按钮用以启动线程
<Button
android: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/张
@Override
public 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/张
@Override
public void run(){
while (ticket>0){
ticket--;
System.out.println(Thread.currentThread().getName() + "卖掉了1张票,剩余票数为:"+ticket);
try {
Thread.sleep(3000);//卖票速度是3s一张
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//Button按下时会开启一个新线程执行卖票
button = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public 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">
//设置按钮用以启动线程
<Button
android: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/张
@Override
public void run(){
while (ticket>0){
ticket--;
System.out.println(Thread.currentThread().getName() + "卖掉了1张票,剩余票数为:"+ticket);
try {
Thread.sleep(1000);//卖票速度是1s一张
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//Button按下时会开启一个新线程执行卖票
button = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public 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封装好的轻量级异步类。
是抽象类,使用时要实现子类
优点:
- 方便实现异步通信:不需使用 “任务线程+Handler” 的复杂组合
- 节省资源:内部采用线程池的缓存现场+复用线程,避免频繁创建/销毁线程带来的系统资源开销
2.3.1 使用步骤
AsyncTask的使用步骤:
- 创建 AsyncTask 子类 & 根据需求实现核心方法
- 创建 AsyncTask子类的实例对象(即 任务实例)
- 手动调用execute()从而执行异步线程任务
/**
* 步骤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:创建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();
举个例子
- 点击按钮 则 开启线程执行线程任务
- 显示后台加载进度
- 加载完毕后更新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">
<Button
android:layout_centerInParent="true"
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="点我加载"/>
<TextView
android:id="@+id/text"
android:layout_below="@+id/button"
android:layout_centerInParent="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="还没开始加载!" />
<ProgressBar
android: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"/>
<Button
android: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()
// 作用:执行 线程任务前的操作
@Override
protected void onPreExecute() {
text.setText("加载中");
// 执行前显示提示
}
// 方法2:doInBackground()
// 作用:接收输入参数、执行任务中的耗时操作、返回 线程任务执行的结果
// 此处通过计算从而模拟“加载进度”的情况
@Override
protected String doInBackground(String... params) {
try {
int count = 0;
int length = 1;
while (count<99) {
count += length;
// 可调用publishProgress()显示进度, 之后将执行onProgressUpdate()
publishProgress(count);
// 模拟耗时任务
Thread.sleep(50);
}
}catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
// 方法3:onProgressUpdate()
// 作用:在主线程 显示线程任务执行的进度
@Override
protected void onProgressUpdate(Integer... progresses) {
progressBar.setProgress(progresses[0]);
text.setText("loading..." + progresses[0] + "%");
}
// 方法4:onPostExecute()
// 作用:接收线程任务执行结果、将执行结果显示到UI组件
@Override
protected void onPostExecute(String result) {
// 执行完毕后,则更新UI
text.setText("加载完毕");
}
// 方法5:onCancelled()
// 作用:将异步任务设置为:取消状态
@Override
protected void onCancelled() {
text.setText("已取消");
progressBar.setProgress(0);
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 绑定UI组件
setContentView(R.layout.activity_main);
button = (Button) findViewById(R.id.button);
cancel = (Button) findViewById(R.id.cancel);
text = (TextView) findViewById(R.id.text);
progressBar = (ProgressBar) findViewById(R.id.progress_bar);
/**
* 步骤2:创建AsyncTask子类的实例对象(即 任务实例)
* 注:AsyncTask子类的实例必须在UI线程中创建
*/
mTask = new MyTask();
// 加载按钮按按下时,则启动AsyncTask
// 任务完成后更新TextView的文本
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
/**
* 步骤3:手动调用execute(Params... params) 从而执行异步线程任务
* 注:
* a. 必须在UI线程中调用
* b. 同一个AsyncTask实例对象只能执行1次,若执行第2次将会抛出异常
* c. 执行任务中,系统会自动调用AsyncTask的一系列方法:onPreExecute() 、doInBackground()、onProgressUpdate() 、onPostExecute()
* d. 不能手动调用上述方法
*/
mTask.execute();
}
});
cancel = (Button) findViewById(R.id.cancel);
cancel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 取消一个正在执行的任务,onCancelled方法将会被调用
mTask.cancel(true);
}
});
}
}
使用时的注意点
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的使用步骤分析:
- 创建 AsyncTask 子类 & 根据需求实现核心方法
- 创建 AsyncTask子类的实例对象(即 任务实例)
- 手动调用execute()从而执行异步线程任务
/**
* 步骤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:创建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对象 ->>分析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);
}
}
总结:
- 创建了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)的对象
// ->>分析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);
}
}
}
总结:
- 执行任务前,通过 任务队列 线程池类(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更新操作
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() ) {
@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();
举个例子
- 点击按钮实现延迟操作
- 最终更新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">
<TextView
android:id="@+id/text1"
android:layout_centerInParent="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="测试结果" />
<Button
android: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 + 显示我爱学习"/>
<Button
android: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 + 显示我不爱学习"/>
<Button
android: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;
@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();
}
});
}
}
注意事项
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() ) {
@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();
步骤1:创建HandlerThread的实例对象
/**
* 具体使用
* 传入参数 = 线程名字,作用 = 标记该线程
*/
HandlerThread mHandlerThread = new HandlerThread("handlerThread");
/**
* 源码分析:HandlerThread类的构造方法
*/
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;
}
...
}
总结
- HandlerThread类继承自Thread类
- 创建HandlerThread类对象 = 创建Thread类对象 + 设置线程优先级 = 新开1个工作线程 + 设置线程优先级
步骤2:启动线程
/**
* 具体使用
*/
mHandlerThread.start();
/**
* 源码分析:此处调用的是父类(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() {
}
总结
- 为当前工作线程(即步骤1创建的线程)创建1个Looper对象 & MessageQueue对象
- 通过持有锁机制来获得当前线程的Looper对象
- 发出通知:当前线程已经创建mLooper对象成功
- 工作线程进行消息循环,即不断从MessageQueue中取消息 & 派发消息
步骤3:创建工作线程Handler & 复写handleMessage()
/**
* 具体使用
* 作用:将 Handler 关联 HandlerThread 的 Looper 对象、实现消息处理操作 & 与其他线程进行通信
* 注:消息处理操作(HandlerMessage())的执行线程 = mHandlerThread所创建的工作线程中执行
*/
Handler workHandler = new Handler( handlerThread.getLooper() ) {
@Override
public boolean handleMessage(Message msg) {
...//消息处理
return true;
}
});
/**
* 源码分析: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;
}
总结
- 在获得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块 缓存了一定数量线程 的区域
作用:
- 复用线程
- 管理线程:统一分配、调优、监控,控制线程池的最大并发数
优点:
- 降低 因线程的创建&销毁带来的性能开销:重用缓存在线程池中的线程
- 提高 线程响应速度 & 执行效率:
重用线程 = 不需要创建线程 即可马上执行
管理线程 = 优化线程的执行顺序,避免大量线程因互相抢占系统资源而导致阻塞现象 - 提高 对线程的管理度
传统多线程方式(继承Thread类 & 实现Runnable接口)的问题:
- 每次 新建/销毁 线程对象 消耗资源、响应速度慢
- 线程缺乏统一管理,容易阻塞
核心参数
上述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 )
注: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() {
@Override
public 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个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>() {
@Override
protected 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>(){
@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值 & 分别获取,结果并未互相干扰
2.8.3 实现原理
- 核心原理:ThreadLocal类中有1个Map(称:ThreadLocalMap):用于存储每个线程 & 该线程设置的存储在ThreadLocal变量的值
- ThreadLocalMap的 键Key = 当前ThreadLocal实例、值value = 该线程设置的存储在ThreadLocal变量的值
- 该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对象 ->>分析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的值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>(){
@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值 & 分别获取,结果并未互相干扰
2. 与同步机制的区别
2.9 线程同步关键字 Synchronized
定义:Synchronized 是 Java中的1个关键字
作用:保证同一时刻最多只有1个线程执行 被Synchronized修饰的方法 / 代码
其他线程 必须等待当前线程执行完该方法 / 代码块后才能执行该方法 / 代码块
应用场景:保证线程安全,解决多线程中的并发同步问题(实现的是阻塞型并发),具体场景如下:
- 修饰 实例方法 / 代码块时,(同步)保护的是同一个对象方法的调用 & 当前实例对象
- 修饰 静态方法 / 代码块时,(同步)保护的是 静态方法的调用 & class 类对象
2.9.1 原理
- 依赖 JVM 实现同步
- 底层通过一个监视器对象(
monitor
)完成, wait()、notify() 等方法也依赖于 monitor 对象
监视器锁(monitor)的本质 依赖于 底层操作系统的互斥锁(Mutex Lock)实现
2.9.2 具体使用
使用规则
- 锁对象设置
a. 修饰代码块时,需1个reference对象 作为锁的对象
b. 修饰实例方法时,默认的锁对象 = 当前对象
c. 修饰类方法(静态)时,默认的锁对象 = 当前类的Class对象 - 根据锁对象不同,一把锁同时最多只能被一个线程持有
a. 若目标锁已被当前线程持有,其他线程只能阻塞等待当前线程释放目标锁
b. 若当前线程已持有目标锁,其他线程仍然可以调用目标类中未被synchronized修饰的方法 - 当对象获取多个锁时,必须以相反的顺序释放 & 在与所有锁被获取时相同的词法范围内释放所有的锁
a. 若线程进入由线程已拥有的监控器保护的synchronized块,就允许线程继续进行
b. 只有线程退出他进入的监控器保护的第一个synchronized块时,才释放锁 - 特别注意:
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)调用synchronized代码时检查对象是否已经被锁,是则检查是否被当前线程锁定,若是则计数+1,不是则加入等待队列
(2)释放时计数-1,直到为0 释放锁 - 重量级
底层是通过一个监视器对象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;
}
}