并发编程(一):理论
为什么需要CPU cache
CPU的频率太快了,快到主存跟不上。这样在处理器时钟周期内,CPU常常需要等待主存,浪费资源。所以cache的出现,是为了缓解CPU和内存之间的速度不匹配问题。
CPU cache的意义(上帝原则)
- 时间局部性:如果某个数据被访问,那么在不久的将来它很可能被再次访问。
- 空间局部性:如果某个数据被访问,那么与它相临的数据很快也可能被访问。
CPU多级缓存 - 缓存一致性(MESI)
- 用于保证多个CPU cache之间缓存共享数据的一致
- Modify(修改)代表只存在当前cpu中并且被修改,如果有其他CPU使用写入主存并变更为E
- Exclusive(独享)只存在该缓存行的该cpu中,未被修改过与主存一致,如其他cpu使用变更S;当该线程修改该缓存时变更为Modify状态
- Shard(共享)该缓存行可能被多个cpu缓存,当其他cpu修改该缓存行时变为Ignore
- Ignore(无效)代表该缓存行是无效的,可能其他cpu修改了该缓存行
CPU多级缓存 - 乱序执行优化
- 处理器为提高运算速度而作出违背代码原有顺序的优化
JAVA内存模型
Synchronized:
- 线程解锁前,必须把共享变量的最新值刷新到主内存。
- 线程加锁时,将清空工作内存中共享变量的值,从而使用共享变量时需要从主内存中重新读取最新的值
Volatile
- 对volatile变量写操作时,会在写操作后加入一条store屏障指令,将本地内存中的共享变量值刷新到主内存
- 对volatile变量读操作时,会在读操作前加入一条load屏障指令,从主内存中读取共享变量。
- 适用于改变不依赖于当前状态,适合做标识量
Happens-before原则
- 程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作。
- 锁定规则:一个unLock操作先行发生于后面对同一个锁的lock操作。
- volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作。
- 传递规则:如果操作a先行发生于操作b,而操作b先行发生于操作c,则可以得出操作a先发生于操作c
- 线程启动规则:Thread对象的start()方法先行发生于此线程的每一个动作。
- 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生。
- 线程终结规则:线程中所有操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行。
- 对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始。
不可变对象
- final
- guava中的Immutable XXX
- java类库中的Collections.unmodifiableXXX
线程封闭
- Ad-Hoc线程封闭:程序控制实现,最糟糕
- 堆栈封闭:局部变量,无并发问题。
- ThreadLocal线程封闭:特别好的封闭方法。
线程不安全类与写法
- StringBuilder -> StringBuffer
- SimpleDateFormat -> JodaTime
- ArrayList,HashSet,HashMap等Collections
线程安全容器
- ArrayList -> Vector,Stack
- HashMap -> HashTable(key、value不能为null)
- Collections.synchronizedXXX(List、Set、Map)
并发容器(J.U.C)
- ArrayList -> CopyOnWriteArrayList
- HashSet、TreeSet -> CopyOnWriteArraySet、ConcurrentSkipListSet
- HashMap、TreeMap -> ConcurrentHashMap、ConcurrentSkipListMap
ReentrantLock 与 锁
- ReentrantLock(可重入锁)和synchronized区别
- 可重入性
- 锁的实现
- 性能的区别
- 功能的区别
- ReentrantLock独有的功能
- 可指定是公平锁还是非公平锁
- 提供了一个Condition类,可以分组唤醒需要唤醒的线程
- 提供能够中断等待锁的线程的机制,lock.lockInterruptibly()
FutureTask
- Callable与Runnable接口对比
- Future接口
- FutureTask类
线程池
- 重用存在的线程,减少对象创建、消亡的开销,性能佳
- 可有效的控制最大并发线程数,提高系统资源利用率,同时可以避免过多资源竞争,避免阻塞
- 提供定时执行、定期执行、单线程、并发数控制等功能。
线程池 - ThreadPoolExecutor
corePoolSize:核心线程数量
maximumPoolSize:线程最大线程数
workQueue:阻塞队列,存储等待执行的任务,很重要,会对线程池运行过程产生重大影响。
keepAliveTime:线程没有任务执行时最多保持多久时间终止
unit:keepAliveTime的时间单位
threadFactory:线程工厂,用来创建线程
rejectHandler:当拒绝处理任务时的策略
线程池-合理配置
- CPU密集型任务,就需要尽量压榨CPU,参考值可以设为NCPU+1
- IO密集型任务,参考值可以设置为2*NCPU
死锁
- 互斥条件
- 请求和保持条件
- 不剥夺条件
- 环路等待条件
避免死锁
- 破坏占用且等待条件(一次性申请全部资源)
- 破坏不可抢占条件(synchronized做不到,lock可以)
- 破坏循环等待条件(按顺序申请锁资源)