volatile变量能保证线程安全性吗?为何?

  在谈及线程安全时,常会说到一个变量——volatile。在《Java并发编程实战》一书中是这么定义volatile的——Java语言提供了一种稍弱的同步机制,即volatile变量,用来确保将变量的更新操做通知到其余线程。书中的这句话说明了两点:①volatile变量是一种稍弱的同步机制;②volatile可以确保将变量的更新操做通知到其余线程——可见性。这两点和咱们探讨“volatile变量是否可以保证线程安全性”息息相关。编程

  什么是同步机制?在并发程序设计中,各进程对公共变量的访问必须加以制约,这种制约称为同步(该定义源于百度百科)。也就是说,同步机制即为对共享资源的一种制约。《Java并发编程实战》中为何说volatile是一种“稍弱的同步机制”呢?这和volatile可以确保可见性这一重要做用相关:缓存

  volatile可以保证字段的可见性:volatile变量,用来确保将变量的更新操做通知到其余线程。volatile变量不会被缓存在寄存器或者对其余处理器不可见的地方,所以在读取volatile类型的变量时总会返回最新写入的值。安全

  可见性和“每一个线程都有本身的缓存(或叫“线程的工做内存”)”有关系:并发

    ①操做没有用volatile来修饰字段时,各个线程都是先从主内存(堆)中复制一份数据到本身的工做内存中,而后操做本身工做内存中的数据,最后再更新到主内存中。测试

    ②当字段被volatile修饰后,各个线程操做该字段时,都是直接在主内存中进行操做的。spa

  由于volatile可以确保可见性,因此,在一些特定情形下可使用 volatile 变量替代锁,例如:在直接修改变量(不需先判断再修改)的状况下,多个线程同时去修改某个变量,一旦某个线程操做成功了,其余线程对这个变量的修改就马上创建在最新的变量值上再进行修改,这样一来就避免了线程安全问题。可是,要使 volatile 变量提供理想的线程安全,必须同时知足两个条件:①对变量的写操做不依赖于当前值;②该变量没有包含在具备其余变量的不变式中;不然依旧会出现线程安全问题。咱们用代码来作说明:线程

class Window implements Runnable {
    private volatile int ticket = 100;

    public void run() {
        for (;;) {
            //经过下面的①②两个步骤咱们能够发现:当不能知足“对变量的操做不依赖与当前值”,天然就会有线程安全问题。
            if (ticket > 0) {
                try {
                    Thread.sleep(100);//①多个线程同时判断到“ticket>0”,而后挂起了
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //②多个线程同时醒来,同时进行“ticket--”操做:
                System.out.println(Thread.currentThread().getName() + ":" + ticket--);
            } else {
                break;
            }
        }
    }
}
public class A03UseVolatileIsNotThreadSafe {
    public static void main(String[] args) {
        Window w = new Window();

        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();
    }
}

  测试结果:(1)出现了大量的重复数字; (2)最后还输出了 “-1”;==》说明变量即便用volatile修饰了但依旧出现了线程安全问题。设计

  代码解析:
    出现问题(1)的缘由:线程存在“先检查后执行”的竞态条件。可能有两个线程同时拥有CPU的执行权(机器是双核的),它们判断到作“if (ticket > 0)”,并同时作“ticket--”操做。
    出现问题(2)的缘由:
      ①当ticket==1时,两个或多个线程同时经过了“if (ticket > 0)”的判断,并进入了判断框中去执行代码;
      ②而后它们执行到“Thread.sleep(100);”就睡了;
      ③睡醒后总有一个线程会先抢到cup的执行权,而后执行“ticket--”操做,并将最新的ticket数值推送告知到每一个线程;
      ④此时那些在判断框中的其余的线程并不会再次作“if (ticket > 0)”的判断,而是直接拿最新的ticket并作“ticket--”操做。
    就算线程在“ticket--”以前每次都作“if (ticket > 0)”的判断,也依旧会有线程安全问题,由于又可能出现①那种同时经过判断的状态。code

  总结:当同时知足“①对变量的写操做不依赖于当前值;②该变量没有包含在具备其余变量的不变式中;”这两个条件时,volatile 变量是可以确保线程安全的。可是,大多数编程情形都会与这两个条件的其中之一冲突(好比上面的测试代码中对变量“先判断后操做”),使得 volatile 变量不能像 synchronized 那样广泛适用于实现线程安全。也就是说,大多数状况下都不能用volatile来确保线程的安全性。blog

相关文章
相关标签/搜索