1.多线程的目的及多线程原子性实现

2021-01-19  本文已影响0人  陪伴你的大数据

1.读线程的目的
压榨多核CPU,多个CPU执行任务,比串行更快速
2.多线程的的挑战
1.上下文切换,线程切换影响执行速度
1.1如何减少上下文切换
减少线程--减少线程或使用协程
减少锁--CAS、无锁编程(hash取模,不同线程执行不同段数据)
1.2如何查看线程使用情况
vmstate 1 每1s查看一次线程情况,cs参数可看到1s切换的次数。
Lmbench3可以查看切换线程的时长
2.死锁
3.资源限制
如果硬件资源有限制,网络、磁盘IO、连接数等都会限制多线程程序的执行。
3.1解决资源限制
池化技术--数据库连接池
Socket复用
任务连接数管控等
3.2调参
根据不同的资源限制,调整程序并发度。

2.并发实现原理
volatile、synchronized

1.volatile原理与实现
1.Lock指令会使处理器缓存写回内存。缓存锁定保证只有1个处理器缓存写回内存。
2.一个处理器缓存写回内存,其他处理器缓存失效。嗅探技术保证内部缓存、系统内存、其他处理器缓存的数据一致性。如果有处理器写回内存,其他处理器缓存失效。
1.2volatile的优化
LinedTransferQueue,补充为64个字节,保证头结点、尾结点在1个高速缓存行。避免队列的出队、入队会频繁锁定头尾节点。
没有必要补位的volatile情况
1.缓存行非64位处理器
2.共享变量不会被频繁地写,锁的几率小。

2.synchronized原理与实现
2.1synchronized锁定对象
普通方法--当前实例对象
静态方法--当前Class对象
同步方法块---括号里的对象

2.2Java对象头
synchronized的锁存在对象头中,对象头的数组类型3个字宽,非数组2个字宽。
有32bit和64bit两种。
对象头包含对象的hashCode对象值、是否偏向锁、锁标志位,还可能有非带年龄

2.3锁的升级与对比
无锁、偏向锁、轻量级锁(自旋锁)、重量级锁(排它锁),只能升级不能降级。
1.偏向锁--只对某一个线程
上锁:在对象头和栈帧的锁记录里记录偏向的线程ID进行上锁。如果对象头的MarkWord中偏向锁标识=1,上锁成功,否则cas竞争锁。
撤销锁:
等到其他线程竞争才会让出锁的机制,到达没有指令执行的全局安全点时,拥有锁的线程会暂停,此时判断拥有锁的线程是否活着,如果死亡,对象头设置为无锁;如果活着,偏向锁的栈执行,遍历偏向对象的锁记录,栈中锁记录和对象头Mark Word要么重新偏向其他线程,要么恢复到无锁或者标记对象不适合偏向锁。
关闭偏向锁:
偏向锁需要启动JVM几秒后才能启用,J 6 J7 默认使用。关闭延迟
-XX:BiasedLockingStartupDelay=0
如果程序线程普遍存在竞争状态,则关闭偏向锁,直接升级为轻量级锁。
-XX:UseBiasedLocking=flase
2.轻量级锁
上锁:在执行同步代码块之前,JVM在当前线程的栈帧中创建存储锁的空间,并将对象头MarkWord复制到栈帧锁记录中,称为Displace Mark Word。然后线程尝试使用CAS将对象头中的MarkWord锁记录指向栈帧锁记录的指针。cas失败,自旋获取。
释放锁:cas操作将Displace MarkWrod替换回对象头。失败,膨胀为重量级锁。

2.4偏向锁、轻量级锁、重量级锁的比较及应用场景。
偏向锁 几乎不需要额外的消耗,只对对象中做标记,适合1个线程访问的同步快。多个线程竞争,会有锁撤销升级的消耗。
轻量级锁:适用于非阻塞,需要响应速度快,同步代码执行速度快的场景。线程多,自旋多,cpu消耗变多。
重量级锁:适用于追求吞吐量,同步代码快执行时间长,可阻塞的场景。不会自旋,消耗cpu时间

3.原子操作实现原理
原子操作---不可中断的一个或一系列操作。
1.处理器的实现
总线加锁+缓存加锁实现
总线加锁:处理器提供一个LOCK#指令信号,将cpu和其他处理器缓存之间的通信锁住。只能有1个cpu核心读改写此共享变量。
缓存加锁:总线加锁开销太大。当某些变量频繁使用会加载在处理器的L123等多级缓存中。处理器缓存写入到内存中,且在lock操作期间,修改内存中的缓存地址,由于缓存一致性协议,其他处理器关于此内存的变量缓存失效,其他处理器访问此变量时,重新从主内存中拉取。

两种不支持缓存锁定
1.访问的数据不能缓存在处理器内部或跨越多个缓存行,需要总线加锁。
2.处理器不支持缓存锁,此时可以使用Lock前缀的各类指令实现。

2.java中原子性
1.CAS类
cas通过处理器CMPXCHG指令,自旋cas操作,直到成功为止。轻量级锁、重量级锁及其他java中的锁都用cas来加锁和释放锁。
cas类存在的3个问题:
1.ABA问题
同一个线程 a 改为b,又改为a,此时其他线程感觉没有变化,但实际变化了。增加版本号,Atomic包中提供了AtomicStampedReference解决ABA问题。
2.cas自旋时间长,cpu开销大
JVM如果支持处理器pause指令可有一定提升,可延迟流水线执行指令,同时避免在退出循环的时候顺序冲突引起的cpu了流水线清空,提升cpu效率。
3.只能保证1个变量的原子操作。
可使用拼接变量或AtomicReference类保证引用对象之间的原子性

上一篇下一篇

猜你喜欢

热点阅读