先看代码:

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字节码角度看线程安全性问题的更多相关文章

  1. 从JDK源码角度看线程池原理

    "池"技术对我们来说是非常熟悉的一个概念,它的引入是为了在某些场景下提高系统某些关键节点性能,最典型的例子就是数据库连接池,JDBC是一种服务供应接口(SPI),具体的数据库连接实 ...

  2. 从JDK源码角度看线程的阻塞和唤醒

    目前在Java语言层面能实现阻塞唤醒的方式一共有三种:suspend与resume组合.wait与notify组合.park与unpark组合.其中suspend与resume因为存在无法解决的竟态问 ...

  3. 从JDK源码角度看Short

    概况 Java的Short类主要的作用就是对基本类型short进行封装,提供了一些处理short类型的方法,比如short到String类型的转换方法或String类型到short类型的转换方法,当然 ...

  4. 从JDK源码角度看Byte

    Java的Byte类主要的作用就是对基本类型byte进行封装,提供了一些处理byte类型的方法,比如byte到String类型的转换方法或String类型到byte类型的转换方法,当然也包含与其他类型 ...

  5. 从JDK源码角度看Object

    Java的Object是所有其他类的父类,从继承的层次来看它就是最顶层根,所以它也是唯一一个没有父类的类.它包含了对象常用的一些方法,比如getClass.hashCode.equals.clone. ...

  6. 从JDK源码角度看Boolean

    Java的Boolean类主要作用就是对基本类型boolean进行封装,提供了一些处理boolean类型的方法,比如String类型和boolean类型的转换. 主要实现源码如下: public fi ...

  7. JVM Java字节码的角度分析switch的实现

    目录 Java字节码的角度分析switch的实现 引子 前置知识 一个妥协而又枯燥的方案 switch的实现 回顾历史 字节码分析 其他实现方式? Java字节码的角度分析switch的实现 作者 k ...

  8. 从 HelloWorld 看 Java 字节码文件结构

    很多时候,我们都是从代码层面去学习如何编程,却很少去看看一个个 Java 代码背后到底是什么.今天就让我们从一个最简单的 Hello World 开始看一看 Java 的类文件结构. 在开始之前,我们 ...

  9. 轻松看懂Java字节码

    java字节码 计算机只认识0和1.这意味着任何语言编写的程序最终都需要经过编译器编译成机器码才能被计算机执行.所以,我们所编写的程序在不同的平台上运行前都要经过重新编译才能被执行. 而Java刚诞生 ...

随机推荐

  1. [转]全网最!详!细!tarjan算法讲解

    转发地址:https://blog.csdn.net/qq_34374664/article/details/77488976 原版的地址好像挂了..... 看到别人总结的很好,自己就偷个懒吧..以下 ...

  2. node中用的cookie-parser插件设置的max-age,和普通正常设置max-age的计算方式不一样

    在cookie-parser中通过max-age设置的cookie的过期时间是按照毫秒计算的; 在普通设置的时候max-age后面的值是按秒计算的;

  3. 解决mysql登录警告问题

    一.前言 我们在登录mysql的时候经常会看到一句警告: Warning: Using a password on the command line interface can be insecure ...

  4. javascript_10-函数

    函数 //定义函数 0-100 相加 function getSum() { var sum = 0; for (let i = 1; i <= 100; i++) { sum += i; } ...

  5. Linux有名管道的 阻塞VS非阻塞 读写

    参考文章: 关于有名管道open时阻塞的问题 Linux有名管道(FIFO)的阻塞和非阻塞读写 挖坑,日后填

  6. C++——构造函数 constructor

    What is constructor C++中,如果你想要创建一个object,有一个函数会自动被调用(不需要programmer显式调用 ),这个函数就是constructor; construc ...

  7. Windows 10安装

    使用U盘安装操作系统教程 本教程介绍如何使用U盘安装操作系统,以安装Windows 10过程作为举例. 1 获取操作系统iso镜像文件 获取操作系统ISO镜像文件有很多途径,此处介绍从微软官网下载wi ...

  8. LoggerFactory is not a Logback LoggerContext but Logback is on the classpath. Either remove Logback or the competing implementation

    2019-06-26 22:19:57.408642475 java.lang.IllegalArgumentException: LoggerFactory is not a Logback Log ...

  9. Jfinal事务操作

    添加删除更新均可用以下事务 import java.sql.Connection; import java.sql.SQLException; import com.jfinal.plugin.act ...

  10. Codeforces #364 (Div. 2) D. As Fa(数学公式推导 或者二分)

    数学推导的博客 http://codeforces.com/contest/701/problem/D  题目 推导的思路就是 : 让每个人乘车的时间相等 ,让每个人走路的时间相等. 在图上可以这么表 ...