原子变量和CAS算法

  先运行下面一段程序:

package concurrent;

class AtomicDemo implements Runnable {

    private int serialNumber = 0;

    public int getSerialNumber() {
        return serialNumber++;
    }

    @Override
    public void run() {

        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        System.out.println(getSerialNumber());
    }

}

public class TestAtomic {

    public static void main(String[] args) {
        AtomicDemo ad = new AtomicDemo();

        for(int i=0;i<10;i++)
            new Thread(ad).start();

    }

}

  运行程序,程序并不是总能分别输出0-9这10个数字:
  
  image_1b8hndnts1d6t14et14p61hjkr619.png-13.9kB
  
  image_1b8hneg4i16eb1nhr40ns86e81m.png-20.7kB
  
  这是因为,java中的自增操作并不是原子操作。i++ 的操作实际上分为三个步骤“读-改-写”,例如下面这道面试题:

   int i = 10;
  int a  = i++; //10

  实际上是分为下面的步骤(仅模拟):

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

  而在本例中,由于没有将自增后的值赋给其他变量,实际上是分为了下面两个步骤:

  int temp = serialNumber; // 第一步
  serialNumber = serialNumber + 1; //第二步

  所以出现上述情况的原因是,当某一个线程A执行完第一步但还没有执行第二步时,读取到某个值比如是5,这时候另一个线程B执行完了第一步和第一步,即读取到了值5,并且将其修改为6然后输出,然后线程A继续执行第二步,也将值5自增得到的6输出了,所以结果中就出现了两个“6”。
  
  我们尝试将serialNumber用volatile修饰,发现也不能解决问题,这是因为,volatile只能解决内存可见性的问题,并不能解决原子性的问题。要解决这个问题,JUC为我们提供了一些原子变量,放在java.util.concurrent.atomic包下:
  
  image_1b8hpfpg1197inv2b3tpgnd0v13.png-51kB
  
  例如,我们现在使用AtomicInteger代替int类型:

package concurrent;

import java.util.concurrent.atomic.AtomicInteger;

class AtomicDemo implements Runnable {

    private AtomicInteger serialNumber = new AtomicInteger(0);

    public int getSerialNumber() {
        return serialNumber.getAndIncrement();
    }

    @Override
    public void run() {

        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        System.out.println(getSerialNumber());
    }

}

public class TestAtomic {

    public static void main(String[] args) {
        AtomicDemo ad = new AtomicDemo();

        for(int i=0;i<10;i++)
            new Thread(ad).start();

    }

}

  运行程序,不会再输出重复的元素:
  
  image_1b8hphrtq1pst1gta59i1vag1521g.png-21.3kB
  
  那么,这些原子变量类型是如何实现同步效果的呢?
  首先,原子变量类型中的所有域都使用了volatile关键字修饰以保证内存可见性;然后,使用了CAS(Compare and Swap)算法保证数据变量的原子性。
  
CAS算法
  CAS算法是硬件对于并发操作的支持,其中包含了三个操作数:内存值,预估值和更新值。每当要执行更新操作时,会先在同步方法中比较内存值和预估值是否相等,如果相等才会用更新值替换内存值,否则什么也不做。
  下面我们可以用一段程序模拟一下CAS算法:

package concurrent;

/*
 * 模拟CAS算法
 */

class CAS {
    // 内存值
    private volatile int value;

    // 获取内存值
    public synchronized int getValue() {
        return 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);
    }
}

public class TestCAS {

    public static void main(String[] args) {

        CAS cas = new CAS();

        //创建10个线程来模拟多线程环境
        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() * 101));
                    System.out.println(b);
                }
            }).start();
        }
    }
}

  运行程序,发现输出结果既有成功,也有失败,这是符合逻辑的:
  
  image_1b8hs34hr6eo1c3o1s8p1pdohr41t.png-22.2kB

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值