Java多线程CAS操作变量

线程读写数据

如果只有单一线程对物理内存里的数据进行计算操作,是不会造成数据差异的。但当有多个线程同时进行读写操作时,就可能会发生数据不一致的问题。
那仅有一个Cpu的情况下,多线程操作会发生数据不一致的问题吗?
如果不加任何读写上的限制,是会产生不正确的数据的。每个线程竞争Cpu的时间来进行操作和线程上下文切换机制,有可能进行到写操作的时候被另一个线程占用,另一个线程也进行了读写操作,造成数据不正确。

互相抢来抢去:
在这里插入图片描述
当然,单核Cpu执行多个线程没有太大必要,线程不断地上下文切换会造成Cpu时间的过渡消耗。

缓存一致性

现在主流多核Cpu来运行进程,Cpu从主存直接拉取数据,远小于从高速缓存Cache中读写数据,对于各自临时变量,从主存读来读去开销过大。
如果Cpu只是各有各的Cache的话,不同Cpu操作共享数据的时候,容易产生偏差。
为了针对这个问题,从而出现了缓存一致性。
在这里插入图片描述

Cpu有各自的一级缓存,还有共享的二级缓存。
假设从主存中的数据x、y,先读取到二级缓存中,然后Cpu1和Cpu2因任务需要读取x、y。
二级缓存:x、y
Cpu1的缓存:x、y
Cpu2的缓存:x、y

Cpu1修改了x的值,并想写入到主存,首先是写入到二级缓存中的。二级缓存的x发生了变化,此时会让其他读取了它的Cpu这个x域失效,需要再从缓存中读取一次。
此时:
二级缓存:x’、y
Cpu1的缓存:x’、y
Cpu2的缓存:x 、y

Java CAS操作

用Java自带的CAS操作,是一种无锁原子性操作,来保证数据操作的有序性,可见性,一致性。Java不像C++,可以从编写上直接对内存控制,从这一方面来保证相应的安全性。需要用到sun.misc的Unsafe类来实现相应操作。

compareAndSwap

这里我们先来看一下Unsafe类中的
boolean compareAndSwapInt(Object class, long valueOffset, int expect, int update );
用native方法先去比较expect预期的值和缓存中的值是否相同,如相同更新为update,返回true,否则返回false。当要去更改变量的值时,都会去循环等待CAS的返回值。退出循环的时候,也就保证了数据的正确性。

具体实现

获取Unsafe的对象

由于Unsafe对象处于安全考虑,并不能直接通过getInstance()来获取,如尝试此,会抛出SecurityException()
用Java反射机制:

 Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
            unsafeField.setAccessible(true);
            unsafe = (Unsafe) unsafeField.get(Unsafe.class);

获取变量的内存偏移量

private volatile static int count = 0;
 static {
        try {
            ....
            valueOffSet = unsafe.staticFieldOffset
                    (Test.class.getDeclaredField("count"));
        } catch (Exception ex) {
            throw new Error(ex);
        }
    }

多线程进行CAS操作

public class Test {
    ...
    static class testUnsafe implements Runnable {

        @Override
        public void run() {
            int tempCount, memoryCount;
            for(int i = 1; i <= 10 ; i++) {
                tempCount = Test.count;
                System.out.println(Thread.currentThread().getName() + ":tempCount = " + tempCount);
                memoryCount = unsafe.getInt(Test.class, valueOffSet);
                System.out.println(Thread.currentThread().getName() + ":memoryCount = " + memoryCount);
                unsafe.getAndAddInt(Test.class, valueOffSet, 1);
                System.out.println(Thread.currentThread().getName() + ":increased = " + unsafe.getInt(Test.class, valueOffSet));
            }
        }
    }

    public static void main(String[] args) {
        new Thread(new testUnsafe(), "Thread-1").start();
        new Thread(new testUnsafe(), "Thread-2").start();
    }
}

Output
Thread-1:tempCount = 0
Thread-2:tempCount = 0
Thread-1:memoryCount = 0
Thread-1:increased = 1
Thread-1:tempCount = 1
Thread-1:memoryCount = 1
Thread-1:increased = 2
Thread-1:tempCount = 2
Thread-1:memoryCount = 2
Thread-1:increased = 3
Thread-1:tempCount = 3
Thread-1:memoryCount = 3
Thread-1:increased = 4
Thread-1:tempCount = 4
Thread-2:memoryCount = 4
Thread-1:memoryCount = 4
Thread-1:increased = 6
Thread-1:tempCount = 6
Thread-1:memoryCount = 6
Thread-1:increased = 7
Thread-2:increased = 5

发现Thread-2一开始持有变量,由于Thread-1的更改,持有的变量发生变化,Thread-2需要读一次,然后再判断更改。

缺点与不足

  1. ABA问题,就是变量改变的历史,CAS操作是不可见的,但是可以给变量加上版本号。
  2. 循环等待时间可能过长,自旋CAS如果循环等待时间过长,可能给Cpu带来过渡的消耗。

本文仅供学习记录之用,如有错误欢迎指正。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值