并发编程笔记1
1、并发编程挑战
1.1、上下文切换:
1.1.1、什么是上下文切换?
单核也支持多线程执行,原因是通过时间片轮换,时间片是CPU分配给各线程的时间,在切换过程中,会先保存上个任务信息,再加载当前任务信息,所以任务从保存到再加载的过程就是一次上下文切换。
1.1.2、并行vs串行
并发执行速度比串行慢的原因是:线程创建以及上下文切换的开销。
1.1.3、查看上下文切换
使用vmstat 中cs(content switch)
1.1.4、如何减少上下文切换
-
- 无锁并发编程。例如:将数据ID按Hash算法取模分段,特定线程处理特定段数据。
- CAS算法。例如 Atomic包使用CAS算法更新数据,不需要加锁
- 使用最少线程。
- l协程:在单线程里实现多任务调度。
1.1.5、减少上下文切换实战
可以通过减少线上大量WAITING的线程,减少上下文切换次数
1.2、死锁
产生死锁的原因:线程t1和线程t2互相持有并等待对方释放锁。
发生死锁查看步骤:不能提供服务,dump线程查看哪个线程出现问题
避免死锁方法:
1、避免一个线程同时获取多个锁;
2、避免一个线程在锁内占用多个资源,尽量保证只占每个锁占用一个资源。
3、使用定时锁,如lock.tryLock(timeout)替代使用内部锁机制。
4、对于数据库锁,加锁和解锁必须在一个数据库连接里,否则会出现解锁失败的情况。
1.3、资源限制的挑战
(1) 什么是资源限制?
硬件资源:带宽上传/下载速度,硬盘读写速度,CPU处理速度,内存
软件资源:数据库连接,socket连接数等
(2) 资源限制引发问题
并发编程速度快原因:将代码中串行执行部分变成并发执行;若并发执行,由于资源限制就会串行化执行,就会增加上下文切换和资源调度的时间消耗。
(3) 如何解决资源限制
采用集群并行执行,例如hadoop,不同的机器处理不同的数据,“数据ID%机器数”,计算得到一个机器编号,然后由对应编号的机器处理这笔数据。
(4) 资源限制下的并发编程
根据不同的资源限制调整程序的并发度
建议使用JDK并发包提供并发容器和工具类来解决并发问题
2、Java并发机制的底层实现原理
2.1、Volatile
2.1.1、volatile的应用
① 保证共享变量“可见性”,可见性:当一个线程修改一个共享变量时,另外一个线程能读到这个修改值;
② 轻量级synchronized,执行成本更低,不会引起线程上下文切换和调度;
③ 避免指令重排;
④ 8字节(long,double),一次性赋值;
2.1.2、Volatile修饰变量,JIT编译生成汇编语言会加Lock前缀,从而引发两件事情:
1)、将当前CPU缓存行的数据(工作内存)写回到系统内存(主存)
2)、这个写回内存的操作会使其他CPU缓存该内存地址数据失效。
缓存一致性:在多处理器下,每个处理器通过嗅探总线的数据来检查自己缓存的值是否过期,当发现自己缓存行对应的内存地址被修改,会将自己的缓存行数据设置无效状态,当处理器对这个数据进行修改操作的时候,会重新从系统内存中将数据重新读取。
Volatile使用优化:LinkedTransferQueue
2.2、synchronized的实现原理和应用
2.2.1、synchronized基础
① Java中每一个对象都可以作为锁;
② 锁普通方法,锁是实例对象;
③ 锁静态方法,锁是当前类Class对象;
④ 锁同步代码块,锁是配置对象。
JVM基本原理:使用Monitor对象,代码块同步是使用monitorenter和monitorexit指令实现。
2.2.2、Java对象头
对象头长度:Mark Word【锁状态,对象的hashCode,对象分代年龄,是否是偏向锁,锁标志位】
2.2.3、锁的升级与对比
锁状态:
无锁状态,偏向锁,轻量级锁,重量级锁
锁 |
优点 |
缺点 |
适用场景 |
偏向锁 |
加锁和解锁不需要额外的消耗,和执行同步非同步方法性能差不多 |
如果线程间存在锁竞争,会带来额外锁撤销的消耗 |
适用于只有一个线程访问同步块场景 |
轻量级锁 |
竞争的线程不会阻塞,提高了响应速度 |
如果始终得不到锁竞争的线程,使用自旋会消耗cpu |
追求响应时间 同步块执行速度非常快 |
重量级锁 |
线程竞争不使用自旋,不会消耗cpu |
线程阻塞,响应时间缓慢 |
追求吞吐量,同步块执行速度较长 |
2.3 原子操作的实现原理
原子操作:不可被中断的一个或一系列操作。
CAS(Compare and Swap):CAS操作需要输入两个数值,一个旧值(期望操作前的值),和一个新值,在操作期间先比较旧值有没有发生变化,如果没有发生变化,才交换心志,发生了变化则不交换。
2.3.1、处理器如何实现原子操作:
(1) 使用总线锁保证原子性;
(2) 使用缓存锁保证原子性;
2.3.2、Java如何实现原子操作
① 使用循环CAS实现原子操作,如Java并发包中Atomic提供
② CAS实现原子操作问题
1) ABA问题,解决方法:AtomicStampedReference就是每个更新把版本号加1
2) 循环时间长开销大;
3) 值能保证一个共享变量的原子操作,(取巧方法,把多个共享变量合并成一个共享变量来操作,如i=2,j=a,合并为ij=2a)