synchronized下的 i+=2 和 i++ i++执行结果居然不一样
起因
逛【博客园-博问】时发现了一段有意思的问题:
问题链接:https://q.cnblogs.com/q/140032/
这段代码是这样的:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class AutomicityTest implements Runnable {
private int i = 0;
public int getValue() {
return i;
}
/**
* 同步方法,加2
*/
public synchronized void evenIncrement() {
i += 2;
// i++;
// i++;
}
@Override
public void run() {
while (true) {
evenIncrement();
}
}
public static void main(String[] args) {
ExecutorService exec = Executors.newCachedThreadPool();
// AutomicityTest线程
AutomicityTest automicityTest = new AutomicityTest();
exec.execute(automicityTest);
// main线程
while (true) {
int value = automicityTest.getValue();
if (value % 2 != 0) {
System.out.println(value);
System.exit(0);
}
}
}
}
代码很简单(一开始我没看清楚问题,还建议楼主去学习下Java语法,尴尬的一批~~~)
简单说明下代码逻辑
一、AutomicityTest类实现了Runnable接口,并实现 run() 方法,run() 方法中有一个 while(true) 循环,循环体中调用了一个 synchronized 修饰的方法 evenIncrement();
二、AutomicityTest类中有一个 int i 成员变量;
三、在 main() 方法中使用线程池执行线程(直接 new Thread().start() 是一样的),然后 while(true) 循环体中不停打印成员变量 i 的值,如果是奇数就退出虚拟机。
大家觉得这段代码会输出什么呢?
没错就是死循环!!!
有意思的地方来了,如果我把同步方法 evenIncrement() 改为下面这样:
public synchronized void evenIncrement() {
// i += 2;
i++;
i++;
}
执行结果是退出了虚拟机!!!
是不是很纳闷儿,要是不纳闷儿,就不用往下看了 >_<
一起来分析一下
1.首先从方法入口开始,main() 方法当中创建了一个AutomicityTest线程 和 本身 main() 所在的main线程,所以AutomicityTest线程是一个写线程,main线程是一个读线程,既然有读有写,又是多个线程,就涉及到工作内存和主存模型,在这里我就不赘述了。
2.写线程是有synchronized修饰的,但是读线程并没有,这就导致了读写不一致,解决方法就是给 getValue() 加上synchronized,此时执行结果就正常了
3.但是问题还没完,为什么 i += 2 死循环,而 【两条】 i++ 却退出了虚拟机呢?
字节码层面分析
public synchronized void evenIncrement() {
i += 2;
}
// 对应的字节码
public synchronized void evenIncrement();
descriptor: ()V
flags: ACC_PUBLIC, ACC_SYNCHRONIZED
Code:
stack=3, locals=1, args_size=1
0: aload_0
1: dup
2: getfield #2 // Field i:I
5: iconst_2
6: iadd
7: putfield #2 // Field i:I
10: return
public synchronized void evenIncrement() {
i++;
i++;
}
// 对应的字节码
public synchronized void evenIncrement();
descriptor: ()V
flags: ACC_PUBLIC, ACC_SYNCHRONIZED
Code:
stack=3, locals=1, args_size=1
0: aload_0
1: dup
2: getfield #2 // Field i:I
5: iconst_1
6: iadd
7: putfield #2 // Field i:I
10: aload_0
11: dup
12: getfield #2 // Field i:I
15: iconst_1
16: iadd
17: putfield #2 // Field i:I
20: return
i+=2;
字节码指令只有一段putfield,没有执行是偶数,执行了也是偶数,所以会死循环。
i++; i++;
字节码指令有两段putfield,由于之前getValue()没有加synchronized,那么在执行getValue()的时候,putfield可能没有执行,可能执行了一次,也可能执行了两次,没有执行是偶数,执行一次是奇数,执行两次是偶数;又因为AutomicityTest线程 run() 是 while (true) {} 的,所以它总能执行到奇数,退出虚拟机。
最后
到此就分析完了,所以该问题的关键就是写操作是原子的,但是读操作不是,导致读出来的数据不是最终的。
相关文章
- 15分钟学会使用Git和远程代码库
- Linux date命令 - 显示和设置系统日期与时间
- 成为 Linux 终端高手的七种武器
- 每日 Ubuntu 小技巧 - 更改 Ubuntu 使用语言
- Linux mpstat 命令- 报告处理器的相关统计信息
- Linux系统 whoami 命令 – 知晓当前登录用户
- 在openSUSE 13.1中配置FTP服务器
- netstat 的10个基本用法
- 重要通知 | 比特币勒索席卷全球,如何防范?
- R语言数据挖掘
- Linux下Nagios的安装与配置
- 在 CentOS 6.4(64位) 安装 docker.io
- 暗渡陈仓:用低功耗设备进行破解和渗透测试
- Linux中显示系统中USB信息的lsusb命令
- Linux 基础命令 – watch
- Linux中命令链接操作符的十个最佳实例
- 嵌入式操作系统风云录:历史演进与物联网未来.
- 如何在 Linux 中生成全景照片
- 实例学习 Linux 的 cd 命令,及对内部命令的解释
- ROS机器人程序设计(原书第2版).