最近,做题的时候经常遇到自增的问题,就此总结整理一下
一、常见情况
自增方式有两种:前置和后置,即i++和++i,它们的不同点在于i++是在程序执行完毕后进行自增,而++i是在程序开始执行前进行自增,示例如下:
public static void main(String[] args){
int i = 1;
System.out.println(i++ + i++);
System.out.println("i=" + i);
System.out.println(i++ + ++i);
System.out.println("i=" + i);
System.out.println(i++ + i++ + i++);
System.out.println("i=" + i);
}
结果如下
不明白的话可看下面的分析,看懂的直接跳过即可
表达式 i++ + i++首先执行第一个i++操作,由于自增操作会稍后执行,因此,运算时,i的值还是1,但自增操作后,i的值变为了2,接着执行第二个i++,运算时,i的值已经为2了,二执行了一个自增操作后,i的值变为了3,所以i++ + i++ = 1 + 2 =3,而运算完成后,i的值变为3
表达式 i++ + ++i首先执行第一个i++,但是自增操作后稍后执行。因此,此时i的值还为3,接着执行++i,此时i的值变为4,同时还要补执行i++的自增操作,所以i++ + ++i = 3 + 5 = 8
同理,i++ + i++ + i++ = 5 + 6 + 7 =18
二、循环中的自增
for循环中,由于for循环的第三个条件是最后判断(先执行循环语句,再执行第三个条件),所以i++和++i无甚区别,上代码说明:
while循环中,要首先判断括号内的条件,所以++i相比i++,少执行一次
三、a=a++?
最初看到这个表达式,没在意,以为和a++一个意思,后来跑了一段代码才发现不对劲
上代码
简单版的解释:可以理解为中间缓存变量机制
a = a++ 等同于
temp = a;
i = i + 1;
i = temp;
而a = ++a 等同于
a = a + 1;
temp = i;
i = temp;
深层次解释(结合jvm解释):
四、i++是线程安全的吗?
看到这个问题一脸蒙逼,i++ 居然还有线程安全问题?
只能说自己了解的不够多,水平有限
假设场景,1000个线程,每个线程对共享变量 count 进行 1000 次 ++ 操作
上代码
static int count = 0;
static CountDownLatch cd1 = new CountDownLatch(1000);
public static void main(String[] args) throws Exception{
CountRunnable countRunnable = new CountRunnable();
for (int i = 0; i <1000; i++) {
new Thread(countRunnable).start();
}
cd1.await();
System.out.println(count);
}
static class CountRunnable implements Runnable{
private void count(){
for (int i = 0; i < 1000; i++) {
count++;
}
}
@Override
public void run(){
count();
cd1.countDown();
}
}
第一次输出结果:995904
第一次输出结果:997097
第一次输出结果:999681
第一次输出结果:995186
第一次输出结果:998063
......
期望的结果应该是 1000000,但运行 N 遍,发现总是不为 1000000
所以,i++ 操作它不是线程安全的
每个线程都有自己的工作内存,每个线程需要对共享变量操作时必须先把共享变量从主内存 load 到自己的工作内存,等完成对共享变量的操作时再 save 到主内存。
问题就出在这了,如果一个线程运算完后还没刷到主内存,此时这个共享变量的值被另外一个线程从主内存读取到了,这个时候读取的数据就是脏数据了,它会覆盖其他线程计算完的值。。。
这也是经典的内存不可见问题,那么把 count 加上 volatile 让内存可见是否能解决这个问题呢? 答案是:不能。因为 volatile 只能保证可见性,不能保证原子性。多个线程同时读取这个共享变量的值,就算保证其他线程修改的可见性,也不能保证线程之间读取到同样的值然后相互覆盖对方的值的情况。
解决方案
对 i++ 操作的方法加同步锁,同时只能有一个线程执行 i++ 操作;
如图所示