java并发编程学习: 原子变量(CAS)
先上一段代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | package test; public class Program { public static int i = 0 ; private static class Next extends Thread { public void run() { i = i + 1 ; System.out.println(i); } } public static void main(String[] args) { Thread[] threads = new Thread[ 10 ]; for ( int i = 0 ; i < threads.length; i++) { threads[i] = new Thread( new Next()); threads[i].start(); } } } |
代码很简单,10个线程,1个共享变量,每个线程在run的时候,将变量+1,反复运行多次,可能会输出类似下面的结果:
1
4
3
6
2
5
7
8
9
9
最后输出了2个9,显然有2个线程打架了,原因:
i = i + 1,虽然只有一行代码,但在计算机内部执行时,至少会拆成3条指令
a) 读取 i 的值,将其复制到本地的(副本)变量中
b) 将本地变量值+1
c) 将本地变量的值,覆盖到 i 上
假如有2个线程先后到达步骤a),但尚未完成步骤b),这时就出问题了,会生成相同的值。要解决这个问题,当然可以通过加锁(或synchronized),类似下面这样,代价是牺牲性能。
1 2 3 4 5 6 7 8 9 | private static class Next extends Thread { public void run() { synchronized ( this ) { i = i + 1 ; } System.out.println(i); } } |
jdk的并发包里提供了很多原子变量,可以在"不加锁"(注:OS底层其实还是有锁的,只不过相对java里的synchronized性能要好很多)的情况下解决这个问题,参考下面的用法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | package test; import java.util.concurrent.atomic.AtomicInteger; public class Program { public static AtomicInteger i = new AtomicInteger( 0 ); private static class Next extends Thread { public void run() { int x = i.incrementAndGet(); System.out.println(x); } } public static void main(String[] args) { Thread[] threads = new Thread[ 10 ]; for ( int i = 0 ; i < threads.length; i++) { threads[i] = new Thread( new Next()); threads[i].start(); } } } |
实现原理,可以从源码略知一二:
1 2 3 4 5 6 7 8 | public final int incrementAndGet() { for (;;) { int current = get(); int next = current + 1 ; if (compareAndSet(current, next)) return next; } } |
1、最外层是一个死循环
2、先获取旧值,将其复制到一个局部变量上
3、将局部变量值+1
4、比较旧值是否变化,如果没变化,说明没有其它线程对旧值修改,直接将新值覆盖到旧值,并返回新值,退出循环
5、如果旧值被修改了,开始下一轮循环,重复刚才这一系列操作,直到退出循环。
所以,第4步的compareAndSet其实是关键,继续看源码:
1 2 3 | public final boolean compareAndSet( int expect, int update) { return unsafe.compareAndSwapInt( this , valueOffset, expect, update); } |
最终看到的是一个native方法(说明依赖不同OS的原生实现)
1 | public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5); |
再往下跟,就得有点c++/c/汇编功底了,有兴趣的可自己研究下参考文章中的第2个链接文章
参考文章:
http://ifeve.com/concurrent-collections-8/
http://www.blogjava.net/mstar/archive/2013/04/24/398351.html
作者:菩提树下的杨过
出处:http://yjmyzz.cnblogs.com
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
出处:http://yjmyzz.cnblogs.com
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
【推荐】还在用 ECharts 开发大屏?试试这款永久免费的开源 BI 工具!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步