目录
- 线程状态
- 线程池
- 线程安全
- 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
线程安全问题
实质就是多线程的同步问题。
为什么会出现同步问题,背景在数据读写这里,先看到读写方式(如图)

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
各种锁示意图

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,避免集合引用导致的泄露。
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
网友评论