文章目录
1、引述
有如下需求,保证 account.withdraw 取款方法的线程安全
interface Account {
/**
* 获取余额
*
* @return Integer 余额
*/
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(t -> {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
long end = System.nanoTime();
System.out.println(account.getBalance()
+ " cost: " + (end - start) / 1000_000 + " ms");
}
}
不安全的实现,至于为什么,不细说,很明显
@Slf4j
public class Test {
public static void main(String[] args) {
Account.demo(new AccountUnsafe(10000));
}
}
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;
}
}
加锁的实现
class AccountUnsafe implements Account {
private Integer balance;
public AccountUnsafe(Integer balance) {
this.balance = balance;
}
@Override
public synchronized Integer getBalance() {
return balance;
}
@Override
public synchronized void withdraw(Integer amount) {
balance -= amount;
}
}
无锁的实现
class AccountCas implements Account {
private final AtomicInteger balance;
public AccountCas(Integer balance) {
this.balance = new AtomicInteger(balance);
}
@Override
public synchronized Integer getBalance() {
return balance.get();
}
@Override
public synchronized void withdraw(Integer amount) {
while (true) {
int prev = balance.get();
int next = prev - amount;
if (balance.compareAndSet(prev, next)) {
break;
}
}
// 可以简化为下面的方法
// balance.addAndGet(-1 * amount);
}
}
2、CAS 与 volatile
前面看到的 AtomicInteger 的解决方法,内部并没有用锁来保护共享变量的线程安全。那么它是如何实现的呢?
- compareAndSet 正是做这个检查,在 set 前,先比较 prev 与当前值,不一致了,next 作废,返回 false 表示失败,比如,别的线程已经做了减法,当前值已经被减成了 990 那么本线程的这次 990 就作废了,进入 while 下次循环重试,如果一致,以 next 设置为新值,返回 true 表示成功
其实 CAS 的底层是 lock cmpxchg 指令(X86 架构),在单核 CPU 和多核 CPU 下都能够保证【比较-交换】的原子性。
进入 AtomicInteger 源代码
获取共享变量时,为了保证该变量的可见性,需要使用 volatile 修饰。它可以用来修饰成员变量和静态成员变量,他可以避免线程从自己的工作缓存中查找变量的值,必须到主存中获取它的值,线程操作 volatile 变量都是直接操作主存。即一个线程对 volatile 变量的修改,对另一个线程可见。CAS 必须借助 volatile 才能读取到共享变量的最新值来实现【比较并交换】的效果
还可以看到我们使用无锁实现,比前两种实现效率都会高,为什么呢?
- 无锁情况下,即使重试失败,线程始终在高速运行,没有停歇(阻塞),而 synchronized 会让线程在没有获得锁的时候,发生上下文切换,进入阻塞,不过如果竞争激烈,可以想到重试必然频繁发生,反而效率会受影响
- CAS 是基于乐观锁的思想:最乐观的估计,不怕别的线程来修改共享变量,就算改了也没关系,我吃亏点再重试呗。
- synchronized 是基于悲观锁的思想:最悲观的估计,得防着其它线程来修改共享变量,我上了锁你们都别想改,我改完了解开锁,你们才有机会。
3、原子整数
JUC 并发包提供了:
- 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));
- 观察方法 updateAndGet 源码
public final int updateAndGet(IntUnaryOperator updateFunction) {
int prev, next;
do {
prev = get();
next = updateFunction.applyAsInt(prev);
} while (!compareAndSet(prev, next));
return next;
}
因为除了加减法后,还有乘除法,这一个操作被抽象为了一个函数式接口 IntUnaryOperator
这样一来,我们只需要将原始值以及更改后的值放入 IntUnaryOperator 中,然后 AtomicInteger 再调用 CAS 操作去修改
4、原子引用
为什么需要原子引用类型,因为数据不一定都是基本类型的,还有小数类型 BigDecimal,那么就可以使用原子引用类型,JUC 并发包提供了:
- AtomicReference
- AtomicMarkableReference
- AtomicStampedReference
4.1、AtomicReference
先做一个不使用 AtomicReference 取款的不安全实现
class DecimalAccountUnsafe implements DecimalAccount {
BigDecimal balance;
public DecimalAccountUnsafe(BigDecimal balance) {
this.balance = balance;
}
@Override
public BigDecimal getBalance() {
return balance;
}
@Override
public void withdraw(BigDecimal amount) {
BigDecimal balance = this.getBalance();
// 减法
this.balance = balance.subtract(amount);
}
}
interface 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());
}
}
使用锁来实现
@Override
public synchronized BigDecimal getBalance() {
return balance;
}
@Override
public synchronized void withdraw(BigDecimal amount) {
BigDecimal balance = this.getBalance();
this.balance = balance.subtract(amount);
}
使用 AtomicReference 实现
class DecimalAccountUnsafe implements DecimalAccount {
AtomicReference<BigDecimal> balance;
public DecimalAccountUnsafe(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;
}
}
}
}
4.2、ABA问题
执行如下代码
static AtomicReference<String> ref = new AtomicReference<>("A");
@SneakyThrows
public static void main(String[] args) {
log.info("main start...");
// 获取值 A
// 这个共享变量被它线程修改过?
String prev = ref.get();
other();
TimeUnit.SECONDS.sleep(1);
// 尝试改为 C
log.info("change A->C {}", ref.compareAndSet(prev, "C"));
}
@SneakyThrows
private static void other() {
new Thread(() -> {
log.info("change A->B {}", ref.compareAndSet(ref.get(), "B"));
}, "t1").start();
TimeUnit.MILLISECONDS.sleep(500);
new Thread(() -> {
log.info("change B->A {}", ref.compareAndSet(ref.get(), "A"));
}, "t2").start();
}
可以发现主线程仅能判断出共享变量的值与最初值 A 是否相同,不能感知到这种从 A 改为 B 又改回 A 的情况,对于绝大部分场景来讲,这对我们的业务没有任何影响,但是如果主线程希望,只要有其它线程【动过了】共享变量,那么自己的 CAS 就算失败,应该怎么办呢?这时候就不能使用 AtomicReference了,而是用 AtomicStampedReference
4.3、AtomicStampedReference
AtomicStampedReference 可以给原子引用加上版本号,追踪原子引用整个的变化过程,如: A -> B -> A -> C ,通过AtomicStampedReference,我们可以知道,引用变量中途被更改了几次。
static AtomicStampedReference<String> ref = new AtomicStampedReference<>("A", 0);
@SneakyThrows
public static void main(String[] args) {
log.info("main start...");
// 获取值 A
String prev = ref.getReference();
// 获取版本号
int stamp = ref.getStamp();
log.info("版本 {}", stamp);
// 如果中间有其它线程干扰,发生了 ABA 现象
other();
TimeUnit.SECONDS.sleep(1);
// 尝试改为 C
log.info("change A->C {}", ref.compareAndSet(prev, "C", stamp, stamp + 1));
}
@SneakyThrows
private static void other() {
new Thread(() -> {
log.info("change A->B {}", ref.compareAndSet(ref.getReference(), "B",
ref.getStamp(), ref.getStamp() + 1));
log.info("更新版本为 {}", ref.getStamp());
}, "t1").start();
TimeUnit.MILLISECONDS.sleep(500);
new Thread(() -> {
log.info("change B->A {}", ref.compareAndSet(ref.getReference(), "A",
ref.getStamp(), ref.getStamp() + 1));
log.info("更新版本为 {}", ref.getStamp());
}, "t2").start();
}
但是有时候,并不关心引用变量更改了几次,只是单纯的关心是否更改过,所以就有了 AtomicMarkableReference
4.4、AtomicMarkableReference
@Slf4j
public class Test {
@SneakyThrows
public static void main(String[] args) {
GarbageBag bag = new GarbageBag("装满了垃圾");
// 参数2 mark 可以看作一个标记,表示垃圾袋满了
AtomicMarkableReference<GarbageBag> ref = new AtomicMarkableReference<>(bag, true);
log.info("主线程 start...");
GarbageBag prev = ref.getReference();
log.info(prev.toString());
new Thread(() -> {
log.info("打扫卫生的线程 start...");
bag.setDesc("空垃圾袋");
while (!ref.compareAndSet(bag, bag, true, false)) {
}
log.info(bag.toString());
}).start();
Thread.sleep(1000);
log.info("主线程想换一只新垃圾袋?");
boolean success = ref.compareAndSet(prev, new GarbageBag("空垃圾袋"), true, false);
log.info("换了么?" + success);
log.info(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 super.toString() + " " + desc;
}
}
5、原子数组
对于前面讲的 AtomicReference 中如果存的是数组的话,我们便不能够线程安全的访问数组内部保存的各个元素,所以就有了原子数组
- AtomicIntegerArray
- AtomicLongArray
- AtomicReferenceArray
现在有如下方法
/**
* supplier 提供者 无中生有 ()->结果
* function 函数 一个参数一个结果 (参数)->结果 , BiFunction (参数1,参数2)->结果
* consumer 消费者 一个参数没结果 (参数)->void, BiConsumer (参数1,参数2)->void
*
* @param arraySupplier 提供数组、可以是线程不安全数组或线程安全数组
* @param lengthFun 获取数组长度的方法
* @param putConsumer 自增方法,回传 array, index
* @param printConsumer 打印数组的方法
*/
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++) {
ts.add(new Thread(() -> {
// 每个线程对数组作 10000 次操作
for (int j = 0; j < 10000; j++) {
// 分摊到每个元素 10000 / 10 = 1000 次自增,外层循环,10个线程分别自增1000
putConsumer.accept(array, j % length);
}
}));
}
// 启动所有线程
ts.forEach(Thread::start);
// 等所有线程结束
ts.forEach(t -> {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
printConsumer.accept(array);
}
5.1、不安全的实现
如果我们不用原子数组,使用普通数组:
demo(
() -> new int[10],
(array) -> array.length,
(array, index) -> array[index]++,
array -> System.out.println(Arrays.toString(array))
);
5.2、安全的实现
上例可以看到,普通数组在多线程的下是不安全的,现在我们使用 AtomicIntegerArray
demo(
() -> new AtomicIntegerArray(10),
(AtomicIntegerArray::length),
(AtomicIntegerArray::getAndIncrement),
atomicIntegerArray -> System.out.println(atomicIntegerArray.toString())
);
6、字段更新器
利用字段更新器,可以针对对象的某个域(Field)进行原子操作,JUC 并发包提供了:
- AtomicReferenceFieldUpdater // 域 字段
- AtomicIntegerFieldUpdater
- AtomicLongFieldUpdater
只能配合 volatile 修饰的字段使用,否则会出现异常
Exception in thread "main" java.lang.IllegalArgumentException: Must be volatile type
简单实现
@Slf4j
public class Test {
@SneakyThrows
public static void main(String[] args) {
Student student = new Student();
AtomicReferenceFieldUpdater<Student, String> updater =
AtomicReferenceFieldUpdater.newUpdater(Student.class, String.class, "name");
updater.compareAndSet(student, null, "两米以下皆凡人");
System.out.println(student.toString());
}
}
@Data
class Student {
volatile String name;
}
7、原子累加器
JDK 8 以后,新增了几个专门用来做累加的类,他们的性能要比诸如 AtomicInteger 的自增高很多
7.1、累加器性能比较
@Slf4j
public class Test {
@SneakyThrows
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
demo(
AtomicLong::new,
AtomicLong::incrementAndGet
);
}
for (int i = 0; i < 5; i++) {
demo(
LongAdder::new,
LongAdder::increment
);
}
}
private static <T> void demo(Supplier<T> adderSupplier, Consumer<T> action) {
T adder = adderSupplier.get();
long start = System.nanoTime();
List<Thread> ts = new ArrayList<>();
// 4 个线程,每人累加 50 万
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);
}
}
性能提升的原因很简单,就是在有竞争时,设置多个累加单元(根据竞争激烈程度合理分配,但是最终都不会超过系统核心数),Therad-0 累加 Cell[0],而 Thread-1 累加 Cell[1]… 最后将结果汇总。这样它们在累加时操作的不同的 Cell 变量,因此减少了 CAS 重试失败,从而提高性能。
7.2、LongAdder源码
LongAdder 类有几个关键域,详见其父类 Striped64
// 累加单元数组, 懒惰初始化
transient volatile Cell[] cells;
// 基础值, 如果没有竞争, 则用 cas 累加这个域
transient volatile long base;
// 在 cells 创建或扩容时, 置为 1, 表示加锁
transient volatile int cellsBusy;
7.2.1、CAS实现锁
cellsBusy 为什么表示加锁呢?CAS 不是没有锁的概念吗,其实用 CAS 是可以实现类似锁的机制的
仅作测试用,生产环境勿用
@Slf4j
class CasLock {
private final AtomicInteger state = new AtomicInteger(0);
public void lock() {
while (true) {
if (state.compareAndSet(0, 1)) {
break;
}
}
}
public void unlock() {
log.info("unlock...");
state.set(0);
}
}
测试
@SneakyThrows
public static void main(String[] args) {
CasLock lock = new CasLock();
new Thread(() -> {
log.info("begin...");
lock.lock();
try {
log.info("lock...");
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}).start();
new Thread(() -> {
log.info("begin...");
lock.lock();
try {
log.info("lock...");
} finally {
lock.unlock();
}
}).start();
}
其实 cellsBusy 在 LongAdder 中就等价于代码中的 state
7.2.2、Cell
//防止缓存行和伪共享
@sun.misc.Contended
static final class Cell {
volatile long value;
Cell(long x) { value = x; }
final boolean cas(long cmp, long val) {
return UNSAFE.compareAndSwapLong(this, valueOffset, cmp, val);
}
// 不安全的机制
private static final sun.misc.Unsafe UNSAFE;
private static final long valueOffset;
static {
try {
UNSAFE = sun.misc.Unsafe.getUnsafe();
Class<?> ak = Cell.class;
valueOffset = UNSAFE.objectFieldOffset
(ak.getDeclaredField("value"));
} catch (Exception e) {
throw new Error(e);
}
}
}
理解这段代码需要从缓存说起
其中缓存与内存的速度比较如下( cycle 表示时钟周期):
从 cpu 到 | 大约需要的时钟周期 |
---|---|
寄存器 | 1 cycle (4GHz 的 CPU 约为0.25ns) |
L1 | 3~4 cycle |
L2 | 10~20 cycle |
L3 | 40~45 cycle |
内存 | 120~240 cycle |
- 因为 CPU 与 内存的速度差异很大,需要靠预读数据至缓存来提升效率。
- 而缓存以缓存行为单位,每个缓存行对应着一块内存,一般是 64 byte(8 个 long)
- 缓存的加入会造成数据副本的产生,即同一份数据会缓存在不同核心的缓存行中
- CPU 要保证数据的一致性,如果某个 CPU 核心更改了数据,其它 CPU 核心对应的整个缓存行必须失效
比如,现有 Cell[2] 数组,因为一个 Cell 占用 24 字节(对象头 16 + long 8 字节),故两个单位的 Cell 占用 48 ,小于一个缓存行 64,所以对于两个 Core 来讲,他们分别都会将这个 Cell[2] 放入自己的缓存行,这样不管哪个 Core 修改了其中一个单元,另一个单元缓存都得失效,这样就不得不重新去内存中读取最新的 Cell[2] (因为 Cell[2] 存在于一行)
@sun.misc.Contended 用来解决这个问题,它的原理是在使用此注解的对象或字段的前后各增加 128 字节大小的 padding,从而让 CPU 将对象预读至缓存时占用不同的缓存行,这样,其中一个 Cell 修改了,并不会造成对方缓存行的失效
7.2.3、自增源码跟踪
入口选择 increment 方法
public void increment() {
add(1L);
}
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);
}
}
看着流程图我们来跟一下
- Cells 是懒加载的,也就是说,一开始还没有出现竞争的时候,Cells 还是 null,所以这个时候使用基础值 base 做 CAS 自增操作,对应的 casBase 方法,如果自增成功,就会直接 return,否则,进入第一层 if 分支,然后直接走到 longAccumulate 方法
- 如果出现过竞争了,则直接进入第一层 if 分支,然后开始根据当前线程判断对应的 Cell 单元是否存在,as[getProbe() & m],如果不存在,直接进入 longAccumulate 方法,如果创建过了,就使用该单元执行 CAS 操作,对应 uncontended = a.cas(v = a.value, v + x),当然,执行成功会直接返回,否则还是进入 longAccumulate 方法
上面步骤都涉及到了 longAccumulate 方法,现在我们进入此方法看看,由于这段代码太长,我们可以拆开来看
- 首先我们看第二个 if 分支,当 cells 还不存在的情况,就会从第一个分支转移到第二个分支,但是也需要 cells 没有被加锁,而且还没有被其他线程创建才会进去
graph
subgraph cells 创建
cells --> lock("加锁")
lock --成功--> create("创建 cells 并初始化一个 cell")
create --> return
lock --失败--> base("cas base 累加")
base --成功--> return
base --失败--> a
a("循环入口") --> cells("cells 不存在 且 未加锁 且 未新建")
end
- 加锁操作便是 casCellsBusy 方法执行的,如果加锁失败了,就会走到最后一个分支,直接对 base 自增,如果自增成功,便直接返回,否则死循环再次判断,如果加锁成功,进入第二个 if 分支
else if (cellsBusy == 0 && cells == as && casCellsBusy()) {
boolean init = false;
try { // Initialize table
// 再次判断是否有其他人初始化 cells
if (cells == as) {
// 初始值为 2
Cell[] rs = new Cell[2];
// 当前线程对应的累加单元初始值为 1,因为这个 x 是外层传递进来的自增 1
rs[h & 1] = new Cell(x);
cells = rs;
init = true;
}
} finally {
// 解锁
cellsBusy = 0;
}
if (init)
break;
}
- 那么当累加单元 cells 已经存在了,再次进入了这个方法后,当前线程对应的 cell 槽位未必就存在,所以进入第一个分支,先看不存在的情况
if ((as = cells) != null && (n = as.length) > 0) {
// 判断当前线程对应的槽位是否存在
if ((a = as[(n - 1) & h]) == null) {
if (cellsBusy == 0) { // Try to attach new Cell
// 创建 cell 对象,初始值也是 x
Cell r = new Cell(x); // Optimistically create
// 尝试上锁
if (cellsBusy == 0 && casCellsBusy()) {
boolean created = false;
try { // Recheck under lock
Cell[] rs; int m, j;
// 再次确认槽位是否为空
if ((rs = cells) != null &&
(m = rs.length) > 0 &&
rs[j = (m - 1) & h] == null) {
// 将创建的单元存入 cells
rs[j] = r;
created = true;
}
} finally {
// 解锁
cellsBusy = 0;
}
if (created)
break;
continue; // Slot is now non-empty
}
}
collide = false;
} else if {
...
}
}
- 如果 cells 和 cell 都存在了,则进入第一个 if 分支的第三个分支,直接对累加单元做 CAS 操作
if ((as = cells) != null && (n = as.length) > 0) {
// 判断当前线程对应的槽位是否存在
if ((a = as[(n - 1) & h]) == null) {
...
}
else if (!wasUncontended) // CAS already known to fail
wasUncontended = true; // Continue after rehash
// 直接做 CAS 累加
else if (a.cas(v = a.value, ((fn == null) ? v + x :
fn.applyAsLong(v, x))))
break;
// 累加失败了,判断数组长度是否超过 CPU 核心数,设置 collide 标志位,控制其走到下方一个分支
else if (n >= NCPU || cells != as)
collide = false; // At max size or stale
// 走了这个分支,就不会走下面扩容分支
else if (!collide)
collide = true;
//扩容操作
else if (cellsBusy == 0 && casCellsBusy()) {
try {
if (cells == as) { // Expand table unless stale
//两倍扩容
Cell[] rs = new Cell[n << 1];
//转移原始数据
for (int i = 0; i < n; ++i)
rs[i] = as[i];
//覆盖
cells = rs;
}
} finally {
cellsBusy = 0;
}
collide = false;
continue; // Retry with expanded table
}
// 尝试改变当前线程的累加单元(老失败,给你换一个)
h = advanceProbe(h);
}
7.2.4、sum
所有累加单元都完成后,需要把所有数据加起来,就用到了 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;
}
8、Unsafe
8.1、获取
Unsafe 对象提供了非常底层的,操作内存、线程的方法,Unsafe 对象不能直接调用,只能通过反射获得,因为其设计的部分偏底层,不建议我们开发人员随意调用
static 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);
}
}
static Unsafe getUnsafe() {
return unsafe;
}
8.2、实现CAS
@SneakyThrows
public static void main(String[] args) {
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
Unsafe unsafe = (Unsafe) theUnsafe.get(null);
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();
System.out.println(student);
// 使用 cas 方法替换成员变量的值
// 返回 true
unsafe.compareAndSwapInt(student, idOffset, 0, 22);
// 返回 true
unsafe.compareAndSwapObject(student, nameOffset, null, "两米以下皆凡人");
System.out.println(student);
}
@Data
class Student {
volatile int id;
volatile String name;
}
8.3、实现原子整数
public class Test {
@SneakyThrows
public static void main(String[] args) {
Account.demo(new MyAtomicInteger(10000));
}
}
interface Account {
/**
* 获取余额
*/
Integer getBalance();
/**
* 取款
*/
void withdraw(Integer amount);
/**
* 方法内会启动 1000 个线程,每个线程做 -10 元 的操作
* 如果初始余额为 10000 那么正确的结果应当是 0
*/
static void demo(Account account) {
List<Thread> ts = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
ts.add(new Thread(() -> {
account.withdraw(10);
}));
}
long start = System.nanoTime();
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");
}
}
class MyAtomicInteger implements Account {
private final int value;
private static final long VALUE_OFFSET;
private static final Unsafe UNSAFE;
static {
UNSAFE = UnsafeAccessor.getUnsafe();
try {
VALUE_OFFSET = UNSAFE.objectFieldOffset(MyAtomicInteger.class.getDeclaredField("value"));
} catch (NoSuchFieldException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
public int getValue() {
return value;
}
public void decrement(int amount) {
while (true) {
int prev = this.value;
int next = prev - amount;
if (UNSAFE.compareAndSwapInt(this, VALUE_OFFSET, prev, next)) {
break;
}
}
}
public MyAtomicInteger(int value) {
this.value = value;
}
@Override
public Integer getBalance() {
return getValue();
}
@Override
public void withdraw(Integer amount) {
decrement(amount);
}
}
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;
}
}