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