volatile  这个关键字可以说是熟悉的陌生人。基本都听说过,但是对于他的使用又似是而非,在单线程运行时基本不会使用这个关键字。而多线程运行时估计也不知道怎么合适的使用。

下面针对这个关键字进行学习总结。 在网上不好找到关于C语言里面volatile关键字的例子,可能自己没找到。

首先给出volatile 关键字的官方定义: volatile 虽然提供了可见性保证,但并不保证操作的原子性。

https://www.cnblogs.com/fly-book/p/11375244.html

上面这个帖子讲述的是Java中volatile和synchronized的区别和使用。

synchronized,volatile都解决了共享变量 value 的内存可见性问题,但是前者是独占锁,同时只能有一个线程调用 get()方法,其他调用线程会被阻塞, 同时会存在线程上下文切换和线程重新调度的开销,这也是使用锁方式不好的地方。 而后者是非阻塞算法,不会造成线程上下文切换的开销。

volatile 虽然提供了可见性保证,但并不保证操作的原子性。

一般在什么时候才使用 volatile 关键字呢?

  • 写入变量值不依赖、变量的当前值时。 因为如果依赖当前值,将是获取一计算一写入 三步操作,这三步操作不是原子性的,而 volatile 不保证原子性。
  • 读写变量值时没有加锁。 因为加锁本身已经保证了内存可见性,这时候不需要把变量声明为 volatile 的

/**
 * volatile不能代替锁
 * volatile无法保证i++的原子性操作
 */
public class VolatileDemo {
    static volatile int i = 0;
    public static class PlusTask implements Runnable {
        @Override
        public void run() {
//            synchronized (VolatileDemo.class){
                for (int j = 0; j < 1000; j++) {
                    i++;
                }
//            }
        }
    }

    public static void main(String[] args) throws InterruptedException{
        Thread[] threads = new Thread[10];
        for (int a = 0; a < 10; a++) {
            threads[a] = new Thread(new PlusTask());
            threads[a].start();
        }
        for (int a = 0; a < 10; a++) {
            threads[a].join();
        }
        System.out.println(i);//i的值小于10000
    }
}

//可见性
public class NoVisibility {
    private static volatile boolean ready = false;
    private static int number;
    public static class ReadThread extends Thread{
        @Override
        public void run() {
            while (!ready);  //没使用volatile声明ready前,主线程修改ready的状态,ReadThread无法"看到"主线程中的修改
            System.out.println(number);
        }
    }
    public static void main(String[] args) throws InterruptedException{
        ReadThread readThread = new ReadThread();
        readThread.start();
        Thread.sleep(1000);
        number = 42;
        ready = true;
        Thread.sleep(1000);
    }
}

上面例子中,给出的注释,如果没用volatile, 说主线程修改的ready无法同步到ReadThread线程,但是实际跑的过程中,发现不是这样的,即使没用volatile修饰,是可以同步到其他线程的的。

例如我自己编写的例子:

  在用下面命令 gcc -g volatileOK.c -o volatile -lpthread 编译后,发现在main线程中修改的flag 可以同步到add2线程。 按照大家的理解,没有添加volatile, 怎么会同步呢?哪儿出问题?

问题出现使用的编译命令,上面的编译命令没有使用优化选项,也就是不优化。我们回过头来再看看volatile的定义:

 volatile 关键字修饰变量的作用:让编译器不对被 volatile 修饰的变量进行优化,从而达到稳定访问内存的目的。保持内存可见性。
注意:虽然 volatile 叫做易变关键字,但这里仅仅是描述它修饰的变量可能会变化,要编译器注意,并不是要求被它修饰的变量必须
变化!这点要特别注意。

可以看出来,上面的解释是说,volatile可以让变量不被编译器优化。 如果编译器本来就没有优化变量,那么当然变量会同步到所有线程。反过来理解就是:volatile的使用场景,一定是开了编译器优化开关,如果没有开,变量自己就会同步到其他线程。

然后我们在添加-O3编译开关测试,正如上面所说,实例中flag就不会同步到add2线程,就需要添加volatile来保证变量的可见性。

但是请记住,正如第一个例子中所写,volatile只能保证可见性,但是保证不了原子性,如果多个线程同时执行一个变量自加,那么volatile只能保证自加的可见性,保证不了原子性,在C语言中想保持原子性,就必须添加锁来实行。

 

我们在回来上面示例中,红色圈圈出来的sleep(1);  发现在添加了-O3优化编译开关后,即使不添加volatile, 如果此处有sleep, flag也能同步过去。想到的是有sleep会有线程之间的切换,但不清楚为啥会导致flag的可见性增强?

 

https://blog.csdn.net/m0_62391199/article/details/123746218

https://www.cnblogs.com/fly-book/p/11375244.html

https://blog.csdn.net/Goforyouqp/article/details/131309962

 

posted on 2024-04-01 16:17  shiyuan310  阅读(3)  评论(0编辑  收藏  举报