JUC学习之原子变量与CAS算法

本文为看视频学习记录,若有错误,请指正,谢谢!

首先介绍一下i++操作的原子性

i++的操作实际上分成3个步骤“读-改-写”

例如:

Int i = 10;

I = i++;

实际上

Int temp = i;

I = i + 1;

I = temp;

看一段代码,表明在开发过程中原子性问题的表现:

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 serialNumber = 0;


    @Override
    public void run() {

        try {
            Thread.sleep(5000);
        }catch (InterruptedException r){

        }

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

    }

    public int getSerialNumber() {
        return serialNumber++;
    }
}
运行结果:

JUC学习之原子变量与CAS算法_第1张图片

进运行一次就发生了问题,看上图红框的内容,两个线程同时输出了8,也就是说两个线程对同一个共享变量进行操作,出现了相同的结果,这就说明上面的代码并没有保证原子性。


JUC学习之原子变量与CAS算法_第2张图片

造成这样的结果的原因是:当线程运行时,会分配独立地缓存,主存中有一个共享数据,线程们若要对数据进行操作,需要线程从主存中读取共享变量的值,复制到独立的缓存中来,然后进行“++”的操作,然后再写回到主存中去。但是此时可能有线程2也进行了与线程1同样的操作,那么就导致了问题的出现。至于volatile能够解决内存可见性的问题,但是针对原子性问题,volatile并不能起作用,不能够保证是否有先后的问题。假设我们能够用volatile来修饰变量,也可以看做两个线程的事情都是在主存中进行操作,但是当线程1对变量操作后写回主存之前,线程2也进行读—改—写的操作,依然存在原子性的问题。最主要的原因是因为i++这个操作是读-改-写的操作,所以volatile只能保证内存可见性的问题,但是不能解决原子性的问题。

于是就有了原子变量这个概念,在jdk1.5之后,java.Util.Concurrent.Atomic包下提供了常用的原子变量

原子变量的特性:
1.是volatile修饰的,保证内存可见性
2.CAS(compare and swap)算法保证数据的原子性:当多个线程对主存中的数据进行修改的时候,有且只有一个线程会成功

CAS算法是硬件对于并发操作共享数据的支持。
Cas包含了三个操作数:

内存值V,预估值A,更新值B。当且仅当  V == A时,V = B时,否则将不做任何操作。

可以将CAS看成是两个操作,第一个读取V的值,第二个,对比A和B的值是否一致,然后复制,上述的每一步都是同步(一次只允许一个线程访问)的。

JUC学习之原子变量与CAS算法_第3张图片

上述图可以看出,如果线程2在线程1将数据写回主存之前读取了serialNumber的值,然后进行操作,就会导致预估值和内存值不相等,而不做任何的操作。


而且,CAS算法的效率要比加锁要高,当线程当前执行的任务不能够成功时,线程不会像加锁时一样,被阻塞,放弃当前CPU的使用时间,而是不断的再次尝试,知道成功为止。

修改后的代码:

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 AtomicInteger serialNumber = new AtomicInteger();


    @Override
    public void run() {

        try {
            Thread.sleep(5000);
        }catch (InterruptedException r){

        }

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

    }

    public int getSerialNumber() {
        return serialNumber.getAndIncrement();//i++
        //return serialNumber.incrementAndGet();//++i
    }
}
运行结果:
JUC学习之原子变量与CAS算法_第4张图片

成功!


模拟CAS算法

public class TestCompareAndSwap {

    public static void main(String[] args){
        final CompareAndSwap cas = new CompareAndSwap();
        for(int i=0;i<10;i++){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    int expectedValue = cas.getValue();
                    //自己进行比较和替换之前再进行一次获取
                    boolean b = cas.compareAndSet(expectedValue,(int)(Math.random() * 100));
                    System.out.println(b);
                }
            }).start();
        }
    }
}


class CompareAndSwap{
    private int value;

    //获取内存值
    public synchronized int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }

    //比较,每次比较和替换的时候获取一次内存值,
    public synchronized int compareAndSwap(int expectedValue,int newValue){

        int oldValue = value;
        if(oldValue == expectedValue){
            this.value = newValue;
        }
        return oldValue;
    }

    //设置
    public synchronized boolean compareAndSet(int expectedValue,int newValue){
        return expectedValue == compareAndSwap(expectedValue,newValue);
    }
} 

运行结果:

JUC学习之原子变量与CAS算法_第5张图片

主要操作,在每次操作进行之前,需要从内存中读取共享变量的值。在真正进行比较和替换之前,再一次读取主存中的值,然后进行比较,若两者的值是相等的,那就进行修改操作,否则,什么都不做!

你可能感兴趣的:(JUC)