"原子操作(atomic operation)是不需要synchronized",这是Java多线程编程的老生常谈了。所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切换到另一个线程)。原子操作是不可分割的,在执行完毕之前不会被任何其它任务或事件中断。
在单处理器系统(UniProcessor)中,能够在单条指令中完成的操作都可以认为是" 原子操作",因为中断只能发生于指令之间。但是,在对称多处理器(Symmetric Multi-Processor)结构中就不同了,由于系统中有多个处理器在独立地运行,即使能在单条指令中完成的操作也有可能受到干扰。
在x86 平台上,CPU提供了在指令执行期间对总线加锁的手段。CPU芯片上有一条引线#HLOCK pin,如果汇编语言的程序中在一条指令前面加上前缀"LOCK",经过汇编以后的机器代码就使CPU在执行这条指令的时候把#HLOCK pin的电位拉低,持续到这条指令结束时放开,从而把总线锁住,这样同一总线上别的CPU就暂时不能通过总线访问内存了,保证了这条指令在多处理器环境中的原子性。
那么原子操作到底用在哪里呢,下面先从一个简单的JAVA例子入手:
3 import java.util.concurrent.atomic.*;
4
5 public class VolatileTest
6 {
7 public static volatile int race = 0;
8 final static AtomicInteger value = new AtomicInteger(0);
9 public static void increase()
10 {
11 value.incrementAndGet();
12 race++;
13 }
14 private static final int THREADS_COUNT = 20;
15
16 public static void main(String[] args)
17 {
18 Thread[] threads = new Thread[THREADS_COUNT];
19 for (int i=0; i<THREADS_COUNT; i++)
20 {
21 threads[i] = new Thread(new Runnable() {
22 @Override
23 public void run() {
24 for (int i=0; i<10000; i++)
25 {
26 increase();
27 }
28 }
29 });
30 threads[i].start();
31 }
32
33 while (Thread.activeCount() > 1)
34 Thread.yield();
35
36 System.out.println(race);
37 System.out.println(value.get());
38 }
39 }
上面例子的执行结果是:
195333
200000
不过多运行几次大家就会发现race的值并不固定,因为increase方法没有加synchronized,但是value的值每次都能保证正确,这是因为value用到了原子操作。到这里大家又要问了,既然synchronized可以保障同步,那么要原子操作干啥,这个是处于效率考虑,大量的线程切换会带来效率上的损失,或许jvm对于synchronized做了一些优化,但是也很难达到原子的效率,再者,为了一个变量的++就考虑使用全方法甚至全对象synchronized是不是太奢侈了。
下面我们来剖析一下incrementAndGet的实现,incrementAndGet->compareAndSet->compareAndSwapInt->Unsafe_CompareAndSwapInt ->Atomic::cmpxchg
public final int incrementAndGet() {
for (;;) {
int current = get(); // 获得value当前值
int next = current + 1;
if (compareAndSet(current, next)) // 此方法比较current跟此时对象value值,如果相等,将next的值赋值给value;如果不相等,说明正在有人改变value,则返回FALSE,循环下次继续
return next;
}
}
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
//比较成功,此函数即返回TRUE
UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))
UnsafeWrapper("Unsafe_CompareAndSwapInt");
oop p = JNIHandles::resolve(obj);
jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);
return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
inline jint Atomic::cmpxchg (jint exchange_value, volatile jint* dest, jint compare_value) {
// alternative for InterlockedCompareExchange
int mp = os::is_MP();
__asm {
mov edx, dest
mov ecx, exchange_value
mov eax, compare_value // eax一般存放函数返回值,即compare_value为返回值
LOCK_IF_MP(mp)
cmpxchg dword ptr [edx], ecx // [edx]与eax如果相等,ecx->[edx];否则[edx]->eax
}
}
以上是几个主要函数,前面两个JAVA代码方法应该不是难懂,compareAndSwapInt到 Unsafe_CompareAndSwapInt 已经不是直接的调用关系了,这一步是JVM的解释器进行的,也就是说Unsafe_CompareAndSwapInt以下就不是java的代码了。两个c++、汇编实现的函数也不是太难理解,其实在X86下就是使用cmpxchg指令实现CAS(比较并交换),不过LOCK_IF_MP这个宏用的还是很精炼的,为了让大家加深Atomic的理解,我们再看一下Atomic中inc(实现原子的++操作)的代码,这个相对简单些:
#define LOCK_IF_MP(mp) __asm cmp mp, 0 \ // mp与0比较
__asm je L0 \ // mp为0则跳到L0标记
__asm _emit 0xF0 \ // _emit伪指令,作用嵌入0xF0到当前代码处,0xF0其实就是lock指令的机器码
__asm L0:
inline void Atomic::inc (volatile jint* dest)
{
// alternative for InterlockedIncrement
int mp = os::is_MP(); // 判断是否为对称多处理器(0为单核,1为多核)
__asm // C/C++中嵌入汇编
{
mov edx, dest; // 地址赋值给edx寄存器
LOCK_IF_MP(mp) // 宏(用法类似函数),详见上面注释
add dword ptr [edx], 1; // [edx]加1
}
}
有了上面的注释,这段代码应该不难理解,效果等价于以下伪代码:
If (单核)
{
__asm // C/C++中嵌入汇编
{
mov edx, dest; // 地址赋值给edx寄存器
add dword ptr [edx], 1; // [edx]加1
}
}
else
{
__asm // C/C++中嵌入汇编
{
mov edx, dest; // 地址赋值给edx寄存器
lock add dword ptr [edx], 1; // [edx]加1
}
}
或许90%的以上的汇编程序员能写出此伪码,但是只有不到10%的程序员会想到用_emit 嵌入指令,很不幸的是,我也在这90%之中,万幸的是,当我第一次看到此代码,立刻就意识到0xF0是lock的机器码。
除了直接提供给java使用之外,Atomic还充斥着jvm的几乎任何其他地方,只是我们平时没注意罢了,可以这样认为,我们的java代码运行时无时无刻都有大量Atomic的方法被调用,也即无时无刻都有大量LOCK_IF_MP被调用(宏毕竟不是函数,这里讲调用不是很恰当,知其意即可),这样就带来了这样一个思索,LOCK_IF_MP这个宏的确写的不错,但是这或许不是一个最恰当的方法,个人认为,在我们java程序运行的时候一般不会发生CPU核心数变化的情况,完全可以使用预编译方式来代替这个无时无刻都要进行的判断,换句话说,单核上使用单核原子版本,多核上使用多核原子版本,在我们安装jdk的时候判断CPU核心数然后安装相应版本就可以了。
后记:如果大家仔细看到这里,会发现一个问题,java中所谓的原子++实际上使用的是CAS,即类似自旋锁之类的机制,而C++中实现原子++并不依赖CAS,很显然,后者效率更高一些,至于为何java中AtomicInteger不用后者的方式来实现原子++,或许是解释执行的时候技术所限,这点需要我们有时间去继续挖掘了。
一、开机默认进入命令行模式1、输入命令:sudo systemctl set-default multi-user.target 2、重启:reboot要进入图形界面,只需要输入命令startx从图形界面切换回命令行:ctrl+alt+F7二、开机默认进入图形用户界面1、输入命令:sudo systemctl set-default graphical.target 2、重启:rebo...
有初学C++者经常会出现如下错误:fatal error C1083: Cannot open include file: 'iostream.h': No such file or directory。这个错误一般在使用Visual C++ .NET 2005时出现。原因,iostream.h为C类库,C++类库中应该为iostream。另外ci...
先上一张图:这是使用UITextView时用到的iOS7新增加的类:NSTextContainer、NSLayoutManager、NSTextStorage及其相互关系:这三个新出的类还没有在官方出独立的class reference,但是在新出的UIKit_Framework上已经有这些类的相关说明及使用方法,当然官方还会更新。以下是
一、模块1、Modulesv1 module: 将所有的公共TensorFlow接口引入到这个模块中。1、v1 模块模块列表:app:通用入口点脚本。 audio:tf.audio命名空间的公共API。 autograph:将普通Python转换为TensorFlow图形代码。 bitwise:操作整数的二进制表示的操作。 compat:Python 2与Python ...
斐波那契与线性递归序列比内公式 斐波那契序列FnFnF_n用递归公式描述如下: F1=F2=1,Fn=Fn−1+Fn−2,n=3,4,5,...F1=F2=1,Fn=Fn−1+Fn−2,n=3,4,5,...F_1=F_2=1,\quad F_n=F_{n-1}+F_{n-2}\quad ,n=3,4,5,... 则斐波那契序列的第nnn项可用公式 Fn=15–√{(1+5–√2)...
Java集合:Set、List、Queue、Map
Android 自动化环境搭建**一.Node.js** Appium server 的运行环境(必备依赖条件)。二.Appium server 两种安装方式: 1)通过Nmp命令来安装。 命令:npm install -g appium 备注:安装完后都是纯命令行。 2)安装appimun desktop 版本 可从官网直接下载最新版本。三.JDK jdk1.8 64位及以上版本。四.Android sd
力扣刷题(python)50天——第四十七天:除自身以外数组的乘积题目描述给定长度为 n 的整数数组 nums,其中 n > 1,返回输出数组 output ,其中 output[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积。示例:输入: [1,2,3,4]输出: [24,12,8,6]说明: 请不要使用除法,且在 O(n) 时间复杂度内完成此题。进阶:你...
Xcode创建的iOS项目内存在两种文件夹:Group(黄色, 伪文件夹) 和Folder(蓝色, 真文件夹):Group:Folder:Images.xcassets或Group文件夹内的PNG图片可通过imageNamed方法直接加载:[UIImage imageNamed:@"photo"];Folder文件夹内的PNG图片通过imageName...
<!-- 传统方式文件上传--> <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.2.1</version> </dependency> <dependency> &
问题:输入正整数n,按从小到大的顺序输出所有形如abcde/fghij=n的表达式,其中a~j恰好为数字0~9的一个排列,2&lt;=n&lt;=79。样例输入: 62样例输出: 79546/01238=62 94736/01528=62解:public class Main { static int[] a = new int[10]; public ...
本文首先介绍了根据网上博文简单介绍了8B/10B的原理,并根据Aurora IP的相关使用方法,参考IP手册进行设计递增数测试用例,并下板进行实际验证。