无锁实例
在一些数量的增加和删除可以使用AtomicInteger来实现原子操作
package cn.itcast;
import java.util.ArrayList;
import java.util.List;
class xxx implements{
@Override
public void withdraw(Integer amount) {
while (true) {
int prev = balance.get();
int next = prev - amount;
if (balance.compareAndSet(prev, next)) {
break;
}
}
// 可以简化为下面的方法
// balance.addAndGet(-1 * amount);
}
}
interface Account {
// 获取余额
Integer getBalance();
// 取款
void withdraw(Integer amount);
/**
* 方法内会启动 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::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");
}
}
CAS与volatile
前面可以看到AtomicInteger的解决方法,内部并没有加锁,但是也可以保护共享变量的线程安全。CAS就是compareAndSet,他必须是原子操作的
他的执行原理是,比较初始值和共享变量的值,如果修改之前是一直则就确认赋值变量,否则失败
CAS必须借助volatile才能读取到共享变量的最新值实现,比较并交换的效果。
为什么无锁的效率高
无锁的情况下,即使重试失败,线程始终在高速运行,没有停歇,而加了synchronized会让线程在没有锁的时候阻塞,发生上下文切换,上下文切换代价是挺高的。
但是在无锁的状态下,因为线程需要保持运行,需要额外CPU支持,虽然不会出现阻塞的情况,但是会分不到时间片,导致一样的上下文切换 。
CAS的特点
结合CAS和volatile可以实现无锁并发,适用于线程数较少,多核CPU的场景下
原子引用类型
主线程仅仅只能判断共享变量的值前后有没有发生变化,但是变化过程中是否经历了变化又变化回来是没有感知的,如果需要感知这一变化,需要加一个版本号