volatile是一个和多线程相关的关键字,主要有一下2点作用(只保证可见性,不保证原子性)
- 防止指令重排(有序性)
JVM在不改变程序执行结果的前提下,在编译时会对指令的顺序进行重新排序,而volatile关键字则能够禁止指令的重新排序 - 能够确保线程内存中的对象对其他内存可见。(可见性)
正常情况下每个线程操作共享变量时需要经历如下几个步骤
如果某个线程(线程01)要操作主内存中的变量A,则该线程会把A变量装载到线程内部的内存中做一个副本,之后线程操作的是线程内存变量A的副本,等到操作完成再将变量的值刷新到主内存中。假设变量A=0,此时有100个线程并发的对它进行+1,理想情况,最后A的值是100.但再设计情况中我们会发现,A的值并不会是100,(假设线程01读到的A的值是0,而线程02读到的是1,但线程02把值刷回主内存,此时A=2,然后线程01执行结束将1刷回主内存,而此时主内存的值从2变成了1),我们将变量A用volatile后,线程操作变量之后,不用等到刷新回写这一步,立即就能直接将A的值刷回主内存,从而保证了变量A对其他内存的可见性。我们再次试验,会发现变量A还是不会等于100.这也就说明了volatile关键字,并不能保证原子性。单靠volatile关键字无法解决数据不一致的问题。(我们可以使用synchronized关键字,它不但能保证可见性,还能保证原子性)
个人建议:在只有读或者写的操作的情况下建议使用volatile关键字修饰,这样可以提高一些并发时候的效率问题。对于一些一已经被加锁的变量则不需要加volatile关键字,因为这样无法发挥出它的优势。
static volatile Integer a=new Integer(0);
static Integer b=new Integer(0);
public static void main(String[] args) {
testHaveVolatileDemo();//使用volati修饰
testNoVolatileDemo();//没有使用volati修饰
}
public static void testHaveVolatileDemo() {
for (int i = 0; i < 100; i++) {
Thread thread = new Thread(new thread01());
thread.setName("thread" + i);
thread.start();
}
System.out.println("A=" + a);
}
public static void testNoVolatileDemo() {
for (int i = 0; i < 100; i++) {
Thread thread = new Thread(new thread02());
thread.setName("thread" + i);
thread.start();
}
System.out.println("B=" + b);
}
static class thread01 implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " read A =" + a);
a++;
}
}
static class thread02 implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " read B =" + b);
b++;
}
}