什么是volatile
Volatile关键字的作用是保证被修饰的变量在多个线程之间的可见性。并不保证线程安全
先来看下面这个例子
class ThreadVolatileDemo extends Thread {
public boolean flag = true;
@Override
public void run() {
System.out.println("开始执行子线程....");
while (flag) {
}
System.out.println("线程停止");
}
public void setRuning(boolean flag) {
this.flag = flag;
}
}
public class ThreadVolatile {
public static void main(String[] args) throws InterruptedException {
ThreadVolatileDemo threadVolatileDemo = new ThreadVolatileDemo();
threadVolatileDemo.start();
Thread.sleep(3000);
threadVolatileDemo.setRuning(false);
System.out.println("flag 已经设置成false");
Thread.sleep(1000);
System.out.println(threadVolatileDemo.flag);
}
}
上面代码中setRuning(false)并不能使线程停止,原因是线程之间是不可见的,读取的是副本,没有及时读取到主内存结果。解决办法使用Volatile关键字将解决线程之间可见性, 强制线程每次读取该值的时候都去“主内存”中取值。
当把变量声明为volatile类型后,编译器与运行时都会注意到这个变量是共享的,因此不会将该变量上的操作与其他内存操作一起重排序。
volatile变量不会被缓存在期存器或者对其他处理器不可见的地方,因此在读取volatile类型的变量时总会返回最新写入的值。
--- 摘自 《java编发编程实战》
变量的可见性是我们期待的结果,但是多线程操作共享变量时,却并非如此。由于处理器将变量存放至缓存行中进行读取,这里的数据对于其他处理器不可见,所以其他处理器上的线程就有可能读取到旧的数据,即失效数据。为了避免读取到失效数据,应该在多线程共享数据时,使用同步机制来保证变量的可见性。
即时线程读取了失效数据,这个数据也是其他线程在某个时间节点修改的结果,是有据可循的,被称为最低安全性。Java内存模型中要求变量的读取和写入必须是原子性,但是对于double和long变量的读写操作会被分解为两个32位操作,当这种读写操作在不同线程中执行时,可能造成数据混乱。所以对于long和double的共享变量必须使用volatile保护起来。
当操作共享变量的所有线程在同一个锁上进行同步操作时,也能保证变量的可见性。所以,加锁的含义不仅仅局限于互斥行为,还包含内存可见性,但必须在同一把锁上。
Volatile非原子性
注意: Volatile非原子性,volatile也不能保证线程安全,也不能保证数据的原子性。
public class VolatileNoAtomic extends Thread {
private static volatile int count;
// private static AtomicInteger count = new AtomicInteger(0);
private static void addCount() {
for (int i = 0; i < 1000; i++) {
count++;
// count.incrementAndGet();
}
System.out.println(count);
}
public void run() {
addCount();
}
public static void main(String[] args) {
VolatileNoAtomic[] arr = new VolatileNoAtomic[100];
for (int i = 0; i < 10; i++) {
arr[i] = new VolatileNoAtomic();
}
for (int i = 0; i < 10; i++) {
arr[i].start();
}
}
}
运行结果如下:
结果发现 数据不同步,因为Volatile不用具备原子性。
使用AtomicInteger原子类
volatile只能保证变量的修改对其他线程可见,不能保证原子性,AtomicInteger是一个提供原子操作的Integer类,通过线程安全的方式操作加减。
public class VolatileNoAtomic extends Thread {
static int count = 0;
private static AtomicInteger atomicInteger = new AtomicInteger(0);
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
//等同于i++
atomicInteger.incrementAndGet();
}
System.out.println(count);
}
public static void main(String[] args) {
// 初始化10个线程
VolatileNoAtomic[] volatileNoAtomic = new VolatileNoAtomic[10];
for (int i = 0; i < 10; i++) {
// 创建
volatileNoAtomic[i] = new VolatileNoAtomic();
}
for (int i = 0; i < volatileNoAtomic.length; i++) {
volatileNoAtomic[i].start();
}
}
}
volatile与synchronized区别
仅靠volatile不能保证线程的安全性。(原子性)
- volatile轻量级,只能修饰变量。synchronized重量级,还可修饰方法
- volatile只能保证数据的可见性,不能用来同步,因为多个线程并发访问volatile修饰的变量不会阻塞。
- synchronized不仅保证可见性,而且还保证原子性,因为,只有获得了锁的线程才能进入临界区,从而保证临界区中的所有语句都全部执行。多个线程争抢synchronized锁对象时,会出现阻塞。
线程安全性
- 线程安全性包括两个方面,①可见性。②原子性。
- 从上面自增的例子中可以看出:仅仅使用volatile并不能保证线程安全性。而synchronized则可实现线程的安全性。