(二)Android 多线程开发
2.1)基础使用
1、继承Thread类
(1)简介
Thread类是Java中实现多线程的具体类,封装了所需线程操作。在Android开发中用于实现多线程。
注:线程对象&运行线程区别
线程对象是运行线程的实体,用来控制线程行为的唯一方式。线程对象通过线程类实例化创建,负责控制线程的状态,如:运行、睡眠、挂起/停止。
优点
实现简单:只要继承Thread类&复写run()即可实现多线程操作
缺点
局限性大:必须集成Thread类(Java规定单继承,即集成Thread类后不可继承其他类)
不适合资源共享:一个线程=一个实例对象,相对独立无法资源共享
消耗资源:Thread线程=一次性消费品&一个耗时任务。执行完一个耗时操作后,线程会被自动销毁,如果有100个耗时任务则必须开100个线程。多次创建&销毁线程,耗费系统资源
(2)使用
2.1)使用步骤
2.2)常规使用
// 步骤1:创建线程类 (继承自Thread类)
class MyThread extends Thread{
// 步骤2:复写run(),内容 = 定义线程行为
@Override
public void run(){
... // 定义的线程行为
}
}
// 步骤3:创建线程对象,即 实例化线程类
MyThread mt=new MyThread(“线程名称”);
// 步骤4:通过 线程对象 控制线程的状态,如 运行、睡眠、挂起 / 停止
// 此处采用 start()开启线程
mt.start();
2.3)匿名类使用
// 步骤1:采用匿名类,直接 创建 线程类的实例
new Thread("线程名称") {
// 步骤2:复写run(),内容 = 定义线程行为
@Override
public void run() {
// 步骤3:通过 线程对象 控制线程的状态,如 运行、睡眠、挂起 / 停止
}.start();
2.4)常规&匿名类使用区别
2、实现Runnable接口
(1)简介 一个与多线程相关的抽象接口,仅定义1个方法=run(),在Android开发中用于实现多线程 适合资源共享:Runnable可被多个线程(Thread实例)共享,适合多线程处理同一资源的情况 灵活:一个类可以继承多个接口,避免集成THread类导致的单继承局限性
补充:Java进阶知识——接口与继承区别 (1)关键字:接口interface,继承extends (2)定义:接口:对功能的描述,继承:具体描述一种类 (3)结构:接口只能定义全局常量、抽象方法,继承可以定义属性方法、常量、变量等等 (4)接口可以实现"多继承",继承只能"单继承" (5)实现接口的类一定要实现接口的抽象方法,继承的类可以调用、重载父类的任意方法
(2)使用
2.1)使用步骤
注:
Java中真正能创建新线程的只有Thread类对象
通过实现Runnable的方式,最终还是通过Thread类对象来创建线程
所以对于 实现了Runnable接口的类,称为 线程辅助类;Thread类才是真正的线程类
2.2)常规使用
// 步骤1:创建线程辅助类,实现Runnable接口
class MyThread implements Runnable{
....
@Override
// 步骤2:复写run(),定义线程行为
public void run(){
}
}
// 步骤3:创建线程辅助对象,即 实例化 线程辅助类
MyThread mt=new MyThread();
// 步骤4:创建线程对象,即 实例化线程类;线程类 = Thread类;
// 创建时通过Thread类的构造函数传入线程辅助类对象
// 原因:Runnable接口并没有任何对线程的支持,我们必须创建线程类(Thread类)的实例,从Thread类的一个实例内部运行
Thread td=new Thread(mt);
// 步骤5:通过 线程对象 控制线程的状态,如 运行、睡眠、挂起 / 停止
// 当调用start()方法时,线程对象会自动回调线程辅助类对象的run(),从而实现线程操作
td.start();
2.3)匿名类使用
// 步骤1:通过匿名类 直接 创建线程辅助对象,即 实例化 线程辅助类
Runnable mt = new Runnable() {
// 步骤2:复写run(),定义线程行为
@Override
public void run() {
}
};
// 步骤3:创建线程对象,即 实例化线程类;线程类 = Thread类;
Thread mt1 = new Thread(mt, "窗口1");
// 步骤4:通过 线程对象 控制线程的状态,如 运行、睡眠、挂起 / 停止
mt1.start();
(3)继承Thread类&实现Runnable接口对比
Android中Thread/Runnable方法使用了java原生的Thread/Runnable的线程形态,详细用法可参考上文Java多线程开发。
3、Handler
见陈小云Android学习之旅:第十章 进程间的通信 之 Handler机制(二)
2.2)复合使用
1、AsyncTask
(1)简介 一个Android已封装好的轻量级异步类,属于抽象类,使用时需要实现子类。用于 实现多线程,如在工作线程中执行耗时任务 异步通信、消息传递,如实现工作线程&主线程(UI线程)之间的通信,即:将工作线程的执行结果传递给主线程,从而在主线程中指向相关UI操作(保证线程安全)
public abstract class AsyncTask<Params, Progress, Result> {
...
}
不需使用"任务线程(如Thread类)+Handler"复杂组合,方便实现异步通信 采用线程池的缓存线程+复用线程,避免频繁创建&销毁线程所带来的系统资源开销 (2)类定义 AsyncTask类属于抽象类,即使用时需 实现子类
public abstract class AsyncTask<Params, Progress, Result> {
...
}
// 类中参数为3种泛型类型
// 整体作用:控制AsyncTask子类执行线程任务时各个阶段的返回类型
// 具体说明:
// a. Params:开始异步任务执行时传入的参数类型,对应excute()中传递的参数
// b. Progress:异步任务执行过程中,返回下载进度值的类型
// c. Result:异步任务执行完成后,返回的结果类型,与doInBackground()的返回值类型保持一致
// 注:
// a. 使用时并不是所有类型都被使用
// b. 若无被使用,可用java.lang.Void类型代替
// c. 若有不同业务,需额外再写1个AsyncTask的子类
}
(3)核心方法
AsyncTask 核心 & 常用的方法如下:
方法执行顺序如下
(4)使用步骤
步骤1:创建AsyncTask子类
a. 继承AsyncTask类
b. 为3个泛型参数指定类型;若不使用,可用java.lang.Void类型代替
c. 根据需求,在AsyncTask子类内实现核心方法
private class MyTask extends AsyncTask<Params, Progress, Result> {
....
// 方法1:onPreExecute()
// 作用:执行 线程任务前的操作
// 注:根据需求复写
@Override
protected void onPreExecute() {
...
}
// 方法2:doInBackground()
// 作用:接收输入参数、执行任务中的耗时操作、返回 线程任务执行的结果
// 注:必须复写,从而自定义线程任务
@Override
protected String doInBackground(String... params) {
...// 自定义的线程任务
// 可调用publishProgress()显示进度, 之后将执行onProgressUpdate()
publishProgress(count);
}
// 方法3:onProgressUpdate()
// 作用:在主线程 显示线程任务执行的进度
// 注:根据需求复写
@Override
protected void onProgressUpdate(Integer... progresses) {
...
}
// 方法4:onPostExecute()
// 作用:接收线程任务执行结果、将执行结果显示到UI组件
// 注:必须复写,从而自定义UI操作
@Override
protected void onPostExecute(String result) {
...// UI操作
}
// 方法5:onCancelled()
// 作用:将异步任务设置为:取消状态
@Override
protected void onCancelled() {
...
}
}
步骤2:创建Async子类的实例对象(任务实例) AsyncTask子类的实例必须在UI线程中创建
MyTask mTask = new MyTask();
步骤3:手动调用execute()从而执行异步线程任务 a.必须在UI线程中调用 b.同一个AsyncTask实例对象只能执行1次,若执行第2次会抛出异常 c.执行任务中,系统会系统会自动调用AsyncTask的一系列方法:onPreExecute() 、doInBackground()、onProgressUpdate() 、onPostExecute() d. 不能手动调用上述方法
mTask.execute();
(5)实例分析 5.1)实例需求 点击按钮 则 开启线程执行线程任务 显示后台加载进度 加载完毕后更新UI组件 期间若点击取消按钮,则取消加载 5.2)代码实现
private class MyTask extends AsyncTask<String, Integer, String> {
// 方法1:onPreExecute()
// 作用:执行 线程任务前的操作
@Override
protected void onPreExecute() {
text.setText("加载中");
// 执行前显示提示
}
// 方法2:doInBackground()
// 作用:接收输入参数、执行任务中的耗时操作、返回 线程任务执行的结果
// 此处通过计算从而模拟“加载进度”的情况
@Override
protected String doInBackground(String... params) {
try {
int count = 0;
int length = 1;
while (count<99) {
count += length;
// 可调用publishProgress()显示进度, 之后将执行onProgressUpdate()
publishProgress(count);
// 模拟耗时任务
Thread.sleep(50);
}
}catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
// 方法3:onProgressUpdate()
// 作用:在主线程 显示线程任务执行的进度
@Override
protected void onProgressUpdate(Integer... progresses) {
progressBar.setProgress(progresses[0]);
text.setText("loading..." + progresses[0] + "%");
}
// 方法4:onPostExecute()
// 作用:接收线程任务执行结果、将执行结果显示到UI组件
@Override
protected void onPostExecute(String result) {
// 执行完毕后,则更新UI
text.setText("加载完毕");
}
// 方法5:onCancelled()
// 作用:将异步任务设置为:取消状态
@Override
protected void onCancelled() {
text.setText("已取消");
progressBar.setProgress(0);
}
}
(6)问题&解决
6.1)关于生命周期
问题:AsyncTask不与任何组件绑定生命周期
解决:在Activity 或 Fragment中使用 AsyncTask时,最好在Activity 或 Fragment的onDestory()调用 cancel(boolean);
6.2)关于内存泄露
问题:若AsyncTask被声明为Activity的非静态内部类,当Activity需销毁时,会因AsyncTask保留对Activity的引用 而导致Activity无法被回收,最终引起内存泄露
解决:AsyncTask应被声明为Activity的静态内部类
线程任务执行结果丢失
问题:当Activity重新创建时(屏幕旋转 / Activity被意外销毁时后恢复),之前运行的AsyncTask(非静态的内部类)持有的之前Activity引用已无效,故复写的onPostExecute()将不生效,即无法更新UI操作
解决:在Activity恢复时的对应方法 重启 任务线程
(7)源码分析
7.1)原理介绍
AsyncTask的实现原理 = 线程池 + Handler
其中:线程池用于线程调度、复用 & 执行任务;Handler 用于异步通信
其内部封装了2个线程池 + 1个Handler,具体介绍如下:
7.2)源码分析
根据AsyncTask使用步骤讲解
步骤1:创建AsbncTask子类
该类复写的方法在后续源码中调用
步骤2:创建AsyncTask子类的实例对象(任务实例)
1、具体使用
MyTask mTask = new MyTask();
2、源码分析:AsyncTask的构造函数
/**
* 源码分析:AsyncTask的构造函数
*/
public AsyncTask() {
// 1. 初始化WorkerRunnable变量 = 一个可存储参数的Callable对象 ->>分析1
mWorker = new WorkerRunnable<Params, Result>() {
// 在任务执行线程池中回调:THREAD_POOL_EXECUTOR.execute()
// 下面会详细讲解
public Result call() throws Exception {
// 添加线程的调用标识
mTaskInvoked.set(true);
Result result = null;
try {
// 设置线程的优先级
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
// 执行异步操作 = 耗时操作
// 即 我们使用过程中复写的耗时任务
result = doInBackground(mParams);
Binder.flushPendingCommands();
} catch (Throwable tr) {
mCancelled.set(true);// 若运行异常,设置取消的标志
throw tr;
} finally {
// 把异步操作执行的结果发送到主线程
// 从而更新UI,下面会详细讲解
postResult(result);
}
return result;
}
};
// 2. 初始化FutureTask变量 = 1个FutureTask ->>分析2
mFuture = new FutureTask<Result>(mWorker) {
// done()简介:FutureTask内的Callable执行完后的调用方法
// 作用:复查任务的调用、将未被调用的任务的结果通过InternalHandler传递到UI线程
@Override
protected void done() {
try {
// 在执行完任务后检查,将没被调用的Result也一并发出 ->>分析3
postResultIfNotInvoked(get());
} catch (InterruptedException e) {
android.util.Log.w(LOG_TAG, e);
} catch (ExecutionException e) {
throw new RuntimeException("An error occurred while executing doInBackground()",
e.getCause());
} catch (CancellationException e) {
//若 发生异常,则将发出null
postResultIfNotInvoked(null);
}
}
};
}
/**
* 分析1:WorkerRunnable类的构造函数
*/
private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
// 此处的Callable也是任务;
// 与Runnable的区别:Callable<T>存在返回值 = 其泛型
Params[] mParams;
}
/**
* 分析2:FutureTask类的构造函数
* 定义:1个包装任务的包装类
* 注:内部包含Callable<T> 、增加了一些状态标识 & 操作Callable<T>的接口
*/
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW;
}
// 回到调用原处
/**
* 分析3:postResultIfNotInvoked()
*/
private void postResultIfNotInvoked()(Result result) {
// 取得任务标记
final boolean wasTaskInvoked = mTaskInvoked.get();
// 若任务无被执行,将未被调用的任务的结果通过InternalHandler传递到UI线程
if (!wasTaskInvoked) {
postResult(result);
}
}
3、总结 创建了1个WorkerRunnable类 的实例对象 & 复写了call()方法 创建了1个FutureTask类 的实例对象 & 复写了 done() 步骤3:手动调用execute(Params...params) 1、具体使用
mTask.execute();
2、源码分析
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
// ->>分析1
}
/**
* 分析1:executeOnExecutor(sDefaultExecutor, params)
* 参数说明:sDefaultExecutor = 任务队列 线程池类(SerialExecutor)的对象
*/
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,Params... params) {
// 1. 判断 AsyncTask 当前的执行状态
// PENDING = 初始化状态
if (mStatus != Status.PENDING) {
switch (mStatus) {
case RUNNING:
throw new IllegalStateException("Cannot execute task:"
+ " the task is already running.");
case FINISHED:
throw new IllegalStateException("Cannot execute task:"
+ " the task has already been executed "
+ "(a task can be executed only once)");
}
}
// 2. 将AsyncTask状态设置为RUNNING状态
mStatus = Status.RUNNING;
// 3. 主线程初始化工作
onPreExecute();
// 4. 添加参数到任务中
mWorker.mParams = params;
// 5. 执行任务
// 此处的exec = sDefaultExecutor = 任务队列 线程池类(SerialExecutor)的对象
// ->>分析2
exec.execute(mFuture);
return this;
}
/**
* 分析2:exec.execute(mFuture)
* 说明:属于任务队列 线程池类(SerialExecutor)的方法
*/
private static class SerialExecutor implements Executor {
// SerialExecutor = 静态内部类
// 即 是所有实例化的AsyncTask对象公有的
// SerialExecutor 内部维持了1个双向队列;
// 容量根据元素数量调节
final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
Runnable mActive;
// execute()被同步锁synchronized修饰
// 即说明:通过锁使得该队列保证AsyncTask中的任务是串行执行的
// 即 多个任务需1个个加到该队列中;然后 执行完队列头部的再执行下一个,以此类推
public synchronized void execute(final Runnable r) {
// 将实例化后的FutureTask类 的实例对象传入
// 即相当于:向队列中加入一个新的任务
mTasks.offer(new Runnable() {
public void run() {
try {
r.run();
} finally {
scheduleNext();->>分析3
}
}
});
// 若当前无任务执行,则去队列中取出1个执行
if (mActive == null) {
scheduleNext();
}
}
// 分析3
protected synchronized void scheduleNext() {
// 1. 取出队列头部任务
if ((mActive = mTasks.poll()) != null) {
// 2. 执行取出的队列头部任务
// 即 调用执行任务线程池类(THREAD_POOL_EXECUTOR)->>继续往下看
THREAD_POOL_EXECUTOR.execute(mActive);
}
}
}
3、总结: 执行任务前,通过 任务队列 线程池类(SerialExecutor)将任务按顺序放入到队列中; 通过同步锁 修饰execute()从而保证AsyncTask中的任务是串行执行的,之后的线程任务执行是 通过任务线程池类(THREAD_POOL_EXECUTOR) 进行的。 继续往下分析:THREAD_POOL_EXECUTOR.execute()
/**
* 源码分析:THREAD_POOL_EXECUTOR.execute()
* 说明:
* a. THREAD_POOL_EXECUTOR实际上是1个已配置好的可执行并行任务的线程池
* b. 调用THREAD_POOL_EXECUTOR.execute()实际上是调用线程池的execute()去执行具体耗时任务
* c. 而该耗时任务则是步骤2中初始化WorkerRunnable实例对象时复写的call()
* 注:下面先看任务执行线程池的线程配置过程,看完后请回到步骤2中的源码分析call()
*/
// 步骤1:参数设置
//获得当前CPU的核心数
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
//设置线程池的核心线程数2-4之间,但是取决于CPU核数
private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
//设置线程池的最大线程数为 CPU核数*2+1
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
//设置线程池空闲线程存活时间30s
private static final int KEEP_ALIVE_SECONDS = 30;
//初始化线程工厂
private static final ThreadFactory sThreadFactory = new ThreadFactory() {
private final AtomicInteger mCount = new AtomicInteger(1);
public Thread newThread(Runnable r) {
return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
}
};
//初始化存储任务的队列为LinkedBlockingQueue 最大容量为128
private static final BlockingQueue<Runnable> sPoolWorkQueue =
new LinkedBlockingQueue<Runnable>(128);
// 步骤2: 根据参数配置执行任务线程池,即 THREAD_POOL_EXECUTOR
public static final Executor THREAD_POOL_EXECUTOR;
static {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
sPoolWorkQueue, sThreadFactory);
// 设置核心线程池的 超时时间也为30s
threadPoolExecutor.allowCoreThreadTimeOut(true);
THREAD_POOL_EXECUTOR = threadPoolExecutor;
}
// 请回到步骤2中的源码分析call()
至此,我们回到步骤2中的源码分析call()
/**
* 步骤2的源码分析:AsyncTask的构造函数
*/
public AsyncTask() {
// 1. 初始化WorkerRunnable变量 = 一个可存储参数的Callable对象
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
// 添加线程的调用标识
mTaskInvoked.set(true);
Result result = null;
try {
// 设置线程的优先级
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
// 执行异步操作 = 耗时操作
// 即 我们使用过程中复写的耗时任务
result = doInBackground(mParams);
Binder.flushPendingCommands();
} catch (Throwable tr) {
mCancelled.set(true);// 若运行异常,设置取消的标志
throw tr;
} finally {
// 把异步操作执行的结果发送到主线程
// 从而更新UI ->>分析1
postResult(result);
}
return result;
}
};
.....// 省略
}
/**
* 分析1:postResult(result)
*/
private Result postResult(Result result) {
@SuppressWarnings("unchecked")
// 创建Handler对象 ->> 源自InternalHandler类—>>分析2
Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult<Result>(this, result));
// 发送消息到Handler中
message.sendToTarget();
return result;
}
/**
* 分析2:InternalHandler类
*/
private static class InternalHandler extends Handler {
// 构造函数
public InternalHandler() {
super(Looper.getMainLooper());
// 获取的是主线程的Looper()
// 故 AsyncTask的实例创建 & execute()必须在主线程使用
}
@Override
public void handleMessage(Message msg) {
AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
switch (msg.what) {
// 若收到的消息 = MESSAGE_POST_RESULT
// 则通过finish() 将结果通过Handler传递到主线程
case MESSAGE_POST_RESULT:
result.mTask.finish(result.mData[0]); ->>分析3
break;
// 若收到的消息 = MESSAGE_POST_PROGRESS
// 则回调onProgressUpdate()通知主线程更新进度的操作
case MESSAGE_POST_PROGRESS:
result.mTask.onProgressUpdate(result.mData);
break;
}
}
}
/**
* 分析3:result.mTask.finish(result.mData[0])
*/
private void finish(Result result) {
// 先判断是否调用了Cancelled()
// 1. 若调用了则执行我们复写的onCancelled()
// 即 取消任务时的操作
if (isCancelled()) {
onCancelled(result);
} else {
// 2. 若无调用Cancelled(),则执行我们复写的onPostExecute(result)
// 即更新UI操作
onPostExecute(result);
}
// 注:不管AsyncTask是否被取消,都会将AsyncTask的状态变更为:FINISHED
mStatus = Status.FINISHED;
}
总结
任务线程池类(THREAD_POOL_EXECUTOR)实际上是1个已配置好的可执行并行任务的线程池
调用THREAD_POOL_EXECUTOR.execute()实际上是调用线程池的execute()去执行具体耗时任务
而该耗时任务则是步骤2中初始化 WorkerRunnable实例对象时复写的call()内容
在call()方法里,先调用 我们复写的doInBackground(mParams)执行耗时操作
再调用postResult(result), 通过 InternalHandler 类 将任务消息传递到主线程;根据消息标识(MESSAGE_POST_RESULT)判断,最终通过finish()调用我们复写的onPostExecute(result),从而实现UI更新操作
7.3)Async源码总结
2、HandlerThread
(1)介绍 HandlerThread是一个Android已封装好的轻量级异步类,用于实现多线程(在工作线程中执行耗时任务)及异步通信、消息传递(工作线程&主线程之间通信)从而保证线程安全 HandlerThread本质上是通过继承Thread类和封装Handler类的使用,从而使得创建新线程和与其他线程进行通信变得更加方便易用(不需要使用"任务线程(如继承Thread类)+Handler"复杂组合) (2)使用
步骤1:创建HandlerThread实例对象
//传入参数 = 线程名字,作用 = 标记该线程
HandlerThread mHandlerThread = new HandlerThread("handlerThread");
步骤2:启动线程
mHandlerThread.start()
步骤3:创建工作线程Handler&复写handleMessage()
// 作用:关联HandlerThread的Looper对象、实现消息处理操作 & 与其他线程进行通信
// 注:消息处理操作(HandlerMessage())的执行线程 = mHandlerThread所创建的工作线程中执行
Handler workHandler = new Handler( handlerThread.getLooper() ) {
@Override
public boolean handleMessage(Message msg) {
...//消息处理
return true;
}
});
步骤4:使用工作线程Handler向工作线程的消息队列发送消息
// 在工作线程中,当消息循环时取出对应消息 & 在工作线程执行相关操作
// a. 定义要发送的消息
Message msg = Message.obtain();
msg.what = 2; //消息的标识
msg.obj = "B"; // 消息的存放
// b. 通过Handler发送消息到其绑定的消息队列
workHandler.sendMessage(msg);
步骤5:结束线程,即停止线程的消息循环
mHandlerThread.quit();
(3)实例
public class MainActivity extends AppCompatActivity {
Handler mainHandler,workHandler;
HandlerThread mHandlerThread;
TextView text;
Button button1,button2,button3;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 显示文本
text = (TextView) findViewById(R.id.text1);
// 创建与主线程关联的Handler
mainHandler = new Handler();
/**
* 步骤1:创建HandlerThread实例对象
* 传入参数 = 线程名字,作用 = 标记该线程
*/
mHandlerThread = new HandlerThread("handlerThread");
/**
* 步骤2:启动线程
*/
mHandlerThread.start();
/**
* 步骤3:创建工作线程Handler & 复写handleMessage()
* 作用:关联HandlerThread的Looper对象、实现消息处理操作 & 与其他线程进行通信
* 注:消息处理操作(HandlerMessage())的执行线程 = mHandlerThread所创建的工作线程中执行
*/
workHandler = new Handler(mHandlerThread.getLooper()){
@Override
// 消息处理的操作
public void handleMessage(Message msg)
{
//设置了两种消息处理操作,通过msg来进行识别
switch(msg.what){
// 消息1
case 1:
try {
//延时操作
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 通过主线程Handler.post方法进行在主线程的UI更新操作
mainHandler.post(new Runnable() {
@Override
public void run () {
text.setText("我爱学习");
}
});
break;
// 消息2
case 2:
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
mainHandler.post(new Runnable() {
@Override
public void run () {
text.setText("我不喜欢学习");
}
});
break;
default:
break;
}
}
};
/**
* 步骤4:使用工作线程Handler向工作线程的消息队列发送消息
* 在工作线程中,当消息循环时取出对应消息 & 在工作线程执行相关操作
*/
// 点击Button1
button1 = (Button) findViewById(R.id.button1);
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 通过sendMessage()发送
// a. 定义要发送的消息
Message msg = Message.obtain();
msg.what = 1; //消息的标识
msg.obj = "A"; // 消息的存放
// b. 通过Handler发送消息到其绑定的消息队列
workHandler.sendMessage(msg);
}
});
// 点击Button2
button2 = (Button) findViewById(R.id.button2);
button2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 通过sendMessage()发送
// a. 定义要发送的消息
Message msg = Message.obtain();
msg.what = 2; //消息的标识
msg.obj = "B"; // 消息的存放
// b. 通过Handler发送消息到其绑定的消息队列
workHandler.sendMessage(msg);
}
});
// 点击Button3
// 作用:退出消息循环
button3 = (Button) findViewById(R.id.button3);
button3.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mHandlerThread.quit();
}
});
}
}
(4)源码解析 4.1)工作原理 内部原理 = Thread类 + Handler类机制 (1)通过继承Thread类,快速创建1个带有Looper对象的新工作线程 (2)通过封装Handler类,快速创建Handler&与其他线程进行通信 4.2)源码解析 步骤1:创建HandlerThread的实例对象 1、具体使用
HandlerThread mHandlerThread = new HandlerThread("handlerThread");
2、源码解析
public class HandlerThread extends Thread {
// 继承自Thread类
int mPriority; // 线程优先级
int mTid = -1; // 当前线程id
Looper mLooper; // 当前线程持有的Looper对象
// HandlerThread类有2个构造方法
// 区别在于:设置当前线程的优先级参数,即可自定义设置 or 使用默认优先级
// 方式1. 默认优先级
public HandlerThread(String name) {
// 通过调用父类默认的方法创建线程
super(name);
mPriority = Process.THREAD_PRIORITY_DEFAULT;
}
// 方法2. 自定义设置优先级
public HandlerThread(String name, int priority) {
super(name);
mPriority = priority;
}
...
}
3、总结 HandlerThread类继承自Thread类 创建HandlerThread类对象 = 创建Thread类对象 + 设置线程优先级 = 新开1个工作线程 + 设置线程优先级 步骤2:启动线程 1、具体使用
mHandlerThread.start();
2、源码解析
/**
* 源码分析:此处调用的是父类(Thread类)的start(),最终回调HandlerThread的run()
*/
@Override
public void run() {
// 1. 获得当前线程的id
mTid = Process.myTid();
// 2. 创建1个Looper对象 & MessageQueue对象
Looper.prepare();
// 3. 通过持有锁机制来获得当前线程的Looper对象
synchronized (this) {
mLooper = Looper.myLooper();
// 发出通知:当前线程已经创建mLooper对象成功
// 此处主要是通知getLooper()中的wait()
notifyAll();
// 此处使用持有锁机制 + notifyAll() 是为了保证后面获得Looper对象前就已创建好Looper对象
}
// 4. 设置当前线程的优先级
Process.setThreadPriority(mPriority);
// 5. 在线程循环前做一些准备工作 ->>分析1
// 该方法实现体是空的,子类可实现 / 不实现该方法
onLooperPrepared();
// 6. 进行消息循环,即不断从MessageQueue中取消息 & 派发消息
Looper.loop();
mTid = -1;
}
}
/**
* 分析1:onLooperPrepared();
* 说明:该方法实现体是空的,子类可实现 / 不实现该方法
*/
protected void onLooperPrepared() {
}
3、总结 1、为当前工作线程(即步骤1创建的线程)创建1个Looper对象 & MessageQueue对象 2、通过持有锁机制来获得当前线程的Looper对象 3、发出通知:当前线程已经创建mLooper对象成功 4、工作线程进行消息循环,即不断从MessageQueue中取消息 & 派发消息 步骤3:创建工作线程Handler & 复写handleMessage() 1、具体使用
Handler workHandler = new Handler( handlerThread.getLooper() ) {
@Override
public boolean handleMessage(Message msg) {
...//消息处理
return true;
}
});
2、源码解析
/**
* 源码分析:handlerThread.getLooper()
* 作用:获得当前HandlerThread线程中的Looper对象
*/
public Looper getLooper() {
// 若线程不是存活的,则直接返回null
if (!isAlive()) {
return null;
}
// 若当前线程存活,再判断线程的成员变量mLooper是否为null
// 直到线程创建完Looper对象后才能获得Looper对象,若Looper对象未创建成功,则阻塞
synchronized (this) {
while (isAlive() && mLooper == null) {
try {
// 此处会调用wait方法去等待
wait();
} catch (InterruptedException e) {
}
}
}
// 上述步骤run()使用 持有锁机制 + notifyAll() 获得Looper对象后
// 则通知当前线程的wait()结束等待 & 跳出循环
// 最终getLooper()返回的是在run()中创建的mLooper对象
return mLooper;
}
3、总结 在获得HandlerThread工作线程的Looper对象时存在一个同步的问题:只有当线程创建成功 & 其对应的Looper对象也创建成功后才能获得Looper的值,才能将创建的Handler 与 工作线程的Looper对象绑定,从而将Handler绑定工作线程 解决方案:即保证同步的解决方案 = 同步锁、wait() 和 notifyAll(),即 在run()中成功创建Looper对象后,立即调用notifyAll()通知 getLooper()中的wait()结束等待 & 返回run()中成功创建的Looper对象,使得Handler与该Looper对象绑定 步骤4:使用工作线程Handler向工作线程的消息队列发送消息 1、具体使用
// a. 定义要发送的消息
Message msg = Message.obtain();
msg.what = 2; //消息的标识
msg.obj = "B"; // 消息的存放
// b. 通过Handler发送消息到其绑定的消息队列
workHandler.sendMessage(msg);
2、源码解析 源码分析:workHandler.sendMessage(msg) 此处的源码即Handler的源码,故不作过多描述 步骤5:结束线程,即停止线程的消息循环 1、具体使用
mHandlerThread.quit();
2、源码解析
/**
* 源码分析:mHandlerThread.quit()
* 说明:
* a. 该方法属于HandlerThread类
* b. HandlerThread有2种让当前线程退出消息循环的方法:quit() 、quitSafely()
*/
// 方式1:quit()
// 特点:效率高,但线程不安全
public boolean quit() {
Looper looper = getLooper();
if (looper != null) {
looper.quit();
return true;
}
return false;
}
// 方式2:quitSafely()
// 特点:效率低,但线程安全
public boolean quitSafely() {
Looper looper = getLooper();
if (looper != null) {
looper.quitSafely();
return true;
}
return false;
}
// 注:上述2个方法最终都会调用MessageQueue.quit(boolean safe)->>分析1
/**
* 分析1:MessageQueue.quit(boolean safe)
*/
void quit(boolean safe) {
if (!mQuitAllowed) {
throw new IllegalStateException("Main thread not allowed to quit.");
}
synchronized (this) {
if (mQuitting) {
return;
}
mQuitting = true;
if (safe) {
removeAllFutureMessagesLocked(); // 方式1(不安全)会调用该方法 ->>分析2
} else {
removeAllMessagesLocked(); // 方式2(安全)会调用该方法 ->>分析3
}
// We can assume mPtr != 0 because mQuitting was previously false.
nativeWake(mPtr);
}
}
/**
* 分析2:removeAllMessagesLocked()
* 原理:遍历Message链表、移除所有信息的回调 & 重置为null
*/
private void removeAllMessagesLocked() {
Message p = mMessages;
while (p != null) {
Message n = p.next;
p.recycleUnchecked();
p = n;
}
mMessages = null;
}
/**
* 分析3:removeAllFutureMessagesLocked()
* 原理:先判断当前消息队列是否正在处理消息
* a. 若不是,则类似分析2移除消息
* b. 若是,则等待该消息处理处理完毕再使用分析2中的方式移除消息退出循环
* 结论:退出方法安全与否(quitSafe() 或 quit()),在于该方法移除消息、退出循环时是否在意当前队列是否正在处理消息
*/
private void removeAllFutureMessagesLocked() {
final long now = SystemClock.uptimeMillis();
Message p = mMessages;
if (p != null) {
// 判断当前消息队列是否正在处理消息
// a. 若不是,则直接移除所有回调
if (p.when > now) {
removeAllMessagesLocked();
} else {
// b. 若是正在处理,则等待该消息处理处理完毕再退出该循环
Message n;
for (;;) {
n = p.next;
if (n == null) {
return;
}
if (n.when > now) {
break;
}
p = n;
}
p.next = null;
do {
p = n;
n = p.next;
p.recycleUnchecked();
} while (n != null);
}
}
}
4.3)总结
(5)问题&解决
5.1)内存泄露
1、问题
In Android, Handler classes should be static or leaks might occur.
2、原因 Handler导致内存泄露:当Handler消息队列 还有未处理的消息 / 正在处理消息时,存在引用关系: “未被处理 / 正处理的消息 -> Handler实例 -> 外部类” 若出现 Handler的生命周期 > 外部类的生命周期 时(即 Handler消息队列 还有未处理的消息 / 正在处理消息 而 外部类需销毁时),将使得外部类无法被垃圾回收器(GC)回收,从而造成 内存泄露 3、解决 将Handler子类设置为静态内部类+使用weakReference弱引用持有Activity实例 5.2)连续发送消息 1、问题 当你连续点击3下时,发现并无按照最新点击的按钮操作显示,而是按顺序的一个个显示出来 2、原因 使用HandlerThread时只是开了一个工作线程,当你点击了n下后,只是将n个消息发送到消息队列MessageQueue里排队,等候派发消息给Handler再进行对应的操作
3、IntentService
(1)介绍 Android里的一个封装类,继承四大组件之一Service,用于处理异步请求&实现多线程。线程任务需按顺序、在后台执行。适用于离线下载,不符合多个数据同时请求的场景(所有任务都在同一个Thread looper里执行) (2)使用 步骤1:定义 IntentService的子类 传入线程名称、复写onHandleIntent()方法
public class myIntentService extends IntentService {
/**
* 在构造函数中传入线程名字
**/
public myIntentService() {
// 调用父类的构造函数
// 参数 = 工作线程的名字
super("myIntentService");
}
/**
* 复写onHandleIntent()方法
* 根据 Intent实现 耗时任务 操作
**/
@Override
protected void onHandleIntent(Intent intent) {
// 根据 Intent的不同,进行不同的事务处理
String taskName = intent.getExtras().getString("taskName");
switch (taskName) {
case "task1":
Log.i("myIntentService", "do task1");
break;
case "task2":
Log.i("myIntentService", "do task2");
break;
default:
break;
}
}
@Override
public void onCreate() {
Log.i("myIntentService", "onCreate");
super.onCreate();
}
/**
* 复写onStartCommand()方法
* 默认实现 = 将请求的Intent添加到工作队列里
**/
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i("myIntentService", "onStartCommand");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
Log.i("myIntentService", "onDestroy");
super.onDestroy();
}
}
步骤2:在Manifest.xml中注册服务
<service android:name=".myIntentService">
<intent-filter >
<action android:name="cn.scu.finch"/>
</intent-filter>
</service>
步骤3:在Activity中开启Service服务
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 同一服务只会开启1个工作线程
// 在onHandleIntent()函数里,依次处理传入的Intent请求
// 将请求通过Bundle对象传入到Intent,再传入到服务里
// 请求1
Intent i = new Intent("cn.scu.finch");
Bundle bundle = new Bundle();
bundle.putString("taskName", "task1");
i.putExtras(bundle);
startService(i);
// 请求2
Intent i2 = new Intent("cn.scu.finch");
Bundle bundle2 = new Bundle();
bundle2.putString("taskName", "task2");
i2.putExtras(bundle2);
startService(i2);
startService(i); //多次启动
}
}
(3)源码解析
3.1)工作原理
若启动IntentService 多次,那么 每个耗时操作 则 以队列的方式 在 IntentService的 onHandleIntent回调方法中依次执行,执行完自动结束
3.2)源码解析
问题1:IntentService如何单独开启1个新的工作线程
IntentService源码中的 onCreate()方法
@Override
public void onCreate() {
super.onCreate();
// 1. 通过实例化andlerThread新建线程 & 启动;故 使用IntentService时,不需额外新建线程
// HandlerThread继承自Thread,内部封装了 Looper
HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
thread.start();
// 2. 获得工作线程的 Looper & 维护自己的工作队列
mServiceLooper = thread.getLooper();
// 3. 新建mServiceHandler & 绑定上述获得Looper
// 新建的Handler 属于工作线程 ->>分析1
mServiceHandler = new ServiceHandler(mServiceLooper);
}
/**
* 分析1:ServiceHandler源码分析
**/
private final class ServiceHandler extends Handler {
// 构造函数
public ServiceHandler(Looper looper) {
super(looper);
}
// IntentService的handleMessage()把接收的消息交给onHandleIntent()处理
@Override
public void handleMessage(Message msg) {
// onHandleIntent 方法在工作线程中执行
// onHandleIntent() = 抽象方法,使用时需重写 ->>分析2
onHandleIntent((Intent)msg.obj);
// 执行完调用 stopSelf() 结束服务
stopSelf(msg.arg1);
}
}
/**
* 分析2: onHandleIntent()源码分析
* onHandleIntent() = 抽象方法,使用时需重写
**/
@WorkerThread
protected abstract void onHandleIntent(Intent intent);
问题2:IntentService 如何通过onStartCommand() 将Intent 传递给服务 & 依次插入到工作队列中
/**
* onStartCommand()源码分析
* onHandleIntent() = 抽象方法,使用时需重写
**/
public int onStartCommand(Intent intent, int flags, int startId) {
// 调用onStart()->>分析1
onStart(intent, startId);
return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}
/**
* 分析1:onStart(intent, startId)
**/
public void onStart(Intent intent, int startId) {
// 1. 获得ServiceHandler消息的引用
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
// 2. 把 Intent参数 包装到 message 的 obj 发送消息中,
//这里的Intent = 启动服务时startService(Intent) 里传入的 Intent
msg.obj = intent;
// 3. 发送消息,即 添加到消息队列里
mServiceHandler.sendMessage(msg);
}
3.3)源码总结 IntentService本质 = Handler + HandlerThread: 1、通过HandlerThread 单独开启1个工作线程:IntentService 2、创建1个内部 Handler :ServiceHandler 3、绑定 ServiceHandler 与 IntentService 4、通过 onStartCommand() 传递服务intent 到ServiceHandler 、依次插入Intent到工作队列中 & 逐个发送给 onHandleIntent() 5、通过onHandleIntent() 依次处理所有Intent对象所对应的任务 因此我们通过复写onHandleIntent() & 在里面 根据Intent的不同进行不同线程操作 即可 3.4)注意事项 注意事项1:工作任务队列 = 顺序执行 即 若一个任务正在IntentService中执行,此时你再发送1个新的任务请求,这个新的任务会一直等待直到前面一个任务执行完毕后才开始执行 原因: 1、由于onCreate()只会调用一次 = 只会创建1个工作线程; 2、当多次调用 startService(Intent)时(即 onStartCommand()也会调用多次),其实不会创建新的工作线程,只是把消息加入消息队列中 & 等待执行。 3、所以,多次启动 IntentService 会按顺序执行事件 若服务停止,则会清除消息队列中的消息,后续的事件不执行 注意事项2:不建议通过 bindService() 启动 IntentService 原因:
// 在IntentService中,onBind()`默认返回null
@Override
public IBinder onBind(Intent intent) {
return null;
}
采用 bindService()启动 IntentService的生命周期如下:
onCreate() ->> onBind() ->> onunbind()->> onDestory()
即,并不会调用onStart() 或 onStartcommand(),故不会将消息发送到消息队列,那么onHandleIntent()将不会回调,即无法实现多线程的操作
此时,你应该使用Service,而不是IntentService
(4)对比
4.1)与Service对比
4.2)与其他线程对比
2.3)高级使用
线程池(ThreadPool)
(1)介绍
线程池是一块缓存了一定线程数量的区域,用于复用线程和管理线程(如1、统一分配、调优&监控2、控制线程池的最大并发数)
降低因线程创建&销毁带来的性能开销(重用缓存在线程池的线程)
提高线程响应速度&执行效率:1、重用线程 = 不需创建线程,即可马上执行2、管理线程 = 优化线程执行顺序(避免大量线程间因互相抢占系统资源而到只阻塞现象)
提高对线程的管理度
注:传统多线程方式(集成Thread类 & 实现Runnable接口)的问题
1、每次新建/销毁线程对象消耗资源、响应速度慢
2、线程缺乏统一管理,容易出现阻塞情况
(2)工作原理
2.1核心参数
上述6个参数的配置 决定了 线程池的功能,具体设置时机 = 创建 线程池类对象时 传入
ThreadPoolExecutor类 = 线程池的真正实现类
开发者可根据不同需求 配置核心参数,从而实现自定义线程池
// 创建线程池对象如下
// 通过 构造方法 配置核心参数
Executor executor = new ThreadPoolExecutor(
CORE_POOL_SIZE,
MAXIMUM_POOL_SIZE,
KEEP_ALIVE,
TimeUnit.SECONDS,
sPoolWorkQueue,
sThreadFactory
);
// 构造函数源码分析
public ThreadPoolExecutor (int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable workQueue>,
ThreadFactory threadFactory )
2.2内部原理逻辑
(3)使用流程
// 1. 创建线程池
// 创建时,通过配置线程池的参数,从而实现自己所需的线程池
Executor threadPool = new ThreadPoolExecutor(
CORE_POOL_SIZE,
MAXIMUM_POOL_SIZE,
KEEP_ALIVE,
TimeUnit.SECONDS,
sPoolWorkQueue,
sThreadFactory
);
// 注:在Java中,已内置4种常见线程池,下面会详细说明
// 2. 向线程池提交任务:execute()
// 说明:传入 Runnable对象
threadPool.execute(new Runnable() {
@Override
public void run() {
... // 线程执行任务
}
});
// 3. 关闭线程池shutdown()
threadPool.shutdown();
// 关闭线程的原理
// a. 遍历线程池中的所有工作线程
// b. 逐个调用线程的interrupt()中断线程(注:无法响应中断的任务可能永远无法终止)
// 也可调用shutdownNow()关闭线程:threadPool.shutdownNow()
// 二者区别:
// shutdown:设置 线程池的状态 为 SHUTDOWN,然后中断所有没有正在执行任务的线程
// shutdownNow:设置 线程池的状态 为 STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表
// 使用建议:一般调用shutdown()关闭线程池;若任务不一定要执行完,则调用shutdownNow()
作者:Carson_Ho
链接:https://www.jianshu.com/p/0e4a5e70bf0e
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。
(4)常见的4类功能线程池 4.1定长线程池(FixedThreadPool) 1、特点 只有核心线程 & 不会被回收、线程数量固定、任务队列无大小限制(超出的线程任务会在队列中等待) 2、应用场景 控制线程最大并发数 3、具体使用 通过 Executors.newFixedThreadPool() 创建
// 1. 创建定长线程池对象 & 设置线程池线程数量固定为3
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
// 2. 创建好Runnable类线程对象 & 需执行的任务
Runnable task =new Runnable(){
public void run(){
System.out.println("执行任务啦");
}
};
// 3. 向线程池提交任务:execute()
fixedThreadPool.execute(task);
// 4. 关闭线程池
fixedThreadPool.shutdown();
4.2定时线程池(ScheduledThreadPool) 1、特点 核心线程数量固定、非核心线程数量无限制(闲置时马上回收) 2、应用场景 执行定时 / 周期性 任务 3、具体使用 通过Executors.newScheduledThreadPool()创建
// 1. 创建 定时线程池对象 & 设置线程池线程数量固定为5
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
// 2. 创建好Runnable类线程对象 & 需执行的任务
Runnable task =new Runnable(){
public void run(){
System.out.println("执行任务啦");
}
};
// 3. 向线程池提交任务:schedule()
scheduledThreadPool.schedule(task, 1, TimeUnit.SECONDS); // 延迟1s后执行任务
scheduledThreadPool.scheduleAtFixedRate(task,10,1000,TimeUnit.MILLISECONDS);// 延迟10ms后、每隔1000ms执行任务
// 4. 关闭线程池
scheduledThreadPool.shutdown();
4.3可缓存线程池(CachedThreadPool) 1、特点 只有非核心线程、线程数量不固定(可无限大)、灵活回收空闲线程(具备超时机制,全部回收时几乎不占系统资源)、新建线程(无线程可用时) 任何线程任务到来都会立刻执行,不需要等待 2、应用场景 执行大量、耗时少的线程任务 3、具体使用 通过Executors.newCachedThreadPool()创建
// 1. 创建可缓存线程池对象
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
// 2. 创建好Runnable类线程对象 & 需执行的任务
Runnable task =new Runnable(){
public void run(){
System.out.println("执行任务啦");
}
};
// 3. 向线程池提交任务:execute()
cachedThreadPool.execute(task);
// 4. 关闭线程池
cachedThreadPool.shutdown();
//当执行第二个任务时第一个任务已经完成
//那么会复用执行第一个任务的线程,而不用每次新建线程。
4.4单线程化线程池(SingleThreadExecutor) 1、特点 只有一个核心线程(保证所有任务按照指定顺序在一个线程中执行,不需要处理线程同步的问题) 2、应用场景 不适合并发但可能引起IO阻塞性及影响UI线程响应的操作,如数据库操作,文件操作等 3、使用 通过Executors.newSingleThreadExecutor()创建
// 1. 创建单线程化线程池
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
// 2. 创建好Runnable类线程对象 & 需执行的任务
Runnable task =new Runnable(){
public void run(){
System.out.println("执行任务啦");
}
};
// 3. 向线程池提交任务:execute()
singleThreadExecutor.execute(task);
// 4. 关闭线程池
singleThreadExecutor.shutdown();
2.4)对比
2.5)其他
1、Synchronized
一.设计目的 (1)JDK为何要设计锁 即同一时刻最多只有1个线程执行被Synchronized修饰的方法/代码,其他线程必须等待当前线程执行完该方法/代码块后才能执行该方法/代码块 (2)应用场景 多线程编程中,有可能会出现多个线程同时访问一个共享、可变资源(临界资源)的情况,这种资源可能是:对象、变量、文件等。由于线程执行的过程是不可控的,所以需要采用同步机制来协同对对象可变状态的访问 加锁目的:序列化访问临界资源,即同一时刻只能有一个线程访问临界资源(同步互斥访问) 1、修饰 实例方法 / 代码块时,(同步)保护的是同一个对象方法的调用 & 当前实例对象 2、修饰 静态方法 / 代码块时,(同步)保护的是 静态方法的调用 & class 类对象 二.设计原理 (1)加锁对象 1、同步实例方法,锁是当前实例对象 2、同步类方法,锁是当前对象 3、同步代码块,锁是括号里的对象 (2)加锁原理 1、依赖 JVM 实现同步 2、底层通过一个监视器对象(monitor)完成, wait()、notify() 等方法也依赖于 monitor 对象 3、监视器锁(monitor)的本质 依赖于 底层操作系统的互斥锁(Mutex Lock)实现
(object){
//monitorenter进入同步块
//业务逻辑
//monitorexit退出同步块
}
(3)JVM加锁过程
三.具体使用
Synchronized 用于 修饰 代码块、类的实例方法 & 静态方法
(1)使用规则
(2)锁的类型&等级
1、类型
Synchronized会修饰代码块、类的实例方法&静态方法
2、区别
(3)使用方式
/**
* 对象锁
*/
public class Test{
// 对象锁:形式1(方法锁)
public synchronized void Method1(){
System.out.println("我是对象锁也是方法锁");
try{
Thread.sleep(500);
} catch (InterruptedException e){
e.printStackTrace();
}
}
// 对象锁:形式2(代码块形式)
public void Method2(){
synchronized (this){
System.out.println("我是对象锁");
try{
Thread.sleep(500);
} catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
/**
* 方法锁(即对象锁中的形式1)
*/
public synchronized void Method1(){
System.out.println("我是对象锁也是方法锁");
try{
Thread.sleep(500);
} catch (InterruptedException e){
e.printStackTrace();
}
}
/**
* 类锁
*/
public class Test{
// 类锁:形式1 :锁静态方法
public static synchronized void Method1(){
System.out.println("我是类锁一号");
try{
Thread.sleep(500);
} catch (InterruptedException e){
e.printStackTrace();
}
}
// 类锁:形式2 :锁静态代码块
public void Method2(){
synchronized (Test.class){
System.out.println("我是类锁二号");
try{
Thread.sleep(500);
} catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
四.特点
2、ThreadLocal
(1)简介 ThreadLocal是线程的局部变量,用于为每个线程提供1个特定空间(即该变量),以保存该线程所独享的资源。适用于隔离线程&放置线程间数据资源共享的场景。 注: a.使每个线程可独立地改变自己空间内的资源(设置、存储的值)而不会和其他线程资源冲突 b.1个变量只能被同一个进程读、写,若第2个线程同时执行1段含有1个ThreadLocal变量引用的代码,它们也无法访问到对方的ThreadLocal变量 (2)使用流程 2.1创建ThreadLocal变量
// 1. 直接创建对象
private ThreadLocal myThreadLocal = new ThreadLocal()
// 2. 创建泛型对象
private ThreadLocal myThreadLocal = new ThreadLocal<String>();
// 3. 创建泛型对象 & 初始化值
// 指定泛型的好处:不需要每次对使用get()方法返回的值作强制类型转换
private ThreadLocal myThreadLocal = new ThreadLocal<String>() {
@Override
protected String initialValue() {
return "This is the initial value";
}
};
// 特别注意:
// 1. ThreadLocal实例 = 类中的private、static字段
// 2. 只需实例化对象一次 & 不需知道它是被哪个线程实例化
// 3. 每个线程都保持 对其线程局部变量副本 的隐式引用
// 4. 线程消失后,其线程局部实例的所有副本都会被垃圾回收(除非存在对这些副本的其他引用)
// 5. 虽然所有的线程都能访问到这个ThreadLocal实例,但是每个线程只能访问到自己通过调用ThreadLocal的set()设置的值
// 即 哪怕2个不同的线程在同一个`ThreadLocal`对象上设置了不同的值,他们仍然无法访问到对方的值
2.2访问ThreadLocal变量
// 1. 设置值:set()
// 需要传入一个Object类型的参数
myThreadLocal.set("初始值”);
// 2. 读取ThreadLocal变量中的值:get()
// 返回一个Object对象
String threadLocalValue = (String) myThreadLocal.get();
(3)具体使用
public class ThreadLocalTest {
// 测试代码
public static void main(String[] args){
// 新开2个线程用于设置 & 获取 ThreadLoacl的值
MyRunnable runnable = new MyRunnable();
new Thread(runnable, "线程1").start();
new Thread(runnable, "线程2").start();
}
// 线程类
public static class MyRunnable implements Runnable {
// 创建ThreadLocal & 初始化
private ThreadLocal<String> threadLocal = new ThreadLocal<String>(){
@Override
protected String initialValue() {
return "初始化值";
}
};
@Override
public void run() {
// 运行线程时,分别设置 & 获取 ThreadLoacl的值
String name = Thread.currentThread().getName();
threadLocal.set(name + "的threadLocal"); // 设置值 = 线程名
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + ":" + threadLocal.get());
}
}
}
测试结果
线程1:线程1的threadLocal 线程2:线程2的threadLocal
从上述结果看出,在2个线程分别设置ThreadLocal值 & 分别获取,结果并未互相干扰 (4)实现原理 4.1)核心原理 ThreadLocal类中有1个Map(称:ThreadLocalMap):用于存储每个线程 & 该线程设置的存储在ThreadLocal变量的值 1、ThreadLocalMap的键Key = 当前ThreadLocal实例、值value = 该线程设置的存储在ThreadLocal变量的值 2、该key是 ThreadLocal对象的弱引用;当要抛弃掉ThreadLocal对象时,垃圾收集器会忽略该 key的引用而清理掉ThreadLocal对象 4.2)源码分析 如何设置 & 获取 ThreadLocal变量里的值
// ThreadLocal的源码
public class ThreadLocal<T> {
...
/**
* 设置ThreadLocal变量引用的值
* ThreadLocal变量引用 指向 ThreadLocalMap对象,即设置ThreadLocalMap的值 = 该线程设置的存储在ThreadLocal变量的值
* ThreadLocalMap的键Key = 当前ThreadLocal实例
* ThreadLocalMap的值 = 该线程设置的存储在ThreadLocal变量的值
**/
public void set(T value) {
// 1. 获得当前线程
Thread t = Thread.currentThread();
// 2. 获取该线程的ThreadLocalMap对象 ->>分析1
ThreadLocalMap map = getMap(t);
// 3. 若该线程的ThreadLocalMap对象已存在,则替换该Map里的值;否则创建1个ThreadLocalMap对象
if (map != null)
map.set(this, value);// 替换
else
createMap(t, value);// 创建->>分析2
}
/**
* 获取ThreadLocal变量里的值
* 由于ThreadLocal变量引用 指向 ThreadLocalMap对象,即获取ThreadLocalMap对象的值 = 该线程设置的存储在ThreadLocal变量的值
**/
public T get() {
// 1. 获得当前线程
Thread t = Thread.currentThread();
// 2. 获取该线程的ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
// 3. 若该线程的ThreadLocalMap对象已存在,则直接获取该Map里的值;否则则通过初始化函数创建1个ThreadLocalMap对象
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value; // 直接获取值
}
return setInitialValue(); // 初始化
}
/**
* 初始化ThreadLocal的值
**/
private T setInitialValue() {
T value = initialValue();
// 1. 获得当前线程
Thread t = Thread.currentThread();
// 2. 获取该线程的ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
// 3. 若该线程的ThreadLocalMap对象已存在,则直接替换该值;否则则创建
if (map != null)
map.set(this, value); // 替换
else
createMap(t, value); // 创建->>分析2
return value;
}
/**
* 分析1:获取当前线程的threadLocals变量引用
**/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
/**
* 分析2:创建当前线程的ThreadLocalMap对象
**/
void createMap(Thread t, T firstValue) {
// 新创建1个ThreadLocalMap对象 放入到 Thread类的threadLocals变量引用中:
// a. ThreadLocalMap的键Key = 当前ThreadLocal实例
// b. ThreadLocalMap的值 = 该线程设置的存储在ThreadLocal变量的值
t.threadLocals = new ThreadLocalMap(this, firstValue);
// 即 threadLocals变量 属于 Thread类中 ->> 分析3
}
...
}
/**
* 分析3:Thread类 源码分析
**/
public class Thread implements Runnable {
...
ThreadLocal.ThreadLocalMap threadLocals = null;
// 即 Thread类持有threadLocals变量
// 线程类实例化后,每个线程对象拥有独立的threadLocals变量变量
// threadLocals变量在 ThreadLocal对象中 通过set() 或 get()进行操作
...
}
(5)补充
5.1)ThreadLocal如何做到线程安全
1、每个线程拥有自己独立的ThreadLocals变量(指向ThreadLocalMap对象 )
2、每当线程 访问 ThreadLocals变量时,访问的都是各自线程自己的ThreadLocalMap变量(键 - 值)
3、ThreadLocalMap变量的键 key = 唯一 = 当前ThreadLocal实例
5.2)与同步机制的区别
#(三)Java/Android 多线程开发联系/区别