一、先看一个程序
public class T1 {
static int p=0;
public static void main(String[] args) throws InterruptedException {
CountDownLatch count = new CountDownLatch(3);
for(int i=0; i<3; i++) {
Thread t = new Thread(()->{
for(int j=0; j<10000; j++) {
p++;
}
count.countDown();
});
t.start();
}
count.await();
System.out.println(p);
}
}
开3个线程对p进行++一万次操作,期望是30000,程序执行结果:
17478
二、为什么结果会少于30000
看下i++的字节码
0 getstatic #2 <T1.p : I> // get static变量
3 iconst_1 // 压栈
4 iadd // 增加
5 putstatic #2 <T1.p : I> // put static变量
8 return
可以看到,虽然i++只有一行代码,但从字节码角度来看是分为3步的,分别是
1.get
2.add
3.put
所以上面的原因就很好理解了,i++中间被打断了,其他线程在i++中间执行了,比如1个线程get p=0还没put时,第二个线程就也get并且put了。
三、如何解决
可以看到,问题产生的原因是i++并非原子性操作。那么解决的思路就是把i变成原子性的
1.加volatile?no
加volatile修饰可以解决这个问题吗?
答案是不能,volatile只保证了可见性和有序性,不能保证原子性。i++依然会分3步执行。
2.加synchronized
synchronized(T1.class) {
p++;
}
synchronized就是上锁,保证不可打断。是一张悲观锁
3.使用原子类
static AtomicInteger p = new AtomicInteger(0);
p.addAndGet(1);
原理是cas操作,是一种乐观锁