【JAVA多线程05】共享模型之无锁

无锁实例

在一些数量的增加和删除可以使用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的场景下

原子引用类型

主线程仅仅只能判断共享变量的值前后有没有发生变化,但是变化过程中是否经历了变化又变化回来是没有感知的,如果需要感知这一变化,需要加一个版本号

经验分享 程序员 微信小程序 职场和发展