++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
}
...