【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的场景下
原子引用类型
主线程仅仅只能判断共享变量的值前后有没有发生变化,但是变化过程中是否经历了变化又变化回来是没有感知的,如果需要感知这一变化,需要加一个版本号
下一篇:
Swagger与knife4j的不同之处