zoukankan      html  css  js  c++  java
  • java-CAS

    锁是用来做并发的最简单的方式,其代价也是最高的,java 在JDK1.5之前都是通过synchronized关键字来保证同步的,他是一种独占锁,使用synchronized同步锁进行线程阻塞和唤醒切换以及用户态内核态间的切换操作额外浪费消耗cpu资源,锁还存在着其它一些缺点,当一个线程正在等待锁时,它不能做任何事。如果一个线程在持有锁的情况下被延迟执行,那么所有需要这个锁的线程都无法执行下去。

    CAS

    无锁的非堵塞算法采用一种比较交换技术CAS(compare and swap)来鉴别线程冲突,一旦检测到冲突,就充实当前操作指导没有冲突为止。CAS基于硬件实现,不需要进入内核,不需要切换线程,因此可以获得更高的性能。但对于资源竞争严重的情况,CAS自旋的概率会比较大,从而浪费更多的CPU资源。

    CAS是项乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。

    CAS(比较并交换)是CPU指令级的操作,只有一步原子操作,CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。CAS语义是“我认为V的值应该是A,如果是,那么就将V的值更新成B, 否则不更新,并告诉V的实际是是多少”。

    伪代码可以这样表示:

    do{   
           备份旧数据;  
           基于旧数据构造新数据;  
    }while(!CAS( 内存地址,备份的旧数据,新数据 ))  
    

    就是指当两者进行比较时,如果相等,则证明共享数据没有被修改,替换成新值,然后继续往下运行;如果不相等,说明共享数据已经被修改,放弃已经所做的操作,然后重新执行刚才的操作。容易看出 CAS 操作是基于共享数据不会被修改的假设,采用了类似于数据库的 commit-retry 的模式。当同步冲突出现的机会很少时,这种假设能带来较大的性能提升。

    CAS缺点

    虽然CAS有效的解决了原子操作的问题,但是其仍然有三个劣势:
    1、ABA问题:因为CAS需要在操作前检查下值有没有发生变化,如果没有则更新。但是如果一个值开始的时候是A,变成了B,又变成了A,那么使用CAS进行检查的时候会发现它的值没有发生变化,但是事实却不是如此。

    ABA问题的解决思路是使用版本号,如A-B-A变成1A-2B-3A

    2、循环时间长开销大:自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。

    3、只能保证一个共享变量的原子操作:对一个共享变量可以使用CAS进行原子操作,但是多个共享变量的原子操作就无法使用CAS,这个时候只能使用锁。 

    Java中的原子操作( atomic operations)

    原子操作指的是在一步之内就完成而且不能被中断。原子操作在多线程环境中是线程安全的,无需考虑同步的问题。在java中,下列操作是原子操作:

    all assignments of primitive types except for long and double(基本数据类型除了long 和double)
    all assignments of references(引用类型)
    all operations of java.concurrent.Atomic* classes(原子类)
    all assignments to volatile longs and doubles(volatile 修饰的long 和double)
    

    为什么long型赋值不是原子操作呢?例如:

    long foo = 65465498L;

    实时上java会分两步写入这个long变量,先写32位,再写后32位。这样就线程不安全了。如果改成下面的就线程安全了:

    private volatile long foo;
    因为volatile内部已经做了synchronized.

    JVM 对CAS支持

    在 JDK5.0 之前,如果不使用本机代码,就不能用 Java 语言编写无等待、无锁定的算法。在JDK1.5中引入了底层的支持,在int、long和对象的引用等类型上都公开了CAS的操作,并且JVM把它们编译为底层硬件提供的最有效的方法,在运行CAS的平台上,运行时把它们编译为相应的机器指令。

    //JDK 8 - AtomicInteger
    //fetch and add
    public final int getAndIncrement() {
        return unsafe.getAndAddInt(this, valueOffset, 1);
    }
    
    //JDK7 - AtomicInteger
     public final int getAndIncrement() {
        for (;;) {
            int current = get();
            int next = current + 1;
            if (compareAndSet(current, next))
                return current;
        }
    }
    
  • 相关阅读:
    SQL分组统计
    实用DOS命令
    Shadertoy 教程 Part 6 使用光线步进算法创建3D场景
    浅谈web前端优化
    如何搭建一套前端监控系统
    with(this)中with的用法及其优缺点
    vue mvvm
    散列表(哈希表)(二)散列函数的构造方法
    作为程序员,你最常上的网站是什么
    散列表(哈希表)(一)散列表的概念
  • 原文地址:https://www.cnblogs.com/xckxue/p/8682059.html
Copyright © 2011-2022 走看看