JAVA中的乐观锁和悲观锁

  • 乐观锁:当处理数据的时候,乐观的认为处理数据过程不会发送多线程冲突,代表AtomicInteger 的CAS(Compare And Swape) ,就是处理数据的时候不会对本段处理过程进行加锁,当更新数据的时候再进行判定数据是否已经发送了冲突或许修改,如果已经冲突则返回失败信息交给用户处理。
  • 悲观锁:当处理数据的时候,会认为任何时候本处理都会受到多线程冲突影响,因此会加锁进行阻塞处理,例如JAVA中的同步锁 synchronized。
  • 悲观锁存在的问题:
    1、处理需要加锁和解锁,加锁解锁过程需要消耗资源。
    2、当对某一部分进行加锁之后,其他线程只能阻塞,直到本线程处理完毕之后才有机会去竞争锁。
    3、当低线程获得锁之后还在处理,优先级高的线程到达之后也需要阻塞等待,因此导致优先级高低线程倒挂处理问题。
  • 乐观锁可能存在的问题(CAS只是乐观锁的一种技术)。
    1、CAS有自旋的风险,因为如果不成功就回循环的来获取,直到获取成功为止;那么这里在数据量非常大的时候,就有问题了。可能因为数据量过大,导致循环时间过长,从而大量占用了CPU资源。
    2、存在读脏数据的风险。例如ABA的问题,当堆栈中存在ABCD数据,当线程T1 访问A对其进行处理;这个时候线程T2来了,目的是把B清除栈外。这个时候存在2个栈ACD 和 B。本来线程T1知道A的next是指向B的,但是,这个时候A其实指向的是null,当A回归的时候需要把B放入栈定,那么这个时候CD会直接丢失。
    3.只能保证一个共享变量的原子操作:当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁,或者有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操作。比如有两个共享变量i=2,j=a,合并一下ij=2a,然后用CAS来操作ij。从Java1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行CAS操作。

通过以上分析,得出了悲观锁和乐观锁使用场景如下:
    1、对于资源竞争较少(线程冲突较轻)的情况,使用synchronized同步锁进行线程阻塞和唤醒切换以及用户态内核态间的切换操作额外浪费消耗cpu资源;而CAS基于硬件实现,不需要进入内核,不需要切换线程,操作自旋几率较少,因此可以获得更高的性能。

    2、对于资源竞争严重(线程冲突严重)的情况,CAS自旋的概率会比较大,从而浪费更多的CPU资源,效率低于synchronized。

   补充: synchronized在jdk1.6之后,已经改进优化。synchronized的底层实现主要依靠Lock-Free的队列,基本思路是自旋后阻塞,竞争切换后继续竞争锁,稍微牺牲了公平性,但获得了高吞吐量。在线程冲突较少的情况下,可以获得和CAS类似的性能;而线程冲突严重的情况下,性能远高于CAS。

    原文作者:java锁
    原文地址: https://blog.csdn.net/wangqiubo2010/article/details/79668749
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞