资讯专栏INFORMATION COLUMN

悲观锁和乐观锁以及CAS机制

levius / 1191人阅读

摘要:加锁才能保证线程安全使用之后,不加锁,也是线程安全的。确保不出现线程安全问题。一般在数据库中使用乐观锁都会拿版本号作为对比值,因为版本号会一直增加,没有重复的,所以不会出现这个问题。

悲观锁:

认为每次获取数据的时候数据一定会被人修改,所以它在获取数据的时候会把操作的数据给锁住,这样一来就只有它自己能够操作,其他人都堵塞在那里。

乐观锁:

认为每次获取数据的时候数据不会被别人修改,所以获取数据的时候并没有锁住整个数据,但是在更新的时候它会去判断一下要更新的数据有没有被别人修改过,例如更新前查询该数据的版本号,更新的时候看看该版本号有没有被人修改过,如果被人修改过了,那就不会去更新。

应用场景:

悲观锁:
因为悲观锁会锁住数据,让其他人都等待,所以当一个系统并发量不大,而且可以接收一定延迟的时候可以选择悲观锁。
乐观锁:
因为乐观锁会在更新前去查数据,所以比较适合读多少写的场景,因为写操作多的话会造成大量的查询操作,给系统带来压力。例如SVN、Git等版本控制管理器就是应用的乐观锁,当你提交数据的时候对比下版本号,如果远程仓库的版本号和本地的不一样就表示有人已经提交过代码了,你需要先更新代码到本地处理一下版本冲突问题,不然是没有办法提交的。

CAS:

CAS是Compare And Set的缩写,中文意思就是比较和操作,是一个非阻塞算法。它其实是一个CPU的指令,它会拿内存值和一个给定的值进行比较,如果相等的话就会把内存值更新为另一个给定的值。其实CAS就是使用一个乐观锁的机制。

Java中CAS机制的应用:

从JDK1.5开始java.util.concurrent.atomic包中新增了一些原子类,AtomicInteger、AtomicLong等等,就是专门解决高并发下的同步问题。因为类似i++、++i的操作不是线程安全的,以前我们都会使用Synchronized关键字,但是现在我们直接使用这些原子类就可以解决线程安全的问题。下面用代码来看看有什么变化。

class Test1 {
    private volatile int count = 0;

    public synchronized void increment() {
    //加锁才能保证线程安全
        count++; 
    }

    public int getCount() {
        return count;
    }
}

class Test2 {
    private AtomicInteger count = new AtomicInteger();

    //使用AtomicInteger之后,不加锁,也是线程安全的。
    public void increment() {
        count.incrementAndGet();
    }
   
    public int getCount() {
        return count.get();
    }
}

下面这些是AtomicInteger提供的别的方法。

//获取当前的值
public final int get() 
//获取当前的值,并设置新的值
public final int getAndSet(int newValue)
//获取当前的值,并自增
public final int getAndIncrement()
//获取当前的值,并自减
public final int getAndDecrement() 
//获取当前的值,并加上预期的值
public final int getAndAdd(int delta) 

我们从源码的角度看看AtomicInteger是怎么实现CAS机制的。unsafe是java提供的用来获取对象内存地址的类,作用是在更新操作时提供“比较并替换”的作用。valueOffset是记录value本身在内存的地址,value被声明为volatile是保证在更新操作时,当前线程可以拿到value最新的值。

public class AtomicInteger extends Number implements java.io.Serializable {
    private static final long serialVersionUID = 6214790243416807050L;

    // setup to use Unsafe.compareAndSwapInt for updates
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;

    static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

    private volatile int value;

比如incrementAndGet方法,是获取当前的值并自增。

public final int incrementAndGet() {
    return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}

我们进getAndAddInt方法看看,getIntVolatile和compareAndSwapInt都是本地方法,就是通过本地方法来实现CAS机制。确保不出现线程安全问题。

public final int getAndSetInt(Object var1, long var2, int var4) {
    int var5;
    do {
        var5 = this.getIntVolatile(var1, var2);
    } while(!this.compareAndSwapInt(var1, var2, var5, var4));
        return var5;
    }
    
    
 public native int getIntVolatile(Object var1, long var2);
 public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

CAS可能会引起的问题

因为CAS并不会锁住数据让其他线程阻塞,所以实际上是自旋锁的原理。自旋锁就是当线程获取锁的时候发现这个锁已经被别的线程抢了,它不是阻塞自己,而是一直循环查看这个锁有没有被释放,这就叫自旋锁。因为一直循环查看所以可以能会造成CPU负担过重,最好设置参数限制查看锁的次数。

死锁问题,有一个线程拿到自旋锁之后,又去拿锁,例如递归的时候会出现这样的情况,自己等待自己释放缩,卡在那里不动。

ABA问题,这个问题就是说当线程1读到内存值为A,然后线程2进来了把内存值改为B,然后又改为了A,这个时候线程1觉得没有问题,就更新了。一般在数据库中使用乐观锁都会拿版本号作为对比值,因为版本号会一直增加,没有重复的,所以不会出现这个问题。Java中也提供了AtomicStampedReference这个类,大致原理也是提供一个版本号来对比。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/69110.html

相关文章

  • 不可不说的Java“”事

    摘要:本文旨在对锁相关源码本文中的源码来自使用场景进行举例,为读者介绍主流锁的知识点,以及不同的锁的适用场景。中,关键字和的实现类都是悲观锁。自适应意味着自旋的时间次数不再固定,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。 前言 Java提供了种类丰富的锁,每种锁因其特性的不同,在适当的场景下能够展现出非常高的效率。本文旨在对锁相关源码(本文中的源码来自JDK 8)、使用场景...

    galaxy_robot 评论0 收藏0
  • CAS 算法 —— Compare and Swap

    摘要:算法算法会先对一个内存变量位置和一个给定的值进行比较,如果相等,则用一个新值去修改这个内存变量位置。因为是非公平锁,所以一上来就尝试抢占锁给定旧值并希望用新值去更新内存变量。 本文翻译和原创各占一半,所以还是厚颜无耻归类到原创好了...https://howtodoinjava.com/jav...java 5 其中一个令人振奋的改进是新增了支持原子操作的类型,例如 AtomicInt...

    mmy123456 评论0 收藏0
  • Java中的以及sychronized实现机制

    摘要:有可能,会造成优先级反转或者饥饿现象。悲观锁在中的使用,就是利用各种锁。对于而言,其是独享锁。偏向锁,顾名思义,它会偏向于第一个访问锁的线程,大多数情况下锁不仅不存在多线程竞争,而且总是由同一线程多次获得。 理解锁的基础知识 如果想要透彻的理解java锁的来龙去脉,需要先了解以下基础知识。 基础知识之一:锁的类型 按照其性质分类 公平锁/非公平锁 公平锁是指多个线程按照申请锁的顺序来获...

    linkin 评论0 收藏0
  • Spring Boot+SQL/JPA实战悲观乐观

    摘要:所以悲观锁是限制其他线程,而乐观锁是限制自己,虽然他的名字有锁,但是实际上不算上锁,只是在最后操作的时候再判断具体怎么操作。悲观锁和乐观锁比较悲观锁适合写多读少的场景。 最近在公司的业务上遇到了并发的问题,并且还是很常见的并发问题,算是低级的失误了。由于公司业务相对比较复杂且不适合公开,在此用一个很常见的业务来还原一下场景,同时介绍悲观锁和乐观锁是如何解决这类并发问题的。 公司业务就是...

    Keven 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<