Java中i++的一些问题总结

作者:小小小小小乐 时间:2022-04-11 18:07:34 

参考内容:

  • 深入理解Java虚拟机(JVM高级特性与最佳实践) ——周志明老师

  • 尚硅谷深入理解JVM教学视频——宋红康老师

在本文展开前,读者需要了解一些字节码有关的知识,以及JVM虚拟机栈中栈帧的局部变量表和操作数栈等知识,笔者在这里只给出一些大概的简述。

字节码

  • Java字节码对于虚拟机,就好像汇编语言对于计算机,属于基本执行指令。

  • 虚拟机的指令由一个字节长度的、代表着某种特定操作含义的数字(称为操作码,Opcode)以及跟随其后的零至多个代表此操作所需参数(称为操作数,Operands)而构成。由于Java虚拟机采用面向操作数栈而不是寄存器的结构,所以大多数的指令都不包含操作数,只有一个操作码。

局部变量表

局部变量表:Local Variables,被称之为局部变量数组或本地变量表

定义为一个数字数组,主要用于存储方法参数和定义在方法体内的局部变量,这些数据类型包括各类基本数据类型、对象引用(reference),以及returnAddress类型。

由于局部变量表是建立在线程的栈上,是线程的私有数据,因此不存在数据安全问题。

局部变量表所需的容量大小是在编译期确定下来的,并保存在方法的Code属性的maximum local variables数据项中。在方法运行期间是不会改变局部变量表的大小的。

操作数栈

操作数栈:Operand Stack ,使用数组实现的。

每一个独立的栈帧除了包含局部变量表以外,还包含一个后进先出(Last - In - First -Out)的 操作数栈,也可以称之为 表达式栈(Expression Stack)

操作数栈,在方法执行过程中,根据字节码指令,往栈中写入数据或提取数据,即入栈(push)和 出栈(pop)

  • 某些字节码指令将值压入操作数栈,其余的字节码指令将操作数取出栈。使用它们后再把结果压入栈

  • 比如:执行复制、交换、求和等操作

  • 操作数栈,主要用于保存计算过程的中间结果,同时作为计算过程中变量临时的存储空间。

接下来就是本文的正式内容,首先,我们先给出两个结论:

  • i++与++i在不同情况下可能会有不同的结论;

  • 实例变量/类变量的i++并不是一个原子性的操作。

首先我们看一下i++与++i的解析:

当i++或者++i没有涉及到其他操作时,两者是没有区别的。


// i++
public void method1(){
int i = 10;
i++;
}
// ++i
public void method2(){
int i = 10;
++i;
}

对应的字节码指令操作为:


// method1
0 bipush 10 // 将10这个整数压入操作数栈
2 istore_1// 将操作数栈栈顶元素保存到局部变量表中索引为1处
3 iinc 1 by 1 // 局部变量表中索引为1处的元素,也就是i进行自增(这一步是在局部变量表上直接进行的,与操作数栈无关)
6 return // 方法返回

// method2
0 bipush 10
2 istore_1
3 iinc 1 by 1 // ++i
6 return

其中关于给出的具体字节码细节以及栈帧中操作数栈、局部变量表在本文开头给出了一些简介,具体内容不展开描述,读者可翻阅与之有关的资料。

通过反编译可以看出,i++与++i的字节码在没有和其他操作组合时,字节码是完全相同的。

当i++或者++i涉及到其他操作时,两者的字节码会有一些改变。


public void method7(){
 int i = 10;
 int a = i++;

int j = 20;
 int b = ++j;
}

对应的的字节码指令:


0 bipush 10
2 istore_1
3 iload_1// i++先从局部变量表中读取i到操作数栈
4 iinc 1 by 1// i直接在局部变量表上进行自增
7 istore_2// 将操作数栈上读取到的i的值赋值给a,也就是10
8 bipush 20
10 istore_3
11 iinc 3 by 1// ++j则先在局部变量表上进行自增
14 iload_3// 再从局部变量表中读取j到操作数栈
15 istore 4// 将操作数栈上读取到的j的值赋值给b,也就是21
17 returns

通过反编译可以看出,i++与++i的字节码在没有和其他操作组合时,i++是先取值再自增,而++i是先自增再取值。

还有一个关于i=i++的解析:


public void method8(){
 int i = 10;
 i = i++;
 System.out.println(i);//10
}

对应的字节码指令:


0 bipush 10
2 istore_1
3 iload_1// 从局部变量表中读取i到操作数栈
4 iinc 1 by 1// i直接在局部变量表上进行自增,此时i = 11
7 istore_1// 将之前操作数栈上读取到的i的值赋值给i,之前自增的值被覆盖了,i = 10
8 getstatic #2 <java/lang/System.out>
11 iload_1
12 invokevirtual #5 <java/io/PrintStream.println>
15 return

然后看一下实例变量i++这行代码的对应的字节码

首先我们定义一个类


/**
* @author XiaoLe
* @create 2020-12-04 21:15
* @description
*/
public class Test {
private int i = 0;
public void test(){
 i++;
}
}

通过反编译查看test方法中的字节码:


0 aload_0
1 dup
2 getfield #2 <day001/Test.i> // 获取到Test的i变量的值
5 iconst_1// 将int类型常量1压入栈
6 iadd// 栈顶两个元素相加后返回值入栈
7 putfield #2 <day001/Test.i>
10 return

可以看到,i++这行代码被拆分为三个字节码,所以在一些并 * 况下,i++如果不做同步处理,就可能会出现数据非一致性。

来源:https://blog.csdn.net/weixin_42856430/article/details/110673239

标签:java,i++
0
投稿

猜你喜欢

  • c#序列化详解示例

    2022-09-12 02:39:00
  • Java获取随机数的3种方法

    2022-08-04 02:51:01
  • C# string格式的日期时间字符串转为DateTime类型的方法

    2022-04-07 09:34:30
  • Android系统制作自定义签名的例子

    2023-02-02 05:10:04
  • Spring+MyBatis实现数据读写分离的实例代码

    2021-08-31 04:34:48
  • Android自定义View之绘制圆形头像功能

    2022-09-27 19:24:29
  • Java注解Annotation原理及自定义注解代码实例

    2023-04-30 10:18:09
  • springboot多环境配置文件及自定义配置文件路径详解

    2021-09-30 03:55:54
  • 详解C# Socket简单例子(服务器与客户端通信)

    2022-01-02 03:12:49
  • C#实现动态执行字符串脚本(优化版)的示例代码

    2022-06-10 19:13:54
  • Android封装高德地图定位工具类Util的详细步骤

    2022-09-10 10:23:17
  • C++ pair的用法案例详解

    2021-09-21 01:40:20
  • C#文件路径操作详细总结

    2021-06-06 04:07:41
  • Spring gateway + Oauth2实现单点登录及详细配置

    2022-12-19 00:16:49
  • 基于WPF实现简单放大镜效果

    2022-02-15 23:19:12
  • 详解Spring中的FactoryBean

    2022-06-08 05:29:55
  • Struts2拦截器登录验证实例

    2021-10-26 06:47:04
  • C#操作注册表的方法

    2022-06-21 19:14:06
  • idea 创建 maven web 工程流程(图文教程)

    2022-06-29 04:51:21
  • 5种方法完美解决android软键盘挡住输入框方法详解

    2021-10-31 17:31:38
  • asp之家 软件编程 m.aspxhome.com