JUC之原子变量与CAS算法

Talk is cheap, show u the code:

public class TestAtomicDemo {

    public static void main(String[] args) {
        AtomicDemo ad = new AtomicDemo();
        for (int i = 0; i < 10; i++) {
            new Thread(ad).start();
        }
    }
}

class AtomicDemo implements Runnable {

    private int seriaNumber = 0;

    @Override
    public void run() {

        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(Thread.currentThread().getName() + ":" + getSeriaNumber());
    }

    public int getSeriaNumber() {
        return seriaNumber++;
    }
}

输出结果是什么?为什么?

输入图片说明

注意: 不一定产生上述相同结果,也不一定产生线程安全问题,线程安全问题是随机发生的,可能需要多次运行

此时已经产生线程安全问题,十个线程未能均匀的将serialNumber加到9

可见性问题?那加上volatile试试

public class TestAtomicDemo {

    public static void main(String[] args) {
        AtomicDemo ad = new AtomicDemo();
        for (int i = 0; i < 10; i++) {
            new Thread(ad).start();
        }
    }
}

class AtomicDemo implements Runnable {

    private volatile int seriaNumber = 0;

    @Override
    public void run() {

        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(Thread.currentThread().getName() + ":" + getSeriaNumber());
    }

    public int getSeriaNumber() {
        return seriaNumber++;
    }
}

输入图片说明

注意: 不一定产生上述相同结果,也不一定产生线程安全问题,线程安全问题是随机发生的,可能需要多次运行

问题原因分析:

i++的原子性问题

比如有:

int i = 10;
i = i++; 
System.out.print("i = " + i); // i = 10

i = i++操作实际上在内存中分为三个步骤

int temp = i;
i = i + 1;
i = temp;

当线程1执行 i = i + 1时,将i的值更新为0,可能此时线程2 已经读了temp的值为 0,可能在执行i = i + 1 后,又将 i 的值更新为 0

解决办法:

利用Atomic变量

public class TestAtomicDemo {

    public static void main(String[] args) {
        AtomicDemo ad = new AtomicDemo();
        for (int i = 0; i < 10; i++) {
            new Thread(ad).start();
        }
    }
}

class AtomicDemo implements Runnable {

//    private volatile int seriaNumber = 0;
    private AtomicInteger seriaNumber = new AtomicInteger();

    @Override
    public void run() {

        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(Thread.currentThread().getName() + ":" + getSeriaNumber());
    }

    public int getSeriaNumber() {
//        return seriaNumber++;
        return seriaNumber.getAndIncrement();
    }
} 

多次运行发现结果不会再出现线程安全问题

原子变量:JDK1.5提供的

  1. 利用volatile保证内存的可见性
  2. CAS(Compare And Swap)算法保证变量的原子性

CAS算法是硬件对于并发操作的共享变量的支持,含有三个操作数:

  • 内存值 v
  • 预估值 A
  • 更新值 B

当且仅当 V == A 时,将V的值更新为B,否则不做任何操作

JUC下的atomic类都是通过CAS来实现的,下面就以AtomicInteger为例来阐述CAS的实现。如下:

```
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;

Unsafe是CAS的核心类,Java无法直接访问底层操作系统,而是通过本地(native)方法来访问。不过尽管如此,JVM还是开了一个后门:Unsafe,它提供了硬件级别的原子操作。

valueOffset为变量值在内存中的偏移地址,unsafe就是通过偏移地址来得到数据的原值的。

value当前值,使用volatile修饰,保证多线程环境下看见的是同一个。

我们就以AtomicInteger的addAndGet()方法来做说明,先看源代码:


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

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;
}


内部调用unsafe的getAndAddInt方法,在getAndAddInt方法中主要是看compareAndSwapInt方法:



public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);



吐血,写不下去了,写这篇学习笔记的时候还是有很多疑惑,先结掉,日后再修改。天呐,路漫漫其修远兮。。

猜你喜欢

转载自my.oschina.net/hensemlee/blog/1807672