博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
java并发编程学习: 原子变量(CAS)
阅读量:5965 次
发布时间:2019-06-19

本文共 2259 字,大约阅读时间需要 7 分钟。

先上一段代码:

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),类似下面这样,代价是牺牲性能。

private static class Next extends Thread {        public void run() {            synchronized (this) {                i = i + 1;            }            System.out.println(i);        }    }

jdk的并发包里提供了很多原子变量,可以在"不加锁"(注:OS底层其实还是有锁的,只不过相对java里的synchronized性能要好很多)的情况下解决这个问题,参考下面的用法:

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();        }    }}

  

实现原理,可以从源码略知一二:

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其实是关键,继续看源码:

public final boolean compareAndSet(int expect, int update) {        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);    }

最终看到的是一个native方法(说明依赖不同OS的原生实现)

public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

再往下跟,就得有点c++/c/汇编功底了,有兴趣的可自己研究下参考文章中的第2个链接文章

 

参考文章:

转载地址:http://olvax.baihongyu.com/

你可能感兴趣的文章
“重试”的实现
查看>>
【MySQL数据库开发之三】MySQL 获得数据库和表操作!
查看>>
[CTO札记]第1天:认识人、熟悉环境
查看>>
linux文件描述符导致squid拒绝服务
查看>>
[APEC中小企业峰会2009上]成功企业 = 理想主义 + 现实主义
查看>>
[转]经典正则表达式
查看>>
基于ARM的嵌入式Linux移植真实体验(3)――操作系统
查看>>
JDBC+Servlet+JSP整合开发之26.JSP内建对象
查看>>
【下载】深入oracle数据库专用虚拟机环境部署方案《VirtualBox+OELR5U7x86_64+Oracle11gR2》...
查看>>
[Web开发] IE8 网页开发参考文档
查看>>
企业架构 - 涉众管理(Stakeholder Management)
查看>>
关于SQLServer2005的学习笔记——生日问题
查看>>
值得推荐的C/C++开源框架和库
查看>>
I/O复用机制概述
查看>>
Android提升篇系列:Activity recreate(Activity 重新创建/自我恢复)机制(一)
查看>>
创建索引时,键列位置的重要性
查看>>
项目开发周期与数据库设计对比
查看>>
Python 图形 GUI 库 pyqtgraph
查看>>
OEA中的缓存模块设计
查看>>
flask使用sqlit3的两种方式
查看>>