Java高并发- 锁的优化及 JVM 对锁优化所做的努力

在高并发环境下,激烈的锁竞争会导致程序的性能下降,所以我们有必要讨论一下有关 锁 的性能问题及注意事项。如:避免死锁,减小锁粒度,锁分离等。

一、锁优化

1.1 减小锁持有时间
在锁竞争过程中,单个线程对锁的持有时间与系统性能有着直接的关系,如果线程持有锁的时间很长,那么相对地,锁的竞争程序也就越激烈。
示例代码:

public void syncMethod(){
fun1();
mutextMethod();
fun2();
}

在 syncMethod 方法中,假设只有 mutextMethod() 方法需要同步,而 fun1() 和 fun2() 不需要同步。如果 fun1() 和 fun2() 分别是重量级的方法,则会花费较大的 CPU 时间。此时如果在并发量较大,使用这种对整个方法做同步的方案,会导致等待线程大量增加。
一个较为优化的方案是,只在必要时进行同步,这样就能明显减少线程持有锁的时间,提高系统吞吐量。

public void syncMethod(){
fun1();
synchronized(this){
mutextMethod();
}
fun2();
)

在改进的代码中,只针对 mutextMethod 方法做同步,锁占用时间相对较短,因此能提高并行度。这种技术手段在 JDK 的源码总也可以嗯容易找到,如处理正则表达式的 Pattern 类:

public Matcher matcher(CharSequence input){
if(!compiled){
synchronized(this){
if(!compiled){
compile();
}
}
}
Matcher m = new Matcher(this,input);
return m;
}

matcher() 方法有添加的进行锁申请,只有在表达式未编译时,进行局部加锁。这种处理大大提高了 matcher() 方法的执行效率和可靠性。
注意:减小锁的持有时间有助于降低锁冲突的可能性,进而提升系统的并发能力。
1.2 减小锁力度
减小锁力度也是一种削弱多线程锁竞争的有效手段,这种技术的使用场景就是 ConcurrentHashMap 类的实现。
ConcurrentHashMap 不是直接锁住整个 HashMap,而是在 ConcurrentHashMap 内部进一步细分了若干个小的 HashMap,称之为段(Segment)。在 ConcurrentHashMap 中,无论是读操作还是写操作都能保证很高的性能:在进行读操作时 (几乎) 不需要加锁,而在写操作时通过锁分段技术只对所操作的段加锁而不影响客户端对其它段的访问。特别地,在理想状态下,ConcurrentHashMap 可以支持 16 个线程执行并发写操作(如果并发级别设为16),及任意数量线程的读操作。
注意:所谓减小锁粒度,就是缩小锁对象的范围,从而减少锁冲突的可能性,进而提高系统的并发能力。
1.3 读写分离锁类代替独占锁
读写锁 ReentrantReadWriteLock 可以提高系统性能,使用读写分离锁来替代独占锁是减小锁粒度的一种特殊情况。读操作本身不会影响数据的完整性和一致性,因此,大部分情况下,可以允许多线程同时读,读写锁正是实现了这种功能。
注意:在读多写少的场合,使用读写锁可以有效提升系统并发能力。
1.4 锁分离
将读写锁思想进一步延伸,就是锁分离。读写锁根据读写操作功能上的不同,进行了有效的锁分离。根据应用程序的功能特点,使用类似的分离思想,也可以对独占锁进行分离。
典型案例就是 LinkedBlockingQueue 的实现,take() 和 put() 分别实现了从队列中取得数据和往队列中增加数据的功能。虽两个数据都对当前队列进行了修改操作,但由于 LinkedBlockingQueue 是基于链表的,因此两个操作分别作用于队列的前端和尾端。
如果使用独占锁,则要求在两个操作进行时获取当前队列的独占锁,那么 take() 和 put() 操作不可能真正的并发,在运行时,它们会彼此等待对方释放资源。这种情况下,锁竞争会相对比较激烈,从而影响呈现的高并发时的性能。
在 JDK 实现中,采用的是分离锁思想,使用了两把不同的锁,分离 take() 和 put() 操作。

/** Lock held by take, poll, etc */
    private final ReentrantLock takeLock = new ReentrantLock();
 /** Wait queue for waiting takes */
private final Condition notEmpty = takeLock.newCondition();
     /** Lock held by put, offer, etc */
    private final ReentrantLock putLock = new ReentrantLock(); 
    /** Wait queue for waiting puts */
    private final Condition notFull = putLock.newCondition();

上面的代码定义了 takeLock 和 putLock 分别在 take() 和 put() 操作中使用,因此 take() 和 put() 相对独立,它们之间不存在锁竞争关系,只需要在 take() 和 take() 之间,put() 和 put() 之间分别对 takeLock 和 putLock 进行竞争。

public E take()throws InterruptedException{
   
E x
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值