- 问题引出
 - 底层原理概述—->CAS
- volatile 在 CAS中的作用
- 为什么CAS+重试(无锁)效率高
 - 乐观锁和悲观锁的特点)">CAS 的特点 (乐观锁和悲观锁的特点)
 
 
 - volatile 在 CAS中的作用
 - 原子类
 - CAS底层原理
 
- Java中 synchronized 和 ReentrantLock 等 独占锁就是 
**悲观锁**思想的体现 。 - 在Java中java.util.concurrent.atomic包下面的原子变量类就是使用基于
乐观锁思想的**CAS**方式实现的 。 - 管程即
monitor是阻塞式的悲观锁实现并发控制,这章我们将通过非阻塞式的乐观锁的来实现并发控制。 
问题引出
使用synchronized等加锁的方式实现线程安全
- 有如下需求,保证
account.withdraw取款方法的线程安全, 下面使用synchronized保证线程安全 
Account接口中有两个接口方法,跟一个测试方法,
interface Account {// 获取余额Integer getBalance();// 取款void withdraw(Integer amount);/*** Java8之后接口新特性, 可以添加默认方法* 方法内会启动 1000 个线程,每个线程做 -10 元 的操作* 如果初始余额为 10000 那么正确的结果应当是 0*/static void demo(Account account) {List<Thread> ts = new ArrayList<>();long start = System.nanoTime();for (int i = 0; i < 1000; i++) {ts.add(new Thread(() -> {account.withdraw(10);}));}ts.forEach(thread -> thread.start());ts.forEach(t -> {try {t.join();} catch (InterruptedException e) {e.printStackTrace();}});long end = System.nanoTime();System.out.println(account.getBalance()+ " cost: " + (end - start) / 1000_000 + " ms");}}
实现
class AccountUnsafe implements Account {private Integer balance;public AccountUnsafe(Integer balance) {this.balance = balance;}@Overridepublic Integer getBalance() {synchronized (this) {return balance;}}@Overridepublic void withdraw(Integer amount) {// 通过这里加锁就可以实现线程安全,不加就会导致线程安全问题synchronized (this) {balance -= amount;}}}
测试
demo方法是静态的,需要接口来调,因为它就是声明在接口的。
public class Test1 {public static void main(String[] args) {Account.demo(new AccountUnsafe(10000));}}
无锁的方式实现线程安全
上面的代码中使用synchronized加锁操作来保证线程安全,但是 synchronized加锁操作太耗费资源 (因为底层使用了操作系统mutex指令, 造成内核态和用户态的切换),这里我们通过原子整型来使用 无锁 思想来解决此问题
class AccountCas implements Account {//使用原子整数类型: 底层使用CAS+重试的机制,而不是传统的Integer整数类型private AtomicInteger balance;public AccountCas(int balance) {this.balance = new AtomicInteger(balance);}@Overridepublic Integer getBalance() {//得到原子整数的值return balance.get();}@Overridepublic void withdraw(Integer amount) {while(true) {//获得修改前的最新值int prev = balance.get();//获得修改后的值int next = prev - amount;/*此时的prev、next都是(局部变量)存储在工作内存中的,还没有同步到主内存中*///真正修改,第一个参数是调用get方法获得的值,第二个参数是要修改的值,修改成功会返回trueif(balance.compareAndSet(prev, next)) {break;}}}}
底层原理概述—->CAS
上面使用AtomicInteger的案例解决方法,内部并没有用锁也能实现共享变量的线程安全。那么它是如何实现的呢?
其中的关键是 compareAndSwap方法(比较并设置值),它的简称就是 CAS (也有 Compare And Swap 的说法),它必须是原子操作。
我们来分析一下它具体是怎么实现的:
流程 :
- 当一个线程要去修改Account对象中的值时,先调用get方法获取值prev(),然后再将其设置为新的值next(调用cas方法)。在调用cas方法时,会将prev与Account中的余额进行比较(满足一定条件才能更新成功)。 
- 如果两者相等,就说明该值还未被其他线程修改,此时便可以进行修改操作。
 - 如果两者不相等,就不设置值,重新获取值prev(调用get方法),然后再将其设置为新的值next(调用cas方法),直到修改成功为止。
 
 - 假如在执行cas方法之前,另外的一个线程就已经将其修改为90;你就得在其他线程修改的基础上再进行修改(人家修改为90,你就得在90的基础上进行你的增减操作)。既然其他线程修改了,所以你之前调用的get方法获取的prev跟共享变量上的最新结果不一样,所以本次修改失败,返回false;再次进行循环,如此反复直到修改成功。
 - cas方法是怎么知道共享变量被其他线程修改的?比较的结果不一样呗
 
注意 :
- 其实 CAS 的底层是 lock cmpxchg 指令(X86 架构),在单核 CPU 和多核 CPU 下都能够保证【比较-交换】的原子性。
 - 在多核状态下,某个核执行到带 lock 前缀的指令时,CPU 会让总线锁住,当当前这个核(线程)把此指令执行完毕,再开启总线。这个过程中不会被线程的调度机制所打断,保证了多个线程对内存操作的准确性,是原子的。
 
volatile 在 CAS中的作用
- 在上面代码中的AtomicInteger类,保存值的value属性使用了volatile 修饰。获取共享变量时,为了保证该共享变量的可见性,需要使用 volatile 修饰。
 

- volatile可以用来修饰 成员变量和静态成员变量,他可以避免线程从自己的工作缓存中查找变量的值,必须到主存中获取它的值,线程操作 volatile 变量都是直接操作主存。这就是可见性的体现,即一个线程对 volatile 变量的修改,对另一个线程可见。
 - 注意: volatile 仅仅保证了共享变量的可见性,让其它线程能够看到最新值,但不能解决指令交错问题(不能保证原子性)
 **CAS 必须借助 volatile 才能读取到共享变量的最新值来实现【比较并交换】的效果**为什么CAS+重试(无锁)效率高
使用CAS+重试—-也就是无锁模式的情况下,即使重试失败,线程始终在高速运行,没有停歇,而 synchronized会让线程在没有获得锁的时候,发生上下文切换,进入阻塞。
- 打个比喻:线程就好像高速跑道上的赛车,高速运行时,速度超快,一旦发生上下文切换,就好比赛车要减速、熄火,等被唤醒又得重新打火、启动、加速… 恢复到高速运行状态,代价比较大。
 
但无锁情况下,因为线程要保持运行,需要额外 CPU 的支持,CPU 在这里就好比高速跑道,没有额外的跑道,线程想高速运行也无从谈起,虽然不会进入阻塞,但由于没有分到时间片**,仍然会进入可运行状态,还是会导致上下文切换。**
CAS 的特点 (乐观锁和悲观锁的特点)
结合 CAS 和 volatile 可以实现无锁并发,适用于线程数少、多核 CPU 的场景下。
CAS 是基于乐观锁的思想:最乐观的估计,不怕别的线程来修改共享变量,就算改了也没关系,我吃亏点再重试呗。
- synchronized 是基于悲观锁的思想:最悲观的估计,得防着其它线程来修改共享变量,我上了锁你们都别想改,我改完了解开锁,你们才有机会。
 - CAS 体现的是无锁并发、无阻塞并发,请仔细体会这两句话的意思 
- 因为没有使用 synchronized,所以线程不会陷入阻塞,这是效率提升的因素之一
 - 但如果竞争激烈(写操作多),可以想到重试必然频繁发生,反而效率会受影响
 
 
原子类
基本类
java.util.concurrent.atomic并发包提供了一些并发工具类:使用原子操作的方式 (共享数据为基本数据类型原子类)
- AtomicInteger:整型原子类
 - AtomicLong:长整型原子类
 - AtomicBoolean :布尔型原子类
 
通过观察源码可以发现这几个类内部都是通过cas的原理来实现的,而cas底层又是使用到了,sun.misc.Unsafe类来实现;换句话说CAS的底层是sun.misc.Unsafe类,这里先不说这个类,后面会具体分析到它,先来看看这几个原子基本类;
这三个类提供的方法几乎相同,所以我们将以 AtomicInteger为例子来介绍。先讨论原子整数类,以 AtomicInteger 为例讨论它的api方法
AtomicInteger API演示
public static void main(String[] args) {AtomicInteger i = new AtomicInteger(0);// 先获取再自增(i = 0, 结果 i = 1, 返回 0),类似于 i++System.out.println("i.getAndIncrement() "+i.getAndIncrement() );System.out.println("i "+i );// 先自增再获取(i = 1, 结果 i = 2, 返回 2),类似于 ++iSystem.out.println(i.incrementAndGet());// 先自减再获取(i = 2, 结果 i = 1, 返回 1),类似于 --iSystem.out.println(i.decrementAndGet());// 获取并自减(i = 1, 结果 i = 0, 返回 1),类似于 i--System.out.println(i.getAndDecrement());// 先获取再加值(i = 0, 结果 i = 5, 返回 0)System.out.println(i.getAndAdd(5));// 先加值再获取(i = 5, 结果 i = 0, 返回 0)System.out.println(i.addAndGet(-5));// 先获取再更新(i = 0, p 为 i 的当前值, 结果 i = -2, 返回 0)// 函数式编程接口,其中函数中的操作能保证原子,但函数需要无副作用System.out.println(i.getAndUpdate(p -> p - 2));// 先更新再获取(i = -2, p 为 i 的当前值, 结果 i = 0, 返回 0)// 函数式编程接口,其中函数中的操作能保证原子,但函数需要无副作用System.out.println(i.updateAndGet(p -> p + 2));// 先获取再计算(i = 0, p 为 i 的当前值, x 为参数1, 结果 i = 10, 返回 0)// 函数式编程接口,其中函数中的操作能保证原子,但函数需要无副作用// getAndUpdate 如果在 lambda 中引用了外部的局部变量,要保证该局部变量是 final 的// getAndAccumulate 可以通过 参数1 来引用外部的局部变量,但因为其不在 lambda 中因此不必是 finalSystem.out.println(i.getAndAccumulate(10, (p, x) -> p + x));// 计算并获取(i = 10, p 为 i 的当前值, x 为参数1值, 结果 i = 0, 返回 0)// 函数式编程接口,其中函数中的操作能保证原子,但函数需要无副作用System.out.println(i.accumulateAndGet(-10, (p, x) -> p + x));}
updateAndGet的实现
上面演示的方法都只能做一些简单的加法减法,这里我们演示一下稍微更复杂一点的操作,比如除法或者乘法;
简单写法:
public static void main(String[] args) {//想要在5的基础上进行乘法运算AtomicInteger i = new AtomicInteger(5);//简单写法i.updateAndGet(value ->value * 10);System.out.println(i.get()); // 2}
想做更复杂的运算的话
步骤:
- 调用updateAndGet方法, 将共享变量i,以及 IntUnaryOperator对象传递过去
 - updateAndGet方法内部, 传过来的operator对象, 调用IntUnaryOperator中的applyAsInt方法, 实际调用的就是传递过来的对象的方法, 进行 / 操作 ```java public static void main(String[] args) {
 
AtomicInteger i = new AtomicInteger(5);updateAndGet(i, new IntUnaryOperator() {@Overridepublic int applyAsInt(int operand) {return operand / 2;}});System.out.println(i.get()); // 2}/*** 如果让我们来实现线程安全计算的话该如何?* 这里我们考虑使用最基本的compareAndSet思想来实现;* 使用一个while(true)循环来实现,一旦有其他线程将其修改,就进行重试* 直到期待值跟最新值相等* @param i* @param operator*/public static void updateAndGet(AtomicInteger i, IntUnaryOperator operator) {while (true) {//获取到当前值int prev = i.get(); // 5//进行计算,并将结果存放在next中int next = operator.applyAsInt(prev);//使用compareAndSet方法进行比较并更新if (i.compareAndSet(prev, next)) {//若果成功了就breakbreak;}}}
<a name="iJjVQ"></a>### 原子引用 (AtomicReference)类原子引用的作用: **保证引用类型的共享变量是线程安全的(确保这个原子引用没有引用过别人**- 为什么需要原子引用类型 ? (引用数据类型原子类)基本类型原子类只能更新一个变量,如果需要原子更新多个变量,需要使用引用类型原子类。- AtomicReference:引用类型原子类- AtomicStampedReference:原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于解决原子的更新数据和数据的版本号,**可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。**- AtomicMarkableReference :原子更新带有标记的引用类型。该类将 boolean 标记与引用关联起来,也可以**解决使用 CAS 进行原子更新时可能出现的 ABA 问题**。例子 : 使用原子引用实现BigDecimal存取款的[线程安全](https://so.csdn.net/so/search?q=%E7%BA%BF%E7%A8%8B%E5%AE%89%E5%85%A8&spm=1001.2101.3001.7020):```javainterface DecimalAccount {// 获取余额BigDecimal getBalance();// 取款void withdraw(BigDecimal amount);/*** 方法内会启动 1000 个线程,每个线程做 -10 元 的操作* 如果初始余额为 10000 那么正确的结果应当是 0*/static void demo(DecimalAccount account) {List<Thread> ts = new ArrayList<>();for (int i = 0; i < 1000; i++) {ts.add(new Thread(() -> {account.withdraw(BigDecimal.TEN);}));}ts.forEach(Thread::start);ts.forEach(t -> {try {t.join();} catch (InterruptedException e) {e.printStackTrace();}});System.out.println(account.getBalance());}}
下面这个是不安全的实现过程:
class DecimalAccountUnsafe implements DecimalAccount {BigDecimal balance;public DecimalAccountUnsafe(BigDecimal balance) {this.balance = balance;}@Overridepublic BigDecimal getBalance() {return balance;}@Overridepublic void withdraw(BigDecimal amount) {BigDecimal balance = this.getBalance();this.balance = balance.subtract(amount);}}
解决代码如下:在AtomicReference类中,存在一个value类型的变量,保存对BigDecimal对象的引用。
public class Test1 {public static void main(String[] args) {DecimalAccount.demo(new DecimalAccountCas(new BigDecimal("10000")));}}class DecimalAccountCas implements DecimalAccount {//原子引用,泛型类型为小数类型,保护BigDecimalprivate final AtomicReference<BigDecimal> balance;public DecimalAccountCas(BigDecimal balance) {//创建的是一个AtomicReferencethis.balance = new AtomicReference<>(balance);}@Overridepublic BigDecimal getBalance() {return balance.get();}@Overridepublic void withdraw(BigDecimal amount) {while (true) {BigDecimal prev = balance.get();BigDecimal next = prev.subtract(amount);if (balance.compareAndSet(prev, next)) {break;}}}}
ABA 问题及解决 (重点)
如下程序所示,虽然 在other方法中存在两个线程对共享变量进行了修改,**但是修改之后又变成了原值,main线程对`修改过共享变量的过程`是不可见的,这种操作这对业务代码并无影响。** ```java public class Test1 { //该变量的初始值是A,我们想在后面将其修改为C static AtomicReference
ref = new AtomicReference<>(“A”); public static void main(String[] args) {
new Thread(() -> {String pre = ref.get();System.out.println("change");try {other();} catch (InterruptedException e) {e.printStackTrace();}Sleeper.sleep(1);//把ref中的A改为CSystem.out.println("change A->C " + ref.compareAndSet(pre, "C"));}).start();
}
static void other() throws InterruptedException {
new Thread(() -> {// 此时ref.get()为A,此时共享变量ref也是A,没有被改过, 此时CAS// 可以修改成功, BSystem.out.println("change A->B " + ref.compareAndSet(ref.get(), "B"));}).start();Thread.sleep(500);new Thread(() -> {// 同上, 修改为ASystem.out.println("change B->A " + ref.compareAndSet(ref.get(), "A"));}).start();
} }
- 主线程仅能判断出共享变量的值与最初值 A是否相同,不能感知到这种从 A 改为 B 又改回 A 的情况,也就是说它不能判断出其他线程将A修改成另外的值然后又修改回A值的情况,在A变为B但没变回A之前,其他线程能做很多事情了!!!- 如果主线程希望:**只要有其它线程【修改过】共享变量,那么自己的 cas 就算失败,这时,仅比较值是不够的,需要再加一个版本号。可以使用AtomicStampedReference来解决。**<a name="TrOYx"></a>#### AtomicStampedReference (加版本号解决ABA问题)- 谁修改过原来的值,就得将版本号进行更新,这样即可解决ABA问题```javaclass Test1 {//指定版本号初始为0static AtomicStampedReference<String> ref = new AtomicStampedReference<>("A", 0);public static void main(String[] args) {new Thread(() -> {//获取的方法也有所变化String pre = ref.getReference();//获得版本号int stamp = ref.getStamp(); // 此时的版本号还是第一次获取的System.out.println("change");try {other();} catch (InterruptedException e) {e.printStackTrace();}try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}//把ref中的A改为C,并比对版本号,如果版本号相同,就执行替换,并让版本号+1System.out.println("change A->C stamp " + stamp + ref.compareAndSet(pre, "C", stamp, stamp + 1));}).start();}static void other() throws InterruptedException {new Thread(() -> {int stamp = ref.getStamp();System.out.println("change A->B stamp " + stamp + ref.compareAndSet(ref.getReference(), "B", stamp, stamp + 1));}).start();Thread.sleep(500);new Thread(() -> {int stamp = ref.getStamp();System.out.println("change B->A stamp " + stamp + ref.compareAndSet(ref.getReference(), "A", stamp, stamp + 1));}).start();}}

AtomicMarkableReference (标记cas的共享变量是否被修改过)
- AtomicStampedReference 可以给原子引用加上版本号,追踪原子引用整个的变化过程,如:A -> B -> A ->C,通过AtomicStampedReference,我们可以知道,引用变量中途被更改了几次。
 - 但是有时候,我们并不关心引用变量更改了几次,只是单纯的关心是否更改过,所以就有了AtomicMarkableReference ,用一个布尔值来记录是否被改动过
 

@Slf4j(topic = "guizy.TestABAAtomicMarkableReference")class TestABAAtomicMarkableReference {public static void main(String[] args) throws InterruptedException {GarbageBag bag = new GarbageBag("装满了垃圾");// 参数2 mark 可以看作一个标记,true表示垃圾袋满了AtomicMarkableReference<GarbageBag> ref = new AtomicMarkableReference<>(bag, true);log.debug("主线程 start...");GarbageBag prev = ref.getReference();log.debug(prev.toString());new Thread(() -> {log.debug("打扫卫生的线程 start...");bag.setDesc("空垃圾袋");// 两个都是bag没有真正进行修改,只是将状态从true改成falsewhile (!ref.compareAndSet(bag, bag, true, false)) {}log.debug(bag.toString());}).start();Thread.sleep(1000);log.debug("主线程想换一只新垃圾袋?");boolean success = ref.compareAndSet(prev, new GarbageBag("空垃圾袋"), true, false);log.debug("换了么?" + success);log.debug(ref.getReference().toString());}}class GarbageBag {//对垃圾的描述String desc;public GarbageBag(String desc) {this.desc = desc;}public void setDesc(String desc) {this.desc = desc;}@Overridepublic String toString() {return super.toString() + " " + desc;}}
AtomicStampedReference和AtomicMarkableReference两者的区别
- AtomicStampedReference 需要我们传入 整型变量 作为版本号,来判定是否被更改过
 - AtomicMarkableReference需要我们传入布尔变量 作为标记,来判断是否被更改过
 
原子数组
- 保证数组内的元素的线程安全
 - 使用原子的方式更新数组里的某个元素 
- AtomicIntegerArray:整形数组原子类
 - AtomicLongArray:长整形数组原子类
 - AtomicReferenceArray:引用类型数组原子类
 
 
上面三个类提供的方法几乎相同,所以我们这里以 AtomicIntegerArray 为例子来介绍。实例代码
普通数组内元素, 多线程访问会造成安全问题
class AtomicArrayTest {public static void main(String[] args) {//想要获得的效果是在一个长度为10的数组里面,将其里面的每个元素从0自增到10000//调用demo方法demo(() -> new int[10],array -> array.length,(array, index) -> array[index]++,array -> System.out.println(Arrays.toString(array)));}/*** 参数1,提供数组、可以是线程不安全数组或线程安全数组* 参数2,获取数组长度的方法* 参数3,自增方法,回传 array, index* 参数4,打印数组的方法*/// 函数式接口一:supplier 表示提供者 无中生有,没有参数,但是需要返回结果,如 ()->结果// 函数式接口二:function 表示函数, 一个参数一个返回结果,如 (参数)->结果 , 还有一种是两个参数返回一个结果,如BiFunction (参数1,参数2)->结果// 函数式接口三:consumer 表示消费者 一个参数但没结果 如 (参数)->void, 另一中两个参数,如BiConsumer (参数1,参数2)->voidprivate static <T> void demo(Supplier<T> arraySupplier, Function<T, Integer> lengthFun,BiConsumer<T, Integer> putConsumer, Consumer<T> printConsumer) {List<Thread> ts = new ArrayList<>();T array = arraySupplier.get();int length = lengthFun.apply(array);for (int i = 0; i < length; i++) {// 创建10个线程, 每个线程对数组作 10000 次操作ts.add(new Thread(() -> {for (int j = 0; j < 10000; j++) {putConsumer.accept(array, j % length);}}));}ts.forEach(t -> t.start()); // 启动所有线程ts.forEach(t -> {try {t.join();} catch (InterruptedException e) {e.printStackTrace();}}); // 等所有线程结束printConsumer.accept(array);}}

使用AtomicIntegerArray来创建安全数组 ```java //方式一: demo(
()-> new AtomicIntegerArray(10),(array) -> array.length(),(array, index) -> array.getAndIncrement(index),array -> System.out.println(array)
); //方式二: demo(
()-> new AtomicIntegerArray(10),AtomicIntegerArray::length,AtomicIntegerArray::getAndIncrement,System.out::println
);
<a name="ooCU1"></a>### 字段更新器保证多线程访问同一个对象的成员变量时, **成员变量**的线程安全性。- AtomicReferenceFieldUpdater —引用类型的属性- AtomicIntegerFieldUpdater —整形的属性- AtomicLongFieldUpdater —长整形的属性注意:利用字段更新器,可以针对对象的某个域(Field)进行原子操作,只能配合 volatile 修饰的字段使用,否则会出现异常。<br />使用示例```javapublic class AtomicFieldTest {public static void main(String[] args) {Student stu = new Student();// 获得原子更新器// 泛型// 参数1 持有属性的类 参数2 被更新的属性的类// newUpdater中的参数:第三个为属性的名称AtomicReferenceFieldUpdater updater = AtomicReferenceFieldUpdater.newUpdater(Student.class, String.class, "name");// 期望的为null, 如果name属性没有被别的线程更改过, 默认就为null, 此时匹配, 就可以设置name为张三System.out.println(updater.compareAndSet(stu, null, "张三"));System.out.println(updater.compareAndSet(stu, stu.name, "王五"));System.out.println(stu);}}class Student {volatile String name;@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +'}';}}

原子累加器 LongAddr极其源码 (重要)
CAS底层原理
概述
java中提供了对CAS操作的支持,具体在sun.misc.unsafe类中。这个unsafe并不是指线程的不安全,而是因为它太底层了不建议程序员直接去操作它。
- Unsafe 对象提供了非常底层的,操作内存、线程的方法,Unsafe 对象不能直接调用,只能通过
**反射**获得(**该对象其源码被声明为单例且私有,故需要反射进行获取**) - 可以发现我们之前学习的CAS、LockSupport、
AtomicInteger以及其他的原子类, 底层都使用的是**Unsafe**类中的相应方法 

- 其中底层的
Unsafe有关CAS的相关方法实现原子操作的方法声明如下(当然该类的其他方法也可以操作内存) ```java /* 参数: var1:表示要操作的对象 var2:表示要操作的对象中属性地址的偏移量,我们只能知道对象的地址,对象中的属性地址得根据偏移量获取 var4:表示要修改的期望值,只有跟修改前一致才能进行修改 var5:表示要修改为的新值 
这几个方法compareAndSwapXXXX,Object表示要修改对象的类型为Object,另外两个方法同理 */ public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5); public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5); public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
这里我们演示一下使用unsafe对象直接对Person对象中的属性进行线程安全的修改。因为它的底层是通过对内存的偏移量来定位到Person对象的属性的,所以比较复杂,在定位到属性后再进行比较操作;所以我们首先要获取到属性的偏移量(偏移地址),这个可以调用unsafe对象的objectFieldOffset方法来获取;获取完偏移地址就可以对里面的属性值进行cas修改了。```javapublic class Test {public static void main(String[] args) throws Exception {// 通过反射获得Unsafe对象Class unsafeClass = Unsafe.class;// 获得构造函数,Unsafe的构造函数为私有的Constructor constructor = unsafeClass.getDeclaredConstructor();// 设置为允许访问私有内容constructor.setAccessible(true);// 创建Unsafe对象Unsafe unsafe = (Unsafe) constructor.newInstance();// 创建Person对象Person person = new Person();// 获得其属性 name 的偏移量long nameOffset = unsafe.objectFieldOffset(Person.class.getDeclaredField("name"));long ageOffset = unsafe.objectFieldOffset(Person.class.getDeclaredField("age"));// 通过unsafe的CAS操作改变值//compareAndSwapObject方法是比较并交换引用地址unsafe.compareAndSwapObject(person, nameOffset, null, "张三");//compareAndSwapInt方法是比较并交换intunsafe.compareAndSwapInt(person, ageOffset, 0, 22);System.out.println(person);}}class Person {// 配合CAS操作,必须用volatile修饰volatile String name;volatile int age;@Overridepublic String toString() {return "Person{" +"name='" + name + '\'' +", age=" + age +'}';}}
使用Unsafe模拟实现原子类
设计的大体思路就是先利用反射获取到一个unsafe对象,并且计算出要保护的变量属性的偏移量;然后利用unsafe对象结合cas操作完成赋值时的原子性。
class MyAtomicInteger{//MyAtomicInteger类中要保护的是int类型的value,因为要配合cas,所以用volatile进行修饰private volatile int value;private static final long valueOffset;//声明一个静态的unsafe对象private static final Unsafe UNSAFE;static {UNSAFE=UnsafeAccessor.getUnsafe();//计算要保护变量的偏移量try {valueOffset=UNSAFE.objectFieldOffset(MyAtomicInteger.class.getDeclaredField("value"));} catch (NoSuchFieldException e) {e.printStackTrace();//要通过unsafe对象操作数据需要先得到域的偏移量,然后得到偏移量时可能抛出找不到域的异常,由于这种异常不会发生,// 所以转化成非受查异常。在后续调用方法就不用处理这个异常。throw new RuntimeException(e);}}public int getValue(){return value;}public void decrement(int amount){//while循环保证赋值时的原子性while (true){int prev = this.value;int next = prev-amount;//利用unsafe对象完成赋值if (UNSAFE.compareAndSwapInt(this,valueOffset,prev,next)){break;}}}}/** 获取一个unsafe对象* */class UnsafeAccessor{private static final Unsafe UNSAFE;static {try {Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");theUnsafe.setAccessible(true);UNSAFE= (Unsafe) theUnsafe.get(null);}catch (NoSuchFieldException | IllegalAccessException e){throw new Error(e);}}public static Unsafe getUnsafe(){return UNSAFE;}}
CAS的实现原理
原理概述
CAS操作是通过调用JNI(Java本地接口)的代码实现,JNI: java Native Interface,允许java调用其它的语言。而compareAndSwapxxx系列的方法就是借助“C语言”来调用cpu底层指令实现的。
以常用的Intel x86(英特尔)平台来说,最终映射到的cpu的指令为“cmpxchg”,这是一个原子指令,涉及汇编语言,给相应指令加了lock前缀进行lock加锁保证原子,cpu执行此命令时,实现比较并替换的操作!
也就是说在现代计算机动不动就上百核心的情况下,cmpxchg是这样保证多核心下的线程安全的:
在系统底层进行CAS操作的时候,会判断当前系统是否为多核心系统,如果是就给“总线”加锁,只有一个线程会对总线加锁成功,加锁成功之后才会执行CAS操作,也就是说CAS的原子性是平台级别的 ! 
CAS存在的ABA问题
在之前原子类那里已经介绍过了,这里不再赘述。
