++i和i++
在解释这两个运算之前,先引入局部变量表和操作数栈。
i++的通俗的解释即是先赋值再自增,其实这里赋值的值是从操作数栈取的值,也就是说先将i的值压入栈中,
而自增是局部变量表的值自增。
而++i则相反,是先自增后赋值,就是局部变量表的自增,然后把局部变量表的值压入栈中。
现在我们用一道经典题来从底层原理解释这两个运算操作
参考链接
https://blog.csdn.net/wenchangwenliu/article/details/104564555
public class Test3 {
public static void main(String[] args) {
int y=0;
//注意"="是赋值,"=="才是相等
//这里的y=++y 是先运算在赋值,先运算++y完了后y的值是1,然后在赋值给原来等于0的y
y=++y;// y==0,++y==y+1; 结果y=++y == y+1 == 0+1 ==1
y=++y;// y==1,++y==y+1; 结果y=++y == y+1 == 1+1 ==2
y=++y;// y==2,++y==y+1; 结果y=++y == y+1 == 2+1 ==3
y=++y;// y==3,++y==y+1; 结果y=++y == y+1 == 3+1 ==4
y=++y;// y==4,++y==y+1; 结果y=++y == y+1 == 4+1 ==5
System.out.println("y="+y);//5
int i =0;
// i==0,i++==0; 结果i=i++ == (记住先赋值后运算)i=i,i=i+1(由于是i++运算这里我们输出的i只取先赋值的结果也就是i=i)
i=i++;
i=i++;
i=i++;
i=i++;
i=i++;
System.out.println("i="+i);//0
System.out.println("================");//1
}
}
i++和++i都是i=i+1的意思,但是过程有些许区别:
i++:先赋值再自加。(例如:i=1;a=1+i++;结果为a=1+1=2,语句执行完后i再进行自加为2)
++i:先自加再赋值。(例如:i=1;a=1+++i;结果为a=1+(1+1)=3,i先自加为2再进行运算)
但是在单独使用时没有区别:如for(int i=0;i<10;i++){ }和for(int i=0;i<10;++i) { }没有区别。
i++和++i的线程安全分为两种情况:
1、如果i是局部变量(在方法里定义的),那么是线程安全的。因为局部变量是线程私有的,别的线程访问不到,其实也可以说没有线程安不安全之说,因为别的线程对他造不成影响。
2、如果i是全局变量(类的成员变量),那么是线程不安全的。因为如果是全局变量的话,同一进程中的不同线程都有可能访问到。
如果有大量线程同时执行i++操作,i变量的副本拷贝到每个线程的线程栈,当同时有两个线程栈以上的线程读取线程变量,假如此时是1的话,那么同时执行i++操作,再写入到全局变量,最后两个线程执行完,i会等于3而不会是2,所以,出现不安全性。
多线程中的i++线程安全吗?为什么?
从底层的角度分析
答案是不安全的,先来看一下i++的实现过程:
1、先是寄存器从内存中读取i,
2、然后在寄存器中进行+1操作,
3、最后+1后的值回传内存,
因为没有保证原子操作,万一出现3步没有执行完而线程的CPU时间片已经用完,导致操作失败,所以并不安全。
这里有两种方法可以保证线程安全:
1.使用synchronize进行加锁操作,但是影响效率
2.使用CAS乐观锁技术,CAS是一种轻量级锁,是通过CPU硬件指令来实现的,通过总线加锁的方式,指令能保证CPU寄存器和内存交换数据是原子操作,具体使用CAS下的原子操作类AtomicInteger
++i与 i++效率考虑
仔细观察后,我们发现前置自增,先自增,后返回原对象的对象;没有产生任何临时对象;而后置自增,先保存原对象,然后自增,最后返回该原临时对象,那么它就需要创建和销毁,这样一来,效率孰高孰低就很清楚了。
对于内置类型,前置和后置自增或者自减在编译器优化的情况下,两者并无多大差别,而对于自定义类型,如无特别需要,人们似乎更加偏爱前置自增或自减,因为后置自增常常会产生临时对象。
i++和++i的实现原理
public class Test {
public void testIPlus() {
int i = 0;
int j = i++;
}
public void testPlusI() {
int i = 0;
int j = ++i;
}
}
将上面的源代码编译之后,使用javap
命令查看编译生成的代码(忽略次要代码)如下:
...
{
...
public void testIPlus();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=3, args_size=1
0: iconst_0 // 生成整数0
1: istore_1 // 将整数0赋值给1号存储单元(即变量i)
2: iload_1 // 将1号存储单元的值加载到数据栈(此时 i=0,栈顶值为0)
3: iinc 1, 1 // 1号存储单元的值+1(此时 i=1)
6: istore_2 // 将数据栈顶的值(0)取出来赋值给2号存储单元(即变量j,此时i=1,j=0)
7: return // 返回时:i=1,j=0
LineNumberTable:
line 4: 0
line 5: 2
line 6: 7
public void testPlusI();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=3, args_size=1
0: iconst_0 // 生成整数0
1: istore_1 // 将整数0赋值给1号存储单元(即变量i)
2: iinc 1, 1 // 1号存储单元的值+1(此时 i=1)
5: iload_1 // 将1号存储单元的值加载到数据栈(此时 i=1,栈顶值为1)
6: istore_2 // 将数据栈顶的值(1)取出来赋值给2号存储单元(即变量j,此时i=1,j=1)
7: return // 返回时:i=1,j=1
LineNumberTable:
line 9: 0
line 10: 2
line 11: 7
}
...