5.1 问题提出
有如下需求,保证 account.withdraw 取款方法的线程安全
public interface Account {
/**
* 获取余额
*
* @return
*/
Integer getBalance();
/**
* 取款
*
* @param amount
*/
void withdraw(Integer amount);
/**
* 测试方法:启动1000个线程 每个线程-10元
* 如果初始余额为10000的话 最终正确结果应该是0
*
* @param account
*/
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::start);
ts.forEach(thread -> {
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
long end = System.nanoTime();
System.out.println("剩余:" + account.getBalance() + " cost:" +
(end - start) / 1000_000 + " ms");
}
}
线程不安全的实现如下
public class AccountUnsafe implements Account {
private Integer balance;
public AccountUnsafe(Integer balance) {
this.balance = balance;
}
@Override
public Integer getBalance() {
return balance;
}
@Override
public void withdraw(Integer amount) {
balance -= amount;
}
public static void main(String[] args) {
Account.demo(new AccountUnsafe(10000));
// 剩余:420 cost:133 ms
}
}
5.1.1 解决思路-锁
给account对象加锁
public class AccountSyncLock implements Account {
private Integer balance;
public AccountSyncLock(Integer balance) {
this.balance = balance;
}
@Override
public synchronized Integer getBalance() {
return balance;
}
@Override
public synchronized void withdraw(Integer amount) {
balance -= amount;
}
public static void main(String[] args) {
Account.demo(new AccountSyncLock(10000));
// 剩余:0 cost:141 ms
}
}
5.1.2 解决思路-无锁
public class AccountAtomicInteger implements Account {
private final AtomicInteger balance;
public AccountAtomicInteger(Integer balance) {
this.balance = new AtomicInteger(balance);
}
@Override
public Integer getBalance() {
return balance.get();
}
@Override
public void withdraw(Integer amount) {
//balance.addAndGet(-1 * amount);
// 与下面方法等价
while (true) {
int prev = balance.get();
int next = prev - amount;
if (balance.compareAndSet(prev, next)) {
break;
}
}
}
public static void main(String[] args) {
Account.demo(new AccountAtomicInteger(10000));
//剩余:0 cost:135 ms
}
}
5.2 CAS 与 volatile
前面看到的 AtomicInteger 的解决方法,内部并没有用锁来保护共享变量的线程安全。那么它是如何实现的呢?
public void withdraw(Integer amount) {
// 需要不断尝试,直到成功为止
while (true) {
// 比如拿到了旧值 1000
int prev = balance.get();
// 在这个基础上 1000-10 = 990
int next = prev - amount;
/*
compareAndSet 正是做这个检查,在 set 前,先比较 prev 与当前值
- 不一致了,next 作废,返回 false 表示失败
别的线程已经做了减法,当前值已经被减成了 990
本线程的这次 990 就作废了,进入 while 下次循环重试
- 一致,以 next 设置为新值,返回 true 表示成功
*/
if (balance.compareAndSet(prev, next)) {
break;
}
}
}
其中的关键是 CompareAndSet,它的简称就是 CAS (也有 CompareAndSwap 的说法),它必须是原子操作。
![image.png](https://img-blog.csdnimg.cn/img_convert/858a8de18647e58799e16a0e7a26e3af.png#averageHue=#fbfbfa&clientId=ud12f13e3-0e42-4&errorMessage=unknown error&from=paste&height=657&id=u7bf3cec3&name=image.png&originHeight=821&originWidth=760&originalType=binary&ratio=1&rotation=0&showTitle=false&size=90255&status=error&style=none&taskId=ub4e42145-e5ff-45b5-ae03-b3892eda555&title=&width=608)
5.2.1 慢动作分析
@Slf4j(topic = "c.SlowMotion")
public class SlowMotion {
public static void main(String[] args) {
AtomicInteger balance = new AtomicInteger(10000);
int mainPrev = balance.get();
log.debug("try get {}", mainPrev);
new Thread(() -> {
Sleeper.sleep(1);
int prev = balance.get();
balance.compareAndSet(prev, 9000);
log.debug(balance.toString());
}, "t1").start();
Sleeper.sleep(2);
log.debug("try set 8000...");
boolean success = balance.compareAndSet(mainPrev, 8000);
log.debug("is success? {}", success);
if (!success) {
mainPrev = balance.get();
log.debug("try set 8000...");
success = balance.compareAndSet(mainPrev, 8000);
log.debug("is success? {}", success);
}
}
}
22:02:53.255 c.SlowMotion [main] - try get 10000
22:02:54.317 c.SlowMotion [t1] - 9000
22:02:55.302 c.SlowMotion [main] - try set 8000...
22:02:55.302 c.SlowMotion [main] - is success? false
22:02:55.302 c.SlowMotion [main] - try set 8000...
22:02:55.302 c.SlowMotion [main] - is success? true
5.2.2 volatile
获取共享变量时,为了保证该变量的可见性,需要使用 volatile 修饰。
它可以用来修饰成员变量和静态成员变量,他可以避免线程从自己的工作缓存中查找变量的值,必须到主存中获取
它的值,线程操作 volatile 变量都是直接操作主存。即一个线程对 volatile 变量的修改,对另一个线程可见。
:::info
volatile 仅仅保证了共享变量的可见性,让其它线程能够看到最新值,但不能解决指令交错问题(不能保证原子性)
:::
CAS 必须借助 volatile 才能读取到共享变量的最新值来实现【比较并交换】的效果
5.2.3 为什么无锁效率高
- 无锁情况下,即使重试失败,线程始终在高速运行,没有停歇,而 synchronized 会让线程在没有获得锁的时候,发生上下文切换,进入阻塞。打个比喻线程就好像高速跑道上的赛车,高速运行时,速度超快,一旦发生上下文切换,就好比赛车要减速、熄火,等被唤醒又得重新打火、启动、加速… 恢复到高速运行,代价比较大
- 但无锁情况下,因为线程要保持运行,需要额外 CPU 的支持,CPU 在这里就好比高速跑道,没有额外的跑道,线程想高速运行也无从谈起,虽然不会进入阻塞,但由于没有分到时间片,仍然会进入可运行状态,还是会导致上下文切换。
![image.png](https://img-blog.csdnimg.cn/img_convert/e51a8a9d8b8ebda668028888b5fa67bd.png#averageHue=#d2d0d0&clientId=ud12f13e3-0e42-4&errorMessage=unknown error&from=paste&height=482&id=u86af18e1&name=image.png&originHeight=603&originWidth=846&originalType=binary&ratio=1&rotation=0&showTitle=false&size=121025&status=error&style=none&taskId=ucdb16d28-ca59-45bb-8ae6-f20662bc57f&title=&width=676.8)
5.2.4 CAS特点
结合 CAS 和 volatile 可以实现无锁并发,适用于线程数少、多核 CPU 的场景下。
- CAS 是基于乐观锁的思想:最乐观的估计,不怕别的线程来修改共享变量,就算改了也没关系,我吃亏点再重试呗。
- synchronized 是基于悲观锁的思想:最悲观的估计,得防着其它线程来修改共享变量,我上了锁你们都别想改,我改完了解开锁,你们才有机会。
- CAS 体现的是无锁并发、无阻塞并发,请仔细体会这两句话的意思
- 因为没有使用 synchronized,所以线程不会陷入阻塞,这是效率提升的因素之一
- 但如果竞争激烈,可以想到重试必然频繁发生,反而效率会受影响
5.3 原子整数
J.U.C 并发包提供了:
AtomicBoolean
AtomicInteger
AtomicLong
以 AtomicInteger 为例
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());
// 获取并自减(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 中因此不必是 final
System.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));
5.4 原子引用
为什么需要原子引用类型?
- AtomicReference
- AtomicMarkableReference
- AtomicStampedReference
有如下方法:
public interface DecimalAccount {
/**
* 获取余额
*
* @return
*/
BigDecimal getBalance();
/**
* 取款
*
* @param amount
*/
void withdraw(BigDecimal amount);
static void demo(DecimalAccount account) {
List<Thread> ts = new ArrayList<>();
long start = System.nanoTime();
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();
}
});
long end = System.nanoTime();
System.out.println("剩余:" + account.getBalance() + " cost:" +
(end - start) / 1000_000 + " ms");
}
}
5.4.1 不安全实现
public class DecimalAccountUnsafe implements DecimalAccount {
private BigDecimal balance;
public DecimalAccountUnsafe(String balance) {
this.balance = new BigDecimal(balance);
}
@Override
public BigDecimal getBalance() {
return balance;
}
@Override
public void withdraw(BigDecimal amount) {
BigDecimal balance = this.getBalance();
this.balance = balance.subtract(amount);
}
public static void main(String[] args) {
DecimalAccount.demo(new DecimalAccountUnsafe("10000"));
//剩余:930 cost:146 ms
}
}
5.4.2 锁实现
public class DecimalAccountSyncLock implements DecimalAccount {
private BigDecimal balance;
public DecimalAccountSyncLock(String balance) {
this.balance = new BigDecimal(balance);
}
@Override
public synchronized BigDecimal getBalance() {
return balance;
}
@Override
public synchronized void withdraw(BigDecimal amount) {
BigDecimal balance = this.getBalance();
this.balance = balance.subtract(amount);
}
public static void main(String[] args) {
DecimalAccount.demo(new DecimalAccountSyncLock("10000"));
//剩余:0 cost:148 ms
}
}
5.4.3 原子引用实现
public class DecimalAccountCas implements DecimalAccount {
private AtomicReference<BigDecimal> refAccount;
public DecimalAccountCas(String balance) {
this.refAccount = new AtomicReference<>(new BigDecimal(balance));
}
@Override
public BigDecimal getBalance() {
return refAccount.get();
}
@Override
public void withdraw(BigDecimal amount) {
while (true) {
BigDecimal prev = refAccount.get();
BigDecimal next = prev.subtract(amount);
if (refAccount.compareAndSet(prev, next)) {
break;
}
}
}
public static void main(String[] args) {
DecimalAccount.demo(new DecimalAccountCas("10000"));
// 剩余:0 cost:134 ms
}
}
5.4.4 ABA问题及解决
ABA问题
/**
* @author zhoujunlin
* @date 2022年07月01日 21:45
* @desc
* 21:53:50.189 c.ABAProblem [main] - main start...
* 21:53:50.252 c.ABAProblem [t1] - change A->B true
* 21:53:51.255 c.ABAProblem [t2] - change B->A true
* 21:53:53.256 c.ABAProblem [main] - change A ->C true
*/
@Slf4j(topic = "c.ABAProblem")
public class ABAProblem {
static AtomicReference<String> ref = new AtomicReference<>("A");
public static void main(String[] args) {
log.debug("main start...");
// 获取值A
// 这个共享变量被其他线程修改过?
String mainPrev = ref.get();
// A->B B->A
other();
Sleeper.sleep(2);
// 尝试修改为C
// 可以修改成功
log.debug("change A ->C {}", ref.compareAndSet(mainPrev, "C"));
}
private static void other() {
new Thread(() -> {
log.debug("change A->B {}", ref.compareAndSet(ref.get(), "B"));
}, "t1").start();
Sleeper.sleep(1);
new Thread(() -> {
log.debug("change B->A {}", ref.compareAndSet(ref.get(), "A"));
}, "t2").start();
}
}
主线程仅能判断出共享变量的值与最初值 A 是否相同,不能感知到这种从 A 改为 B 又 改回 A 的情况,如果主线程希望只要有其它线程【动过了】共享变量,那么自己的 cas 就算失败,这时,仅比较值是不够的,需要再加一个版本号。
5.4.5 AtomicStampedReference
/**
* @author zhoujunlin
* @date 2022年07月01日 21:45
* @desc
* 22:01:33.122 c.ABAStampedRef [main] - main start...
* 22:01:33.122 c.ABAStampedRef [main] - 值:A, 版本号:0
* 22:01:33.153 c.ABAStampedRef [t1] - change A->B true
* 22:01:34.169 c.ABAStampedRef [t2] - change B->A true
* 22:01:36.169 c.ABAStampedRef [main] - change A ->C false
*/
@Slf4j(topic = "c.ABAStampedRef")
public class ABAStampedRef {
/**
* 初始值+版本号
*/
static AtomicStampedReference<String> ref = new AtomicStampedReference<>("A", 0);
public static void main(String[] args) {
log.debug("main start...");
// 获取值A
String mainPrev = ref.getReference();
// 获取版本号
int mainStamp = ref.getStamp();
log.debug("值:{}, 版本号:{}", mainPrev, mainStamp);
// A->B B->A
other();
Sleeper.sleep(2);
// 尝试修改为C且版本号+1
log.debug("change A ->C {}", ref.compareAndSet(mainPrev, "C", mainStamp, ++mainStamp));
}
private static void other() {
new Thread(() -> {
log.debug("change A->B {}", ref.compareAndSet(ref.getReference(), "B", ref.getStamp(), ref.getStamp() + 1));
}, "t1").start();
Sleeper.sleep(1);
new Thread(() -> {
log.debug("change B->A {}", ref.compareAndSet(ref.getReference(), "A", ref.getStamp(), ref.getStamp() + 1));
}, "t2").start();
}
}
AtomicStampedReference 可以给原子引用加上版本号,追踪原子引用整个的变化过程,如: A -> B -> A -> C ,通过AtomicStampedReference,我们可以知道,引用变量中途被更改了几次。
但是有时候,并不关心引用变量更改了几次,只是单纯的关心是否更改过,所以就有了AtomicMarkableReference。
5.4.6 AtomicMarkableReference
![image.png](https://img-blog.csdnimg.cn/img_convert/60ec10f5c8c8ee3c403738725e8cde17.png#averageHue=#fdfcfc&clientId=u6e967555-1dd0-4&errorMessage=unknown error&from=paste&height=367&id=u67bf00ee&name=image.png&originHeight=459&originWidth=469&originalType=binary&ratio=1&rotation=0&showTitle=false&size=32643&status=error&style=none&taskId=u2547226b-f82f-4eb8-905e-ab6253d092a&title=&width=375.2)
@Slf4j(topic = "c.ABAStampedRef")
public class ABAMarkableRef {
public static void main(String[] args) {
GarbageBag bag = new GarbageBag("装满了垃圾");
// TRUE 装满了垃圾
AtomicMarkableReference<GarbageBag> ref = new AtomicMarkableReference<>(bag, Boolean.TRUE);
log.debug("主线程start...");
// 此时 mainPrev == bag
GarbageBag mainPrev = ref.getReference();
log.debug(mainPrev.toString());
new Thread(() -> {
// 满垃圾袋->空垃圾袋 成功为止
log.debug("打扫卫生线程start...");
bag.setDesc("空垃圾袋");
while (!ref.compareAndSet(ref.getReference(), bag, Boolean.TRUE, Boolean.FALSE)) {
}
log.debug(bag.toString());
}, "打扫卫生线程").start();
Sleeper.sleep(1);
log.debug("主线程想换一只新垃圾袋");
// 此时已经是空垃圾袋了 mainPrev:空垃圾袋
boolean success = ref.compareAndSet(mainPrev, new GarbageBag("空垃圾袋"), Boolean.TRUE, Boolean.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 + '\'' +
'}';
}
}
输出:
:::info
22:11:25.217 c.ABAStampedRef [main] - 主线程start…
22:11:25.233 c.ABAStampedRef [main] - GarbageBag{desc=‘装满了垃圾’}
22:11:25.280 c.ABAStampedRef [打扫卫生线程] - 打扫卫生线程start…
22:11:25.280 c.ABAStampedRef [打扫卫生线程] - GarbageBag{desc=‘空垃圾袋’}
22:11:26.281 c.ABAStampedRef [main] - 主线程想换一只新垃圾袋
22:11:26.281 c.ABAStampedRef [main] - 换了么?false
22:11:26.281 c.ABAStampedRef [main] - GarbageBag{desc=‘空垃圾袋’}
:::
可以注释掉打扫卫生线程代码,再观察输出
:::info
22:20:37.114 c.ABAStampedRef [main] - 主线程start…
22:20:37.114 c.ABAStampedRef [main] - GarbageBag{desc=‘装满了垃圾’}
22:20:38.114 c.ABAStampedRef [main] - 主线程想换一只新垃圾袋
22:20:38.114 c.ABAStampedRef [main] - 换了么?true
22:20:38.114 c.ABAStampedRef [main] - GarbageBag{desc=‘空垃圾袋’}
:::
5.5 原子数组
- AtomicIntegerArray
- AtomicLongArray
- AtomicReferenceArray
有如下方法:
/**
* 测试安全/非安全数组
*
* @param arraySupplier 提供一个数组
* @param lengthFunc 获取数组的长度
* @param putConsumer 自增方法
* @param printConsumer 打印数组的方法
* @param <T>
*/
private static <T> void demo(
Supplier<T> arraySupplier,
Function<T, Integer> lengthFunc,
BiConsumer<T, Integer> putConsumer,
Consumer<T> printConsumer) {
// 操作线程
List<Thread> ts = new ArrayList<>();
// 获取数组
T array = arraySupplier.get();
// 获取数组长度
Integer length = lengthFunc.apply(array);
for (int i = 0; i < length; i++) {
// 对数组每个元素开启一个线程
ts.add(new Thread(() -> {
// 每个线程对数组中元素做+1操作(分散到每个元素上)
for (int j = 0; j < 10000; j++) {
putConsumer.accept(array, j % length);
}
}));
}
// 启动线程
ts.forEach(Thread::start);
ts.forEach(t -> {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
// 所有线程操作完毕 打印
// 如果数组是线程安全的 则 每个元素值为10000
// 如果数组是不安全的 则每个元素值 不定
printConsumer.accept(array);
}
不安全数组
public static void main(String[] args) {
demo(
() -> new int[10],
(array) -> array.length,
(array, index) -> array[index]++,
array -> System.out.println(Arrays.toString(array))
);
//[9635, 9592, 9558, 9555, 9562, 9569, 9631, 9561, 9566, 9577]
}
安全的数组
public static void main(String[] args) {
demo(
() -> new AtomicIntegerArray(10),
AtomicIntegerArray::length,
AtomicIntegerArray::incrementAndGet,
System.out::println
);
// [10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000]
}
5.6 字段更新器
- AtomicReferenceFieldUpdater // 域 字段
- AtomicIntegerFieldUpdater
- AtomicLongFieldUpdater
利用字段更新器,可以针对对象的某个域(Field)进行原子操作,只能配合 volatile 修饰的字段使用,否则会出现异常
Exception in thread "main" java.lang.IllegalArgumentException: Must be volatile type
public class AtomicUpdaterTest {
/**
* 属性需要有volatile修饰否则会有如下异常
* java.lang.IllegalArgumentException: Must be volatile type
*/
private volatile int intField;
public static void main(String[] args) {
AtomicIntegerFieldUpdater<AtomicUpdaterTest> intFieldUpdater =
AtomicIntegerFieldUpdater.newUpdater(AtomicUpdaterTest.class, "intField");
AtomicUpdaterTest atomicUpdaterTest = new AtomicUpdaterTest();
intFieldUpdater.compareAndSet(atomicUpdaterTest, 0, 10);
// 修改成功 0 -> 10
System.out.println(atomicUpdaterTest.intField); // 10
intFieldUpdater.compareAndSet(atomicUpdaterTest, 10, 20);
// 修改成功 10 -> 20
System.out.println(atomicUpdaterTest.intField); // 20
intFieldUpdater.compareAndSet(atomicUpdaterTest, 10, 30);
// 修改失败 10 -> 30
System.out.println(atomicUpdaterTest.intField); // 20
}
}
5.7 原子累加器
累加器性能比较
public class AtomicAdderTest {
private static <T> void demo(Supplier<T> adderSupplier, Consumer<T> action) {
T adder = adderSupplier.get();
long start = System.nanoTime();
List<Thread> ts = new ArrayList<>();
for (int i = 0; i < 40; i++) {
ts.add(new Thread(() -> {
for (int j = 0; j < 500000; j++) {
action.accept(adder);
}
}));
}
ts.forEach(Thread::start);
ts.forEach(t -> {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
long end = System.nanoTime();
System.out.println("累加结果:" + adder + " cost: " + (end - start) / 1000_000 + "ms");
}
public static void main(String[] args) {
demo(LongAdder::new, LongAdder::increment);
demo(AtomicLong::new, AtomicLong::incrementAndGet);
//累加结果:20000000 cost: 71ms
//累加结果:20000000 cost: 596ms
}
}
性能提升的原因很简单,就是在有竞争时,设置多个累加单元,Therad-0 累加 Cell[0],而 Thread-1 累加
Cell[1]… 最后将结果汇总。这样它们在累加时操作的不同的 Cell 变量,因此减少了 CAS 重试失败,从而提高性
能。
5.7.1 源码之 LongAdder
LongAdder 是并发大师 @author Doug Lea 的作品,设计的非常精巧
LongAdder 类有几个关键域
// 累加单元数组, 懒惰初始化
transient volatile Cell[] cells;
// 基础值, 如果没有竞争, 则用 cas 累加这个域
transient volatile long base;
// 在 cells 创建或扩容时, 置为 1, 表示加锁
transient volatile int cellsBusy;
cas锁
public class LockCas {
private AtomicInteger state = new AtomicInteger(0);
public void lock() {
while (true) {
if (state.compareAndSet(0, 1)) {
break;
}
}
}
public void unlock() {
log.debug("unlock...");
state.set(0);
}
}
测试
LockCas lock = new LockCas();
new Thread(() -> {
log.debug("begin...");
lock.lock();
try {
log.debug("lock...");
sleep(1);
} finally {
lock.unlock();
}
}).start();
new Thread(() -> {
log.debug("begin...");
lock.lock();
try {
log.debug("lock...");
} finally {
lock.unlock();
}
}).start()
18:27:07.198 c.Test42 [Thread-0] - begin...
18:27:07.202 c.Test42 [Thread-0] - lock...
18:27:07.198 c.Test42 [Thread-1] - begin...
18:27:08.204 c.Test42 [Thread-0] - unlock...
18:27:08.204 c.Test42 [Thread-1] - lock...
18:27:08.204 c.Test42 [Thread-1] - unlock...
5.7.2 原理之伪共享
其中 Cell 即为累加单元
// 防止缓存行伪共享
@sun.misc.Contended
static final class Cell {
volatile long value;
Cell(long x) { value = x; }
// 最重要的方法, 用来 cas 方式进行累加, prev 表示旧值, next 表示新值
final boolean cas(long prev, long next) {
return UNSAFE.compareAndSwapLong(this, valueOffset, prev, next);
}
// 省略不重要代码
}
得从缓存说起
缓存与内存的速度比较
![image.png](https://img-blog.csdnimg.cn/img_convert/e559e7a81c2c04037dcb601f790c067c.png#averageHue=#f9f9f9&clientId=ub16819cb-803c-4&errorMessage=unknown error&from=paste&height=443&id=u0df33260&name=image.png&originHeight=554&originWidth=868&originalType=binary&ratio=1&rotation=0&showTitle=false&size=78644&status=error&style=none&taskId=u811cdb15-7d84-4401-9883-447f1d34107&title=&width=694.4)![image.png](https://img-blog.csdnimg.cn/img_convert/7a1b6728c14e42dfb89cc709c89228fe.png#averageHue=#f8f8f7&clientId=ub16819cb-803c-4&errorMessage=unknown error&from=paste&height=227&id=u60f0b49e&name=image.png&originHeight=284&originWidth=851&originalType=binary&ratio=1&rotation=0&showTitle=false&size=39612&status=error&style=none&taskId=ucf238414-25f4-420e-b173-bd110000139&title=&width=680.8)
因为 CPU 与 内存的速度差异很大,需要靠预读数据至缓存来提升效率。
而缓存以缓存行为单位,每个缓存行对应着一块内存,一般是 64 byte(8 个 long)
缓存的加入会造成数据副本的产生,即同一份数据会缓存在不同核心的缓存行中
CPU 要保证数据的一致性,如果某个 CPU 核心更改了数据,其它 CPU 核心对应的整个缓存行必须失效
![image.png](https://img-blog.csdnimg.cn/img_convert/1b57219ca8667cbc22d048ab4098011d.png#averageHue=#dbe8ae&clientId=ub16819cb-803c-4&errorMessage=unknown error&from=paste&height=278&id=u3d07fd9b&name=image.png&originHeight=347&originWidth=860&originalType=binary&ratio=1&rotation=0&showTitle=false&size=68746&status=error&style=none&taskId=u1764e021-a574-49d2-9a67-90ba2415cbe&title=&width=688)
因为 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 将对象预读至缓存时占用不同的缓存行,这样,不会造成对方缓存行的失效
![image.png](https://img-blog.csdnimg.cn/img_convert/f5651a9b79d0d51a8906ae022b95f589.png#averageHue=#ddebaf&clientId=ub16819cb-803c-4&errorMessage=unknown error&from=paste&height=277&id=u1480431e&name=image.png&originHeight=346&originWidth=861&originalType=binary&ratio=1&rotation=0&showTitle=false&size=59450&status=error&style=none&taskId=uad15db6e-73cf-4989-9edc-5df617e48b6&title=&width=688.8)
累加主要调用下面的方法
public void add(long x) {
// as 为累加单元数组
// b 为基础值
// x 为累加值
Cell[] as; long b, v; int m; Cell a;
// 进入 if 的两个条件
// 1. as 有值, 表示已经发生过竞争, 进入 if
// 2. cas 给 base 累加时失败了, 表示 base 发生了竞争, 进入 if
if ((as = cells) != null || !casBase(b = base, b + x)) {
// uncontended 表示 cell 没有竞争
boolean uncontended = true;
if (
// as 还没有创建
as == null || (m = as.length - 1) < 0 ||
// 当前线程对应的 cell 还没有
(a = as[getProbe() & m]) == null ||
// cas 给当前线程的 cell 累加失败 uncontended=false ( a 为当前线程的 cell )
!(uncontended = a.cas(v = a.value, v + x))
) {
// 进入 cell 数组创建、cell 创建的流程
longAccumulate(x, null, uncontended);
}
}
}
add 流程图
![image.png](https://img-blog.csdnimg.cn/img_convert/f3f2b8aaaa56fb2d6e8b530b7d2107e1.png#averageHue=#fbfad9&clientId=ub16819cb-803c-4&errorMessage=unknown error&from=paste&height=231&id=u9aae6c29&name=image.png&originHeight=289&originWidth=828&originalType=binary&ratio=1&rotation=0&showTitle=false&size=47312&status=error&style=none&taskId=ue2150481-0581-4fad-8772-77586251995&title=&width=662.4)
final void longAccumulate(long x, LongBinaryOperator fn,
boolean wasUncontended) {
int h;
// 当前线程还没有对应的 cell, 需要随机生成一个 h 值用来将当前线程绑定到 cell
if ((h = getProbe()) == 0) {
// 初始化 probe
ThreadLocalRandom.current();
// h 对应新的 probe 值, 用来对应 cell
h = getProbe();
wasUncontended = true;
}
// collide 为 true 表示需要扩容
boolean collide = false;
for (;;) {
Cell[] as; Cell a; int n; long v;
// 已经有了 cells
if ((as = cells) != null && (n = as.length) > 0) {
// 还没有 cell
if ((a = as[(n - 1) & h]) == null) {
// 为 cellsBusy 加锁, 创建 cell, cell 的初始累加值为 x
// 成功则 break, 否则继续 continue 循环
}
// 有竞争, 改变线程对应的 cell 来重试 cas
else if (!wasUncontended)
wasUncontended = true;
// cas 尝试累加, fn 配合 LongAccumulator 不为 null, 配合 LongAdder 为 null
else if (a.cas(v = a.value, ((fn == null) ? v + x : fn.applyAsLong(v, x))))
break;
// 如果 cells 长度已经超过了最大长度, 或者已经扩容, 改变线程对应的 cell 来重试 cas
else if (n >= NCPU || cells != as)
collide = false;
// 确保 collide 为 false 进入此分支, 就不会进入下面的 else if 进行扩容了
else if (!collide)
collide = true;
// 加锁
else if (cellsBusy == 0 && casCellsBusy()) {
// 加锁成功, 扩容
continue;
}
// 改变线程对应的 cell
h = advanceProbe(h);
}
// 还没有 cells, 尝试给 cellsBusy 加锁
else if (cellsBusy == 0 && cells == as && casCellsBusy()) {
// 加锁成功, 初始化 cells, 最开始长度为 2, 并填充一个 cell
// 成功则 break;
}
// 上两种情况失败, 尝试给 base 累加
else if (casBase(v = base, ((fn == null) ? v + x : fn.applyAsLong(v, x))))
break;
}
}
longAccumulate 流程图
![image.png](https://img-blog.csdnimg.cn/img_convert/28869fd83dd878aae734d363e90b80a3.png#averageHue=#fafad8&clientId=ub16819cb-803c-4&errorMessage=unknown error&from=paste&height=184&id=u64d78a1a&name=image.png&originHeight=230&originWidth=793&originalType=binary&ratio=1&rotation=0&showTitle=false&size=31825&status=error&style=none&taskId=uaeba9bfc-de2c-44d4-ab35-8105a30b47f&title=&width=634.4)
![image.png](https://img-blog.csdnimg.cn/img_convert/31d78f7251288e763d117024efd249e6.png#averageHue=#fbfad9&clientId=ub16819cb-803c-4&errorMessage=unknown error&from=paste&height=190&id=ubb04601e&name=image.png&originHeight=238&originWidth=780&originalType=binary&ratio=1&rotation=0&showTitle=false&size=27310&status=error&style=none&taskId=uadd98f91-853f-4493-b86b-d78672cd936&title=&width=624)
每个线程刚进入 longAccumulate 时,会尝试对应一个 cell 对象(找到一个坑位)
![image.png](https://img-blog.csdnimg.cn/img_convert/7bf11db159e1e0f27447e679d74826b6.png#averageHue=#fafad9&clientId=ub16819cb-803c-4&errorMessage=unknown error&from=paste&height=248&id=u7a60bb52&name=image.png&originHeight=310&originWidth=825&originalType=binary&ratio=1&rotation=0&showTitle=false&size=46656&status=error&style=none&taskId=u2340c56f-0591-41e2-8bf2-d449cea0742&title=&width=660)
获取最终结果通过 sum 方法
public long sum() {
Cell[] as = cells; Cell a;
long sum = base;
if (as != null) {
for (int i = 0; i < as.length; ++i) {
if ((a = as[i]) != null)
sum += a.value;
}
}
return sum;
}
5.8 Unsafe
Unsafe 对象提供了非常底层的,操作内存、线程的方法,Unsafe 对象不能直接调用,只能通过反射获得
public final class UnsafeUtil {
private static final Unsafe unsafe;
static {
try {
final Field declaredField = Unsafe.class.getDeclaredField("theUnsafe");
declaredField.setAccessible(true);
unsafe = (Unsafe) declaredField.get(null);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
public static Unsafe getUnsafe() {
return unsafe;
}
}
Unsafe CAS 操作
public class UnsafeTest {
public static void main(String[] args) throws NoSuchFieldException {
Unsafe unsafe = UnsafeUtil.getUnsafe();
// 获取成员属性
Field id = Student.class.getDeclaredField("id");
Field name = Student.class.getDeclaredField("name");
// 获取成员变量偏移量
long idOffset = unsafe.objectFieldOffset(id);
long nameOffset = unsafe.objectFieldOffset(name);
Student student = new Student();
unsafe.compareAndSwapInt(student, idOffset, 0, 20);
unsafe.compareAndSwapObject(student, nameOffset, null, "张三");
System.out.println(student);
}
}
@Data
class Student {
volatile int id;
volatile String name;
}
使用自定义的 AtomicData 实现之前线程安全的原子整数 Account 实现
public class MyAtomicAccount implements Account {
private final AtomicData atomicData;
public MyAtomicAccount(Integer amount) {
this.atomicData = new AtomicData(amount);
}
@Override
public Integer getBalance() {
return atomicData.getData();
}
@Override
public void withdraw(Integer amount) {
atomicData.decrease(amount);
}
public static void main(String[] args) {
Account.demo(new MyAtomicAccount(10000));
//剩余:0 cost:135 ms
}
}
class AtomicData {
private final int data;
private static final Unsafe unsafe;
private static final long DATA_OFFSET;
static {
unsafe = UnsafeUtil.getUnsafe();
try {
DATA_OFFSET = unsafe.objectFieldOffset(AtomicData.class.getDeclaredField("data"));
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
}
}
public AtomicData(int data) {
this.data = data;
}
public int getData() {
return data;
}
public void decrease(int amount) {
int oldValue;
while (true) {
oldValue = this.data;
// cas尝试修改data
if (unsafe.compareAndSwapInt(this, DATA_OFFSET, oldValue, oldValue - amount)) {
break;
}
}
}
}