1.为什么使用volatile
在 深入理解Java虚拟机中提到过jvm内存模型。在多线程操作中,普通变量在线程间传递均需要通过主内存实现。正常情况下,每个线程在主内存中获取变量,放到自己的工作内存中,然后读写变量值。线程完成后将变量放入主内存。不同线程之间的变量不能直接访问。所以线程不能及时获取其他线程对同一变量修改的值。造成变量不可见。所以为了确保某个线程写入的变量对于其他线程来说是可见的,需要加同步。
2.volatile使用
修饰类变量 :volatile boolean b = true;
优点
1.保证了变量在线程之间的可见性
有了volatile的修饰,当一个线程修改了这个变量的值,volatile 保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新。变量不会被缓存到寄存器或者对其他处理器不可见的地方。
2.禁止指令重排序优化
赋值后多执行了一个“load addl $0x0, (%esp)”操作,这个操作相当于一个内存屏障(指令重排序时不能把后面的指令重排序到内存屏障之前的位置)。
缺点
不能保证操作的原子性。
volatile int count = 0;
/*synchronized*/ void m(){
for(int i = 0; i < 10000; i++){
System.out.println(Thread.currentThread().getName() + " - " + i);
count++;
}
}
public static void main(String[] args) {
final Test_10 t = new Test_10();
List<Thread> threads = new ArrayList<>();
for(int i = 0; i < 10; i++){
threads.add(new Thread(new Runnable() {
@Override
public void run() {
t.m();
}
}));
}
for(Thread thread : threads){
thread.start();
}
for(Thread thread : threads){
try {
//join() 等线程执行结束,主线程再执行。
thread.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println(t.count);
}
期望的结果是100000 但实际上会小于100000。
count++; 是可以拆分的,不是一个原子操作,非原子操作都会存在线程安全问题。
解决方法:加锁或者改成原子操作。
即:
1) synchronized (this){
count++;
}
2)
AtomicInteger count = new AtomicInteger(0);
count.incrementAndGet();
incrementAndGet()是原子操作。
3.使用情景
1.对变量的写入操作不依赖变量的当前值,或者你能确保只有单个线程更新变量的值
2.该变量不会与其他状态变量一起纳入不变性条件中
3.在访问变量时候不需要加锁