目录
写在前面
一、了解Android中的线程
1.1、线程的几种创建方式
1.2、线程的优先级
1.3、线程状态及常用方法
1.4、线程间消息通信
二、线程安全
2.1、了解线程安全
2.1.1、什么是线程并发安全
2.1.2、线程安全的几种分类
2.2、如何保证线程安全
2.2.1、原子类
2.2.2、synchronized
2.2.3、ReentrantLock
2.2.4、ReentrantReadWriteLock
三、线程池
3.1、为什么要引入线程池
3.2、Java中的默认线程池
3.2.1、如何构建线程池
3.2.2、Executors提供的几种线程池
3.2.3、流转状态及任务流程
3.3、实战多线程开发框架
嘿嘿!可能是年前最后一篇了,珍惜吧,骚年!
本文参考资料:转自慕课网Android移动端架构师体系课电子书:《线程与线程池开发核心技术》
Android应用在运行的时候必然会存在一个进程,一个进程中至少会存在一个线程,进程在执行的过程中拥有独立的内存空间,而线程运行在进程内,线程也是CPU调度的最基本的单位。在Android世界中,线程可以粗略的分为两大类:1、UI线程;2、工作线程。UI线程用来更新UI控件,工作线程用来处理耗时任务,两者都是线程所以互为竞争关系,都需要等待CPU分配时间片才能真正运行起来,因此如何高效的使用工作线程来辅助提高UI的流畅度是我们需要注意的问题。
①、new Thread
//方式一:定义类继承自Thread,并重写run方法
class MyThread:Thread(){
override fun run() {
super.run()
println("第一种方式")
}
}
MyThread().start()
//方式二:传递Runnable
Thread(Runnable {
println("第二种方式")
}).start()
②、AsyncTask
方式一:适用场景:需要知道任务执行进度并且更新UI
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//适用于知道任务执行进度更新UI的业务场景
MyAsyncTask().execute("Test AsyncTask Execute")
// MyAsyncTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,"Test AsyncTask Execute")
}
//三个泛型参数分别为:任务入参的类型,执行进度的类型,执行结果的类型
class MyAsyncTask : AsyncTask() {
override fun doInBackground(vararg params: String): String {
for (i in 0..9) { //模拟任务执行进度计算
publishProgress(i * 10)
}
return params[0]
}
//获取任务的执行进度
override fun onProgressUpdate(vararg values: Int?) {
Log.e("onProgressUpdate:" ,""+values[0])
}
//获取任务的执行结果
override fun onPostExecute(result: String) {
Log.e("onPostExecute: ", result)
}
}
}
执行结果为:
方式二:适用场景:无需获取任务进度
//无需获取任务执行进度,以这种方式提交的任务,所有任务串行执行
//如果有一条任务休眠或者执行时间过长,后面所有的任务都将被阻塞
AsyncTask.execute { Log.e("Run", "AsyncTask execute") }
方式三:适用场景:并发任务执行
//并发任务执行
AsyncTask.THREAD_POOL_EXECUTOR.execute { Log.e("Run", "AsyncTask THREAD_POOL_EXECUTOR execute") }
③、HandlerThread
class MainActivity : AppCompatActivity() {
companion object{
val MSG_WHAT = 1
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//创建一个线程名为handler-thread的线程
val handlerThread = HandlerThread("handler-thread")
handlerThread.start()
//将handlerThread跟Hander绑定
val myHander = MyHander(handlerThread.looper)
myHander.sendEmptyMessage(MSG_WHAT)
}
//等同于Java中的静态内部类
class MyHander(looper: Looper) : Handler(looper) {
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
when (msg.what) {
MSG_WHAT -> Log.e("currentThreadName", Thread.currentThread().name)
}
}
}
}
执行之后会打印出当前线程名:
④、IntentService
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//开启服务
val intent = Intent(this,WorkService::class.java)
startService(intent)
}
//创建IntentService
class WorkService: IntentService("WorkService") {
//重写onHandleIntent方法
override fun onHandleIntent(intent: Intent?) {
val path = intent?.getStringExtra("download")?:"默认路径"
//模拟下载任务等...
}
}
}
⑤、线程池
Executors.newCachedThreadPool() //线程可复用线程池
Executors.newFixedThreadPool() //固定线程数量的线程池
Executors.newScheduledThreadPool() //可指定定时任务的线程池
Executors.newSingleThreadExecutor() //线程数量为1的线程池
当我们创建线程并且调用线程的start()方法之后,实际上这个线程并不会立刻得到执行,因为会受到线程优先级的影响,一般情况下,线程的优先级越高,线程获得CPU时间片的概率越大,该线程就能够越早得到执行,进而线程中的任务就能够越早得到执行,注意这里说的仅仅是概率,并不是一定。
val thread = Thread()
thread.start()
Log.e("ui_priority",Thread.currentThread().priority.toString())
Log.e("th_priority",thread.priority.toString())
val thread = Thread()
thread.priority = Thread.MAX_PRIORITY
val thread = Thread(Runnable {
Process.setThreadPriority(Process.THREAD_PRIORITY_AUDIO)
}).start()
当一个线程调用了它的start()方法并且获取到CPU的时间片之后,它就有权限执行任务了,但是在任务执行的时候,该线程会涉及到多种状态的切换。下面先来看一下线程的几种状态:
线程的状态切换过程中经常使用到的方法有如下几个:
下面对wait/notify机制做一个简单的实战:
class MainActivity : AppCompatActivity() {
companion object{
val obj = Object()
@Volatile
var hasNotify = false
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val thread1 = Thread(Runnable1())
val thread2 = Thread(Runnable2())
thread1.start()
thread2.start()
}
//适用于一个线程需要等待另一个线程的执行结果或者部分结果的多线程同步的场景
//要保证wait-notify这两个方法的调用顺序
class Runnable1 : Runnable {
override fun run() {
Log.e("run: ", "thread1 start")
//同步代码块中传入object,则object成为资源对象
//只有获取到资源对象锁的线程才有机会执行同步锁下面的同步代码块
synchronized(obj) {
try {
if (!hasNotify) { //确保不会出现假死现象
obj.wait(1000)
}
} catch (e: InterruptedException) {
e.printStackTrace()
}
}
Log.e("run: ", "thread1 end")
}
}
class Runnable2 : Runnable {
override fun run() {
Log.e("run: ", "thread2 start")
synchronized(obj) {
obj.notify()
hasNotify = true
}
Log.e("run: ", "thread2 end")
}
}
}
接着对join做一个实战:在主线程中创建一个工作线程,然后调用join方法,那么UI线程会等到工作线程执行完毕之后才会继续执行:
//一个线程需要等待另一个线程执行完毕才能继续执行
//join可以理解为向当前线程插入一条任务
val thread = Thread(Runnable {
Log.e("run: 1", "" + System.currentTimeMillis())
try {
Thread.sleep(1000)
} catch (e: InterruptedException) {
e.printStackTrace()
}
Log.e("run: 2", "" + System.currentTimeMillis())
})
thread.start()
try {
thread.join() //执行join方法
} catch (e: InterruptedException) {
e.printStackTrace()
}
Log.e("UI-Thread: ", "run: " + System.currentTimeMillis())
yield是暂停当前线程并且不会释放资源锁,这个平时几乎用不到,我们就不做演示了,sleep比较简单就是休眠线程,需要注意的是如果放在synchronized同步代码块中执行的话它不会释放资源锁,直到任务执行完毕。
关于子线程向主线程发送消息并处理消息的使用相信大家都很熟悉了,这里就不再多说了,我们主要来说说主线程如何向子线程发送消息并处理消息。
class MainActivity : AppCompatActivity() {
companion object{
val MSG_WHAT = 1
val obj = Object()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val looperThread = LooperThread("Looper-Thread")
looperThread.start()
//让looperthread中循环的消息能够被handler处理,这里需要给它绑定Looper对象
val handler = object : Handler(looperThread.getLooper()!!) {
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
when (msg.what) {
MSG_WHAT -> Log.e("handleMessage: ", Thread.currentThread().name)
}
}
}
handler.sendEmptyMessage(MSG_WHAT)
}
//主线程向子线程发送消息,那么子线程必须要具备消息循环及处理能力
class LooperThread(name: String) : Thread(name) {
private var looper: Looper? = null
fun getLooper(): Looper? {
//调用该方法时Looper对象可能为空,所以让调用方也就是主线程进入等待状态
synchronized(obj) {
if (looper == null && isAlive) {
try {
obj.wait()
} catch (e: InterruptedException) {
e.printStackTrace()
}
}
}
return looper
}
override fun run() {
//这里需要使用Looper进行消息循环
Looper.prepare()
synchronized(obj) {
//LooperThread线程执行的时候创建Looper对象
looper = Looper.myLooper()
obj.notify() //唤起等待方,即主线程
}
Looper.loop() //开启消息循环
}
}
}
线程安全的本质是能够让并发线程有序的运行(这里的有序有可能是先来后到排队,有可能有人插队,但无论如何,同一时刻只能一个线程有权访问同步资源),线程执行的结果能够对其他线程可见。
代码演示:
//一个用原子类修饰,一个用volatile修饰,多线程情况下自增,然后输出最后的值
public class AtomicDemo {
public static void main(String[] args) throws InterruptedException {
final AtomicTask task = new AtomicTask();
Runnable runnable = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
task.incrementVolatile();
task.incrementAtomic();
}
}
};
//开启两个线程,同时修改两个值
Thread thread1 = new Thread(runnable);
Thread thread2 = new Thread(runnable);
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("原子类的结果:" + task.atomicInteger.get());
System.out.println("volatile关键字修饰的结果:" + task.volatileValue);
}
static class AtomicTask {
AtomicInteger atomicInteger = new AtomicInteger();
volatile int volatileValue = 0;
void incrementAtomic() {
atomicInteger.getAndIncrement();
}
void incrementVolatile() {
volatileValue++; //非原子操作
// volatileValue=volatileValue+1;
// volatileValue = 20000;
}
}
}
结果如下:volatile关键字修饰的结果不正确,因为该操作非原子操作
该关键字的作用场景分为3种:
synchronized void printThreadName() { }
static synchronized void printThreadName() { }
void printThreadNam() {
String name = Thread.currentThread().getName();
System.out.println("线程:" + name + " 准备好了...");
synchronized (this) { }
}
下面通过代码来看下具体的应用:
首先来看synchronized作用于方法上,并且多个线程在执行时是访问同一个对象testDemo的同步方法,执行结果是正确的,如果访问的是不同的对象,那么结果就是不正确的,会出现多个乘客购买到同一张票的情况。如果在同步方法前面加上static关键字,不管是否访问的同一个对象,结果都是能够保证线程安全的。
public class SynchronizedDemo {
private static List tickets = new ArrayList<>();
public static void main(String[] args) {
for (int i=0;i<5;i++){
tickets.add("火车票"+(i+1));
}
sellTickets();
}
private static void sellTickets() {
final SynchronizedTestDemo testDemo = new SynchronizedTestDemo();
for (int i = 0; i < 5; i++) {
new Thread(new Runnable() {
@Override
public void run() {
testDemo.printThreadDemo(); //访问同一个对象的情况
//new SynchronizedTestDemo().printThreadDemo(); //访问不同对象的情况
}
}).start();
}
}
private static class SynchronizedTestDemo{
synchronized void printThreadDemo() {
//static synchronized void printThreadDemo() { //如果加上static同样也是线程安全的
String name = Thread.currentThread().getName();
System.out.println("乘客"+name+"准备好了...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("乘客"+name+"买到的票是:"+tickets.remove(0));
}
}
}
然后我们来修改printThreadDemo()方法,使用synchronized代码块来修饰:
void printThreadDemo() {
String name = Thread.currentThread().getName();
System.out.println("乘客"+name+"准备好了...");
synchronized (this){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("乘客"+name+"正在买票...");
}
System.out.println("乘客"+name+"买到的票是:"+tickets.remove(0));
}
结果如下:从结果中可以看到,同步代码块之外的代码所有的线程都可以访问,同步代码块内的代码同样的是只有持有对象资源锁的对象才能够访问
总结:
首先通过代码来看下它的基本用法:这里还是演示买票功能
public class ReentrantLockDemo {
public static void main(String[] args) {
final ReentrantLockTask task = new ReentrantLockTask();
Runnable runnable = new Runnable() {
@Override
public void run() {
task.buyTicket();
}
};
for (int i = 0; i < 5; i++) {
new Thread(runnable).start();
}
}
static class ReentrantLockTask {
ReentrantLock reentrantLock = new ReentrantLock();
void buyTicket() {
String name = Thread.currentThread().getName();
try {
reentrantLock.lock();
System.out.println(name + "准备好了...");
Thread.sleep(100);
System.out.println(name + "买好了");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
reentrantLock.unlock();
}
}
}
}
效果跟上一部分是差不多的:
接着来看ReentrantLock可以在不释放锁的情况下多次重复的获取锁,直接在上面的代码中简单修改以下:
结果如下:可以看到Thread-0直到执行完成下一个线程才能够获取到锁对象
然后再来看公平锁和非公平锁,它俩的区别就在于ReentrantLock的构造方法中传入true还是false,true是公平锁,false是非公平锁,同样的还是修改ReentrantLockTask这个类中的代码:
结果如下所示:
下面来举个例子看下:
public class ReentrantReadWriteLockDemo {
static class ReentrantReadWriteLockTask {
private final ReentrantReadWriteLock.ReadLock readLock;
private final ReentrantReadWriteLock.WriteLock writeLock;
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
ReentrantReadWriteLockTask() {
readLock = lock.readLock();
writeLock = lock.writeLock();
}
//读操作
void read() {
String name = Thread.currentThread().getName();
try {
readLock.lock();
System.out.println("线程" + name + "正在查看文档...");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
readLock.unlock();
System.out.println("线程" + name + "释放了读锁");
}
}
//写操作
void write() {
String name = Thread.currentThread().getName();
try {
writeLock.lock();
System.out.println("线程" + name + "正在编辑文档...");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
writeLock.unlock();
System.out.println("线程" + name + "释放了写锁");
}
}
}
public static void main(String[] args) {
final ReentrantReadWriteLockTask task = new ReentrantReadWriteLockTask();
for (int i = 0; i < 3; i++) {
new Thread(new Runnable() {
@Override
public void run() {
task.read();
}
}).start();
}
for (int i = 0; i < 3; i++) {
new Thread(new Runnable() {
@Override
public void run() {
task.write();
}
}).start();
}
}
}
结果如下:从结果中不难看出读操作是所有线程同时进行的,写操作是每次只有一个线程
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,
long keepAliveTime, TimeUnit unit,BlockingQueue workQueue,
ThreadFactory threadFactory, RejectedExecutionHandler handler)
参数说明情况如下表所示:
//单一线程数,同时只有一个线程存活,但线程等待队列无界
Executors.newSingleThreadExecutor();
//线程可复用线程池,核心线程数为0,最大可创建的线程数为Interger.max,线程复用存活时间是60s.
Executors.newCachedThreadPool();
//固定线程数量的线程池
Executors.newFixedThreadPool(int corePoolSize);
//可执行定时任务,延迟任务的线程池
Executors.newScheduledThreadPool(int corePoolSize);
线程池中的重要方法
void execute(Runnable run)//提交任务,交由线程池调度
void shutdown()//关闭线程池,等待任务执行完成
void shutdownNow()//关闭线程池,不等待任务执行完成
int getTaskCount()//返回线程池找中所有任务的数量
int getCompletedTaskCount()//返回线程池中已执行完成的任务数量
int getPoolSize()//返回线程池中已创建线程数量
int getActiveCount()//返回当前正在运行的线程数量
基于以上内容简单封装一个多线程操作的框架:
package com.jarchie.base.executor;
import android.os.Handler
import android.os.Looper
import androidx.annotation.IntRange
import com.jarchie.base.log.JLog
import java.util.concurrent.*
import java.util.concurrent.atomic.AtomicLong
import java.util.concurrent.locks.Condition
import java.util.concurrent.locks.ReentrantLock
/**
* 作者: 乔布奇
* 日期: 2021-01-14 18:50
* 邮箱: [email protected]
* 支持按任务的优先级去执行,
* 支持线程池暂停.恢复(批量文件下载,上传)
* 异步结果主动回调主线程
*/
object JExecutor {
private const val TAG: String = "JExecutor"
private var isPaused: Boolean = false
private var executor: ThreadPoolExecutor
private var lock: ReentrantLock = ReentrantLock()
private var pauseCondition: Condition
private val mainHandler = Handler(Looper.getMainLooper());
init {
pauseCondition = lock.newCondition()
val cpuCount = Runtime.getRuntime().availableProcessors()
val corePoolSize = cpuCount + 1
val maxPoolSize = cpuCount * 2 + 1
val blockingQueue: PriorityBlockingQueue = PriorityBlockingQueue()
val keepAliveTime = 30L
val unit = TimeUnit.SECONDS
val seq = AtomicLong()
val threadFactory = ThreadFactory {
val thread = Thread(it)
//executor-0
thread.name = "Jexecutor-" + seq.getAndIncrement()
return@ThreadFactory thread
}
executor = object : ThreadPoolExecutor(
corePoolSize,
maxPoolSize,
keepAliveTime,
unit,
blockingQueue as BlockingQueue,
threadFactory
) {
override fun beforeExecute(t: Thread?, r: Runnable?) {
if (isPaused) {
lock.lock()
try {
pauseCondition.await()
} finally {
lock.unlock()
}
}
}
override fun afterExecute(r: Runnable?, t: Throwable?) {
//监控线程池耗时任务,线程创建数量,正在运行的数量
JLog.e(TAG, "已执行完的任务的优先级是:" + (r as PriorityRunnable).priority)
}
}
}
@JvmOverloads
fun execute(@IntRange(from = 0, to = 10) priority: Int = 0, runnable: Runnable) {
executor.execute(PriorityRunnable(priority, runnable))
}
@JvmOverloads
fun execute(@IntRange(from = 0, to = 10) priority: Int = 0, runnable: Callable<*>) {
executor.execute(PriorityRunnable(priority, runnable))
}
abstract class Callable : Runnable {
override fun run() {
mainHandler.post { onPrepare() }
val t: T = onBackground();
//移除所有消息.防止需要执行onCompleted了,onPrepare还没被执行,那就不需要执行了
mainHandler.removeCallbacksAndMessages(null)
mainHandler.post { onCompleted(t) }
}
open fun onPrepare() {
//加载进度框
}
abstract fun onBackground(): T
abstract fun onCompleted(t: T)
}
class PriorityRunnable(val priority: Int, private val runnable: Runnable) : Runnable,
Comparable {
override fun compareTo(other: PriorityRunnable): Int {
return if (this.priority < other.priority) 1 else if (this.priority > other.priority) -1 else 0
}
override fun run() {
runnable.run()
}
}
fun pause() {
lock.lock()
try {
if (isPaused) return
isPaused = true
} finally {
lock.unlock()
}
JLog.e(TAG, "JExecutor is paused")
}
fun resume() {
lock.lock()
try {
if (!isPaused) return
isPaused = false
pauseCondition.signalAll()
} finally {
lock.unlock()
}
JLog.e(TAG, "JExecutor is resumed")
}
}
使用(简单测试):
新建一个页面,内部添加两个按钮,第一个按钮测试线程优先级,第二个按钮测试线程执行完毕之后将结果回调到主线程:
然后在Activity中调用线程池框架:
public class HomeActivity extends AppCompatActivity {
@BindView(R.id.btnTest1)
private TextView mBtnTest1;
@BindView(R.id.btnTest2)
private TextView mBtnTest2;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home);
ViewUtils.inject(this);
}
@OnClick({R.id.btnTest1, R.id.btnTest2})
private void onClick(View view) {
switch (view.getId()) {
case R.id.btnTest1:
for (int i = 0; i < 5; i++) {
final int priority = i;
JExecutor.INSTANCE.execute(priority, new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000 - priority * 100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
break;
case R.id.btnTest2:
JExecutor.INSTANCE.execute(new JExecutor.Callable() {
@Override
public void onCompleted(String s) {
Log.e("乔布奇--->", "onCompleted-当前线程:" + Thread.currentThread().getName());
Log.e("乔布奇--->", "onCompleted-执行结果:" + s);
}
@Override
public String onBackground() {
Log.e("乔布奇--->", "onBackground-当前线程:" + Thread.currentThread().getName());
return "异步任务执行";
}
});
break;
}
}
}
观察结果:可以看到5个线程的执行顺序正是按照优先级排序执行的;线程执行完毕之后也能够正常回调到主线程中;
OK,到这里今天要说的内容就差不多了,大部分内容在本文开篇给出的参考资料里面都有,大家可以参考着看一下。
话不多说,各位再见!
祝:工作顺利!