首页 > 技术文章 > volatile 可见性的模拟分析示例

loveyoumi 2018-08-12 17:41 原文

  volatile 作为java的关键字之一,必然有它存在的必要性;在很多的资料中,各位大神级的人物都对volatile做了深入的分析,在这里就不在赘述了;不清的朋友可以迁移到这个地址详细了解:https://www.cnblogs.com/dolphin0520/p/3920373.html

  那么已经了解volatile的作用。这里呢?将使用java代码将把volatile底层 “可见性”给扩大,并已代码的形式展示,volatile的可见性到底是怎么一回事;

  注意:本人亦不清楚volatile的底层是如何实现的,只是,仅仅只是通过各种资料中对volatile的分析,然后领悟出来的想法;(volatile 可能不是如此实现,切莫较真;有大神知道,也请爽快指教)

  最后提示:此文仅供参考,切勿入坑;

  具体实现:

  1,首先模拟主内存,在该模拟的主内存中只存在一个地址,该地址用于存放一个共享变量;代码如下;

import io.netty.util.internal.ConcurrentSet;

public class MainMemory {

    //模拟主内存中的一块内存地址,并存储有一个数据 0 
    private int data = 0;
    
    //记录持有该内存地址数据的所有对象,以便在内存地址数据被改变时,通知这些对象持有的数据无效;
    private ConcurrentSet<ICacheStatus> cacheHolder = new ConcurrentSet<>();

    /**
     * 模拟使用volatile关键字修饰的变量从主内存读取数据:主内存将保持读取者的一个状态修改通知器,当主内存的数据被修改时,会第一时间通知到数据持有者;
     * read方法和write方式使用synchronized关键字修饰,是为了模拟内存地址数据操作的原子性;
     */
    public synchronized int volatileRead(ICacheStatus cache) {
        cache.setStatus(true);
        cacheHolder.add(cache);
        return data;
    }
    
    /**
     * 模拟非volatile关键字修饰的变量从主内存中读取数据
     */
    public synchronized int read() {
        return data;
    }
    
    /**
     * 模拟向内存地址中写入数据
     */
    public synchronized void write(int outdata) {
        data = outdata;
        //通知缓存持有者,已持有的数据无效
        for(ICacheStatus holder : cacheHolder) {
            holder.setStatus(false);
        }
    }
    
    /**
     * 模拟缓存持有者释放缓存,主内存将在以后的数据改变时,不通知改对象;
     * @param cacheHolder
     */
    public void releaseCache(ICacheStatus outcacheHolder) {
        cacheHolder.remove(outcacheHolder);
    }
    
}

  2, 缓存状态通知器接口,代码如下:

public interface ICacheStatus {

    void setStatus(boolean status);
}

  3,模拟线程本地缓存对象,该对象针对于变量是否被volatile变量修饰,提供两种不同的操作:1,volatile修饰的变量,在使用时会检查本地缓存的数据是否过期,如过期,则向主内存重新获取; 2,非volatile修饰的变量,不检查是否过期(当然,这不合理,sun也应该不是这么实现的,仅供模拟   “volatile可见性”的演示;重要的事说n遍);代码如下:public class ThreadLocalCache implements ICacheStatus     //本地缓存从主内存中读取到的数    private int cache = -1;    /*

     * 模拟当前缓存数据的状态,true可用 ,false为不可用:需要向主内存中再次读取数据;
     */
    private boolean cacheFlag;
    
    //主内存
    private MainMemory mainMemory;
    
    public  ThreadLocalCache(MainMemory mm) {
        this.mainMemory = mm;
     this.cache = mm.volatileRead(this); } @Override
public void setStatus(boolean status) { this.cacheFlag = status; } //模拟变量被volatile关键字修饰,在使用前会检查当期缓存的数据是否过期,如果过期则向主内存从新读取; public int volatileRead() { if(!cacheFlag) cache = mainMemory.volatileRead(this); return cache; } //模拟非volatile变量的使用; public int read() {
return cache; } public void write(int data) { mainMemory.write(data); } /* * 模拟gc释放资源 */ @Override protected void finalize() throws Throwable { // TODO Auto-generated method stub super.finalize(); mainMemory.releaseCache(this); } }

  4, 测试:模拟可见性的影响

  

    static public void main(String[] args) {
        
        //模拟一个主内存,并且在该主内存中存在一个共享变量
        final MainMemory mainMemory = new MainMemory();
        
        //1.0 模拟 《使用volatile关键字修饰的变量的方式读写数据时,volatile可见性的体现以及对共享变量的影响》
        //1.1模拟创建2个线程的 缓存
        ThreadLocalCache threadCache1 = new ThreadLocalCache(mainMemory);
        ThreadLocalCache threadCache2 = new ThreadLocalCache(mainMemory);
        
        //1.2 模拟两个2线程同时读取了一个volatile关键字修饰变量的值;
        int d1 = threadCache1.volatileRead();
        int d2 = threadCache2.volatileRead();
        System.out.println("独立的线程缓存获取到的数据:"  + " ;d1 = " + d1 + " ;d2 = " + d2);
        
        //1.3模拟线程1向主内存中写入了数据,并打印主内存的值;
        threadCache1.write(threadCache1.volatileRead() + 1);
        System.out.println("当线程1修改主内存后,主内存中的值 = " + mainMemory.read());
        
        // 注意: 这里将模拟volatile可见性,以及可见性对其它线程的影响;
        //1.4模拟线程2使用共享变量,并在该共享变量上加1;
        //在使用volatile修饰的共享变量时,会检查当前线程缓存中的值是否可用,否则向 主内存中重新读取;
        //这里线程1在之前已修改了主内存的值,所以线程2值已被通知不可用,线程2向主内存重新读取最新值;
        threadCache2.write(threadCache2.volatileRead() + 1);
        System.out.println("当线程1修改主内存后,主内存中的值 = " + mainMemory.read());
        
        
        //非volatile变量发生多个线程同时读写,修改值预期不一致演示
        
        //模拟一个主内存,并且在该主内存中存在一个共享变量
        final MainMemory mainMemory2 = new MainMemory();
        
        //2.0 模拟 《使用非volatile关键字修饰变量的方式读写数据时,对共享变量的影响》
        //1.1模拟创建2个线程的 缓存
        ThreadLocalCache threadCache3 = new ThreadLocalCache(mainMemory2);
        ThreadLocalCache threadCache4 = new ThreadLocalCache(mainMemory2);
        
        //1.2 模拟两个2线程同时读取了一个非volatile关键字修饰的共享变量的值;
        int d3 = threadCache3.read();
        int d4 = threadCache4.read();
        System.out.println("独立的线程缓存获取到的数据: "+ " ;d3 = " + d3 + " ;d4 = " + d4);
        
        //1.3模拟线程3向主内存中写入了数据,并打印主内存的值;
        threadCache3.write(threadCache3.read() + 1);
        System.out.println("当线程3修改主内存后,主内存中的值 = " + mainMemory2.read());
        
        //注意 : 这里将模拟使用非volatile变量,在线程缓存中的操作,因为当前缓存不知道数据已过期,并将已过期的数据
        //拿来使用,造成最后得到的值,与预期值不同;
        //1.4模拟线程4向主内存中写入了数据,并打印主内存的值;
        threadCache4.write(threadCache4.read() + 1);
        System.out.println("当线程4修改主内存后,主内存中的值 = " + mainMemory2.read());
        
    }
    

  

推荐阅读