04.CAS

102 阅读2分钟

1.CAS(compare and swap) 对比交换

CAS ,意思是compare and swap,是指程序对比变量,并且交换变量的操作。 一般现代CPU 都增添了一个CAS 指令,由CPU 来保证这个CAS指令操作的原子性,当在进行操作时候,CPU 会一直循环这个指令,直到成功。 示例如图:

image.png 在JDK 里面一些如AtomicInteger等命名的变量都是利用的这种形式实现的。

2.CAS 的缺陷

CAS 也有自己的局限性:

  • ABA 问题.
  • 开销问题.
  • 只能保证共享原子变量的更新. 当一般我们用syncnized进行同步时候,可以认为是一个原子操作.在执行命令时候,不会被其他线程抢占CPU.

在解决ABA 问题时候,解决方式是使用版本戳,在修改版本时候,加上版本号.JDK里面也有提供类似的解决方案的类. AtomicMarkableReferenceAtomicStamedReference. 这两者的区别在于前置只关心变量是否是被修改过了,后者关心变量被修改几次.

在解决只能保证共享原子变量的更新 使用AtomicReference ,索引一个变量,进行更新的原子操作.

JDK 里面实现的一些CAS 类:

image.png

3. 代码示例

package com.company.test.atomictest;

import java.util.concurrent.atomic.AtomicMarkableReference;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicStampedReference;

public class AtomicTest {
    static AtomicStampedReference<String> reference = new AtomicStampedReference<String>("mark", 0);
    static AtomicMarkableReference<String> markableReference = new AtomicMarkableReference<String>("mark", true);
    static AtomicReference<User> userReference = new AtomicReference<User>();

    public static void main(String[] args) {
        final int oldVersion = reference.getStamp();
        final String oldStr = reference.getReference();
        final String markStr = markableReference.getReference();
        User tom = new User("Tom", 12);
        userReference.set(tom);
        Thread thread1 = new Thread("java") {
            @Override
            public void run() {
                super.run();
                System.out.println(Thread.currentThread().getName() + ",current Str=" + oldStr + ",current version=" + oldVersion);
                reference.compareAndSet(oldStr, oldStr + "Java", oldVersion, oldVersion + 1);
            }
        };

        Thread thread2 = new Thread("test") {
            @Override
            public void run() {
                super.run();
                System.out.println(markableReference.compareAndSet("mark", markStr + "able", true, false)
                );
            }
        };
        Thread thread3= new Thread("test3") {
            @Override
            public void run() {
                super.run();
                User user = new User("jack", 22);
                boolean b = userReference.compareAndSet(tom,user);
                System.out.println(b);
            }
        };
        thread1.start();
        thread2.start();
        thread3.start();
        try {
            Thread.sleep(8000);
            System.out.println(Thread.currentThread().getName() + ",AtomicStampedReference Str=" + reference.getReference() + ",current " +
                    "version=" + reference.getStamp());
            System.out.println(Thread.currentThread().getName() + ",markableReference Str=" + markableReference.getReference());
            System.out.println(userReference.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

    static class User {
        private String name;
        private int age;

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

        public String getName() {
            return name;
        }

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

        public int getAge() {
            return age;
        }

        public void setAge(int age) {
            this.age = age;
        }

        @Override
        public String toString() {
            return "User{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }
}