volatile 做爲java的關鍵字之一,必然有它存在的必要性;在不少的資料中,各位大神級的人物都對volatile作了深刻的分析,在這裏就不在贅述了;不清的朋友能夠遷移到這個地址詳細瞭解:https://www.cnblogs.com/dolphin0520/p/3920373.htmlhtml
那麼已經瞭解volatile的做用。這裏呢?將使用java代碼將把volatile底層 「可見性」給擴大,並已代碼的形式展現,volatile的可見性究竟是怎麼一回事;java
注意:本人亦不清楚volatile的底層是如何實現的,只是,僅僅只是經過各類資料中對volatile的分析,而後領悟出來的想法;(volatile 可能不是如此實現,切莫較真;有大神知道,也請爽快指教)緩存
最後提示:此文僅供參考,切勿入坑;ide
具體實現:測試
1,首先模擬主內存,在該模擬的主內存中只存在一個地址,該地址用於存放一個共享變量;代碼以下;this
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, 緩存狀態通知器接口,代碼以下:spa
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; /*.net
* 模擬當前緩存數據的狀態,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()); }