首页 > 试题广场 >

关于volatile关键字,下列描述不正确的是?

[不定项选择题]
关于volatile关键字,下列描述不正确的是?
  • 用volatile修饰的变量,每次更新对其他线程都是立即可见的。
  • 对volatile变量的操作是原子性的。
  • 对volatile变量的操作不会造成阻塞。
  • 不依赖其他锁机制,多线程环境下的计数器可用volatile实现。
推荐

一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:

1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。

2)禁止进行指令重排序。

volatile只提供了保证访问该变量时,每次都是从内存中读取最新值,并不会使用寄存器缓存该值——每次都会从内存中读取。

而对该变量的修改,volatile并不提供原子性的保证。

由于及时更新,很可能导致另一线程访问最新变量值,无法跳出循环的情况

多线程下计数器必须使用锁保护。

编辑于 2016-04-25 14:55:04 回复(15)
所谓 volatile的措施,就是
1. 每次从内存中取值,不从缓存中什么的拿值。这就保证了用 volatile修饰的共享变量,每次的更新对于其他线程都是可见的。
2. volatile保证了其他线程的立即可见性,就没有保证原子性。
3.由于有些时候对 volatile的操作,不会被保存,说明不会造成阻塞。不可用与多线程环境下的计数器。
发表于 2016-03-21 16:03:23 回复(10)

转载于:http://www.ibm.com/developerworks/cn/Java/j-jtp06197.html


Java 语言中的 volatile 变量可以被看作是一种 “程度较轻的 synchronized”;与synchronized 块相比,volatile 变量所需的编码较少,并且运行时开销也较少,但是它所能实现的功能也仅是 synchronized 的一部分。本文介绍了几种有效使用 volatile 变量的模式,并强调了几种不适合使用 volatile 变量的情形。

锁提供了两种主要特性:互斥(mutual exclusion) 和可见性(visibility)。互斥即一次只允许一个线程持有某个特定的锁,因此可使用该特性实现对共享数据的协调访问协议,这样,一次就只有一个线程能够使用该共享数据。可见性要更加复杂一些,它必须确保释放锁之前对共享数据做出的更改对于随后获得该锁的另一个线程是可见的 —— 如果没有同步机制提供的这种可见性保证,线程看到的共享变量可能是修改前的值或不一致的值,这将引发许多严重问题。

Volatile 变量

Volatile 变量具有 synchronized 的可见性特性,但是不具备原子特性。这就是说线程能够自动发现 volatile 变量的最新值。Volatile 变量可用于提供线程安全,但是只能应用于非常有限的一组用例:多个变量之间或者某个变量的当前值与修改后值之间没有约束。因此,单独使用 volatile 还不足以实现计数器、互斥锁或任何具有与多个变量相关的不变式(Invariants)的类(例如 “start <=end”)。

出于简易性或可伸缩性的考虑,您可能倾向于使用 volatile 变量而不是锁。当使用 volatile 变量而非锁时,某些习惯用法(idiom)更加易于编码和阅读。此外,volatile 变量不会像锁那样造成线程阻塞,因此也很少造成可伸缩性问题。在某些情况下,如果读操作远远大于写操作,volatile 变量还可以提供优于锁的性能优势。

正确使用 volatile 变量的条件

您只能在有限的一些情形下使用 volatile 变量替代锁。要使 volatile 变量提供理想的线程安全,必须同时满足下面两个条件:

  • 对变量的写操作不依赖于当前值。
  • 该变量没有包含在具有其他变量的不变式中。


实际上,这些条件表明,可以被写入 volatile 变量的这些有效值独立于任何程序的状态,包括变量的当前状态。

第一个条件的限制使 volatile 变量不能用作线程安全计数器。虽然增量操作(x++)看上去类似一个单独操作,实际上它是一个由读取-修改-写入操作序列组成的组合操作,必须以原子方式执行,而 volatile 不能提供必须的原子特性。实现正确的操作需要使x 的值在操作期间保持不变,而 volatile 变量无法实现这点。(然而,如果将值调整为只从单个线程写入,那么可以忽略第一个条件。)

大多数编程情形都会与这两个条件的其中之一冲突,使得 volatile 变量不能像 synchronized 那样普遍适用于实现线程安全。

发表于 2016-10-24 16:21:44 回复(4)
选BD
解释一下volatile:



在JVM中,有主内存和工作内存的概念,每个线程对应一个工作内存,并共享主内存数据,
1. 对于普通变量:读操作会优先读取工作内存的数据,如果工作内存不存在,则从主内存中拷贝一份数据到工作内存,写操作只会修改工作内存中的副本数据,这种情况下,其他线程就无法读取变脸的最新值。
2. 对于volatile变量:读操作时JVM会把工作内存中对应的值设置为无效,要求线程从主内存中读取数据,写操作JVM也会把工作内存中对应的数据刷新到主内存中,这种情况下,其他线程就可以读取变量的最新值。

volatile变量的内存可见性,是基于内存屏蔽实现的,内存屏蔽也就是一个CPU指令。在程序运行的时候,为了提高执行性能,编译器和处理器会对指令进行重排序,JVM为了保证不同的编译器和CPU上有相同的结果,通过插入特定类型的内存屏蔽来禁止特定类型的编译器重排序和处理器重排序,插入一条内存屏蔽会告诉编译器和CPU,不管什么指令都不能和这条内存屏蔽指令重排序。

处理器为了提高处理速度,不直接和内存进行通讯,而是将系统内存的数据独到内部缓存后再进行操作,但操作完后不知什么时候会写到内存。

如果对声明了volatile变量进行写操作时,JVM会向处理器发送一条Lock前缀的指令,将这个变量所在缓存行的数据写会到系统内存。 这一步确保了如果有其他线程对声明了volatile变量进行修改,则立即更新主内存中数据。

但这时候其他处理器的缓存还是旧的,所以在多处理器环境下,为了保证各个处理器缓存一致,每个处理会通过嗅探在总线上传播的数据来检查 自己的缓存是否过期,当处理器发现自己缓存行对应的内存地址被修改了,就会将当前处理器的缓存行设置成无效状态,当处理器要对这个数据进行修改操作时,会强制重新从系统内存把数据读到处理器缓存里。 这一步确保了其他线程获得的声明了volatile变量都是从主内存中获取最新的。

1. volatile只保证了可见性和防止了指令重排序,并没有保证原子性。
2. volatile修饰的变量只是保证了每次读取时都从主存中读,每次修改时,都将修改后的值重新写入了主存。
3. 在synchronized修饰的方法体或者常量(final)不需要使用volatile。
4. 由于使用了volatile屏蔽掉了JVM中必要的代码优化,所以在效率上比较低,因此一定在必要的时候才能使用该关键字。

发表于 2018-07-13 11:26:56 回复(7)
原理:

当一个变量被定义为volatile之后,就可以保证此变量对所有线程的可见性,即当一个线程修改了此变量的值的时候,变量新的值对于其他线程来说是可以立即得知的。可以理解成:对volatile变量所有的写操作都能立刻被其他线程得知。但是这并不代表基于volatile变量的运算在并发下是安全的,因为volatile只能保证内存可见性,却没有保证对变量操作的原子性。比如下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
 * 发起20个线程,每个线程对race变量进行10000次自增操作,如果代码能够正确并发,
 * 则最终race的结果应为200000,但实际的运行结果却小于200000。
 *
 * @author Colin Wang
 *
 */
public class VolatileTest {
    public static volatile int race = 0;
 
    public static void increase() {
        race++;
    }
 
    private static final int THREADS_COUNT = 20;
 
    public static void main(String[] args) {
        Thread[] threads = new Thread[THREADS_COUNT];
 
        for (int i = 0; i < THREADS_COUNT; i++) {
            threads[i] = new Thread(new Runnable() {
 
                @Override
                public void run() {
                    for (int i = 0; i < 10000; i++) {
                        increase();
                    }
                }
            });
            threads[i].start();
        }
         
        while (Thread.activeCount() > 1)
            Thread.yield();
 
        System.out.println(race);
    }
}

这便是因为race++操作不是一个原子操作,导致一些线程对变量race的修改丢失

发表于 2015-10-02 11:28:02 回复(2)
一旦一个共享变量,被volatile修饰之后,就具备了两层语义:
(1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某一个变量的值,这新值对其他线程来说是立即可见的
(2)禁止进行指令重排序
volatile只提供了保证访问该变量时,每次都是从内存读取最新值,并不会使用寄存器缓存该值,每次都会从内存中读取。而对该变量的修改,volatile并不提供原子性的保证。
由于及时更新,很可能导致另一线程访问最新变量值,无法跳出循环的情况
多线程下计数器必须使用锁保护
发表于 2015-09-15 21:15:08 回复(1)
大家主要疑惑的应该是CD选项:
A选项中,volatile的定义就是:用volatile修饰的变量,每次更新对其他线程都是立即可见的。所以不需要有疑问,所以A正确;
B选项中,volatile只能保证可见性,不能保证原子性;如果保证了原子性那还要锁干嘛,保证原子性是锁的工作,所以B错误;
C选项中,volatile修饰一个变量时,是不会加锁的;而只有在加锁情况下才会造成阻塞,所以C正确;
D选项中,多线程情况下,volatile是无法保证线程安全的,而计数器又要求线程安全,所以不能用volatile实现计数器,所以D错误;
发表于 2020-07-15 19:31:45 回复(1)
1.在访问volatile变量时不会执行加锁操作,因此也就不会使执行线程阻塞,因此volatile变量是一种比sychronized关键字更轻量级的同步机制.
2.第一个条件的限制使 volatile 变量不能用作线程安全计数器。虽然增量操作(x++)看上去类似一个单独操作,实际上它是一个由读取-修改-写入操作序列组成的组合操作,必须以原子方式执行,而 volatile 不能提供必须的原子特性。实现正确的操作需要使 x 的值在操作期间保持不变,而 volatile 变量无法实现这点。
编辑于 2017-04-18 15:40:43 回复(0)
正确  or  不正确 总是看岔是什么贵,驾考宝典看多了,看代码都眼睛花了
发表于 2018-12-04 18:14:07 回复(0)
答案BD我选AC **** **** 最假的是我没有看错题-_-|| 对,我选的就是不正确的
发表于 2017-04-08 16:39:14 回复(3)
volatile不支持原子性,因为对volatile变量进行++操作时,volatile变量值可能改变
发表于 2022-03-02 16:52:23 回复(0)
volatile修饰一个变量时,是不会加锁的;而只有在加锁情况下才会造成阻塞!!!
发表于 2022-01-31 10:52:56 回复(0)
以为选正确的,就选了BD
编辑于 2021-10-24 17:19:40 回复(0)
保持可见性
禁止重排序
不是原子操作
发表于 2021-09-21 09:20:33 回复(0)
看下这个博客吧,里面有详细讲解volatile
发表于 2018-04-11 13:18:42 回复(0)
volatile保证可见性,不保证原子性,错误的是 B  D
发表于 2016-04-29 15:17:59 回复(0)

Volatile

可见性   不保证原子性  禁止指令重排取消发表

原子性:不可分割 要么同时成功要么同时失败

不加lock跟synchronized怎么保证原子性

java,util,concurrent,atomic

使用原子类,解决原子性问题

原子类为什么这么高级

把变量改为原子类的AtomicInteger


他的底层方法都是native方法 即本地方法

他特别高效 这些类的底层都直接跟操作系统挂钩!在内存中修改值!Unsafe类是一个很特殊的存在!

指令重排

什么是指令重排:你写的程序,计算机并不是按照你写的那样去执行的。

源代码--> 编译器优化的重拍-->指令并行可也可能会重排--》内存系统也会重排---》 执行

指令重排有前提的,他会考虑数据之间的依赖性!

可能造成影响的结果

多线程操作多个数据 进行指令重排的时候可能会出现诡异结果

1000万次都不一定存在 但是逻辑上是可能的

volatile 可以避免指令重排

内存屏障 CPu指令。作用

一丶保证特定的操作执行顺序

二丶保证某些变量的内存可见性(利用这些特性 就可以保证volatile实现了可见性)

volatile可以保证可见性不可以保证原子性 由于内存屏障可以避免指令重排的现象产生!

发表于 2021-03-31 20:03:52 回复(1)
发现很多人对C选项存有疑惑,我在这里说一下我的理解,如有错误的地方,还请指出。
对volatile修饰的变量进行操作的时候是不会造成阻塞的,原因是volatile能够保证可见性,这里的可见性就是JMM(Java Memory Model)提供的一种机制。JMM规定所有变量都存储在主内存中,主内存是共享内存区域,所有线程都可以访问。但线程对变量的操作(读取赋值等)必须在工作内存中进行,首先要将变量从主内存拷贝到线程自己的工作内存空间,然后对变量进行操作,操作完成后再将变量写回主内存。因此不同线程间无法访问对方的工作内存(私有数据区域),线程间的通信(传值)必须通过主内存来完成。
因此,由于volatile提供的可见性机制保证了在对修饰的变量进行操作时是不会造成阻塞的,因为他们操作的都是自己工作内存中的副本。
发表于 2020-06-17 11:14:29 回复(0)
Volatile关键字可以修饰(类的成员变量,局部变量)
修饰之后:
1)不同线程对这个变量的操作可见 对其他线程立即可见
2)每次其他线程方法这个成员变量只会从内存中返回最新值
所以这个变量不具有原子性,也不会造成阻塞,多线程的计数器必须进行锁保护

发表于 2018-09-04 16:13:58 回复(0)
竟然完全避开了正确答案
编辑于 2018-05-14 07:49:24 回复(0)