volatile的两大性质

   日期:2020-09-20     浏览:97    评论:0    
核心提示:一、可见性public class SynctestApplication { //底层使用了lock指令实现锁缓存行 //-XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -Xcomp private volatile boolean flag = true; public void refresh() { flag = false; System.out.println(Thread.

一、可见性

public class SynctestApplication { 

    //底层使用了lock指令实现锁缓存行
    //-XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -Xcomp
    private volatile boolean flag = true;

    public void refresh() { 
        flag = false;
        System.out.println(Thread.currentThread().getName() + "fix flag");
    }

    public void load() { 
        System.out.println(Thread.currentThread().getName() + "start------");
        int i = 0;
        while (flag) { 
            i++;


            
            //什么也不做

            
            

            

            //System.out.println("-----------");

            

            //休眠1秒 ----- 能
            //shortWait(1000000); 缓存是否失效,线程栈中的缓存有个过期时间

            //休眠0.1秒 ---- 不能
            //shortWait(1000);
        }
        System.out.println(Thread.currentThread().getName() + "jump for: i= " + i);
    }

    public static void main(String[] args) { 
        SynctestApplication test = new SynctestApplication();
        new Thread(() -> test.load(), "threadA").start();

        try { 
            Thread.sleep(2000);
            new Thread(() -> test.refresh(), "threadB").start();
        } catch (InterruptedException e) { 
            e.printStackTrace();
        }
    }

    //1毫秒=1000微秒 1毫秒=1000 000纳秒
    public static void shortWait(long interval) { 
        //nanoTime()返回的是纳秒
        long start = System.nanoTime();
        long end;
        do { 
            end = System.nanoTime();
        } while(start + interval >= end);
    }

}


上图中是硬件架构,其实JMM内存模型和这个模型是一样的,只不过JMM操作的各个逻辑块,其实底层都是和上面的各个硬件有映射关系。
volatile的可见性其实就是利用了缓存一致协议(锁缓存行)(MESI)(M:修改 <----E:独占 <---- S:共享 <---- I:失效)。多个线程共享一个变量的时候,会将共享变量缓存到自己的缓存栈中,当当前这个变量是被valatile修饰的时候,那个这个变量所在的缓存行,就会被上面的MESI四种状态所标记,当前就会保证只要有一个线程修改了共享变量,总线利用总线嗅探会监测到当前的变化,并把其它缓存中的该变量的状态修改为I无效,其它线程如果需要获取该变量,都需要重新从主存中读取数据。
修改当前缓存中的共享变量对于当前线程来说是线程安全的,但是对被volatile修饰的变量来说,多个线程共同操作这个变量的时候,这个过程就不是线程安全的,所以volatile并不是线程安全的。下面是一个例子

private static volatile long sum = 0;

    public static void main(String[] args) { 

        for (int i=0; i<5; ++i) { 
            new Thread(()->{ 
                for (int j=0; j<1000; ++j) { 
                    sum++;
                }
            }).start();
        }

        System.out.println(sum);//sum每次输出来的值,都不是我想要的
}

注意:如果多个核的线程在操作同一个缓存行中的不同变量数据,那么就会出现频繁的缓存失效,即使在代码层面看这两个线程操作的数据之间完全没有关系。这种不合理的资源竞争情况就是伪共享

public class FalseSharing { 
    public static void main(String[] args) { 
        try { 
            testPointer(new Pointer());
        } catch (InterruptedException e) { 
            e.printStackTrace();
        }
    }

    private static void testPointer(Pointer pointer) throws InterruptedException { 
        long start = System.currentTimeMillis();
        Thread t1 = new Thread(() -> { 
            for (int i = 0; i < 100000000; i++) { 
                pointer.x++;
            }
        });

        Thread t2 = new Thread(() -> { 
            for (int i = 0; i < 100000000; i++) { 
                pointer.y++;
            }
        });

        t1.start();
        t2.start();
        t1.join();
        t2.join();

        System.out.println(System.currentTimeMillis() - start);
    }
}
class Pointer { 
    
    public volatile long x;
    public long a,b,c,d,e,f,g;//避免伪共享,加上x刚好是64字节,没加这一行,执行时间大概是3秒,加了以后,执行时间是0.5毫秒
    public volatile long y;
}

二、有序性(实现了相当于内存屏障的功能)

public class ReOrder { 
    //保证有序性,禁止重排序
    private volatile static ReOrder myInstance;

    public static ReOrder getMyInstance() { 
        if (myInstance == null) { 
            synchronized (ReOrder.class) { 
                if (myInstance == null) { 
                    
                    myInstance = new ReOrder();
                }
            }
        }
        return myInstance;
    }

    public static void main(String[] args) { 
        ReOrder.getMyInstance();
    }
}

为什么会有重排序呢???

从上图中可以看出来(1)(2)执行的结果都一样,但是对应的数据加载的指令却不一样,一个load代表从内存中加载一次数据通过高速缓存到cpu的过程,如果有重排序,那么就会减少一次这样的过程,这样的性能就比较高

 
打赏
 本文转载自:网络 
所有权利归属于原作者,如文章来源标示错误或侵犯了您的权利请联系微信13520258486
更多>最近资讯中心
更多>最新资讯中心
0相关评论

推荐图文
推荐资讯中心
点击排行
最新信息
新手指南
采购商服务
供应商服务
交易安全
关注我们
手机网站:
新浪微博:
微信关注:

13520258486

周一至周五 9:00-18:00
(其他时间联系在线客服)

24小时在线客服