Java并发编程:CAS概念、原子操作类(乐观锁思想)、LongAddr和AtomicLong、Unsafe
共享模型之无锁
- Java中 synchronize 和 ReentranLock等独占锁就是
悲观锁
思想的实现- 在Java中 java.util.concurretn.atomic包下面的原子变量类就是使用了
乐观锁
的一种实现方式CAS
实现的
- 管程即
monitor
是阻塞式的悲观锁
实现并发控制,这章我们将通过非阻塞式乐观锁
的来实现并发控制
1、 问题提出
- 有如下需求,保证
account.withdraw取款方法
的线程安全, 下面使用synchronized
保证线程安全
@Slf4j
public class Test1 {
public static void main(String[] args) {
Account.demo(new AccountUnsafe(1000));
Account.demo(new AccountCas(1000));
}
}
class AccountUnsafe implements Account{
private Integer blance;
public AccountUnsafe(Integer blance){
this.blance=blance;
}
@Override
public Integer getBlance() {
synchronized (this){
return blance;
}
}
@Override
public void withdraw(Integer amount) {
//通过这里加锁就可以实现线程安全,不加就会导致线程安全问题
synchronized (this){
blance-=amount;
}
}
}
class AccountCas implements Account{
//使用原子整数:底层使用CAS+重试的机制
private AtomicInteger balance;
public AccountCas(int blance){
this.balance=new AtomicInteger(blance);
}
@Override
public Integer getBlance() {
return balance.get();
}
@Override
public void withdraw(Integer amount) {
while(true){
//获得修改前的值
int prev=balance.get();
//获取修改后的值
int next =prev-amount;
//比较并设置值
if(balance.compareAndSet(prev,next)){
break;
}
}
}
}
interface Account{
//获取余额
Integer getBlance();
//取款
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.getBlance()
+ "cost:"+(end-start)/1000_000 +"ms");
}
}
使用ReentranLock
class AccountReenLock implements Account{
ReentrantLock lock =new ReentrantLock();
private Integer balance;
public AccountReenLock(int balance){
this.balance=balance;
}
@Override
public Integer getBlance() {
return this.balance;
}
@Override
public void withdraw(Integer amount) {
lock.lock();
try {
balance-=amount;
}finally {
lock.unlock();
}
}
}
解决思路-无锁
- 上面的代码使用
synchronize加锁
操作来保证线程安全
, 但是synchronize加锁太消耗资源(因为底层使用了操作系统mutex指令,造成内核态和用户态的切换),这里我们使用无锁
来解决此问题
class AccountCas implements Account{
//使用原子整数:底层使用CAS+重试的机制
private AtomicInteger balance;
public AccountCas(int blance){
this.balance=new AtomicInteger(blance);
}
@Override
public Integer getBlance() {
return balance.get();
}
@Override
public void withdraw(Integer amount) {
while(true){
//获得修改前的值
int prev=balance.get();
//获取修改后的值
int next =prev-amount;
//比较并设置值
if(balance.compareAndSet(prev,next)){
break;
}
}
}
}
结果如下:
0cost:670ms //synchronize
0cost:562ms //CAS自旋锁
0cost:644ms //ReentranLock锁
CAS与volatile(重点
)
使用原子操作来保证线程访问共享资源的安全性,cas+重试的机制来确保(乐观锁思想),相对于悲观锁思想的synchronize,ReentranLock
来说,cas的方式效率更好!
cas+重试的原理
- 前面看到的
AtomicInteger
的解决方法,内部并没有用锁
来保护共享变量
的线程安全。那么它是如何实现的呢?
@Override
public void withdraw(Integer amount) {
// 核心代码
// 需要不断尝试,直到成功为止
while (true){
// 比如拿到了旧值 1000
int prev = balance.get();
// 在这个基础上 1000-10 = 990
int next = prev - amount;
/*
compareAndSet 保证操作共享变量安全性的操作:
① 线程A首先获取balance.get(),拿到当前的balance值prev
② 根据这个prev值 - amount值 = 修改后的值next
③ 调用compareAndSet方法, 首先会判断当初拿到的prev值,是否和现在的
balance值相同;
3.1、如果相同,表示其他线程没有修改balance的值, 此时就可以将next值
设置给balance属性
3.2、如果不相同,表示其他线程也修改了balance值, 此时就设置next值失败,
然后一直重试, 重新获取balance.get()的值,计算出next值,
并判断本次的prev和balnce的值是否相同...重复上面操作
*/
if (atomicInteger.compareAndSet(prev,next)){
break;
}
}
}
- 其中的关键是
compareAndSwap(比较并且设置值)
,它的简称就是CAS
(也有Compare And Swap的说法),它必须是原子操作
流程:
- 当一个线程要去修改
Account对象
中的值时,先获取值prev(调用get方法)
,然后在讲其设置为新的值next
(调用cas方法)。在调用cas方法时,会将prev
与Account中的余额
进行比较。- 如果两者
相等
,就说明该值还未被其他线程修改,此时便可以进行修改操作。 - 如果两者
不相等
,就不设置值,重新获取值prev(调用get方法),然后再将其设置为新的next值(cas方法),直到修改成果为止。
- 如果两者
注意:
- 其实
CAS
的底层就是 lock cmpxchg指令(X86架构),在单核CPU和多核CPU下都能狗保证[比较-交换]
的原子性
。 - 在多核状态下,某个和执行到带lock的指令时, CPU会让总线锁定,当这个核把此指令执行完毕,再开启总线。这个过程中不会被线程的调度机制所打断,保证了多个线程对内存操作的准确性,是原子的。
volatile的作用
- 在上面代码中的
AtomicInteger类
,**保存值的value属性
使用了volatile修饰
。**获取共享变量时,为了保证该变量的可见性
,需要使用 volatile修饰。 - volatile可以用来修饰 **成员变量和静态成员变量,**他可以避免线程从自己的工作缓存中查找变量的值。必须要到主存中获取它的值,线程操作volatile变量都是直接操作主存。
即一个线程对volatile变量的修改,对另一个线程可见。
注意
:volatile仅仅保证了共享变量的可见性
,让其他线程能够看到最新值,但不能仅仅指令交错问题(不能保证原子性)
CAS必须借助volatile才能读取到共享变量的最新值来实现【比较并交换】的效果
为什么CAS+重试(无锁)效率高
使用CAS+重试---无锁
情况下,即使重试失败
,线程始终在高速运行,没有停歇,而synchronize
会让线程在没有获得锁的时候,发生上下文切换,进入阻塞
- 打个比喻:线程就好像高速跑到上的赛车,高速运行时,速度超快,一旦发生上下文切换,就好比赛车要减速、熄火,等被唤醒又得重新打火、启动、加速…恢复到高速运行,代价比较大
- 但无锁情况下,因为线程要保持运行,需要额外CPU的支持,CPU在这里就好比高速跑道,没有额外的跑道,线程想高速运行也无从谈起,虽然不会进入阻塞,但由于没有分到时间片,仍然会进入可运行状态,还是会导致上下文切换。
CAS的特点(乐观锁和悲观锁的特点)
结合CAS和volatile
可以实现无锁并发
,适用于线程数少、多核CPU
的场景下。
-
CAS
是基于乐观锁
的思想:最乐观的估计,不怕别的线程来修改共享变量,就算改了也没关系,我吃点亏再重试呗。 -
synchronize
是基于悲观锁
的思想:最悲观的估计,得放着其他线程来修改共享变量,我上了锁你们都别想该,我改完了解开锁,你们才有机会。 -
CAS的体现是
无锁并发、无阻赛并发
,请仔细体会这两句话的意识- 因为没有使用synchronize,所以线程不会阻塞,这是效率提高的因素之一
- 但如果竞争激烈(写操作多),可以想到重试必然频繁发生,反而效率会受影响
、
原子整数(内部通过CAS来实现-AtomicInteger)
java.util.concurrent.atomic并发包
提高了一些开发工具类,这里把它分为五类:- 使用原子的方式(共享数据为基本数据类型的原子类)
AtomicInteger:
整形原子量- AtomicLong:长整型原子类
- AtomicBoolean:布尔型原子类
- 上面三个类提供的方法几乎相同,所以我们将从
AtomicInteger
为例子来介绍先讨论原子整数类
,以AtomicInteger
为例讨论它的api接口:通过观察源码可以发现 - AtomicInteger内部都是通过
cas原理
来实现的
package com.atomic;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author HillCheung
* @version 1.0
* @date 2021/4/13 18:28
*/
public class Test2 {
public static void main(String[] args) {
AtomicInteger i =new AtomicInteger(0);
//获取并自增(i=0,结果i=1,返回0),类似于i++
System.out.println(i.getAndIncrement());
//自增并获取(i=1,结果i=2,返回2),类似于++i
System.out.println(i.incrementAndGet());
//自减并获取(i=2,结果i=1,返回1)类似于--i
System.out.println(i.decrementAndGet());
//获取并之间()
System.out.println(i.getAndDecrement());
//获取并加值
System.out.println(i.getAndAdd(5));
System.out.println(i.addAndGet(-5));
System.out.println(i.getAndUpdate(p ->p-2));
System.out.println(i.updateAndGet(p ->p+2));
System.out.println(i.getAndAccumulate(10,(p,x)->p+x));
System.out.println(i.accumulateAndGet(-10,(p,x)->p+x));
}
}
举个例子: updateAndGet的实现
package com.atomic;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.IntUnaryOperator;
/**
* @author HillCheung
* @version 1.0
* @date 2021/4/13 21:00
*/
public class Test3 {
public static void main(String[] args) {
AtomicInteger i =new AtomicInteger(5);
updateAndGet(i,(operand -> {
System.out.println(operand/2);
return operand/2;
}));
}
public static void updateAndGet(AtomicInteger i, IntUnaryOperator operator){
while(true){
int prev=i.get(); //5
int next =operator.applyAsInt(prev);
if(i.compareAndSet(prev,next)){
break;
}
}
}
}
步骤:
- 调用
updateAndGet
方法,将共享变量i
,IntUnaryOperator对象
传递过去 - updateAndGet方法内部,传过来的
operator
对象,调用IntUnaryOperator中的applyAsInt
方法,实际调用的就是传递过来的对象的方法,进行/操作
原子引用(AtomicRefe)
原子引用的作用:保证应用类型的共享变量是线程安全的(确保这个原子引用没有引用过别人)
- 为什么需要原子引用类型?(引用数据类型原子类)
AtomicReference
AtomicMarkableReference
AtomicStamedReference
(可以解决ABA问题)
为什么需要原子引用类型
?保证引用类型的共享变量是线程安全的
(确保这个原子引用没有引用过别人。)
基本类型原子类只能更新一个变量,如果需要原子更新多个变量,需要使用引用类型原子类。
- **AtomicReference:**引用类型原子类
- **AtomicStampedReference:**原子更新带有
版本号
的引用类型。该类将整数值与应用类型关联起来,可用于解决原子的更新数据和数据的版本号,可以解决使用CAs进行原子更新时可能出现的ABA问题。
- AtomicMarkableReference :原子更新带有
标记
的引用类型。该类将boolean标记与引用关联起来~~,也可以解决使用CAS进行原子更新可能出现的ABA问题。~~
例子:使用原子引用实现BigDecimal存储款的线程安全:
下面这个是不安全的实现过程:
public class DecimalAccountUnsafe implements DecimalAccount{
BigDecimal balance;
@Override
public BigDecimal getBalance() {
return balance;
}
@Override
public void withdraw(BigDecimal amount) {
BigDecimal balance =this.getBalance();
this.balance=balance.subtract(amount);
}
}
解决代码如下:在AtomicReference类
中,存在一个value类型的变量,保存对BigDecimal对象的引用。
package com.atomic;
import java.math.BigDecimal;
import java.util.concurrent.atomic.AtomicReference;
/**
* @author HillCheung
* @version 1.0
* @date 2021/4/13 21:15
*/
public class Test4 {
public static void main(String[] args) {
DecimalAccount.demo(new DecimalAccountCas(new BigDecimal("10000")));
DecimalAccount.demo(new DecimalAccountUnsafe(new BigDecimal("10000")));
}
}
class DecimalAccountCas implements DecimalAccount{
//原子引用,泛型类型为小数类型
private final AtomicReference<BigDecimal> balance;
DecimalAccountCas(BigDecimal balance) {
this.balance = new AtomicReference(balance);
}
@Override
public BigDecimal getBalance() {
return balance.get();
}
@Override
public void withdraw(BigDecimal amount) {
while (true){
BigDecimal prev =balance.get();
BigDecimal next = prev.subtract(amount);
if(balance.compareAndSet(prev, next)){
break;
}
}
}
}
结果如下:
0
210
ABA问题及解决(重点
)
- 如下程序所示,虽然 在other方法中存在两个线程对共享变量进行了修改,但是修改之后又变成了原值,main线程对
修改过共享变量的过程
是不可见的,这种操作这对业务代码并无影响。
public class Test1 {
static AtomicReference<String> 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改为C
System.out.println("change A->C " + ref.compareAndSet(pre, "C"));
}).start();
}
static void other() throws InterruptedException {
new Thread(() -> {
// 此时ref.get()为A,此时共享变量ref也是A,没有被改过, 此时CAS
// 可以修改成功, B
System.out.println("change A->B " + ref.compareAndSet(ref.get(), "B"));
}).start();
Thread.sleep(500);
new Thread(() -> {
// 同上, 修改为A
System.out.println("change B->A " + ref.compareAndSet(ref.get(), "A"));
}).start();
}
}
AtomicMarkableReference(标记cas的共享变量是否修改过)
AtomicMarkableReference
可以给原子引用
加上版本号
,追踪原子引用整个的变化过程,如:A->B->A->c,通过AtomicMarkableReference,我们可以知道,引用变量中途被更改了几次。- 但是有时候,
并不关心引用变量更改了几次,只是单纯的关心是否被更改过
,所以就有了AtomicMarkableReference
@Slf4j
public class TestABAAtomicMarkableReference {
@SneakyThrows
public static void main(String[] args) {
GarbageBag bag =new GarbageBag("装满了垃圾");
//参数2 mark 可以看做一个标记,表示垃圾装满了
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("空垃圾袋");
while(!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;
}
@Override
public String toString() {
return "GarbageBag{" +
"desc='" + desc + '\'' +
'}';
}
}
运行结果如下
23:00:24.062 guizy.TestABAAtomicMarkableReference [main] - 主线程 start...
23:00:24.069 guizy.TestABAAtomicMarkableReference [main] - com.guizy.cas.GarbageBag@2be94b0f 装满了垃圾
23:00:24.312 guizy.TestABAAtomicMarkableReference [Thread-0] - 打扫卫生的线程 start...
23:00:24.313 guizy.TestABAAtomicMarkableReference [Thread-0] - com.guizy.cas.GarbageBag@2be94b0f 空垃圾袋
23:00:25.313 guizy.TestABAAtomicMarkableReference [main] - 主线程想换一只新垃圾袋?
23:00:25.314 guizy.TestABAAtomicMarkableReference [main] - 换了么?false
23:00:25.314 guizy.TestABAAtomicMarkableReference [main] - com.guizy.cas.GarbageBag@2be94b0f 空垃圾袋
举例2
package com.atomic;
import java.util.concurrent.atomic.AtomicMarkableReference;
/**
* @author HillCheung
* @version 1.0
* @date 2021/4/14 11:34
*/
public class TestAtomicMarkableReferenceABA2 {
//指定版本号
static AtomicMarkableReference<String> ref =new AtomicMarkableReference<>("A",true);
public static void main(String[] args) {
new Thread(()->{
String pre= ref.getReference();
System.out.println("change");
other();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("change A->C mark"+ref
.compareAndSet(pre,"C",true
,false));
}).start();
}
private static void other() {
new Thread(()->{
System.out.println("change A-> A mark"+ref
.compareAndSet(ref.getReference(),"A",true,false));
}).start();
}
}
AtomicStampedReference和AtomicMarkableReference两的却比
AtomicStampedReference
需要我们传入整形变量
作为版本号
,来判定是否更改过AtomicMarkableReference
需要我们传入布尔变量
作为标记
,来判定是否更改过
原子数组(AtomicIntegerArray)
- 保证数组内的元素的
线程安全
- 使用原子的方式更新数组里面的某个元素
AtomicIntegerArray
:整形数组原子类AtomicLongArray
:长整形数组原子类AtomicReferenceArray:
引用类型数组原子类
上面三个类提供的方法几乎相同,所以我们这里以 AtomicIntegerArray
为例子来介绍。实例代码
- 普通数组内元素, 多线程访问造成安全问题
public class AtomicArrayTest {
public static void main(String[] args) {
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)->void
private 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);
}·
}
结果如下:
[9870, 9862, 9774, 9697, 9683, 9678, 9679, 9668, 9680, 9698]
- 使用
AtomicIntegerArray
来创建安全数组
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
);
[10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000]
字段更新器
保证多线程
访问同一个对象的成员变量
时,成员变量的线程安全性。
AtomicReferenceFieldUpdater
—引用类型的属性- AtomicIntegerFieldUpdater——整形的属性
- AtomicLongFiledUpdater——长整型的属性
注意:利用字段更新器,可以针对对象的某个域(Filed)进行原子操作
,只能配合volatile修饰
的字段使用,否则会出现异常。
Exception in thread "main" java.lang.IllegalArgumentException: Must be volatile type
- 例子
package com.atomic;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
/**
* @author HillCheung
* @version 1.0
* @date 2021/4/14 18:17
*/
@Slf4j
public 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;
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
'}';
}
}
true
true
Student{name='王五'}
原子累加器(LongAddr)(重点
)
LongAddr
- LongAccmulator
- DoubleAddr
- DoubleAccumulator
累加器性能比较AtomicLong,Longaddr
package com.atomic;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.LongAdder;
import java.util.function.Consumer;
import java.util.function.Supplier;
/**
* @author HillCheung
* @version 1.0
* @date 2021/4/15 14:55
*/
@Slf4j
public class TestAtomicLongAndLongAddr {
public static void main(String[] args) {
System.out.println("——-AtomicLong———");
for (int i = 0; i < 5; i++) {
demo(() ->new AtomicLong(),addr-> addr.getAndIncrement());
}
System.out.println("---LongAddr---");
for (int i = 0; i < 5; i++) {
demo(()->new LongAdder(),addr->addr.increment());
}
}
private static <T> void demo(Supplier<T> adderSuppier, Consumer<T> action){
T addr =adderSuppier.get();
long start =System.nanoTime();
ArrayList<Thread> tasks = new ArrayList<>();
//4个线程,没人累加50玩
for (int i = 0; i < 40; i++) {
tasks.add(new Thread(()->{
for (int j = 0; j < 500000; j++) {
action.accept(addr);
}
}));
}
tasks.forEach( t->t.start());
tasks.forEach( t->{
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
long end = System.nanoTime();
System.out.println(addr +"cost" +(end-start)/1000_000);
}
}
运行结果如下:
——-AtomicLong———
20000000cost394
20000000cost408
20000000cost397
20000000cost375
20000000cost413
---LongAddr---
20000000cost54
20000000cost46
20000000cost47
20000000cost44
20000000cost50
Process finished with exit code 0
LongAddr
- **性能提升的原因很简单,就是在有竞争时,设置多个
累加单元
(但不会超过CPU的核心数),Thread-0累加Cell[0].而Thread-1累加Cell[1]…最终结果汇总,这样他们在累加时操作的不同的Cell变量,因此减少了CAS重试失败
,从而提高性能。 **
AtomicLong
- 之前AtomicLong等都是在一个
共享资源变量
上进行竞争,while(true)
循环进行CAS重试,性能没有LongAdder
高
LongAddr原理(重点
)
原理之伪共享
- 缓存行伪共享得从
缓存
说起 - 缓存与内存的速度比较
- 因为
CPU与内存的速度差异很大
,需要靠预测数据
至 缓存来提高效率,而缓存以 缓存行为单位,每个缓存行对应着一块内存,一般是 64 byte(8个long)缓存的加入会造成数据副本的产生,即同一份数据会缓存在不同核心的缓存行中 - CPU要保证数据的 一致性(缓存一致性),如果某个CPU核心 更改了数据,其他CPU核心对应的整个缓存行必须 失效
- 因为Cell是数组形式,在内存中连续存储的,一个Cell为24字节(16字节的对象头和8字节的value),因此缓存航可以存下2个Cell对象。这样问题就来了:
- Core-0要修改Cell【0】
- Core-1要修改Cell【1】
无论谁修改成果,都会导致对方Core的缓存行失效。
- 比如Core-0中Cell【0】=6000,cell【1】=8000要累加cell【0】=6001,cell【1】=8000,这时会让Core-1的缓存行失效
- @sun.misc.Contended用来解决这个问题,它的原理是在使用此注解的对象或字段的 前后各增加128字节大小的padding(空白),从而让CPU将对象预读至缓存时 占用不同的缓存行,这样,就不会造成对方缓存的失效。
源码:
public void add(long x) {
Cell[] as; long b, v; int m; Cell a;
if ((as = cells) != null || !casBase(b = base, b + x)) {
boolean uncontended = true;
if (as == null || (m = as.length - 1) < 0 ||
(a = as[getProbe() & m]) == null ||
!(uncontended = a.cas(v = a.value, v + x)))
longAccumulate(x, null, uncontended);
}
}
累加流程图
Unsafe(重点
)
- Unsafe对象提供了非常底层的,操作内存、线程的方法,Unsafe对象不能直接调用,只能通过
反射
获得 - 可以发现
AtomicInteger
以及其他的原子类,底层都是使用Unsafe
类
- 使用底层的
Unsafe
实现原子操作
package com.atomic;
import sun.misc.Unsafe;
import java.lang.reflect.Constructor;
/**
* @author HillCheung
* @version 1.0
* @date 2021/4/15 15:26
*/
public class TestUnsafe {
public static void main(String[] args) throws Exception{
//通过反射获得对象
Class unsafeClass= Unsafe.class;
//获取构造函数,Unsafe的构造函数为私有的.
Constructor constructor = unsafeClass.getDeclaredConstructor();
//设置为允许访问私有内容
constructor.setAccessible(true);
//创建Unsafe对象
Unsafe uns = (Unsafe) constructor.newInstance();
//创建Person对象
Person person =new Person();
//获取其属性name的偏移量
long nameOffset = uns.objectFieldOffset(person.getClass().getDeclaredField("name"));
long age = uns.objectFieldOffset(person.getClass().getDeclaredField("age"));
//通过unsafe的CAS操作来改变值
uns.compareAndSwapObject(person,nameOffset,null,"HilLCheung");
uns.compareAndSwapInt(person,age,0,22);
System.out.println(person);
}
}
class Person{
//配合CAS操作,必须使用volatile修饰
volatile String name;
volatile int age;
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}