以下多线程对int型变量x的操作,哪几个不需要进行同步( )
A: x=y; B: x++; C: ++x; D: x=1;
经典评论解析:
链接: https://www.nowcoder.com/questionTerminal/11a7d8d366d54ff9a07c1fa054d6541b
来源:牛客网
A.由于y的值不确定,所以要加锁;
B,C 两个在多线程情况下是必须要加锁的,因为他们是先被读入寄存器,然后再进行+1操作,如果没有加锁,那么可能会出现数据异常;
D 原子操作,所以不需要加锁
原子性:指该操作不能再继续划分为更小的操作。
Java中的原子操作包括:
1、除long和double之外的基本类型的赋值操作
2、所有引用reference的赋值操作
3、java.concurrent.Atomic.* 包中所有类的一切操作
链接:https://www.nowcoder.com/questionTerminal/11a7d8d366d54ff9a07c1fa054d6541b
来源:牛客网
A:在X86上它包含两个操作:读取y至寄存器,再把该值写入x。读y的值这个操作本身是原子的,把值写入x也是原子的,但是两者合起来不是原子操作,因为它不是不可再分的操作,所以需要进行同步
B、C:x++和++x这样的操作在多线程环境下是需要同步的。因为X86会按三条指令的形式来处理这种语句:从内存中读x的值到寄存器中,对寄存器加1,再把新值写回x 所处的内存地址
D:x=1是原子操作,不可再分因为x是int类型,32位CPU上int占32位,在X86上由硬件直接提供了原子性支持。实际上不管有多少个线程同时执行类似x=1这样的赋值语句,x的值最终还是被赋的值(而不会出现例如某个线程只更新了x的低16位然后被阻塞,另一个线程紧接着又更新了x的低24位然后又被阻塞,从而出现x的值被损坏了的情况)
百度解惑:
https://blog.youkuaiyun.com/zhaifengmin/article/details/46315003
Java中的原子操作包括:
1)除long和double之外的基本类型的赋值操作
2)所有引用reference的赋值操作
3)java.concurrent.Atomic.* 包中所有类的一切操作。但是java对long和double的赋值操作是非原子操作!!long和double占用的字节数都是8,也就是64bits。 在32位操作系统上对64位的数据的读写要分两步完成,每一步取32位数据。这样对double和long的赋值操作就会有问题:如果有两个线程同时写一个变量内存,一个进程写低32位,而另一个写高32位,这样将导致获取的64位数据是失效的数据。因此需要使用volatile关键字来防止此类现象。 volatile本身不保证获取和设置操作的原子性,仅仅保持修改的可见性。但是java的内存模型保证声明为volatile的long和double变量的get和set操作是原子的。(from http://www.iteye.com/topic/213794)
举个例子来说:(example is from http://stackoverflow.com/questions/17481153/long-and-double-assignments-are-not-atomic-how-does-it-matter)
public class UnatomicLong implements Runnable { private static long test = 0; private final long val; public UnatomicLong(long val) { this.val = val; } @Override public void run() { while (!Thread.interrupted()) { test = val; //两个线程都试图将自己的私有变量val赋值给类私有静态变量test } } public static void main(String[] args) { Thread t1 = new Thread(new UnatomicLong(-1)); Thread t2 = new Thread(new UnatomicLong(0)); System.out.println(Long.toBinaryString(-1)); System.out.println(pad(Long.toBinaryString(0), 64)); t1.start(); t2.start(); long val; while ((val = test) == -1 || val == 0) { //如果静态成员test的值是-1或0,说明两个线程操作没有交叉 } System.out.println(pad(Long.toBinaryString(val), 64)); System.out.println(val); t1.interrupt(); t2.interrupt(); } // prepend 0s to the string to make it the target length private static String pad(String s, int targetLength) { int n = targetLength - s.length(); for (int x = 0; x < n; x++) { s = "0" + s; } return s; } }
运行发现程序在while循环时进入了死循环,这是因为使用的JVM是64bits。 在64位JVM中double和long的赋值操作是原子操作。
在eclipse中修改jre为一个32bit的JVM地址,则会有如下运行结果:
1111111111111111111111111111111111111111111111111111111111111111
000000000000000000000000 000000000000000000000000 0000000000000000
000000000000000000000000 000000001111111111111111 1111111111111111
//很明显test的值被破坏了
4294967295
https://blog.youkuaiyun.com/u014104269/article/details/82777928
java内存模型要求lock、unlock、read、load、assign、use、store、write这8个操作都具有原子性,但是对于64位的数据类型(long和double),在模型中特别定义了一条先对宽松的规定:允许虚拟机将没有被volatile修饰的64位数据的读写操作划分为2次32位的操作来执行,即允许虚拟机实现选择可以不保证64位数据类型的read、store、load和write这4个操作的原子性。这就是所谓的long和double的非原子性协定(Nonatomic Treatment of double and long Variables)--《深入理解JVM虚拟机第二版》
首先:以上针对于32位虚拟机来讲的,64位JVM虚拟机不用考虑了,不存在这种情况;
其次:针对商业虚拟机来讲,基本都把long和double的操作实现为原子性操作;
以上,不必担心。(虽然虚拟机规定volatile修饰的long和double具有原子性),完全没有必要添加。
未完待续...............