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(),内容 = 定义线程行为@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 好在哪?
- 适合资源共享: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(),定义线程行为@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封装好的轻量级异步类。
是抽象类,使用时要实现子类
优点:
- 方便实现异步通信:不需使用 “任务线程+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()// 作用:执行 线程任务前的操作// 注:根据需求复写@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();
举个例子
- 点击按钮 则 开启线程执行线程任务
- 显示后台加载进度
- 加载完毕后更新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"><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的使用步骤分析:
- 创建 AsyncTask 子类 & 根据需求实现核心方法
- 创建 AsyncTask子类的实例对象(即 任务实例)
- 手动调用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();
举个例子
- 点击按钮实现延迟操作
- 最终更新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个Looper对象 & MessageQueue对象
- 通过持有锁机制来获得当前线程的Looper对象
- 发出通知:当前线程已经创建mLooper对象成功
- 工作线程进行消息循环,即不断从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块 缓存了一定数量线程 的区域
作用:
- 复用线程
- 管理线程:统一分配、调优、监控,控制线程池的最大并发数
优点:
- 降低 因线程的创建&销毁带来的性能开销:重用缓存在线程池中的线程
- 提高 线程响应速度 & 执行效率:
重用线程 = 不需要创建线程 即可马上执行
管理线程 = 优化线程的执行顺序,避免大量线程因互相抢占系统资源而导致阻塞现象 - 提高 对线程的管理度
传统多线程方式(继承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() {@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个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变量的值
- 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对象 ->>分析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修饰的方法 / 代码
其他线程 必须等待当前线程执行完该方法 / 代码块后才能执行该方法 / 代码块
应用场景:保证线程安全,解决多线程中的并发同步问题(实现的是阻塞型并发),具体场景如下:
- 修饰 实例方法 / 代码块时,(同步)保护的是同一个对象方法的调用 & 当前实例对象
- 修饰 静态方法 / 代码块时,(同步)保护的是 静态方法的调用 & 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; } }