zoukankan      html  css  js  c++  java
  • 悲观锁与乐观锁

    java中根据某一特性定义的锁进行分类,

     1、悲观锁与乐观锁

         分类定义:根据线程要不要锁住同步资源  

    针对于同一并发数据操作

      悲观锁:悲观锁认为自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会先加锁,确保数据不会被别的线程修改。Java中,synchronized关键字和Lock的实现类都是悲观锁。

      乐观锁:乐观锁认为自己在使用数据时不会有别的线程修改数据,所以不会添加锁,只是在更新数据的时候去判断之前有没有别的线程更新了这个数据。如果这个数据没有被更新,当前线程将自己修改的数据成功写入。如果数据已经被其他线程更新,则根据不同的实现方式执行不同的操作(例如报错或者自动重试)。

      乐观锁在Java中是通过使用无锁编程来实现,最常采用的是CAS算法,Java原子类中的递增操作就通过CAS自旋实现的。

      悲观锁:悲观锁适合写操作多的场景,先加锁报装数据的准确性

    乐观锁:乐观锁适合读操作多的场景,不加锁的特点能够使其读操作的性能大幅提升。

    例:

    针对乐观锁主要实现方式:CAS全称 Compare And Swap(比较与交换),是一种无锁算法。在不使用锁(没有线程被阻塞)的情况下实现多线程之间的变量同步。java.util.concurrent包中的原子类就是通过CAS来实现了乐观锁。

    CAS算法涉及到三个操作数:

    • 需要读写的内存值 V。

    • 进行比较的值 A。

    • 要写入的新值 B。

    过程: 当且仅当 V 的值等于 A 时,CAS通过原子方式用新值B来更新V的值(“比较+更新”整体是一个原子操作),否则不会执行任何操作。一般情况下,“更新”是一个不断重试的操作。

     

     根据OpenJDK 8的源码我们可以看出,getAndAddInt()循环获取给定对象o中的偏移量处的值v,然后判断内存值是否等于v。如果相等则将内存值设置为 v + delta,否则返回false,继续循环进行重试,直到设置成功才能退出循环,并且将旧值返回。整个“比较+更新”操作封装在compareAndSwapInt()中,在JNI里是借助于一个CPU指令完成的,属于原子操作,可以保证多个线程都能够看到同一个变量的修改值。

    CAS虽然很高效,但是它也存在三大问题,这里也简单说一下:

    1. ABA问题。CAS需要在操作值的时候检查内存值是否发生变化,没有发生变化才会更新内存值。但是如果内存值原来是A,后来变成了B,然后又变成了A,那么CAS进行检查时会发现值没有发生变化,但是实际上是有变化的。ABA问题的解决思路就是在变量前面添加版本号,每次变量更新的时候都把版本号加一,这样变化过程就从“A-B-A”变成了“1A-2B-3A”。

    JDK从1.5开始提供了AtomicStampedReference类来解决ABA问题,具体操作封装在compareAndSet()中。compareAndSet()首先检查当前引用和当前标志与预期引用和预期标志是否相等,如果都相等,则以原子方式将引用值和标志的值设置为给定的更新值。

    2. 循环时间长开销大。CAS操作如果长时间不成功,会导致其一直自旋,给CPU带来非常大的开销。

    3. 只能保证一个共享变量的原子操作。对一个共享变量执行操作时,CAS能够保证原子操作,但是对多个共享变量操作时,CAS是无法保证操作的原子性的。

    Java从1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,可以把多个变量放在一个对象里来进行CAS操作。

    学有所思,思有所成。
  • 相关阅读:
    [转]关于ORA00979 不是 GROUP BY 表达式错误的解释
    【转】mongodb简介及源码编译安装mongo2.0.0服务器
    mysql 缓存(转)
    iostat命令详解(转)
    C++虚函数,纯虚函数,抽象基类,虚基类(转)
    C++虚类的作用(转)
    C++ 纯虚类(转)
    虚函数的应用特性(转)
    2、Linux多线程,线程的分离与结合(转)
    mongodb js shell不能使用退格键的问题(转)
  • 原文地址:https://www.cnblogs.com/lqh969696/p/14662993.html
Copyright © 2011-2022 走看看