• java并发编程学习: 原子变量(CAS)


    先上一段代码:

    package test;
    
    public class Program {
    
        public static int i = 0;
    
        private static class Next extends Thread {
    
            public void run() {
                i = i + 1;
                System.out.println(i);
            }
        }
    
        public static void main(String[] args) {
            Thread[] threads = new Thread[10];
            for (int i = 0; i < threads.length; i++) {
                threads[i] = new Thread(new Next());
                threads[i].start();
            }
        }
    }
    

    代码很简单,10个线程,1个共享变量,每个线程在run的时候,将变量+1,反复运行多次,可能会输出类似下面的结果:

    1
    4
    3
    6
    2
    5
    7
    8
    9
    9

    最后输出了2个9,显然有2个线程打架了,原因:

    i = i + 1,虽然只有一行代码,但在计算机内部执行时,至少会拆成3条指令

    a) 读取 i 的值,将其复制到本地的(副本)变量中

    b) 将本地变量值+1

    c) 将本地变量的值,覆盖到 i 上

    假如有2个线程先后到达步骤a),但尚未完成步骤b),这时就出问题了,会生成相同的值。要解决这个问题,当然可以通过加锁(或synchronized),类似下面这样,代价是牺牲性能。

        private static class Next extends Thread {
    
            public void run() {
                synchronized (this) {
                    i = i + 1;
                }
                System.out.println(i);
            }
        }
    

    jdk的并发包里提供了很多原子变量,可以在"不加锁"(注:OS底层其实还是有锁的,只不过相对java里的synchronized性能要好很多)的情况下解决这个问题,参考下面的用法:

    package test;
    
    import java.util.concurrent.atomic.AtomicInteger;
    
    public class Program {
    
        public static AtomicInteger i = new AtomicInteger(0);
    
        private static class Next extends Thread {
    
            public void run() {
                int x = i.incrementAndGet();
                System.out.println(x);
            }
        }
    
        public static void main(String[] args) {
            Thread[] threads = new Thread[10];
            for (int i = 0; i < threads.length; i++) {
                threads[i] = new Thread(new Next());
                threads[i].start();
            }
        }
    }
    

      

    实现原理,可以从源码略知一二:

        public final int incrementAndGet() {
            for (;;) {
                int current = get();
                int next = current + 1;
                if (compareAndSet(current, next))
                    return next;
            }
        }
    

    1、最外层是一个死循环

    2、先获取旧值,将其复制到一个局部变量上

    3、将局部变量值+1

    4、比较旧值是否变化,如果没变化,说明没有其它线程对旧值修改,直接将新值覆盖到旧值,并返回新值,退出循环

    5、如果旧值被修改了,开始下一轮循环,重复刚才这一系列操作,直到退出循环。

    所以,第4步的compareAndSet其实是关键,继续看源码:

        public final boolean compareAndSet(int expect, int update) {
            return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
        }
    

    最终看到的是一个native方法(说明依赖不同OS的原生实现)

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

    再往下跟,就得有点c++/c/汇编功底了,有兴趣的可自己研究下参考文章中的第2个链接文章

    参考文章:

    http://ifeve.com/concurrent-collections-8/

    http://www.blogjava.net/mstar/archive/2013/04/24/398351.html

    http://ifeve.com/13840/

  • 相关阅读:
    ImageButton按压效果失效
    ListView.setSelection(position)不起作用
    活动(Activity)
    在微信公众号开发(微站)过程中用Zepto/jquery的on/live绑定的click事件点击无效(不能执行)
    解决Angular图片ng-src指令不马上更新图片的问题
    Angular简易分页设计(二):封装成指令
    Angular回到顶部按钮指令
    Angular简易分页设计(一):基本功能实现
    Python之反射,正则
    Python之模块,迭代器与生成器
  • 原文地址:https://www.cnblogs.com/yjmyzz/p/java-cas-atomic.html
Copyright © 2020-2023  润新知