Android并发/多线程的基础与应用

Android并发/多线程的基础与应⽤
本篇⽂章主要⽬的为总结 覆盖80%场景的20% Android端并发所需基础知识和应⽤。
Android 端应⽤主要使⽤ Java 语⾔开发,所以基础与 Java 的并发基础基本⼀样,深⼊了解推荐细读《Java并发编程实践》。应⽤部分就会掺杂 Android 的东西了。
并发与线程、线程与线程不安全
讨论并发其实就是在讨论多线程。
⽽并发这个编程主题长久地被⼈拎出来讨论的原因,被前⼈们总结成⼀个词 “线程安全”。
没接触过多线程编程的⼩⽩,很容易会掉进像下⾯这样的不安全陷阱:
读取-修改-写⼊ 的中途打断;
单例模式意外怀孕(多个线程同时初始化获取第⼀个单例);
同⼀个变量,在不同线程读取到的值不⼀样。
更多的例⼦不写了,我总结为:
1.⾮原⼦性状态变更的中途打断;
2.多线程对同⼀状态的观察失去同步。
这两点,也对应着后⽂提到的并发编程核⼼问题和核⼼关键词。
线程世界安全局核⼼领导⼈物——Volatile\Synchronize
Volatil (“曼哈顿博⼠,直接观察基本粒⼦”),解决可见性问题
说到可见性问题,⾸先要理解⼏个概念。
物理内存+⾼速缓存
计算机运⾏⼀个程序的时候,变量最终还是需要存储在⼀个物理位置的。这就导致了每次对变量的操作,⽐如 i++,就需要 T(i++ ⼀次循环) = T(读IO) + T(+1运算)+T(写IO)。运算往往很快,拖后腿的是IO。
为此,物理层加⼊了⼀个⾼速缓存层。这个层读写速度飞快,在初始化的时候从内存拷⼀次变量值到⾼速缓存,之后程序再改这个变量就不⽤再等乌龟内存层了,直接跟⾼速缓存打交道,⾼速缓存先处理了再跟乌龟慢慢交涉。
Jvm 内存+线程本地内存副本
当理解了 物理内存与⾼速缓存 ,问题就好说了。我告诉你,JVM 也⽤了类似的设计: 物理内存–>JVM内存,⾼速缓存–>本地内存副本。
物理内存+⾼速缓存 服务与程序;Jvm 内存+线程本地内存副本 服务于线程。
设计的维度和颗粒度不⼀样,但理念是相通的。
可见性
可见性问题就是,两个线程都分别从主内存拷贝了⼀个变量A副本,然后各⾃修改的就其实都是⾃⼰线程⼯作空间上的那个副本,对主内存的变量A没有影响,对其他线程的那个变量A更没有影响。
Volatile 就可以让他们修改后同步到主内存,读取时主动从主内存读取。还可以禁⽌指令重排。
通过汇编指令“lock”前缀做到,具体的原理可细读《深⼊学习Java虚拟机》。
Synchronize(“游侠锁罗”),同时解决可见性、原⼦性问题
当它⽤来修饰⼀个⽅法或者⼀个代码块时,保证在同⼀时刻最多只有⼀个线程执⾏该段代码。
通过使⽤内置锁,来实现对变量的同步操作,进⽽实现了对变量擦偶哦的【原⼦性】和其他线程对变量的【可见性】。
内置锁
Java 中每⼀个对象都有⼀个与之关联的锁,称为内置锁。
个⼈将内置锁分为 实例锁 和 类锁
Synchronize 修饰⾮静态⽅法时,⽤的就是该⽅法实例的内置锁,也就是this。修饰静态⽅法时⽤的就是类锁。
需要多把锁的时候
随便 new 个 object 就可以当锁。也有显⽰的锁 Lock lock = …;(形形式式的各种加了特效的显式锁)
锁的持有者是线程,锁可重⼊
持有对应的锁,就可以进⼊所有被锁的地⽅,⽆论这个地⽅在哪⾥,在⼦类还是在⽗类等。
水过滤板线程世界安全局马仔
ConcurrentHashMap
CopyonWriteArrayList
LinkedBlockingQueue
线程世界的资源局——线程池、 HabdlerThread、IntentService
辣鸡 new Thread
Runnable作为匿名内部类还持有了外部类的引⽤,在线程退出之前,该引⽤会⼀直存在,阻碍外部类对象被GC回收,在⼀段时间内造成内存泄漏。
1.每次都需要new Thread,新建对象性能差。
2.线程缺乏统⼀管理,可能⽆限制新建线程,相互之间竞争,极可能占⽤过多系统资源导致死机或OOM。
金折网3.缺乏更多功能,如定时执⾏、定期执⾏、线程中断
线程池
final ExecutorService executor = wCachedThreadPool();
@Override
public void run() {
final Bitmap bitmap = downloadImage(beautyUrl);
runOnUiThread(new Runnable() {
@Override
public void run() {
mBeautyImageView.setImageBitmap(bitmap);
}
});
executor.shutdown();
}
});
引⼊的好处:
1.提升性能,创建和消耗对象费时费CPU资源。
2.防⽌内存过度消耗,控制活动线程的数量,防⽌并发线程过多。
如何选择?
使⽤wCashedThreadpool通常是个不错的选择,因为它不需要配置,并且⼀般情况下都能够正确地完成⼯作。但是对于⼤负载的服务器来说,缓存的线程池就不是很好的选择了!在缓存的线程池中,被提交的任务没有排成队列。⽽是直接交给线程执⾏。如果没有线程可⽤,就创建⼀个新的线程。如果服务器负载的太重,以致他所有的CPU都完全被占⽤了,当有更多的任务时,就会创建更
多的线程,这样只会使情况变得更糟。因此在⼤负载的产品服务器中,最好使⽤wFixedThreadPool,它为你提供了⼀个包含固定线程数⽬的线程池,或者为了最⼤的限度地控制它,就直接使⽤ThreadPoolExcutor类。
HabdlerThread——轻量级任务处理器
HandlerThread是Android中提供特殊的线程类,使⽤这个类我们可以轻松创建⼀个带有Looper的线程,同时利⽤Looper我们可以结合Handler实现任务的控制与调度。以Handler的post⽅法为例,我们可以封装⼀个轻量级的任务处理器。
private Handler mHandler;
private LightTaskManager() {
HandlerThread workerThread = new HandlerThread("LightTaskThread");
workerThread.start();
mHandler = new Looper());
}
public void post(Runnable run) {
mHandler.post(run);
}
public void postAtFrontOfQueue(Runnable runnable) {
mHandler.postAtFrontOfQueue(runnable);
}
public void postDelayed(Runnable runnable, long delay) {
mHandler.postDelayed(runnable, delay);
}
public void postAtTime(Runnable runnable, long time) {
mHandler.postAtTime(runnable, time);
}
在本例中,我们可以按照如下规则提交任务
post 提交优先级⼀般的任务
postAtFrontOfQueue 将优先级较⾼的任务加⼊到队列前端
postAtTime 指定时间提交任务
postDelayed 延后提交优先级较低的任务
上⾯的轻量级任务处理器利⽤HandlerThread的单⼀线程 + 任务队列的形式,可以处理类似本地IO(⽂件或数据库读取)的轻量级任务。在具体的处理场景下,可以参考如下做法:
对于本地IO读取,并显⽰到界⾯,建议使⽤postAtFrontOfQueue
对于本地IO写⼊,不需要通知界⾯,建议使⽤postDelayed
⼀般操作,可以使⽤post。
(实验怎么回调)
(强化参考:)
过handlerThread.quit()或者quitSafely()使线程结束⾃⼰的⽣命周期。
IntentService
耗时逻辑应放在onHandleIntent(Intent intent)的⽅法体⾥,它同样有着退出启动它的Activity后不会被系统杀死的特点,⽽且当任务执⾏完后会⾃动停⽌,⽆须⼿动去终⽌它。例如在APP⾥我们要实现⼀个下载功能,当退出页⾯后下载不会被中断,那么这时候IntentService 就是⼀个不错的选择了。
IntentService背后其实也有⼀个HandlerThread来串⾏的处理Message Queue,从IntentService的onCreate⽅法可以看出。跟我们的轻量任务处理器类似。
通讯: ⼯作线程⼲活后怎么在必要的时候切回主线程?
Future,FutureTask和Callable
这个还没深⼊研究过。
AsyncTask
恕我直⾔,⼀坨⼀坨的��⼀样好烦⼈。只能从UIThread发出, 也有隐式的持有外部类对象引⽤的问题,需要特别注意防⽌出现意外的内存泄漏。在不同的系统版本上串⾏与并⾏的执⾏⾏为不⼀致。反正我是不爱⽤。
Thread+Handler+MessageQueue+Looper
(!todo 原理解析,案例)
内置UI线程操作底层也是利⽤了这个原理。
内置UI线程操作
Activity.runOnUiThread(Runnable)
View.post(Runnable) ,View.postDelayed(Runnable, long)
Handler(UI线程的)的postDelayed
Boardcast
线程优先级调整
在Android应⽤中,将耗时任务放⼊异步线程是⼀个不错的选择,那么为异步线程调整应有的优先级则是⼀件锦上添花的事情。众所周知,线程的并⾏通过CPU的时间⽚切换实现,对线程优先级调整,最主要的策略就是降低异步线程的优先级,从⽽使得主线程获得更多的CPU 资源。
Android中的线程优先级和Linux系统进程优先级有些类似,其值都是从-20⾄19。其中Android中,开发者可以控制的优先级有:
* THREAD_PRIORITY_DEFAULT,默认的线程优先级,值为0
* THREAD_PRIORITY_LOWEST,最低的线程级别,值为19
* THREAD_PRIORITY_BACKGROUND 后台线程建议设置这个优先级,值为10墨菲氏滴管
* THREAD_PRIORITY_MORE_FAVORABLE 相对
* THREAD_PRIORITY_DEFAULT稍微优先,值为-1
* THREAD_PRIORITY_LESS_FAVORABLE 相对
* THREAD_PRIORITY_DEFAULT稍微落后⼀些,值为1
为线程设置优先级也⽐较简单,通⽤的做法是在run⽅法体的开始部分加⼊下列代码
new Thread(new Runnable() {
海鲜机@Override
电梯应急装置public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
}
去污剂}).start();
在我们决定新启⼀个线程执⾏任务的时候,⾸先要问⾃⼰这个任务在完成时间上是否重要到要和UI线程争夺CPU资源。如果不是,降低线程优先级将其归于background group,如果是,则需要进⼀步的profile看这个线程是否造成UI线程的卡顿。
通常设置优先级的规则如下:
* ⼀般的⼯作者线程,设置成THREAD_PRIORITY_BACKGROUND
* 对于优先级很低的线程,可以设置THREAD_PRIORITY_LOWEST
* 其他特殊需求,视业务应⽤具体的优先级
其他
SharePreference是否线程安全?多线程操作是否存在隐患?
线程安全。可以⽤作线程间少量数据共享,但是不能⽤在进程间。
进程不安全。虽然在API Level>=11即Android 3.0可以通过Context.MODE_MULTI_PROCESS属性来实现SharedPreferences多进程共享。但每个进程操作的SharedPreferences其实都是⼀个单独的实例, 也不能通过锁来解决,这导致了多进程间通过SharedPreferences来共享数据是不安全的,这个问题只能通过多进程间其它的通信⽅式或者是在确保不会同时操作SharedPreferences 数据的前提下使⽤SharedPreferences来解决。
android中数据库⽀持多线程吗?
sqlite⽀持多线程。但是安卓在封装sqlite的java接⼝的时候加了表锁,所以会造成多个线程读⼀个表的时候阻塞。这也是为什么读写数据库的操作要放到线程⾥做,防⽌ANR。
默认情况下,同⼀应⽤的所有组件均在相同的进程中运⾏,且⼤多数应⽤都不会改变这⼀点。 但是,如果您发现需要控制某个组件所属的进程,则可在清单⽂件中执⾏此操作。
Final Tips
“同构任务”和“并⾏”更配哦��
异步时要注意Activity的⽣命周期
异步时最容易出错的就是忽略Activity的⽣命周期。⽐如,当异步执⾏完成了,Activity却退出了前台,或者已经结束,如果异步完成时要操作UI,那么这种情况下肯定会报错,具体的错误取决于场景。这个问题的解法就是在异步操作完成后要⽤Activity.isFinishing()来判断下Activity是否还是alive的。或者设置⼀个变量来查看Activity是否还在前台。
另外,即使异步操作中不涉及UI,那么当Activity转⼊后台,或者退出时,也要及时的终⽌⼯作线程,否则也会造成Activity的对象⽆法及时销毁⽽最终导致内存泄露。这个问题需要在设计异步task时把可取消考虑进去,当Activity退出前台时发送消息给线程,让其终⽌执⾏。对于常见的费时操作,⽐如IO,⽹络,复杂计算等在都要考虑取消,每⼀个⼩步骤执⾏前都要判断取消标志位,以及时终⽌操作。通常这需要在Activity中持有任务的引⽤,或者使⽤Executors来管理任务,或者有⼀个类似的对象来管理异步任务,当Activity退出时,来终⽌任务。或者使⽤EventBus这类⼯具来降低耦合。

本文发布于:2024-09-24 18:22:32,感谢您对本站的认可!

本文链接:https://www.17tex.com/tex/4/277389.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:线程   内存   任务   操作   并发
留言与评论(共有 0 条评论)
   
验证码:
Copyright ©2019-2024 Comsenz Inc.Powered by © 易纺专利技术学习网 豫ICP备2022007602号 豫公网安备41160202000603 站长QQ:729038198 关于我们 投诉建议