基于Java的AtomicInteger类实现原子操作

原子的本意是:“不能被进一步分割的最小粒子”,而原子操作的意思是:“不可被中断的一个或者一系列操作”。
首先先说说处理通过怎样的方式实现了原子操作:

使用总线锁保证原子性
首先,我们应该明白什么是总线锁

总线锁:使用处理器提供的一种LOCK #信号,当一个处理器在总线处理一个变量时,其他处理器的请求被阻塞,正在处理当前变量的处理器独占共享内存。

1、方法提出背景
当多个处理器对同一个共享变量进行读改写操作时,共享变量会被多个处理器同时操作,不能够保证操作的原子性。举个例子,有一个共享变量 i 现在要进行i++的操作,凉饿处理同时操作,我们的期望值应该是3,但是有可能记过是2。因为处理器在进行数据的操作时,都是在各自的缓存中获取到这个 i ,分别进行 + 1 操作,然后写入系统内存中。如果要保证操作的原子性,就必须保证在CPU1粗粒该变量时,CPU2就不能操作缓存了该变量的共享内存的缓存。这就引入了总线锁的概念。

使用缓存锁保证原子性操作

缓存锁定:内存区域如果被缓存到处理器的缓存行中,并且在Lock操作期间被锁定,当它执行锁操作写回内存时,处理器不会在总线上进行声言LOCK # 信号,而是修改内部的内存地址,通过它的缓存一致性原则来保证操作的原子性,当其他处理器写回已被锁定的缓存行数据时,会被缓存行失效。

JAVA实现原子操作

在JAVA中,一般通过锁和循环CAS来实现原子操作。
1)使用循环CAS实现原子操作(在JVM中该操作是利用了处理器提供的CMPXCHG指令来完成的)

public class Counter {
    private int i = 0;
    private AtomicInteger atomicInteger = new AtomicInteger(0);

    public static void main(String[] args) {

        final Counter counter = new Counter();
        //定义一个装有Thread的List集合
        List<Thread> threadList = new ArrayList<>(600);
        Long start = System.currentTimeMillis();
        //创建100个线程进行同步操作
        for (int j = 0; j < 100; j++) {
            Thread t = new Thread(new Runnable() {
                @Override
                public void run() {
                    //每个线程中实现1000次的计数
                    for (int i = 0; i < 1000; i++) {
                        counter.count();
                        counter.safeCount();
                    }
                }
            });
            //将当前线程加入到List集合中
            threadList.add(t);
        }
        //让所有的线程运行
        for (Thread t : threadList) {
            t.start();
        }
        //等待所有线程执行完成
        for (Thread t : threadList) {
            try {
                t.join();//将线程挂起
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //这里分别输出用两种方法进行计数的最终值
        System.out.println(counter.i);
        System.out.println(counter.atomicInteger.get());
        //一个线程计数1000,100个就是100000
        System.out.println("The Time is => " + (System.currentTimeMillis() - start));
    }


    /**
     * 使用CAS实现线程安全的计数
     */
    private void safeCount() {
        for (; ; ) {
            int i = atomicInteger.get();//获取到当前的i
           /* public final boolean compareAndSet ( int expectedValue, int newValue){
                return U.compareAndSetInt(this, VALUE, expectedValue, newValue);
            }*/
            //调用上面的方法进行计数增加
            boolean suc = atomicInteger.compareAndSet(i, ++i);
            if (suc) {
                break;
            }
        }
    }

    /**
     * 非线程安全的计数器
     */
    private void count() {
        i++;
    }
}
这样虽然保证了线程安全,但是忽略了一个问题——ABA问题

当一个处理器在处理一个变量 A 时,先将 A 变量改为 B,之后又改为 A,那么使用CAS检查值时,就会发现其实没有发生变化,但是,确实有操作进行了变量更改。解决这个问题的方法,就是可以加版本号,每更改一次就使其版本号加一,这样,检查时发现版本号不一致,就停止操作。正如上面所说的那个方法:compareAndSet首先检查当前引用是否等于与其引用,并检查当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和标志位的值进行替换。

还有CAS循环时间开销大、只能保证一个共享变量的原子操作等问题。

2)使用锁机制实现原子操作
该方法保证了只有获得锁的线程才能够操作锁定的内存区域。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值