马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
- volatile可以保证变量的可见性。
- 这里的变量包括类变量、实例变量,但不包括局部变量和方法参数,因为后者是线程私有的,不存在线程竞争问题
- java内存模型(JMM)规定,所有变量都存储在主内存中,同时每个线程还有自己的工作内存。
- 线程对变量的所有操作(读取、赋值等),都必须在工作内存中进行,而不能直接读写主存中的数据
- 不同线程也无法访问对方工作内存中的变量,线程间变量值的传递需要通过主存来完成(引自周志明《深入理解Java虚拟机》)
- volatile可以保证
- (1)任何线程更新volitale修饰的变量后都会立即同步到主存中
- (2)线程每次读取volitale修饰的变量,都必须到主存中取最新值
- 以上亮点保证了“可见性”,即任意线程对变量的修改能立即被其他线程所知
复制代码- package com.concurrent;
- public class VolatileDemo {
- // 如果不加volatile,你会发现线程2已经结束很久了,线程1还在死循环。
- // 但是一旦加上volatile,线程2一执行,线程1会立刻跳出循环
- // 这是因为volatile可以保证变量的可见性。
- // 这里的变量包括类变量、实例变量,但不包括局部变量和方法参数,因为后者是线程私有的,不存在线程竞争问题
- // java内存模型(JMM)规定,所有变量都存储在主内存中,同时每个线程还有自己的工作内存。
- // 线程对变量的所有操作(读取、赋值等),都必须在工作内存中进行,而不能直接读写主存中的数据,
- // 不同线程也无法访问对方工作内存中的变量,线程间变量值的传递需要通过主存来完成。
- // volatile可以保证
- // (1)任何线程更新volitale修饰的变量后都会立即同步到主存中
- // (2)线程每次读取volitale修饰的变量,都必须到主存中取最新值
- // 以上亮点保证了“可见性”,即任意线程对变量的修改能立即被其他线程所知
- //
- //public static Integer money = 1000;
- public volatile static Integer money = 1000;
- public static void main(String[] args) throws InterruptedException {
- //线程1
- new Thread(() -> {
- while (money == 1000) {
- }
- System.out.println("存款已经不是" + money + "了");
- }).start();
- Thread.sleep(2000);
- //线程2
- new Thread(() -> {
- money = 900;
- System.out.println("存款现在是" + money);
- }).start();
- }
- }
复制代码 但是,volatile并不能保证原子性。下述代码如果能保证原子性的情况下应该返回200000,但现实执行后打印的结果远小于这个值。这是由于race++这个看似简单的操作现实上包含3个步骤:
(1)从主存获取值
(2)执行加1(iconst_1,iadd指令)
(3)写回主存
在线程执行iconst_1,i_add这些指令的时候,其他线程可能已经把race的值改变了,因此当前内存写回主存时就可能覆盖掉最新的值而把老值加1的结果写回去。(此处代码息争释均引自周志明《深入理解Java虚拟机》)
- package com.concurrent;
- public class VolatileDemo2 {
- 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.length; i++) {
- threads[i] = new Thread(()->{
- for (int j = 0; j < 10000; j++) {
- increase();
- }
- });
- threads[i].start();
- }
- while(Thread.activeCount() > 2){
- Thread.yield();
- }
- System.out.println(race);
- }
- }
复制代码 同时,volatile能防止指令重排。所谓指令重排,就是虚拟机在执行执行时,不考虑并发的影响,再保证结果稳定的情况下,将部分指令重新排序的征象,也就是所谓的“线程内表现为串行”(With-Thread as-if-serial semantics).
一个典型的例子是懒汉是单例的双重检测锁模式。如果不加volatile修饰,由于new对象并非原子操作,就有可能出现指令重排的征象。new对象分为三个步骤(1)分配内存空间 (2)执行构造方法,初始化对象 (3)把对象指针执行这片空间。指令重排后原本(1)(2)(3)的顺序可能酿成(1)(3)(2),这样有可能线程A执行完(1)(3),正要执行(2)的时候,线程B抢到执行权,判定lazyMan == null,为false,于是返回对象,但是此时的对象还并没有初始化,这时候去使用此对象显然会有题目。比如这里的使用就是打印,那就有可能出现先打印对象,然后再执行构造方法中的打印。但是,经过多次尝试,并没有出现1次这个题目,是概率太小还是理论有误?
- package com.concurrent;
- public class LazyMan {
- private LazyMan(){
- System.out.println(Thread.currentThread().getName()+"ok");
- }
- //private static LazyMan lazyMan = null;
- private static volatile LazyMan lazyMan = null;
-
- //双重检测锁模式的懒汉式单例 DCL懒汉式
- public static LazyMan getInstance(){
- if(lazyMan == null){
- synchronized (LazyMan.class){
- if(lazyMan == null){
- lazyMan = new LazyMan();
- }
- }
- }
- return lazyMan;
- }
- public static void main(String[] args) {
- for (int i = 0; i < 100; i++) {
- new Thread(new Runnable() {
- @Override
- public void run() {
- LazyMan lazyMan = LazyMan.getInstance();
- System.out.println(lazyMan);
- }
- }).start();
- }
- }
- }
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |