分析以下案例:
public class Test06 {
public static void main(String[] args) {
MyThread run=new MyThread();
for(int i=0;i<5;i++){
new Thread(run).start();
}
//等待线程执行完毕
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(run.count);
}
}
class MyThread implements Runnable{
//volatile保证count的可见性
volatile int count =0;
@Override
public synchronized void run() {
for(int i=0;i<1000;i++){
count++;
}
}
}
思考:输出的count的值会是多少呢?
分几种情况:
- count属性没有加volatile关键字修饰,方法没有加synchronized修饰。
此种情况我们知道在多线程访问下,没有加任何的处理,最后结果一定是小于等于5000
- count属性加了volatile关键字修饰,方法没有加snnchronized修饰。
此种情况count对于每一个线程都是可见的,但是方法没有进行加锁处理,同一时刻,多个线程可能读到同一个值进行添加,所以count的值也可能小于等于5000
- count属性加了volatile关键字修饰,方法加了synchronized修饰。
此种情况,count对于每一个线程都是可见的,count++操作又有synchronized修饰,结果肯定是5000。答案正确吗?不是绝对正确的。为什么呢?
解释:因为++操作不是原子操作,可能会存在指令重排,在高并发的情况下,计算机为了提高性能,就会进行指令重排,就可能出现问题,不能得到想要的答案。
那么我们现在思考:这个问题要怎么解决呢?
java8的java.util.concurrent包中给我们提供了新的方法:
public class Test {
public static void main(String[] args) {
MyThread2 run = new MyThread2();
for(int i=0;i<5;i++){
new Thread(run).start();
}
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(run.count.get());//
}
}
class MyThread2 implements Runnable{
//AtomicInteger通过CAS原理实现的
volatile AtomicInteger count=new AtomicInteger(0);
@Override
public void run() {
for(int i=0;i<1000;i++){
count.getAndIncrement();//对count++操作
}
}
}
使用此种方式可以保证所得到的结果一定是5000。如果对CAS感兴趣的可以进一步深入的了解。