1 继承Thread类
2 实现Runnable接口
a.常规使用
// 步骤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();
b.匿名类
// 步骤1:通过匿名类 直接 创建线程辅助对象,即 实例化 线程辅助类
Runnable mt = new Runnable() {
// 步骤2:复写run(),定义线程行为
@Override
public void run() {
}
};
// 步骤3:创建线程对象,即 实例化线程类;线程类 = Thread类;
Thread mt1 = new Thread(mt, "窗口1");
// 步骤4:通过 线程对象 控制线程的状态,如 运行、睡眠、挂起 / 停止
mt1.start();
2.1 Thread和Runnable的区别
3 Handler
3.1 Handler工作原理
工作流程
1.异步通信准备
2.消息发送
3.消息循环
4.消息处理
3.2 工作流程
3.3 使用Handler.sendMessage()
class mHandler extends Handler {
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
}
}
void useHandler() {
Handler mHandler = new mHandler();
Message message = Message.obtain();
message.what = 1;
message.obj = "AA";
mHandler.sendMessage(message);
}
3.4 使用Handler.post
void useHandler() {
Handler mHandler = new mHandler();
mHandler.post(new Runnable() {
@Override
public void run() {
}
});
}
3.5 具体类
4 AsyncTask
属于抽象类 使用时要实现子类
4.1 作用
1 实现多线程
在工作线程中执行任务
2 异步通信 消息传递
实现工作线程和主线程直接的通信
4.2 优点
- 方便实现异步通信
不需要使用 "任务线程 + Handler" 的复杂组合 - 节省资源
采用线程池的缓存线程 + 复用线程 避免了频繁创建和销毁线程所带来的系统资源开销
4.3 类和方法
4.3.1 类定义
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的子类
}
4.3.2 核心方法
-
方法执行顺序
4.4 使用步骤
- 创建AsyncTask 子类 和 根据需求实现核心方法
2 创建AsyncTask 子类的实例对象
3 手动调用execute() 从而执行异步线程任务
public class MyTask extends AsyncTask<String, Integer, String> {
//执行线程前的操作
@Override
protected void onPreExecute() {
super.onPreExecute();
}
//接受输入参数 执行任务中的耗时操作 返回线程任务执行的结果
@Override
protected String doInBackground(String... strings) {
return null;
}
//在主线程 显示线程任务执行的进度
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
}
//接受线程任务执行结果 将执行结果显示到UI组件
@Override
protected void onPostExecute(String s) {
super.onPostExecute(s);
}
//将异步任务设置为取消状态
@Override
protected void onCancelled() {
super.onCancelled();
}
}
4.5 生命周期
AsyncTask 不与任何组件绑定声明周期
在Activity 或 Fragment中使用 AsyncTask时,最好在Activity 或 Fragment的onDestory()调用 cancel(boolean)
4.6 内存泄露
若AsyncTask被声明为Activity的非静态内部类,当Activity需销毁时,会因AsyncTask保留对Activity的引用 而导致Activity无法被回收,最终引起内存泄露
把AsyncTask声明为Activity的静态内部类
4.7 线程任务执行结果 丢失
当Activity重新创建时(屏幕旋转 / Activity被意外销毁时后恢复),之前运行的AsyncTask(非静态的内部类)持有的之前Activity引用已无效,故复写的onPostExecute()将不生效,即无法更新UI操作
在Activity恢复时的对应方法 重启 任务线程
4.8 工作原理
AsyncTask的实现原理 = 线程池 + Handler
5. ThreadPool
5.1 内部原理逻辑
5.2 常见的4类功能线程池
- 定长线程池(FixedThreadPool)
- 定时线程池(ScheduledThreadPool )
- 可缓存线程池(CachedThreadPool)
- 单线程化线程池(SingleThreadExecutor)
5.2.1 定长线程池
只有核心线程 不会被回收 线程数量固定 任务队列无大小限制(超出的线程任务会在队列中等待)
应用场景:控制线程最大并发数
//创建定长线程池对象 设置线程池数量固定为3
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
//创建好Runnable
Runnable task = new Runnable() {
@Override
public void run() {
System.out.println("执行任务");
}
};
// 向线程池提交任务
fixedThreadPool.execute(task);
// 关闭线程池
fixedThreadPool.shutdown();
5.2.2 定时线程池
核心线程数量固定、非核心线程数量无限制(闲置时马上回收)
执行定时 / 周期性 任务
// 1. 创建 定时线程池对象 & 设置线程池线程数量固定为5
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);
//创建好Runnable
Runnable task = new Runnable() {
@Override
public void run() {
System.out.println("执行任务");
}
};
// 向线程池提交任务
scheduledExecutorService.schedule(task,1, TimeUnit.SECONDS);// 延迟1s后执行任务
//延迟10毫秒 每隔1000毫秒执行任务
scheduledExecutorService.scheduleAtFixedRate(task,10,1000,TimeUnit.MILLISECONDS);
//关闭线程
scheduledExecutorService.shutdown();
5.2.3 可缓存线程池
只有非核心线程,线程数量不固定(可无限大),灵活回收空闲线程(具备超时机制,全部回收时几乎不占系统资源),新建线程(无限城可用时)
任何线程任务到来都会立刻执行,不需要等待
执行大量、耗时少的线程任务
// 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();
//当执行第二个任务时第一个任务已经完成
//那么会复用执行第一个任务的线程,而不用每次新建线程。
5.2.4 单线程化线程池
只有一个核心线程(保证所有任务按照指定顺序在一个线程中执行,不需要处理线程同步的问题)
不适合并发但可能引起IO阻塞及影响UI线程的操作
// 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();
5.3 线程池对比
5.4 多线程对比
6. Synchronized关键字
6.1 原理
- 依赖JVM实现同步
2.底层通过一个监视器对象(monitor)完成,wait() notify() 等方法也依赖于monitor对象
监视器锁(monitor)的本质 依赖于 底层操作系统的互斥锁(Mutex Lock)实现
6.2 锁的类型和等级
6.2.1 区别
6.3 使用规则
//对象锁
class Test {
//方法锁
public synchronized void Method1() {
System.out.println("我是对象锁也是方法锁");
try {
Thread.sleep(500);
}catch (InterruptedException e) {
e.printStackTrace();
}
}
//代码块形式
public void Method2() {
synchronized (this) {
System.out.println("我是对象锁");
try {
Thread.sleep(500);
}catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//方法锁
public synchronized void Method1() {
System.out.println("我是对象锁也是方法锁");
try {
Thread.sleep(500);
}catch (InterruptedException e) {
e.printStackTrace();
}
}
//类锁
class Test {
//锁静态方法
public static synchronized void Method1() {
System.out.println("我是对象锁也是方法锁");
try {
Thread.sleep(500);
}catch (InterruptedException e) {
e.printStackTrace();
}
}
//锁静态代码块
public void Method2() {
synchronized (Test.class){
System.out.println("我是类锁二号");
try{
Thread.sleep(500);
} catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
6.4 特别注意
若使用synchronized 关键字修饰线程类的run() 由于run()在线程的整个生命周期内一直在运行,因此将会导致它对本类恩和synchronized 方法的调用都永远不会成功
- 解决方案
使用 Synchronized关键字声明代码块
synchronized(syncObject) {
// 访问或修改被锁保护的共享状态
// 上述方法 必须 获得对象 syncObject(类实例或类)的锁
}
6.5 特点
7. ThreadLocal
7.1 使用流程
7.1.1 创建ThreadLocal变量
//1.直接创建对象
private ThreadLocal myThreadLocal = new ThreadLocal();
//2.创建泛型对象
private ThreadLocal threadLocal = new ThreadLocal<String>();
//3.创建泛型对象 & 初始化值
//指定泛型的好处 不需要每次对使用get()方法返回的值作强制类型转换
private ThreadLocal local = new ThreadLocal<String>() {
@Nullable
@Override
protected String initialValue() {
return "This is the initial value";
}
};
1.ThreadLocal 实例 = 类中的private static字段
2.只需实例化对象一次 & 不需要知道它是被哪个线程实例化
3.每个线程都保持 对其他线程局部变量副本的隐式引用
4.线程消失后 其线程举报变量实例的所有副本都会被垃圾回收(除非存在对这些副本的其他引用)
5.虽然所有的线程都能访问到这个ThreadLocal实例 但是每个线程只能访问到自己通过调研ThreadLocal的set()设置的值(哪怕2个不同的线程在同一个ThreadLocal
对象上设置了不同的值,他们仍然无法访问到对方的值)
7.1.2 访问ThreadLocal变量
// 1. 设置值:set()
// 需要传入一个Object类型的参数
myThreadLocal.set("初始值”);
// 2. 读取ThreadLocal变量中的值:get()
// 返回一个Object对象
String threadLocalValue = (String) myThreadLocal.get();
7.2 具体使用
class ThreadLocalTest {
public static void main(String[] args) {
MyRunnable runnable = new MyRunnable();
new Thread(runnable,"线程1").start();
new Thread(runnable,"线程2").start();
}
public static class MyRunnable implements Runnable {
private ThreadLocal<String> threadLocal = new ThreadLocal<>() {
@Nullable
@Override
protected Object initialValue() {
return "初始化值";
}
};
@Override
public void run() {
//运行线程时 分别设置 & 获取ThreadLocal的值
String name = Thread.currentThread().getName();
threadLocal.set(name + "的threadLocal");
try {
Thread.sleep(1000);
}catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + ":" + threadLocal.get());
}
}
}
7.3 实现原理
ThreadLocal类中有1个Map(称:ThreadLocalMap):用于存储每个线程 & 该线程设置的存储在ThreadLocal变量的值
1.ThreadLocalMap的键key = 当前ThreadLocal实例、值value = 该线程设置的存储在ThreadLocal变量的值
2.该key是 ThreadLocal对象的弱引用;当要抛弃掉ThreadLocal对象时,垃圾收集器会忽略该 key的引用而清理掉ThreadLocal对象