Java并发(二)-原子性和Atomic类

来源:互联网 发布:盼盼交友软件 编辑:程序博客网 时间:2024/06/08 15:32

上一个博文中说了多线程运行中的内存可见性问题,这次说一下原子性问题,可以这样原子是组成物质的基本粒子(在这里忽略质子夸克等微观粒子),可以看成一个操作是不可分割的。

典型的例子:

int i = 0;i = i++;System.out.println(i);//i=10

这个例子里面i的值为什么不为11呢?就是因为在jvm虚拟机执行这段代码的时候吧i++分成了三部分,读-改-写,导致代码编写的直观感觉和真是发生的不一样。这个例子的具体讲解参考下面的博客:
i=i++问题的讲解博客参考

举个例子:

创建一个类实现Runnable接口,并在run方法中读取自身的变量值

class AtomicDemo implements Runnable{    private volatile int value = 0;    @Override    public void run() {        try {            Thread.sleep(200);        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println("value:"+getValue());    }    protected int getValue(){        return value++;    }}

在测试类的main函数中创建10个线程,每个线程都调用run方法来打印value的值

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

输出结果:

value:1value:5value:4value:3value:2value:0value:6value:1value:0value:7

我们发现0和1出现了两次,这是为什么呢?明明加上了volatile关键字了?

原因分析

根据之前了解的,在jvm调用的时候i=i++其实是分为了三部的,假若i=0
1. 先把i的值压倒栈顶此时是0
2. 再对i执行++操作,此时i的值是1,但是该步骤不影响栈顶的值
3. 然后把栈顶的值给i,此时栈顶的值是0,所以i就是0
4. 也就是说,i++是在赋值给j之前就完成的,但是j得到的值确实jvm虚拟机相当于中间变量一样的值。
看到这里,我们大概了解的原因了,(即便还不清楚,只要知道i++不是一部完成的就好了)正是因为i++被不是一步完成的,即便是被volatile修饰也会存在问题,加入A线程对i++,加完之后还没赋值,B线程得到了cpu的执行权,获得i为改变之前的值也进行++操作,B线程执行完了,A开始把值刷新并打印,此时发信两个的值是一样的。

解决方法CAS算法

什么是CAS算法:
CompareAndSwap(比较并且交换)(计算机硬件底层对 CAS 进行的支持 JVM 支持该算法)
在java.util.concurrent.atomic 包下提供了一些原子变量
1. volatile :保证了内存可见性
2. CAS算法保证了原子性

CompareAndSwap(比较然后交换)
CAS 算法是硬件对于并发操作的支持
CAS 包含了三个操作数:
内存值:V
预估值:A (栈顶的旧值)
更新值:B
当且仅当 V == A 的时候,V = B;否则不作任何操作;

====== 理解 CAS 算法:
当线程A,B两个对主内存的i(i=0)进行操作的时候,A,B同时获取了i的值V,放在栈顶,此时栈顶
的值为0,然后在自己的线程缓存中有一个拷贝B,对B进行加1操作,B的值为1,然后准备向内存中写
值的时候先判断V和栈顶的值是否相同,如果V的值没有改变则就把B的值赋给V,这个时候线程B也准备
向V中赋值,此时C判断自己栈顶的值和V的值,发现不一样,就不会给V进行赋值的,这样就保证了公共
变量的原子性,不会被重复赋值。

修改AtomicDemo类:

class AtomicDemo implements Runnable{    private AtomicInteger ai = new AtomicInteger();    @Override    public void run() {        try {            Thread.sleep(200);        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println("value:"+getValue());    }    protected int getValue(){        return ai.getAndIncrement();//先得到在加加,相当于++操作    }}

主函数不用做修改,执行结果为:

value:0value:4value:5value:6value:3value:7value:1value:2value:8value:9

执行多次之后发现之前的的重复的情况不见了,可见CAS算法保证了原子性

模拟CAS算法

这里的模拟仅仅是对原理的理解

/* * 模拟 CAS 算法 * */class MyCas{    // 定义一个被 volatile 修饰的变量 value    private volatile Integer value;    //synchronized修饰return value方法 ,保证线程安全    public synchronized Integer getValue(){        return this.value;    }    // 比较 预估值(旧值)类似i++返回的是旧值    public synchronized Integer compareAndSwap(Integer expectedValue,Integer newValue){        // 先把得到旧值        Integer oldValue = this.value;        // 判断旧值和预估值(栈顶的值)是否一样        if(oldValue == expectedValue){            //满足条件就把新值赋给value            this.value = newValue;        }        return oldValue;    }    //设置新值返回boolean类型表示是否设置成功    public synchronized boolean compareAndSet(Integer expectedValue,Integer newValue){        return expectedValue == compareAndSwap(expectedValue, newValue);    }}

测试方法:

final MyCas mc = new MyCas();        for(int i=0;i<10;i++){            new Thread(new Runnable() {                @Override                public void run() {                    try {                        Thread.sleep(200);                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                    boolean b = mc.compareAndSet(mc.getValue(),(int)(Math.random()*100));                    System.out.println(b);                }            }).start();        }

输出:

falsefalsefalsefalsefalsefalsetruefalsefalsefalse

创建10个线程随机修改cas对象的value值,观察输出结果,当值值合同的时候不会修改原来的值,保证了原子性

0 0
原创粉丝点击