标签:很多 style err adl 释放缓存 ack interface nal his
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()); }
标签:很多 style err adl 释放缓存 ack interface nal his
原文地址:https://www.cnblogs.com/loveyoumi/p/9463749.html