从java字节码角度看线程安全性问题
先看代码:
package com.roocon.thread.t3;
public class Sequence {
private int value;
public int getNext(){
return value++;
}
public static void main(String[] args) {
Sequence sequence = new Sequence();
new Thread(new Runnable() {
@Override
public void run() {
while (true){
System.out.println(Thread.currentThread().getName()+" "+sequence.getNext());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while (true){
System.out.println(Thread.currentThread().getName()+" "+sequence.getNext());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while (true){
System.out.println(Thread.currentThread().getName()+" "+sequence.getNext());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}
运行结果:仔细发现,出现了两个84,但代码想要的结果是,每个线程每次执行,就在原来的基础上加一。因此,这里就是线程的安全问题。
Thread-0 0
Thread-1 1
Thread-2 2
...
Thread-2 81
Thread-1 82
Thread-0 83
Thread-2 84
Thread-1 84
Thread-0 85
Thread-2 86
解释原因:
return value++; 通过字节码分析,它其实不是原子操作,value = value + 1;首先,要先读取value的值,然后再对value的值加1,最后将value+1后的结果赋值给原来的value。
如果有线程1和线程2,假设value此时为83。
1.线程1读取value的值,为83。
2.线程1对value进行加1操作,得到值是84,但此时cpu被线程2抢走了,线程2还没来得及将计算后的值赋值给原来的value。
3.线程2读取value的值,仍然为83。
4.线程2对value进行加1操作,得到84,此时cpu被线程1抢走了,线程1继续执行赋值操作,将它计算得到的结果值84赋值给value,于是,线程1输出了84。
5.线程2此时再次抢到了cpu执行权,于是,将它计算得到的结果值84赋值给value,最后输出84。
下面来查看字节码文件验证:

继续往下查看字节码文件的getNext方法:

这些指令告诉我们,value++并不是原子操作。其中,getfield就代表读取value这个字段的值,iadd就表示对value值进行加1操作,而putfield就代表将jia1操作得到的值赋值给原来的value。
那么,如何解决上面的问题呢?如何保证多线程的安全性问题呢?
最简单的办法就是,加同步锁。
package com.roocon.thread.t3;
public class Sequence {
private int value;
public synchronized int getNext(){
return value++;
}
public static void main(String[] args) {
Sequence sequence = new Sequence();
new Thread(new Runnable() {
@Override
public void run() {
while (true){
System.out.println(Thread.currentThread().getName()+" "+sequence.getNext());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while (true){
System.out.println(Thread.currentThread().getName()+" "+sequence.getNext());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while (true){
System.out.println(Thread.currentThread().getName()+" "+sequence.getNext());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}
运行结果:
Thread-0 0
Thread-1 1
Thread-2 2
...
Thread-0 81
Thread-1 82
Thread-2 83
Thread-0 84
Thread-1 85
Thread-2 86
Thread-0 87
解决线程安全性问题有很多解决方案,因为,如果所有的解决方案都是加同步锁,那么,所谓的多线程并发最后变成了串行了。那么,多线程就显得没意义了。
最后,总结下何时会有如上线程安全性问题:
1.多线程环境下。
2.多个线程共享一个资源。如servlet就不是线程安全的。在它的service方法中操作同一个实例变量,如果多个线程同时访问,由于多个线程共享该变量,因此存在线程安全问题。
3.对线程进行非原子性操作。
从java字节码角度看线程安全性问题的更多相关文章
- 从JDK源码角度看线程池原理
"池"技术对我们来说是非常熟悉的一个概念,它的引入是为了在某些场景下提高系统某些关键节点性能,最典型的例子就是数据库连接池,JDBC是一种服务供应接口(SPI),具体的数据库连接实 ...
- 从JDK源码角度看线程的阻塞和唤醒
目前在Java语言层面能实现阻塞唤醒的方式一共有三种:suspend与resume组合.wait与notify组合.park与unpark组合.其中suspend与resume因为存在无法解决的竟态问 ...
- 从JDK源码角度看Short
概况 Java的Short类主要的作用就是对基本类型short进行封装,提供了一些处理short类型的方法,比如short到String类型的转换方法或String类型到short类型的转换方法,当然 ...
- 从JDK源码角度看Byte
Java的Byte类主要的作用就是对基本类型byte进行封装,提供了一些处理byte类型的方法,比如byte到String类型的转换方法或String类型到byte类型的转换方法,当然也包含与其他类型 ...
- 从JDK源码角度看Object
Java的Object是所有其他类的父类,从继承的层次来看它就是最顶层根,所以它也是唯一一个没有父类的类.它包含了对象常用的一些方法,比如getClass.hashCode.equals.clone. ...
- 从JDK源码角度看Boolean
Java的Boolean类主要作用就是对基本类型boolean进行封装,提供了一些处理boolean类型的方法,比如String类型和boolean类型的转换. 主要实现源码如下: public fi ...
- JVM Java字节码的角度分析switch的实现
目录 Java字节码的角度分析switch的实现 引子 前置知识 一个妥协而又枯燥的方案 switch的实现 回顾历史 字节码分析 其他实现方式? Java字节码的角度分析switch的实现 作者 k ...
- 从 HelloWorld 看 Java 字节码文件结构
很多时候,我们都是从代码层面去学习如何编程,却很少去看看一个个 Java 代码背后到底是什么.今天就让我们从一个最简单的 Hello World 开始看一看 Java 的类文件结构. 在开始之前,我们 ...
- 轻松看懂Java字节码
java字节码 计算机只认识0和1.这意味着任何语言编写的程序最终都需要经过编译器编译成机器码才能被计算机执行.所以,我们所编写的程序在不同的平台上运行前都要经过重新编译才能被执行. 而Java刚诞生 ...
随机推荐
- Spring Boot整合Spring Security自定义登录实战
本文主要介绍在Spring Boot中整合Spring Security,对于Spring Boot配置及使用不做过多介绍,还不了解的同学可以先学习下Spring Boot. 本demo所用Sprin ...
- jQuery AJAX基础
一.JSON 定义: JSON(JavaScript Object Notation, JS对象标记)是一种轻量级的数据交换格式. 它基于 ECMAScript ...
- http://go.microsoft.com/fwlink/?linkid问题
今天不知怎的,打开一个网页出现了http://go.microsoft.com/fwlink/?linkid=XXX 烦死了,在百度搜了下方法,什么改注册表,策略模板等不见起效.其实打开IE的设置, ...
- OS X系统特有文件目录说明
os x特有的目录 OS X系统中,除了标准的unix目录外,还增加了特有的目录. /Applications 应用程序目录,默认所有的GUI应用程序都安装在这里: /Library 系统的数据文件. ...
- js钩子函数实现一个简单动画
<!DOCTYPE html> <html> <head> <title></title> <meta charset="u ...
- 关于iview下拉菜单无法添加点击事件的解决办法
效果如下图所示,点击下拉菜单,点击退出,然后跳到登录界面 代码如下: <Dropdown trigger="click" style="margin-left: 2 ...
- English-手机销售英文场景
http://www.engpx.com/news/18216.html https://wenku.baidu.com/view/d67a103203768e9951e79b89680203d8ce ...
- SR开启时LOG_MODE必须是normal
SR开启时LOG_MODE必须是normal 需要一个初始化备份,
- L ==> E · L · K
三剑客:Elastic Stack 在学习ELK前,先对 Lucene作基本了解. 今天才知道关系型数据库的索引是 B-Tree,罪过... 减少磁盘寻道次数 ---> 提高查询性能 Lucen ...
- CI,CD理解
一.什么是CI,CD 当我们在谈论现代的软件编译和发布流程的时候,经常会听到CI 和CD这样的缩写短语.CI很容易理解,就是持续集成. 但是CD既可以指代码持续交付,也可理解为代码持续部署.C ...