java深入學習(一)
- 為什麼介面要規定成員變數必須是public static final的呢?
答:
首先介面是一種高度抽象的”模版”,,而介面中的屬性也就是’模版’的成員,就應當是所有實現”模版”的實現類的共有特性,所以它是public static的 ,是所有實現類共有的 .假如可以是非static的話,因一個類可以繼承多個介面,出現重名的變數,如何區分呢?
其次,介面中如果可能定義非final的變數的話,而方法又都是abstract的,這就自相矛盾了,有可變成員變數但對應的方法卻無法操作這些變數,雖然可以直接修改這些靜態成員變數的值,但所有實現類對應的值都被修改了,這跟抽象類有何區別? 又介面是一種更高層面的抽象,是一種規範、功能定義的宣告,所有可變的東西都應該歸屬到實現類中,這樣接口才能起到標準化、規範化的作用。所以介面中的屬性必然是final的。
最後,介面只是對事物的屬性和行為更高層次的抽象 。對修改關閉,對擴充套件(不同的實現implements)開放,介面是對開閉原則(Open-Closed Principle )的一種體現。
- 介面的所有成員都應該公開,所以是 public(我覺得因為介面必須被他類實現,所以必須為public)
- 介面不能例項化,所以只有靜態成員: static(非static成員變數必須通過物件進行操作,而介面不能例項化,所以只能為static)
介面的成員一定應該是常量,所以是 final。(這個正如上面所解釋的,如果介面的成員是變數,而一旦實現的類改變了該值,則所有實現類都被改變了,因為static)
interface 在設計角度上通俗的定義standard(標註),從面向物件來說我們可以把它看做一個USB介面,所以:
- 既然是標準,那就應該向外開放,於是介面的所有成員都應該 public
- 既然是標準,那就不能輕易改變,而且要滿足開放性,於是變數需要static (滿足開放性),final(滿足不可變性)
總而言之,介面是一種高階抽象的規範,它設計的宗旨就是 不可修改,只可擴充套件!
volatile:
volatile關鍵字的兩層語義
一旦一個共享變數(類的成員變數、類的靜態成員變數)被volatile修飾之後,那麼就具備了兩層語義:
1)保證了不同執行緒對這個變數進行操作時的可見性,即一個執行緒修改了某個變數的值,這新值對其他執行緒來說是立即可見的。
2)禁止進行指令重排序。
先看一段程式碼,假如執行緒1先執行,執行緒2後執行://執行緒1 boolean stop = false; while(!stop){ doSomething(); } //執行緒2 stop = true;
這段程式碼是很典型的一段程式碼,很多人在中斷執行緒時可能都會採用這種標記辦法。但是事實上,這段程式碼會完全執行正確麼?即一定會將執行緒中斷麼?不一定,也許在大多數時候,這個程式碼能夠把執行緒中斷,但是也有可能會導致無法中斷執行緒(雖然這個可能性很小,但是隻要一旦發生這種情況就會造成死迴圈了)。
下面解釋一下這段程式碼為何有可能導致無法中斷執行緒。在前面已經解釋過,每個執行緒在執行過程中都有自己的工作記憶體,那麼執行緒1在執行的時候,會將stop變數的值拷貝一份放在自己的工作記憶體當中。
那麼當執行緒2更改了stop變數的值之後,但是還沒來得及寫入主存當中,執行緒2轉去做其他事情了,那麼執行緒1由於不知道執行緒2對stop變數的更改,因此還會一直迴圈下去。
但是用volatile修飾之後就變得不一樣了:
第一:使用volatile關鍵字會強制將修改的值立即寫入主存;第二:使用volatile關鍵字的話,當執行緒2進行修改時,會導致執行緒1的工作記憶體中快取變數stop的快取行無效(反映到硬體層的話,就是CPU的L1或者L2快取中對應的快取行無效);
第三:由於執行緒1的工作記憶體中快取變數stop的快取行無效,所以執行緒1再次讀取變數stop的值時會去主存讀取。
那麼線上程2修改stop值時(當然這裡包括2個操作,修改執行緒2工作記憶體中的值,然後將修改後的值寫入記憶體),會使得執行緒1的工作記憶體中快取變數stop的快取行無效,然後執行緒1讀取時,發現自己的快取行無效,它會等待快取行對應的主存地址被更新之後,然後去對應的主存讀取最新的值。
那麼執行緒1讀取到的就是最新的正確的值。volatile保證原子性嗎?
從上面知道volatile關鍵字保證了操作的可見性,但是volatile能保證對變數的操作是原子性嗎?
下面看一個例子:
public class Test { public volatile int inc = 0; public void increase() { inc++; } public static void main(String[] args) { final Test test = new Test(); for(int i=0;i<10;i++){ new Thread(){ public void run() { for(int j=0;j<1000;j++) test.increase(); }; }.start(); } while(Thread.activeCount()>1) //保證前面的執行緒都執行完 Thread.yield(); System.out.println(test.inc); }
}
大家想一下這段程式的輸出結果是多少?也許有些朋友認為是10000。但是事實上執行它會發現每次執行結果都不一致,都是一個小於10000的數字。可能有的朋友就會有疑問,不對啊,上面是對變數inc進行自增操作,由於volatile保證了可見性,那麼在每個執行緒中對inc自增完之後,在其他執行緒中都能看到修改後的值啊,所以有10個執行緒分別進行了1000次操作,那麼最終inc的值應該是1000*10=10000。
這裡面就有一個誤區了,volatile關鍵字能保證可見性沒有錯,但是上面的程式錯在沒能保證原子性。可見性只能保證每次讀取的是最新的值,但是volatile沒辦法保證對變數的操作的原子性。
在前面已經提到過,自增操作是不具備原子性的,它包括讀取變數的原始值、進行加1操作、寫入工作記憶體。那麼就是說自增操作的三個子操作可能會分割開執行,就有可能導致下面這種情況出現:
假如某個時刻變數inc的值為10,
執行緒1對變數進行自增操作,執行緒1先讀取了變數inc的原始值,然後執行緒1被阻塞了;
然後執行緒2對變數進行自增操作,執行緒2也去讀取變數inc的原始值,由於執行緒1只是對變數inc進行讀取操作,而沒有對變數進行修改操作,所以不會導致執行緒2的工作記憶體中快取變數inc的快取行無效,所以執行緒2會直接去主存讀取inc的值,發現inc的值時10,然後進行加1操作,並把11寫入工作記憶體,最後寫入主存。
然後執行緒1接著進行加1操作,由於已經讀取了inc的值,注意此時線上程1的工作記憶體中inc的值仍然為10,所以執行緒1對inc進行加1操作後inc的值為11,然後將11寫入工作記憶體,最後寫入主存。
那麼兩個執行緒分別進行了一次自增操作後,inc只增加了1。
解釋到這裡,可能有朋友會有疑問,不對啊,前面不是保證一個變數在修改volatile變數時,會讓快取行無效嗎?然後其他執行緒去讀就會讀到新的值,對,這個沒錯。這個就是上面的happens-before規則中的volatile變數規則,但是要注意,執行緒1對變數進行讀取操作之後,被阻塞了的話,並沒有對inc值進行修改。然後雖然volatile能保證執行緒2對變數inc的值讀取是從記憶體中讀取的,但是執行緒1沒有進行修改,所以執行緒2根本就不會看到修改的值。
根源就在這裡,自增操作不是原子性操作,而且volatile也無法保證對變數的任何操作都是原子性的。
把上面的程式碼改成以下任何一種都可以達到效果:
採用synchronized:public class Test { public int inc = 0; public synchronized void increase() { inc++; } public static void main(String[] args) { final Test test = new Test(); for(int i=0;i<10;i++){ new Thread(){ public void run() { for(int j=0;j<1000;j++) test.increase(); }; }.start(); } while(Thread.activeCount()>1) //保證前面的執行緒都執行完 Thread.yield(); System.out.println(test.inc); } }
volatile能保證有序性嗎?
在前面提到volatile關鍵字能禁止指令重排序,所以volatile能在一定程度上保證有序性。
volatile關鍵字禁止指令重排序有兩層意思:
1)當程式執行到volatile變數的讀操作或者寫操作時,在其前面的操作的更改肯定全部已經進行,且結果已經對後面的操作可見;在其後面的操作肯定還沒有進行;
2)在進行指令優化時,不能將在對volatile變數訪問的語句放在其後面執行,也不能把volatile變數後面的語句放到其前面執行。
可能上面說的比較繞,舉個簡單的例子:
//x、y為非volatile變數
//flag為volatile變數x = 2; //語句1 y = 0; //語句2 flag = true; //語句3 x = 4; //語句4 y = -1; //語句5
由於flag變數為volatile變數,那麼在進行指令重排序的過程的時候,不會將語句3放到語句1、語句2前面,也不會講語句3放到語句4、語句5後面。但是要注意語句1和語句2的順序、語句4和語句5的順序是不作任何保證的。
並且volatile關鍵字能保證,執行到語句3時,語句1和語句2必定是執行完畢了的,且語句1和語句2的執行結果對語句3、語句4、語句5是可見的。
那麼我們回到前面舉的一個例子:
//執行緒1: context = loadContext(); //語句1 inited = true; //語句2 //執行緒2: while(!inited ){ sleep() } doSomethingwithconfig(context);
前面舉這個例子的時候,提到有可能語句2會在語句1之前執行,那麼久可能導致context還沒被初始化,而執行緒2中就使用未初始化的context去進行操作,導致程式出錯。
這裡如果用volatile關鍵字對inited變數進行修飾,就不會出現這種問題了,因為當執行到語句2時,必定能保證context已經初始化完畢。
下面這段話摘自《深入理解Java虛擬機器》:
“觀察加入volatile關鍵字和沒有加入volatile關鍵字時所生成的彙編程式碼發現,加入volatile關鍵字時,會多出一個lock字首指令”
lock字首指令實際上相當於一個記憶體屏障(也成記憶體柵欄),記憶體屏障會提供3個功能:
1)它確保指令重排序時不會把其後面的指令排到記憶體屏障之前的位置,也不會把前面的指令排到記憶體屏障的後面;即在執行到記憶體屏障這句指令時,在它前面的操作已經全部完成;
2)它會強制將對快取的修改操作立即寫入主存;
3)如果是寫操作,它會導致其他CPU中對應的快取行無效。
相關推薦
java深入學習(一)
為什麼介面要規定成員變數必須是public static final的呢? 答: 首先介面是一種高度抽象的”模版”,,而介面中的屬性也就是’模版’的成員,就應當是所有實現”模版”的實現類的共有特性,所以它是public static的 ,是所有實現類共有的
spring深入學習(一)深入理解 Spring IOC
1、IOC理論 IOC英文縮寫:Inversion of control, 另一個縮寫為DI:依賴注入(Denpency Injection) 用作控制反轉 理解:spring IOC就是負責物件生命週期和物件之間的關係 以找女朋友為例子: 一般情況下我們是如何來找女
代理模式深入學習(一)——動態代理的實現及解析
關於代理模式,就在不久的前的幾天,大概是8月17日左右,我帶領的小組還曾經被分配任務去給大家講解代理模式,總共給了兩天時間,但是, 依然,我們有很多問題沒有解決。比如動態代理的一些問題等
Java基礎學習(一)資料結構
基礎問題 1. 幾類資料結構的定義和區別是什麼? 2. 容器的資料結構底層是怎麼實現的?怎麼進行擴容? 3. 容器的執行緒安全怎麼實現? 一、List容器 資料有序,允許重複資料,執行緒不安全。 1. linkedList 底層用雙向連結串列實現,操作速度快,可以在頭、尾、[n]操作資料。 2. Arr
腦皮層學習演算法 ---nupic的深入學習(一)
1.nupic簡介 1.1 背景 說到AI,我們首先就會想到深度學習,tensorflow等。目前以深層的神經網路佔據了AI的半壁江山。從本質上來說,深度學習所表述的神經網路,其實是源自於一個“類腦”的想法—通過模仿人類大腦的神經元的相互連線,結合權重,反向
Java併發學習(一)
1.要響應執行緒中斷 執行緒接受到中斷訊號後要及時的對中斷進行響應。響應方式: 1.捕捉InterruptException for(;;){ try { doXXX(); } catch (InterruptedException e) { Sys
js內存深入學習(一)
棧內存 fun 解釋 content fifo ring asc 成了 undefine 一. 內存空間儲存 某些情況下,調用堆棧中函數調用的數量超出了調用堆棧的實際大小,瀏覽器會拋出一個錯誤終止運行。這個就涉及到內存問題了。 1. 數據結構類型 棧: 後進先出(
java鎖學習(一)
作用 能夠保證同一時刻,最多隻有一個執行緒執行該段程式碼,以達到併發安全的效果 主要用於同時刻對執行緒間對任務進行鎖 地位 syn
java學習(一) 環境搭建、hello world的demo
環境變量 網上 類庫 .com java開發 www cnblogs rgs .class 本程序媛搞前端的,上班偶有空閑,不妨來學習學習,不然怎麽包養小白臉,走上人生巔峰? 說實話,每個語言都相通,有了javascript的基礎,並且有了兩三年跟java打交道的經
Java集合框架學習(一)List
collect 有序集合 original package images 遍歷 容量 exp 子類 先附一張Java集合框架圖。 從上面的集合框架圖可以看到,Java集合框架主要包括兩種類型的容器,一種是集合(Collection),存儲一個元素集合,另一種是圖(M
IDEA 學習筆記之 Java項目開發深入學習(1)
java項目 bsp 重構 str 代碼提示 log pan ora tro Java項目開發深入學習(1): 定義編譯輸出路徑: 繼承以上工程配置 重新定義新的項目編譯路徑 添加source目錄:點擊添加,再點擊移除: 編譯項目: 常用快捷鍵總結: Ctr
Java學習(一)基礎概述
java 所在 應用 enter 代碼實現 ase 產品 stand 就是 寫代碼: 1,明確需求。我要做什麽? 2,分析思路。我要怎麽做?1,2,3。 3,確定步驟。每一個思路部分用到哪些語句,方法,和對象。 4,代碼實現。用具體的java語言代碼把思路體現出來。 學
關於最近java學習(一)
編程 編譯 關於 識別 興趣 進展 只讀 三種 print 學習java也一個月多了,我這個人吧總感覺格局有點小,太小家子氣,在學習新東西的過程中我開始逐漸對自己有一些認識吧。首先就是學習的進度不夠快,總是執著與基礎,就像高中的時候吧,雖然當時在我們班我的成績足夠好了,
Java學習(一)
exc 換行符 種類型 ati short style {} package gpo 在Eclipse中使用快捷鍵: main+ Alt+/ 得到publi static void main(String[] args){} sysout+Alt+/ 得到System.ou
Java後臺開發Servlet學習(一)
一、Servlet介紹 開始接觸一個新的東西,首先要知道它是幹什麼的吧。Servlet(Server Applet)是Java Servlet的簡稱,稱為小服務程式或服務聯結器,用Java編寫的伺服器端程式,主要功能在於互動式地瀏覽和修改資料,生成動態Web內容。我自己的理解就是對客戶端或者瀏覽器傳送的
Java學習(一):第一章 計算機、程式和Java概述
第一章 計算機、程式和Java概述 (1)匯流排--》 儲存裝置、記憶體、CPU、通訊裝置、輸入裝置、輸出裝置; (2)語言: 機器語言: 二進位制形式
深入理解java虛擬機器(一)java虛擬機器的記憶體區域
一、 java虛擬機器記憶體區域主要有:方法區、堆、虛擬機器棧、本地方方法棧、程式計數器 按照執行緒私有和共有來分:執行緒私有的有--程式計數器,虛擬機器棧,本地方法棧。共有的有--本地方法區,堆 1、程式計數器:主要功能是控制程式
Java基礎之繼承方面的學習(一)
此文章全部摘自部落格園: 連結:https://www.cnblogs
深入理解java虛擬機器(一)java的記憶體區域
程式計數器:可以看作當前執行緒所執行的位元組碼的行號指示器,位元組碼直譯器工作時就是通過改變這個計數器的值來選取下一條 需要執行的位元組碼指令,分支、迴圈、跳轉、異常處理、執行緒恢復等基礎功能都需要依賴這個計數器來實現。每一個執行緒都有一個獨立的程式計數器,各個執行緒之間的計數器互不影響,獨立
JAVA 學習(一):16進位制字串自增的實現
JAVA學習系列,並不是從基礎去講java的知識,而是把我在學習或是工作中,一些思想、邏輯總結出來。 原先在工作中,因為測試的需要,經常要往資料庫中批量的插資料。而表的主鍵用的是UUID,是由16進位制字元加“-”組成的,還有裝置的mac地址是由16進位制字元加“:”組成的,那個時候,我剛學ja