并发控制——锁机制的优化

来源:互联网 发布:数据挖掘 十大算法代码 编辑:程序博客网 时间:2024/06/05 14:12

问题背景:

锁的开销:多线程使系统除处理功能需求外,另有维护多线程的额外开销,包括:
1. 线程本身的元数据
2. 线程调度
3. 线程上下文切换
死锁:线程间相互等待又不释放自身资源,形成循环等待

锁的优化方法:

我们需要通过合理的手段,降低锁竞争、锁冲突,以提高并发能力

代码层面:

1. 减少锁持有时间:只在必要的代码篇幅上加锁,比较容易注意到的降低锁竞争的方法。
2. 锁粗化:与上条相对。由于锁不断请求、同步、释放也很耗资源,某些情况下我们需要对锁进行适当的粗化。如:
a. 将锁置于循环外
b. 快速执行的功能点不需要进行锁分离
3. 减少锁粒度:通过分割数据结构,缩小锁定对象范围,拆分锁实现锁分离,如:ConcurrentHashMap中将HashMap拆分成多个Segement分段锁
缺点:系统需要获得全局锁时耗费资源较多。
4. 锁分离:通过对系统不同功能点(不同操作)的分割进行锁分离
4.1 读写锁换独占锁:读操作不会影响数据的完整性和一致性,应允许多线程并行。读锁相容,写锁独占。写锁未释放的,无法往对象上加任何锁,如:CopyOnWriteList适合读多写少的场景
4.2 重入锁与内部锁:
    a.重入锁:利用ReentrantLock和Condition结合,对独占锁进行分离。重入锁提供锁等待、锁中断、快速轮训、有助于避免死锁,如LinkedBlockingQueue
    b.内部锁:用synchronized结合Object.wait()及Object.notify()实现。使用简单,JVM内部有优化。
    c.在可正常实现系统功能情况下,推荐使用内部锁

JVM层面:

1. 自旋锁: 防止锁等待小段CPU时,做线程切换,进行频繁的挂起、恢复,提升线程执行的连贯性。用于锁竞争不激烈,锁占用时间段的并发场景
    -XX:+UseSpirinning 开启自旋锁
    -XX:+PreBlockSpin 设置等待次数
2. 锁消除: JVM编译时,进行上下文扫描,进行逃逸分析,消除不存在资源竞争的锁,如:方法中局部变量(线程私有,不会逃逸出栈帧)为JDK内置线程安全的数据结构(如StringBuffer),可消除该数据结构中带有的加锁操作
    -XX:+DoEscapeAnalysis 开启逃逸分析
    -XX:+EliminateLock 开启锁消除
3. 锁偏向: 在锁竞争激烈的场合,应该禁用
    -XX:-UseBiaseLocking 关闭锁偏向

CAS:

无锁机制保证数据的一致性,基于比较并交换(CompareAndSwap),一种非阻塞同步的方法,是乐观锁的一种实现。
优点:消除锁竞争,没有相互等待,对死锁问题免疫。TheadLocal、java.util.concurrent.atomic中的原子类是基于此实现的。
缺点:将冲突交由应用层实现,增加了开发难度。不过现在有现成的无锁并行框架,如Apache的Amino框架。


共享数据的并发访问控制方法

volatile 保证变量可见性
Semaphore 信号量。实现对象池
ThreadLock 线程局部变量。生成副本、无锁机制

原创粉丝点击