Java并发-原子操作CAS

395 阅读2分钟

阻塞锁的缺点

  • 被阻塞的线程下优先级高
  • 拿到锁的线程一直不释放锁怎么办?
  • 大量的竞争,消耗CPU,并且带来死锁或者其他的安全问题他
  • 太重了,不实用与小的操作

CAS原理

CAS(Compare And Swap),利用了现代处理器都支持的CAS的指令,循环这个指令,直到成功为止

三个运算符:一个内存地址V,一个期望值A,一个修改值B

基本思路

for(;;){
	if(V.value == A){
			V.value = B
	}
}

如果地址V上的值和期望的值A相等,就给地址V赋给新值B,如果不是,不做任何操作。

循环(死循环,自旋)里不断的进行CAS操作

CAS的问题

  • ABA问题:
    • 若CPU在读内存地址的时候值为A,让后地址的值被改为B,之后又被改为A,CPU在做比较的时候发现地址的值没变A--->B---->A
    • 解决办法,引入版本号,记录每次操作后的版本A1--->B2---->A3
  • 开销问题
  • 只能保证一个共享变量的原子操作

JDK中相关原子类的使用

  • 更新基本类型类:AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference

  • 更新数组类:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray

  • 更新引用类型:AtomicReference

    • AtomicMarkableReference 带版本号的,返回值为boolean ,有没有动过
    • AtomicStampedReference 动过几次
  • 原子更新字段类: AtomicReferenceFieldUpdater,AtomicIntegerFieldUpdater,AtomicLongFieldUpdater

具体使用如下

UseAtomicInteger

public class UseAtomicIn {
    static AtomicInteger atomicInteger  = new AtomicInteger(10);

    public static void main(String[]  args){
        System.out.println(atomicInteger.getAndIncrement());
        System.out.println(atomicInteger.incrementAndGet());
        System.out.println(atomicInteger.get());
    }
}
//输出
//10
//12
//12

UseAtomicArray

public class UseAtomicArr {

    static int[] a  = new int[]{1,2};

    static AtomicIntegerArray integerArray = new AtomicIntegerArray(a);

    public static void main(String[] args){
        integerArray.getAndSet(0,3);
        System.out.println(integerArray.get(0));
        System.out.println(a[0]);
    }
}
//输出
//3
//1

UseAtomicReference

public class UseAtomicReferenc {
    static class User{
        private String name;
        private Integer age;

        public User(String name, Integer age) {
            this.name = name;
            this.age = age;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public Integer getAge() {
            return age;
        }

        public void setAge(Integer age) {
            this.age = age;
        }
    }
    static  AtomicReference<User> userRef = new AtomicReference<User>();
    public static void main(String[] args){
        User u = new User("sam",22);
        userRef.set(u);
        User max = new User("max", 27);
        userRef.compareAndSet(u,max);
        System.out.println(userRef.get().getAge());
        System.out.println(userRef.get().getName());
        System.out.println(u.getAge());
        System.out.println(u.getName());
    }
}
//27
//max
//22
//sam

UseAtomicStampedReference

public class UseAtomicStampedReference {

    static AtomicStampedReference<String> asr  =
            new AtomicStampedReference<String>("Sam",0);

    public static void main(String[] args) throws InterruptedException {
        final int oldStamp = asr.getStamp();
        final String oldRef = asr.getReference();

        System.out.println(oldRef+"==========="+oldStamp);
        Thread rghtThread = new Thread(new Runnable() {
            @Override
            public void run() {

                System.out.println(Thread.currentThread().getName()+" 当前变量值 "+oldRef+" 当前版本戳 "+oldStamp+" -"
                        +asr.compareAndSet(oldRef,oldRef+"me",oldStamp,oldStamp+1));


            }
        });
        Thread wrongThread = new Thread(new Runnable() {
            @Override
            public void run() {

                System.out.println(Thread.currentThread().getName()+" 当前变量值 "+asr.getReference()+" 当前版本戳 "+asr.getStamp()+" -"
                        +asr.compareAndSet(asr.getReference(),asr.getReference()+"me",oldStamp,oldStamp+1));


            }
        });

        rghtThread.start();
        rghtThread.join();
        wrongThread.start();
        wrongThread.join();
        System.out.println(asr.getReference()+"==========="+asr.getStamp());
    }

}
//输出
//Sam===========0
//Thread-0 当前变量值 Sam 当前版本戳 0 -true
//Thread-1 当前变量值 Samme 当前版本戳 1 -false
//Samme===========1

小测试

现在有一个残缺的AtomicInteger类只实现了线程安全的:

get方法和compareAndSet()方法

请在理解了循环CAS后尝试自行实现它的递增方法

public class Test {

    static AtomicInteger a = new AtomicInteger(0);

    private static void increa(){
        for (;;){
            int i = a.get();
            if(a.compareAndSet(i, ++i))
                break;
        }
    }


    public static void main(String[] args){
        System.out.println(a.get());
        increa();
        System.out.println(a.get());
    }

}
//输出 
//0
//1