Thread

83 阅读8分钟

基本使用

  • 继承Thread类

Thread{ TODO() }.start();

  • 实现Runnable接口

val runnable = Runnable { TODO() } Thread(runnable).start()

Runnable还可以各线程共享资源,实例:

// 创建Runnable线程类 class thread1: Runnable { var num = 100 override fun run() { while(num>0){ num--.apply { Log.d("TAG", "onCreate: $this") } Thread.sleep(1000) } } } // Runnable线程类实例 val thread = thread1() // 创建两个线程共用 Thread(thread).start() Thread(thread).start()

可以看到1秒内num自减两次

复合使用

HandlerThread

🗝介绍

Thread 的生命周期在一些情况下是不可控制的,比如直接 new Thread().start() 这种方式在项目中是不推荐使用的,实际上 Android 的源码中也有很多地方用到了 HandlerThread。

Handler的源码中我们可以了解到,要使用Handler还要初始化Looper,在主线程中,系统已经自动帮我们创建好了,如果我们想在子线程使用,可以使用HandlerThread。它本质也是一个线程,只是其持有了handler,自带Looper,所以可在子线程进行消息处理和分发。

🖐使用

class MainActivity : AppCompatActivity() { val handler:Handler val handlerThread = HandlerThread("suibian").apply { start() }.also { handler = Handler(it.looper) } var target = 100 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) handler.post { while(target > 0){ target--.apply { Log.d("TAG", "onCreate: $this") } Thread.sleep(1000) } } } override fun onDestroy() { super.onDestroy() handlerThread.quit() } }

可以看到num在子线程的Looper循环数据,实现自减

🎈总结

  • 如果经常要开启线程,接着又是销毁线程,这是很耗性能的,HandlerThread 很好的解决了这个问题;
  • HandlerThread 由于异步操作是放在 Handler 的消息队列中的,所以是串行的,但只适合并发量较少的耗时操作。
  • HandlerThread 用完记得调用退出方法

handlerThread.quit()。

  • 注意使用 handler 避免出现内存泄露

IntentService

🗝介绍

正如名称,IntentService是一个封装类,继承四大组件之一的Service。

由于Service在主线程中,不能进行耗时操作。IntentService在Service中创建一个工作线程,并且处理完成后自动关闭Service。既能异步操作,同时也具有Service不容易被系统杀死的优点。

🖐使用1.继承IntentService,并且复写

onHandleIntent(intent: Intent?)

2.在Manifest.xml中注册服务

3.启动Service,使用Intent传递数据

ThreadPool

🗝介绍

能够更好的去管理线程、复用线程。降低线程创建销毁带来的性能开销

🖐使用

构造方法与使用

ThreadPoolExecutor mExecutor = new ThreadPoolExecutor(corePoolSize,// 核心线程数 maximumPoolSize, // 最大线程数 keepAliveTime, // 闲置线程存活时间 TimeUnit.MILLISECONDS,// 时间单位 new LinkedBlockingDeque(),// 线程队列 Executors.defaultThreadFactory(),// 线程工厂 new ThreadPoolExecutor.AbortPolicy()// 队列已满,而且当前线程数已经超过最大线程数时的异常处理策略 ); threadPool.execute(new Runnable() { @Override public void run() { ... // 线程执行任务 } }); // shutdown():设置线程池的状态为SHUTDOWN,然后中断所有没有正在执行任务的线程 // 原理:逐个遍历线程池中的工作线程,并调用interrupt() // 或者使用shutdownNow():设置线程池的状态为STOP,然后尝试停止所有的正在执行或暂停任务的 线程,并返回等待执行任务的列表 threadPool.shutdown();

常见阻塞队列:

ArrayBlockingQueue由数组构成的有界阻塞队列
LinkedBlockingQueue由链表构成的有界阻塞队列
PriorityBlockingQueue支持优先级排序的无界阻塞队列
DelayQueue使用优先队列实现的无界阻塞队列
SynchroniouQueue不储存元素的阻塞队列
LinkedTransferQueue由链表结构组成的无界阻塞队列
LinkedBlockingDeque由链表结构组成的双向阻塞队列

常用功能线程池

FixedThreadPool

🗝介绍

  • 核心线程数和最大线程数相等
  • 没有所谓的非空闲时间
  • 阻塞队列为无界队列LinkedBlockingQueue

FixedThreadPool 适用于处理CPU密集型的任务,确保CPU在长期被工作线程使用的情况下,尽可能的少的分配线程,即适用执行长期的任务。

🎯实现

public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue()); }

🖐使用

private void three(){ ExecutorService executorService = Executors.newFixedThreadPool(3); executorService.execute(new Runnable() { @Override public void run() { //执行线程操作 } }); }

ScheduledThreadPool

🗝介绍

  • 最大线程数为Integer.MAX_VALUE
  • 阻塞队列是DelayedWorkQueue
  • keepAliveTime为0
  • scheduleAtFixedRate() :按某种速率周期执行
  • scheduleWithFixedDelay():在某个延迟后执行

创建一个定时线程池,适用于定时及周期性任务场景

🎯实现

public ScheduledThreadPoolExecutor(int corePoolSize) { super(corePoolSize, Integer.MAX_VALUE, DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS, new DelayedWorkQueue()); }

🖐使用

private void four(){ ExecutorService executorService = Executors.newScheduledThreadPool(3); executorService.execute(new Runnable() { @Override public void run() { //执行线程操作 } }); }

CachedThreadPool

🗝介绍

  • 核心线程数为0
  • 最大线程数为Integer.MAX_VALUE
  • 阻塞队列是SynchronousQueue
  • 非核心线程空闲存活时间为60秒

当提交任务的速度大于处理任务的速度时,每次提交一个任务,就必然会创建一个线程。极端情况下会创建过多的线程,耗尽 CPU 和内存资源。

🎯实现

public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue()); }

🖐使用

private void two(){ ExecutorService executorService = Executors.newCachedThreadPool(); executorService.execute(new Runnable() { @Override public void run() { //执行线程操作 } }); }

SingleThreadExecutor

🗝介绍

  • 核心线程数为1
  • 最大线程数也为1
  • 阻塞队列是LinkedBlockingQueue
  • keepAliveTime为0

顾名思义,这是一个单线程化的线程池,只会用唯一的工作线程来执行任务,保证所有任务按先进先出的顺序执行。

🎯实现

public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService( new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue()) ); }

🖐使用

private void one(){ ExecutorService executorService = Executors.newSingleThreadExecutor(); executorService.execute(new Runnable() { @Override public void run() { //执行线程操作 } }); }

WorkManger

WorkManager最低能兼容API Level 14+的设备。

🗝特点

  • 不需要及时完成的任务
  • 保证任务一定会执行

经过测试,即使手机关机重启。只要APP处于存活状态时(无论APP退出前台,或者从任务栏杀死,都会继续处于存活状态,除非点击应用详情->强行停止)WorkManager都会去执行。

感觉会慢慢成为后台执行的一个趋势

🎯实现

在API Level 23+,通过JobScheduler来完成任务,而在API Level 23以下的设备中,通过AlarmManager和Broadcast Receivers组合完成任务。但无论采用哪种方案,任务最终都是交由Executor来完成。

🖐基本使用

和Service一样,需要创建一个类,用于WorkManager需要执行的信息

class Worker1(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) { override fun doWork(): Result { return Result.success(Data.Builder().build()) } }

创建任务

val Worker1 = OneTimeWorkRequestBuilder() .build() // 单次任务或者周期性任务 PeriodicWorkRequest

可以添加约束,数据,执行时间等等

// 网络情况等约束执行条件 // 设置了约束条件后,WorkManager会一直等待到条件允许后自动执行 val constraints = Constraints.Builder() .setRequiresCharging(true) .setRequiredNetworkType(NetworkType.CONNECTED) .setRequiresStorageNotLow(true) .build() val inputData = Data.Builder() .putString("input_data", "Hello World!") .build() val Worker1 = OneTimeWorkRequestBuilder() .setConstraints(constraints) .setInputData(inputData) .setInitialDelay(6,TimeUnit.SECONDS) .build()

监听状态

WorkManager.getInstance(context).getWorkInfoByIdLiveData(Worker1.getId()) .observe(context as AppCompatActivity, object : Observer<WorkInfo?> { override fun onChanged(t: WorkInfo?) { Log.d("TAG", "onChanged: $t") } })

加入WorkManager执行队列

WorkManager.getInstance(context) .enqueue(Worker1) // 或者链式执行 WorkManager.getInstance(context) .beginWith(listOf(Worker1,Worker1)) .then(Worker1) .enqueue()

线程安全

  • 保证共享资源在同一时间只能由一个线程进行操作(原子性,有序性)
  • 将线程操作的结果及时刷新,保证其他线程可以立即获取到修改后的最新数据(可见性)

volatile对于一个变量,只有一个线程执行写操作,其它线程都是读操作,这时候可以用

volatile修饰这个变量

Synchronized

Synchronized 可以保证同一时刻,只有一个线程可执行某个方法或某个代码块

等待/通知机制Java多线程的等待/通知机制是基于Object类的

wait(),

notify(),

notifyAll()方法来实现的,

wait(),

notify()方法必须写在

synchronized代码块里面:

  • wait()

就是线程在获取对象锁后,主动释放cpu资源、释放对象锁,同时休眠本线程

  • 直到有其它线程调用对象的

notify()唤醒该线程,才能继续获取对象锁,并继续执行

  • notifyAll()

会叫醒所有正在等待的线程,被唤醒的线程重新在就绪队列中按照一定算法最终再次被处理机获得并进行处理,而不是立马重新获得处理机

  • thread.join()会让当前线程陷入等待,直到

thread执行完毕

ThreadLocal

🎗是什么如果创建了一个

ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个本地拷贝,多个线程操作这个变量的时候,实际是在操作自己本地内存里面的变量,从而起到线程隔离的作用,避免了并发场景下的线程安全问题。

🏍使用场景

  • 多个线程访问同一数据库连接时
  • 全局存储用户信息(用户信息存入

ThreadLocal,那么当前线程在任何地方需要时,都可以使用)

🧊内存泄漏

  • Thread线程类有一个类型为

ThreadLocal.ThreadLocalMap的实例变量

threadLocals,即每个线程都有一个属于自己的

ThreadLocalMap

  • ThreadLocal的原理其实是用

ThreadLocalMap来维护,

key是

ThreadLocal的弱引用,

value是

ThreadLocal泛型对象的值

  • 并发多线程场景下,每个线程

Thread,在往

ThreadLocal里设置值的时候,都是往自己的

ThreadLocalMap里存,读也是以某个

ThreadLocal作为引用,在自己的

map里找对应的

key,从而可以实现了线程隔离

关于ThreadLocal内存泄漏,网上比较流行的说法是这样的:

ThreadLocalMap使用ThreadLocal的弱引用作为key,当ThreadLocal变量被手动设置为null,即一个ThreadLocal没有外部强引用来引用它,当系统GC时,ThreadLocal一定会被回收。这样的话,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程再迟迟不结束的话(比如线程池的核心线程),这些key为null的Entry的value就会一直存在一条强引用链:Thread变量 -> Thread对象 -> ThreaLocalMap -> Entry -> value -> Object 永远无法回收,造成内存泄漏。

实际上,

ThreadLocalMap的设计中已经考虑到这种情况。所以也加上了一些防护措施:即在

ThreadLocal的

get,

set,

remove方法,都会清除线程

ThreadLocalMap里所有

key为

null的

value。

👴Q&A:

  1. 为什么要用使用弱引用作为key?

A: 如果ThreadLocal外部不存在强引用的话,ThreadLocal势必会被GC掉,这样就会导致ThreadLocalMap中的key为null,而value不会被GC掉,如果Thread迟迟不结束的话,从而会导致value不被回收,从而内存泄漏。弱引用则是加了一层保证。

  1. 为什么会引发内存泄漏,如何避免?

A:如上。避免方法:每次使用完ThreadLocal都调用它的remove()方法清除数据。

  1. 使用弱引用会不会由于系统GC清理掉?A:不会,因为有

ThreadLocal变量引用着它,除非将它置空(

ThreadLocal = null)。

参考: 微信公众号文章:ThreadLocal

并发包

ConcurrentHashMap

我们都知道HashMap是线程不安全的数据结构,HashTable则在HashMap基础上,get方法和put方法加上Synchronized修饰变成线程安全,不过在高并发情况下效率底下,最终被ConcurrentHashMap替代