1 问题
一个账户取钱的例子
interface Account {
// 获取余额
Integer getBalance();
// 取款
void withdraw(Integer amount);
// 测试方法
static void demo(Account account) {
List<Thread> threads = new ArrayList<>();
// 1000个线程
for (int i = 0; i < 10000; i++) {
threads.add(new Thread(() -> {
account.withdraw(10);
}));
}
long start = System.nanoTime();
threads.forEach(Thread::start);
threads.forEach(t -> {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
long end = System.nanoTime();
// 打印执行后的余额 和 花费时间
System.out.println("balance: " + account.getBalance());
System.out.println("cost: " + (end - start) / 1000_000 + "ms");
}
}
1.1 不安全的实现
/**
* 不使用锁安全的实现
*/
class AccountSafeNoLock implements Account {
private Integer balance;
public AccountSafeNoLock(Integer balance) {
this.balance = balance;
}
@Override
public Integer getBalance() {
return this.balance;
}
@Override
public synchronized void withdraw(Integer amount) {
this.balance -= amount;
}
}
1.2 有锁安全的实现
/**
* 使用锁安全的实现
*/
class AccountSafeNoLock implements Account {
private Integer balance;
public AccountSafeNoLock(Integer balance) {
this.balance = balance;
}
@Override
public Integer getBalance() {
return this.balance;
}
@Override
public synchronized void withdraw(Integer amount) {
this.balance -= amount;
}
}
1.2 无锁安全的实现
/**
* 不使用锁安全的实现
*/
class AccountSafeUseLock implements Account {
private final AtomicInteger balance;
public AccountSafeUseLock(Integer balance) {
this.balance = new AtomicInteger(balance);
}
@Override
public Integer getBalance() {
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;
}
}
}
2 CAS 与 volatile
@Override
public void withdraw(Integer amount) {
while (true) {
int prev = balance.get();
int next = prev - amount;
// 比较并设值
if (balance.compareAndSet(prev, next)) break;
}
}
2.1 工作方式
注意
CAS底层是 lock cmpxchg指令(X86架构),在单核CPU和多核CPU下都能抱枕【比较-交换】的原子性
- 在多核状态下,某个核执行到带lock的指令时,CPU会让总线锁住,当 这个核把指令执行完毕,再离开总线。这个过程不会被线程的调度机制所打断,保证了多个线程对内存操作的准确性,是原子的。
2.2 volatile
源码上共享变量是被 volatile 修饰的,为了保证该变量的可见性
public class AtomicInteger extends Number implements java.io.Serializable {
//...
private volatile int value;
//...
}
2.3 无锁效率
- 无锁情况下,即使重试失败,线程始终在高速运行,没有停歇,而synchronized会让没有获得锁的线程进入阻塞状态,发生上下文切换。
- 无锁情况,线程要保持高速运行需要额外CPU的支持,如果是单核CPU,虽然不会进入阻塞,由于没有时间片,任然会从运行状态进入可运行状态,还是会导致上下文切换。
2.4 CAS特点
- 适合线程数量少,多核CPU的情况下
- CAS是基于乐观锁的思想:乐观的认为不会认为有其他线程来修改共享变量,改了我就重试
- synchronized基于悲观锁的思想:悲观的认为共享资源一定会被人改,我修改的适合必上锁,我改完了被人才能动
- CAS体现的是无锁并发,无阻塞并发
-
- 因为没有使用synchronized,所以线程不会陷入阻塞,大大提升了工作效率
-
- 如果竞争激烈,重试会频繁发生,反而效率更低
3 Atomic
3.1 原子整数
以 AtomicInteger 为例
AtomicInteger ai = new AtomicInteger(0);
// 获取 ai 值: 0
int i = ai.get();
System.out.println("i: " + i);
System.out.println("ai: " + ai.get());
System.out.println("------------------");
// i = ++ai : 1
i = ai.incrementAndGet();
System.out.println("i: " + i);
System.out.println("ai: " + ai.get());
System.out.println("------------------");
// i = ai++ : 1
i = ai.getAndIncrement();
System.out.println("i: " + i);
System.out.println("ai: " + ai.get());
System.out.println("------------------");
// i = ai-- : 2
i = ai.getAndDecrement();
System.out.println("i: " + i);
System.out.println("ai: " + ai.get());
System.out.println("------------------");
// i = --ai : 0
i = ai.decrementAndGet();
System.out.println("i: " + i);
System.out.println("ai: " + ai.get());
System.out.println("------------------");
// i = ai
// ai += 8
i = ai.getAndAdd(8);
System.out.println("i: " + i);
System.out.println("ai: " + ai.get());
System.out.println("------------------");
// ai += 8
// i = ai
i = ai.addAndGet(8);
System.out.println("i: " + i);
System.out.println("ai: " + ai.get());
System.out.println("------------------");
// ai = ai * 10
// i = ai
i = ai.updateAndGet(x -> x * 10);
System.out.println("i: " + i);
System.out.println("ai: " + ai.get());
System.out.println("------------------");
// i = ai
// ai = ai * 10
i = ai.getAndUpdate(x -> x / 10);
System.out.println("i: " + i);
System.out.println("ai: " + ai.get());
System.out.println("------------------");
3.2 原子引用
简单来说就是里面 Atomic 存的是对象引用
3.2.1 AtomicReference
有一个狗对象,狗对象有一个属性是骨头,1000个人(线程)每人给狗一个骨头,无锁实现线程安全
class Dog {
private int bone;
public Dog(int bone) {
this.bone = bone;
}
public int getBone() {
return bone;
}
public void setBone(int bone) {
this.bone = bone;
}
}
AtomicReference<Dog> ar = new AtomicReference<>(new Dog(0));
List<Thread> threads = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
threads.add(new Thread(() -> {
while (true) {
Dog dog = ar.get();
if (ar.compareAndSet(dog, new Dog(dog.getBone() + 1))) {
break;
}
}
}));
}
threads.forEach(Thread::start);
for (Thread thread : threads) {
thread.join();
}
System.out.println(ar.get().getBone());
3.2.2 ABA 问题
主线程一开始拿到的值是 A,但是 other() 方法讲 A 修改成 B 又修改成 A
这对主线程来说,它完全不知道
static AtomicReference<String> ar = new AtomicReference<>("A");
public static void main(String[] args) throws InterruptedException {
String prev = ar.get();
String next = "B";
other();
TimeUnit.SECONDS.sleep(1);
boolean b = ar.compareAndSet(prev, next);
System.out.println("是否修改成功!" + b);
}
public static void other() {
new Thread(() -> {
String prev = ar.get();
String next = "C";
ar.compareAndSet(prev, next);
}).start();
new Thread(() -> {
String prev = ar.get();
String next = "A";
ar.compareAndSet(prev, next);
}).start();
}
3.2.3 AtomicStampedReference
- AtomicStampedReference 比 AtomicReference 多一个参数 stamp
- stamp 相当于版本,每次修改的时候我们约定去修改它,这样我们就知道原引用是否被修改
static AtomicStampedReference<String> ar = new AtomicStampedReference<>("A", 1);
public static void main(String[] args) throws InterruptedException {
String prev = ar.getReference();
int stamp = ar.getStamp();
String next = "B";
other();
TimeUnit.SECONDS.sleep(1);
boolean b = ar.compareAndSet(prev, next, stamp, ++stamp);
System.out.println("是否修改成功!" + b);
}
public static void other() {
new Thread(() -> {
String prev = ar.getReference();
String next = "C";
int stamp = ar.getStamp();
ar.compareAndSet(prev, next, stamp, ++stamp);
}).start();
new Thread(() -> {
String prev = ar.getReference();
String next = "A";
int stamp = ar.getStamp();
ar.compareAndSet(prev, next, stamp, ++stamp);
}).start();
}
3.2.4 AtomicMarkableReference
- AtomicMarkableReference比 AtomicReference 多一个参数 mark
- mark 代表标记 布尔类型
public AtomicMarkableReference(V initialRef, boolean initialMark) {
pair = Pair.of(initialRef, initialMark);
}
3.3 原子数组
- AtomicIntegerArray 保证数组里面元素的修改是安全的
3.4 字段更新器
- 字段的更新
狗对象 name属性
class Dog {
volatile String name;
public Dog(String name) {
this.name = name;
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
'}';
}
}
Dog dog = new Dog("旺财");
AtomicReferenceFieldUpdater<Dog, String> updater = AtomicReferenceFieldUpdater.newUpdater(Dog.class, String.class, "name");
updater.compareAndSet(dog, dog.name, "大黄");
System.out.println(dog);
3.5 原子累加器
效率更改执行累加操作的类
LongAdder longAdder = new LongAdder();
longAdder.increment();
System.out.println(longAdder.longValue());
4 LongAdder 源码
/**
* Table of cells. When non-null, size is a power of 2.
* 累加单元数组,懒惰初始化
*/
transient volatile Cell[] cells;
/**
* Base value, used mainly when there is no contention, but also as
* a fallback during table initialization races. Updated via CAS.
* 基础值,如果没有竞争,则用 cas 累加这个锁
*/
transient volatile long base;
/**
* Spinlock (locked via CAS) used when resizing and/or creating Cells.
* 在 cells 创建或扩容时,置为 1 ,表示加锁
*/
transient volatile int cellsBusy;