欢迎访问昆山宝鼎软件有限公司网站! 设为首页 | 网站地图 | XML | RSS订阅 | 宝鼎邮箱 | 后台管理


新闻资讯

MENU

软件开发知识

根据Jav 图纸加密 a内存模型

点击: 次  来源:宝鼎软件 时间:2018-01-18

原文出处: 占小狼

媒介

CAS(Compare and Swap),即较量并替换,实现并发算法时常用到的一种技能,Doug lea大神在java同步器中大量利用了CAS技能,巧夺天工的实现了多线程执行的安详性。

CAS的思想很简朴:三个参数,一个当前内存值V、旧的预期值A、即将更新的值B,当且仅当预期值A和内存值V沟通时,将内存值修改为B并返回true,不然什么都不做,并返回false。

问题

一个n++的问题。

public class Case {

    public volatile int n;

    public void add() {
        n++;
    }
}

通过javap -verbose Case看看add要领的字节码指令

public void add();
    flags: ACC_PUBLIC
    Code:
      stack=3, locals=1, args_size=1
         0: aload_0       
         1: dup           
         2: getfield      #2                  // Field n:I
         5: iconst_1      
         6: iadd          
         7: putfield      #2                  // Field n:I
        10: return

n++被拆分成了几个指令:

  1. 执行getfield拿到原始n;
  2. 执行iadd举办加1操纵;
  3. 执行putfield写把累加后的值写回n;

通过volatile修饰的变量可以担保线程之间的可见性,但并不能担保这3个指令的原子执行,在多线程并发执行下,无法做到线程安详,获得正确的功效,那么应该如何办理呢?

如何办理

在add要领加上synchronized修饰办理。

public class Case {

    public volatile int n;

    public synchronized void add() {
        n++;
    }
}

这个方案虽然可行,可是机能上差了点,尚有其它方案么?

再来看一段代码

public int a = 1;
public boolean compareAndSwapInt(int b) {
    if (a == 1) {
        a = b;
        return true;
    }
    return false;
}

假如这段代码在并发下执行,会产生什么?

假设线程1和线程2都过了a==1的检测,都筹备执行对a举办赋值,功效就是两个线程同时修改了变量a,显然这种功效是无法切合预期的,无法确定a的最终值。

办理要领也同样暴力,在compareAndSwapInt要领加锁同步,酿成一个原子操纵,同一时刻只有一个线程才气修改变量a。

除了低机能的加锁方案,我们还可以利用JDK自带的CAS方案,在CAS中,较量和替换是一组原子操纵,不会被外部打断,且在机能上更占有优势。

下面以AtomicInteger的实现为例,阐明一下CAS是如何实现的。

public class AtomicInteger extends Number implements java.io.Serializable {
    // 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;
    public final int get() {return value;}
}
  1. Unsafe,是CAS的焦点类,由于Java要领无法直接会见底层系统,需要通过当地(native)要领来会见,Unsafe相当于一个后门,基于该类可以直接操纵特定内存的数据。
  2. 变量valueOffset,暗示该变量值在内存中的偏移地点,因为Unsafe就是按照内存偏移地点获取数据的。
  3. 变量value用volatile修饰,担保了多线程之间的内存可见性。

看看AtomicInteger如何实现并发下的累加操纵:

public final int getAndAdd(int delta) {    
    return unsafe.getAndAddInt(this, valueOffset, delta);
}

//unsafe.getAndAddInt
public final int getAndAddInt(Object var1, long var2, int var4) {
    int var5;
    do {
        var5 = this.getIntVolatile(var1, var2);
    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
    return var5;
}

假设线程A和线程B同时执行getAndAdd操纵(别离跑在差异CPU上):

  1. AtomicInteger内里的value原始值为3,即主内存中AtomicInteger的value为3,昆山软件开发,按照Java内存模子,线程A和线程B各矜持有一份value的副本,值为3。
  2. 线程A通过getIntVolatile(var1, var2)拿到value值3,这时线程A被挂起。
  3. 线程B也通过getIntVolatile(var1, var2)要领获取到value值3,命运好,线程B没有被挂起,并执行compareAndSwapInt要领较量内存值也为3,乐成修改内存值为2。
  4. 这时线程A规复,执行compareAndSwapInt要领较量,发明本身手里的值(3)和内存的值(2)纷歧致,说明该值已经被其它线程提前修悔改了,那只能从头来一遍了。
  5. 从头获取value值,因为变量value被volatile修饰,昆山软件公司,所以其它线程对它的修改,线程A老是可以或许看到,线程A继承执行compareAndSwapInt举办较量替换,直到乐成。