java-高并发-锁专题

锁专题

高并发下,激烈的锁竞争会导致程序性能的下降。并行计算之所以能提高系统的性能,并不能因为它“少干活”了,而是因为并行计算可以更合理地进行任务调度,充分利用各个CPU资源。合理的并发才能将多核CPU的性能发挥到极致。

有助于提高锁性能的几点建议:
  1. 减少锁持有时间

    如同100个人只用一支笔🖊填写自己的身份信息,如果某个人拿笔的时间过长,那么其他人就会被长时间阻塞住而无法填写信息,那么最好让每个人尽量减少持笔时间,想好了再拿笔。

    减少锁的持有时间有助于降低锁冲突的可能性,进而提升系统的并发能力

  2. 减小锁粒度

    减小锁粒度:就是指缩小对象的范围,从而降低锁冲突的可能性,进而提高系统的并发能力。

    例子

    高性能ConcurrentHashMap的实现原理中就通过减小锁粒度来提升性能。对于ConcurrentHashMap类,它的内部进一步细分为若干个小的HashMap,称之为段(SEGMENT),在默认情况下,一个ConcurrentHashMap类被分为16个段。这样在增加表项的时候就可以通过对所在段加锁,而不需要对整个HashMap加锁。

    缺点:当系统需要取得全局锁时,其就需要同时取得所有段的锁,消耗的资源比较多。当类似于size()的全局信息方法调用频繁时,就不能提高系统的吞吐量。

  3. 用读写分离锁来替换独占锁

    读操作不会影响系统的完整性和一致性,在读多写少的场合使用读写锁可以有效的提高系统的并发能力。

  4. 锁分离

    对独占锁进行分离,例如java.util.concurrent.LinkedBlockingQueue的实现,其中两个方法take()和put()(分别实现了前端和尾端操作)。如果使用独占锁,那么两者在运行时,锁竞争比较激烈。采用锁分离,定义了takeLock()和putLock()锁,这样锁的竞争只会在take()和take()方法间和put()和put()方法之间,削弱了锁竞争的可能性。

  5. 锁粗化

    如果同一个锁不停地进行请求、同步和释放,其本身也会消耗系统宝贵的资源,反而不利于性能的优化。所以虚拟机在遇到一连串连续地对同一个锁不断进行请求和释放的操作时,便会把所有的锁操作整合成对锁的一次请求,从而减少对锁的请求同步次数,这个操作就是锁粗化。

性能优化进行对真实情况各个资源点进行权衡折中的过程。

Java虚拟机对锁优化所做的努力
  1. 锁偏向

    锁偏向是一种针对加锁操作的优化手段。思想:如果一个线程获得了锁,那么锁就会进入偏向状态,等下一次同一个线程再次申请锁的时候就无须再做同步操作,节省了大量有关锁申请的操作,从而提高了系统性能。

  2. 轻量级锁

    如果偏向锁失败,虚拟机会使用轻量级锁的优化手段:**将对象头部作为指针指向持有锁的线程堆栈的内部,来判断一个线程是否持有对象锁。**如果线程获得轻量级锁成功,会顺利进行临界区。如果轻量级锁加锁失败,则表示其他线程抢先争夺到了锁,当前线程的锁请求就会膨胀为重量级锁。

  3. 自旋锁

    锁膨胀后,为了避免线程真实地在操作系统层面挂起,虚拟机会进行自旋锁的操作。系统假设不久后的将来,线程可以得到锁,因此,虚拟机会让当前线程做几个空循环(和名字对应),多次循环后,如果可以得到锁,就进入临界区,如果不能,将线程在操作系统层面挂起。

  4. 锁消除

    通过对运行上下文的扫描,去除不可能存在共享资源竞争的锁(而这些锁有可能是程序员无意间应用进来的),通过锁消除,可以节省毫无意义的请求锁时间。

    注意:局部变量在虚拟机栈中。

    逃逸分析:观察某一个变量是否会逃逸出某一个作用域,如果逃逸出作用域,虚拟机就不能消除变量中的锁操作。

无锁

并发控制下,锁是一种悲观的状态,无锁则是一种乐观的策略。

CAS(Compare And Swap,比较和交换策略)

优点:

  • 由于其阻塞性,不会发生死锁,并且线程间的相互影响也远远小于比基于锁的方式更小。
  • 没有锁竞争而带来的系统开销
  • 没有线程间频繁调度带来的开销。

算法原理:

  • 包含有三个参数CAS(V,E,N),V-表示要更新的变量,E-表示预期值,N-表示新值。仅当V值等于E值的时候。N才被更新为V,否则不更新。当多个线程使用CAS对一个变量进行操作的时候,只有一个修改成功,其余修改失败,之后修改失败的可以不断请求。

简单的说:CAS需要你额外给出一个期望值,也就是你认为这个变量应该是什么样子。如果不是你想的那样,则说明已经被别人修改了。重新读取,再次尝试修改就好了。

java中的指针:Unsafe类

我们无法直接使用,是一个jdk内部使用的专属类。

其他的无锁:无锁的线程安全整数:AtomicInteger、无锁的对象引用:AtomicReference、带有时间戳的对象引用:AtomicStampReference、AtomicIntegerArray、AtomicIntegerFieldUpdater、无锁的Vector实现、SynchronousQueue的实现

参考自:Java高并发程序设计-葛一鸣

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值