起因
逛【博客园-博问】时发现了一段有意思的问题:
问题链接: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) {} 的,所以它总能执行到奇数,退出虚拟机。
最后
到此就分析完了,所以该问题的关键就是写操作是原子的,但是读操作不是,导致读出来的数据不是最终的。
synchronized下的 i+=2 和 i++ i++执行结果居然不一样的更多相关文章
- 内置锁(二)synchronized下的等待通知机制
一.等待/通知机制的简介 线程之间的协作: 为了完成某个任务,线程之间需要进行协作,采取的方式:中断.互斥,以及互斥上面的线程的挂起.唤醒:如:生成者--消费者模式.或者某个动作完成,可以唤醒下一 ...
- 解决Chrome Safari Opera环境下 动态创建iframe onload事件同步执行
我们先看下面的代码: setTimeout(function(){ alert(count); },2000); var count = []; document.body.appendChild(c ...
- Linux下获取当前的目录,需执行以下命令: $(cd `dirname $0`;pwd)
Linux下获取当前的目录,需执行以下命令: $(cd `dirname $0`;pwd) 其中, dirname $0,取得当前执行的脚本文件的父目录 cd `dirname $0` ...
- mac 查找当前目录下所有同一类型文件,并执行命令行
以TexturePacker举例 MAC下用TexturePacker命令行打包当前目录下所有的 *.tps文件 1.配置好tps文件需要配置好路径.参数等.(也可不配置,用命令行实现.具体参考:ht ...
- ASP.NET MVC下的异步Action的定义和执行原理
一.基于线程池的请求处理ASP.NET通过线程池的机制处理并发的HTTP请求.一个Web应用内部维护着一个线程池,当探测到抵达的针对本应用的请求时,会从池中获取一个空闲的线程来处理该请求.当处理完毕, ...
- linux下 /etc/profile、~/.bash_profile ~/.profile的执行过程
关于登录linux时,/etc/profile.~/.bash_profile等几个文件的执行过程. 在登录Linux时要执行文件的过程如下: 在刚登录Linux时,首先启动 /etc/profile ...
- linux 下source、sh、bash、./执行脚本的区别
原文地址:http://blog.csdn.net/caesarzou/article/details/7310201 source命令用法: source FileName 作用:在当前bash环境 ...
- window10下的eclipse用java连接hadoop执行mapreduce任务
一.准备工作 1.eclipse连接hadoop的插件,需要版本匹配,这有几个常用的 2 版本的插件 hadoop2x-eclipse-plugin-master 密码:feg1 2.hadoop-c ...
- mybatis下使用log4j打印sql语句和执行结果
转载自:https://www.cnblogs.com/jeevan/p/3493972.html 本来以为很简单的问题, 结果自己搞了半天还是不行; 然后google, baidu, 搜出来各种方法 ...
- Loadrunner:win10下Vuser 运行脚本通过,Controller执行用户并发报错
现象:win7安装LR可以正常使用,将win7升级到win10之后,运行场景之后报错:Error (-81024): LR_VUG: The 'QTWeb' type is not supported ...
随机推荐
- java中时间比较
package com.newtouch.test; import java.text.SimpleDateFormat;import java.util.Date; public class Tim ...
- 深入分析ENode的内部实现流程和关键地方的幂等设计
ENode 2.0 - 深入分析ENode的内部实现流程和关键地方的幂等设计 前言 ENode架构图 ENode框架内部实现流程分析 Command的幂等处理 Domain Event持久化时的并发冲 ...
- linux命令综合
查找文件中指定字符串并且高亮显示: find .|xargs grep --color=auto "hello" dos下查找: netstat -ano|findstr &quo ...
- solr-搭建与使用过程中问题总结-链接
以下错误可以确定在CDH版本Hbase集群+Lily hbase indexer+solrCloud的环境中可以解决,有开源版本解决成功案例的请在下方评论. 1.If you see this err ...
- Redis非关系型数据库
1.简介 Redis是一个基于内存的Key-Value非关系型数据库,由C语言进行编写. Redis一般作为分布式缓存框架.分布式下的SESSION分离.分布式锁的实现等等. Redis速度快的原因: ...
- mysql使用navicat和mysqldump导出数据
1.navicat 方式一:选中表,右键转储:(含有表结构和数据) 方式二:选择右上角工具,点击数据传输,在这个页面右侧选择数据库,左侧选择文件. 点击下一步,选择导出的表名和各种函数什么的,然后点击 ...
- DSO windowed optimization 代码 (1)
这里不想解释怎么 marginalize,什么是 First-Estimates Jacobian (FEJ).这里只看看代码,看看Hessian矩阵是怎么构造出来的. 1 优化流程 整个优化过程,也 ...
- Asp.Net WebApi服务的创建
Web API一种REST架构风格的Web服务.所谓的REST架构与技术无关,而是面向资源的一种软件架构设计. WCF自3.5之后也提供了对REST风格的支持,但和WebAPI来比较显得较为笨重,We ...
- python调用.net动态库
# python调用.net动态库 ### pythonnet简介------------------------------ pythonnet是cpython的扩展- pythonnet提供了cp ...
- linux $* [email protected] 特定位置参数
举例说:脚本名称叫test.sh 入参三个: 1 2 3运行test.sh 1 2 3后$*为"1 2 3"(一起被引号包住)[email protected]为"1" "2&qu ...
![[generation confrontation network learning III] reading notes of Bigan paper and its principle understanding](/img/6b/0f0815e20cdf6da28793562bcaede1.png)








