Android线程学习索引

目录

  • 线程状态
  • 线程池
  • 线程安全
  • Java Memory Model
  • Volatile
  • Sychornized
  • ReentrantLock
  • 乐观悲观
  • 死锁
  • jmm和jvm的区别
  • 线程通信handler机制
    • IntentService
    • ThreadLocal
  • 原子类(没写)
  • AsyncTask
  • 其他的同步
  • 进程
  • 参考链接

线程状态

线程状态转换图


new,runnable,running,dead,blocked,waiting/time waiting

状态转换的几个方法及其比较

start()/run()的区别:

start是让线程处于就绪状态真正的实现多线程,内部是调用了run方法。而run方法不是,继续同步调用。

其余几个方法的比较:

yield—线程静态方法,释放锁,放弃这次cpu机会,下次再竞争,只给不比自己优先级低的线程机会,执行后线程进入runnable状态

sleep--静态方法,不释放锁,不考虑优先级,执行后进入block状态,有的也说进入timewaiting状态,从不释放锁这点来看,我觉得是block状态。

join--实例方法,使该实例进程阻塞,当被join的线程执行完才执行自己,执行进入wait/timewaiting状态

wait—Object类实例方法,释放锁,必须放在同步块里使用,自己则进入对象等待池,执行后进入wait/timewaiting,wait(time)就是进入timewaiting状态,能提前notify么?

notify--实例方法,该实例对象移除对象等待池,进入锁标志等待池

notifyAll--实例方法,唤醒所有的线程,让其竞争上岗


怎样使用多线程

继承thread,实现runnable接口,实现callable接口


线程池

线程池ThreadPool的使用executorService.excute,

根据其构造方法理解线程池

比如asyncTask的核心线程数Math.max(2,Math.min(CPU_COUNT,4)),至少2个,至多4个
最大线程数 CPU_COUNT*2-1

okhttp的核心线程数0,最大数INT_MAX---为什么会这样?后文有

优化点就在减少线程的创建和销毁时间,开发者更多的关注任务

构造方法的四个队列及其特点

linkedBlockQueue,ArrayBlockQueue,DelayQueue,SynchronizedQueue

Okhttp的内部实现

可以借助OkHttpClient.newCall(request).equeue(Callback)来理解线程池的应用
参考okhttp章节的发送请求段落
https://www.jianshu.com/p/6fa13048a6cf


线程安全问题

实质就是多线程的同步问题。
为什么会出现同步问题,背景在数据读写这里,先看到读写方式(如图)

image.png

1.程序运行过程中的临时数据是存放在主存(物理内存)当中的;
cpu访问速度和顺序:register寄存器-->cpu cache mem(高速缓存)-->RAM 主存
2.由于CPU执行速度很快,而从内存读取数据和向内存写入数据的过程跟CPU执行指令的速度比起来要慢的多;
3.如果任何时候对数据的操作都要通过和内存的交互来进行,会大大降低指令执行的速度;
4.所以最终在CPU里面就有了高速缓存;
线程会从主存(堆区)copy一份到栈内存里(高速缓存里),处理完后就把这个副本刷回去
5.但是问题在,有缓存不一致的问题,也就是多线程访问会访问到缓存,而不是实时数据;
6.于是有了加锁的概念(线程安全的问题)。


Java Memory Model

解决线程安全模型,先来了解jmm

什么是Java内存模型?

https://mp.weixin.qq.com/s/_4AtyCVPj6E4AkHzVHsKbg

http://www.cnblogs.com/dolphin0520/p/3920373.html

因此并发可能遇到的三个问题
1.原子性:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行;
2.可见性:是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值;
3.有序性:即程序执行的顺序按照代码的先后顺序执行。

再更详细的说
java内存模型的毛病
http://cmsblogs.com/?p=2161

实例化一个对象要分为三个步骤:

1.分配内存空间
2.初始化对象
3.将内存空间的地址赋值给对应的引用

但是由于重排序的缘故,步骤2、3可能会发生重排序,其过程如下:
1.分配内存空间
2.将内存空间的地址赋值给对应的引用
3.初始化对象

如果2、3发生了重排序就会导致第二个判断会出错,singleton != null,但是它其实仅仅只是一个地址而已,
此时对象还没有被初始化,所以return的singleton对象是一个没有被初始化的对象

两个解决办法:

1.不允许初始化阶段步骤2 、3发生重排序。
2.允许初始化阶段步骤2 、3发生重排序,但是不允许其他线程“看到”这个重排序。


Volatile

volatile关键字,用来修饰变量
volatile在以下三方面影响java内存模型,从而保证线程安全

1.原子性:Java中对基本数据类型的变量的读取和赋值操作是原子性操作,即这些操作是不可被中断的,要么执行,要么不执行,其余都是非原子操作;
*volatile不保证原子性操作,所以对原子性没帮助

2.可见性:Java里的一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值;
*volatile保证修改的值会立即被更新到主存

3.有序性:JavaJ内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性;
*volatile原则属于 happens-before原则的一部分,能部分保证有序性

从汇编角度了解volatile禁止重排序,了解happens-before原则
http://cmsblogs.com/?p=2148


Synchronize

Synchronize关键字,本身不是锁,只是去拿锁

从jmm角度分析synchronize和volatile的区别
1.原子性
代表了原子性的操作(几个步骤是一个原子)在线程执行过程中不会被中断,是一个整体;
原子性是拒绝多线程操作的,不论是多核还是单核,具有原子性的量,同一时刻只能有一个线程来对它进行操作,所以synchronize可以保证变量原子性;
volatile能保证变量在私有内存和主内存间的同步,但是多个线程操作仍然可能打断,所以不能保证变量的原子性;
2.可见性
volatile是变量在多线程之间的可见性(使得缓存数据无效),synchronize并不会马上更新到主存里,只不过只有一个线程操作,数据继续是缓存->主存;
3.有序性
都是一定程度保证有序性,volatile的有序性规则不细说,synchronize是保证同步代码块内的代码不会重排序到同步块之外(其实也是monitor屏障)
4.其他方面
volatile是线程同步的轻量级实现,多线程访问volatile不会发生阻塞,而synchronize会发生阻塞,所以volatile的性能要比synchronize好;
volatile只能用于修饰变量,synchronize可以用于修饰方法、代码块,对于多线程访问同一个实例变量还是需要加锁同步。

锁范围

Java并发之synchronized深度解析
https://mp.weixin.qq.com/s/xPUKTIIwz4sU1cK1eWy21w

锁思想
https://www.jianshu.com/p/94cf9ebd8932
包含,悲观(就是锁上)乐观(认为不会修改,写少的场景,无锁,CAS操作)。
锁升级: 自旋锁(先不释放锁,下次申请锁还是自己就不用再加锁消耗),轻量锁(等待,里面有个id,判断到没到自己),重量锁(直接挂起等待唤醒)

结合单例DCL去讲

饿汉法写一个单例
https://blog.csdn.net/Jo__yang/article/details/52117031

https://mp.weixin.qq.com/s/N6UqsoWLEUWFFu2S_OT75w

那些年,我们一起写的单例模式
https://mp.weixin.qq.com/s/pixuEDQ_OZ0RFjciTThKlQ

那为什么用enum写单例是线程安全的?
http://www.hollischuang.com/archives/197


各种锁示意图

图源https://mp.weixin.qq.com/s/tQ4dgerk9-FR4HIW5rwHpQ

ReentrantLock

reentrantLock重入锁,—这个没咋看,不常用
结合DelayQueue的take方法
https://blog.csdn.net/kobejayandy/article/details/46833623


乐观悲观

锁继续发散就是乐观锁和悲观锁
这两种不是真正的锁,而是一种思想,前面说的加锁都是悲观锁,cas是一种乐观锁的实现形式。
悲观锁就是每个操作前都锁定,乐观锁就是先读取操作后再比较,适用于读高发的场景。具体参考
CAS (compareAndSwap)
https://www.cnblogs.com/qjjazry/p/6581568.html


死锁

死锁的概念
代码实现一个死锁,如何解决死锁?
http://cmsblogs.com/?p=1312


jvm 跟jmm的区别

jvm是指java虚拟机的内部结构模型,内部包括: 线程共享方法区&堆区,线程独享栈区,本地方法区,程序计数器。

数据结构的线程安全问题

有时候是从数据结构深入到线程安全。
比如先问HashMap和LinkedHashMap的区别,然后问HashMap的缺点,
再对比HashMap和HashTable,Collection.SynchornizedMap的优缺,提出改进方案用ConcurrentHashMap--->结构特点

这里再发散一下
有点像ArrayList和LinkedList比较,再比较Vector,和CopyOnWriteArrayList


多线程中的通信问题

就是考Handler的消息机制

首先handler收发消息的图要会画

然后通过各自的构造方法去梳理一下

handler/thread/threadLocal/looper/messageQueue 的对应关系

以activity在主线程启动的为例(handler有关的)

1.ActivityThread的main方法里Looper.prepareMainLooper(就是prepare方法);
2.activityThread就绑定了这个线程,虽然我不知道跟这个handler有啥用;
3.并且主动调用loop循环取消息;

prepareLooper方法做了两件事

1.判断当前线程的threadLocal里有没有looper,有就报错,因为一个线程里只能有一个looper;
2.threadLocal(looper类的饿汉初始化)里没有looper,则初始化looper并set进去;
这里细节
Looper prepare的时候,
先检查 threadLocal,先get,看能不能拿到looper
再set,把looper set到threadLocal里
ThreadLocal 的get方法,很诡异
看起来是key,value,key是thread,value是looper

实际上是获取 thread里私有的threadLocal,
因为ThreadLocal.ThreadLocalMap是线程私有的。
这个ThreadLocalMap是ThreadLocal的静态内部类。

然后并不是真正的map。而是一个 Entry数组。
这个数组存的方式也很奇怪,index是threadLocal的hashCode
value是Entry。
entry的key是 ThreadLocal,value是其持有的私有对象值,这里是looper

然后,这个entry,继承的是一个WeakReference<ThreadLocal<?>>
这个能保证,weakReference里的threadLocal 就不算强引用,可以正常回收,threadlocalMap也就可以回收,这样里面的一些私有变量就可以被回收。

举个例子, userInfo放到threadLocal里,做线程私有变量,当userInfo不用了,userInfo==null,
但是threadLocal还持有userInfo(集合持有),threadLocal生命周期跟thread一起,可能这个thread就一直没释放,也就没回收,导致内部的threadLocalMap就一直保留着userInfo.

这里就可以做到,threadLocal的remove函数,会主动清理掉entry,避免集合引用导致的泄露。

这里可以看这篇文章
https://mp.weixin.qq.com/s/8Emvbmbn1CgBPjddETEjMA

Looper的初始化方法里做了两件事

1.绑定一个messageQueue,所以一个looper只有一个msgQueue;
2.绑定当前所处线程;
所以主线程默认是有一个looper,一个msgQueue,主线程的handler是不需要额外prepare的

然后就是1个线程可以有多个handler(实际就是1个looper可以有多个handler)参考主线程,其余都是一一拥有关系。

收发消息

handler.sendMsg/obtainMsg的区别

handler怎样做到MsgDelay

->handler.sendMsgDelay
->msg内部存储了一个时间戳
->sendMessageAtTime方法绑定一个msgQueue(也从侧面说明,一个handler只能同一时间处理一个msgQueue的消息)
->handler.enqueueMessage
->msgQueue.enqueueMessage
->内部有个死循环来判断排队

这里经常会被问到为什么msg delay不会被阻塞,比如sendMsgDelay ,第一个msg delay 30s,第二个msg delay 5s
那么第二个msg真的会在第35s才被接收吗?
这里涉及到的就是msg的阻塞问题,

实际上结合MessageQueue的enquene方法,和msg的next方法,内部维护一个needAwake
首先判断的是消息是否退出,如果要退出就回收消息,然后获取到正在使用的消息并将传递过来的时间(即延时时间)赋值给msg.when。而p又是什么?其实p = mMessages,第一次进来p是null,接着判断消息是否为null,当前消息的when是否为0或者小于p.when满足这个条件,p 就是下一个要执行的消息即(msg.next),再给needWake赋值时刻唤醒消息。如果不满足上面的条件(即线程阻塞了)则开启一个死循环将其插入到队列中,让when = 0的消息或者when < p.when(即延时小的消息)先执行。

loop方法的内部实现

内部一个死循环不断去取msg,取到之后msg.target.dispatchMsg,这个msg.target本质是handler对象,
在handler.obtain的时候,msg就绑定上了一个target,handler.send的是在equeueMsg的时候绑上的,最后由这个handler去handleMsg

这里会被问到,为什么这个loop的死循环不会阻塞UI线程?
在MessageQueue.next()方法里,会调用一个native方法:nativePollOnce(long ptr, int timeoutMillis),当主线程没有消息可处理的时候,该方法会阻塞主线程。在这种情况下,用户点击一下屏幕
nativePollOnce()方法继续执行了,并且调用了InputEventReceiver.dispatchInputEvent()
调用nativePollOnce()方法挂起主线程之后,当有一些事件到来时,native层会唤醒主线程
https://www.zhihu.com/question/34652589

IntentService/HandlerThread的学习与使用

https://www.jianshu.com/p/b9427d4011e0

ThreadLocal

面试必问:Android Handler机制之ThreadLocal
https://mp.weixin.qq.com/s/WOYUWPSSrGIaX1uuboRrqg

ThreadLocal解析
https://mp.weixin.qq.com/s/VNBwPPJeENyU9iyxJcN1qw


AsyncTask

使用
https://www.jianshu.com/p/7d2fa022b647

源码
https://www.jianshu.com/p/97da1c0f21c4

强烈推荐看这个
关于创建ayncTask的
https://mp.weixin.qq.com/s/5CeZ6NHF6dm3qN6RgzaGDQ


其他的同步

Condition 与传统多线程协作区别问题解析
https://mp.weixin.qq.com/s/naUaxbcenTZeiQ441wyQbA

Android Exchanger
https://mp.weixin.qq.com/s/bOFsVALa8oBbhvcrzCIbYw


进程

Android技能树 — 多进程相关小结
https://mp.weixin.qq.com/s/DgMXCqRh5sQiIDEUeTPzcw


参考链接

android 多线程 — 综述
https://www.jianshu.com/p/52d752dac4aa

还有一些线程的问题
https://mp.weixin.qq.com/s/QQt8sPOjsqQ0kHtfGsSlMg

JAVA多线程和并发基础面试问答
http://blog.jobbole.com/76308/

Java线程面试题 Top 50
http://www.importnew.com/12773.html

40个Java多线程问题总结
http://www.cnblogs.com/xrq730/p/5060921.html

handler实现原理,从源码分析
http://www.2cto.com/kf/201605/507567.html

多线程的实际应用例子
简易断点续传下载器实现
https://www.jianshu.com/p/5b2e22c42467

Android多线程断点续传下载
https://www.jianshu.com/p/2b82db0a5181

Android里怎么回主线程操作
https://www.cnblogs.com/jingmo0319/p/5730963.html

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容