Android 线程安全(二)CAS

CAS

java中CAS (Compare-and-Swap),比较替换,利用交换指令CMPXCHG来实现,能够保证操作的原子性

public final boolean compareAndSet(int expect, int update)
参数 (旧值,新值)
旧值用于校验是否是期待的值,是就能替换成功,不是则失败,可以继续重试来更新。java中自旋锁就是通过循环替换markwork指向来实现的,,线程不会挂起,提高了效率

private int counts = 0;
    private AtomicInteger atomicInteger = new AtomicInteger(0);

    private void testCAS() {
        List<Thread> threadList = new ArrayList<>();
        for (int i = 0; i < 1000; i++) {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 10000; i++) {
                        count();
                        safeCount();
                    }
                }
            });
            threadList.add(thread);
        }

        for (Thread thread : threadList) {
            thread.start();
        }
        for (Thread thread : threadList) {
            try {
                thread.join();  // 挂起测试线程 ,测试线程等所有计数线程执行完毕才会输出结果
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        Log.i("qinxue", "counts: " + counts);
        Log.i("qinxue", "atomicInteger: " + atomicInteger.get());
    }

    private void count() {
        counts++;
    }

    private void safeCount() {
        int oldValue = atomicInteger.get();	//原子操作,可看源码 volatile 的。
        for (; ; ) {
            boolean r = atomicInteger.compareAndSet(oldValue, ++oldValue);	//循环尝试更新
            if (r) {
                break;
            }
        }
    }

结果

counts: 9633822
atomicInteger: 10000000

counts 由于++不是原子性操作,count也没有volatile修饰,各个线程修改后同步到内存,有出现错误。例如2个线程缓存了主存的100,都变成101同步到主存,本来主存应该是102的,现在就出现了错误,正如结果小于10000000
atomicInteger: 1volatile修饰,读取、写入值时是安全的, 2、CAS是原子的,如果在读取到CAS这时间里,别的线程CAS了,本线程的CAS会失败。重试就好了,保证了数据的正确性。
问题
1、ABA问题,例如其他线程修改了1变为2又变回1,本线程CAS没有察觉到起始此值已经变化过了,JDK的AtomicStampedReference解决了这个问题,给值添加了版本号。保证不出现ABA。
2、性能消耗,线程不挂起,无效自旋肯定消耗性能的。
3、只能保证一个变量原子性,不如加锁自由。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章