Java常用關鍵字

static

字面上,意思是靜態的,一旦被static修飾,說明被修飾的對象在一定範圍內是共享的,這時候需要注意併發讀寫的問題。

static 修飾類成員

static 修飾類成員時,如何保證線程安全是我們常常需要考慮的。當多個線程同時對共享變量進行讀寫時,很有可能會出現併發問題,如我們定義了:public static List<String> list = new ArrayList();這樣的共享變量。這個 list 如果同時被多個線程訪問的話,就有線程安全的問題,這時候一般有兩個解決辦法:

  1. 把線程不安全的 ArrayList 換成 線程安全的 CopyOnWriteArrayList;
  2. 每次訪問時,手動加鎖。

static 修飾方法

當 static 修飾方法時,代表該方法和當前類是無關的,任意類都可以直接訪問(如果權限是 public 的話)。
有一點需要注意的是,該方法內部只能調用同樣被 static 修飾的方法,不能調用普通方法。
我們常用的 util 類裏面的各種方法,我們比較喜歡用 static 修飾方法,好處就是調用特別方便,無需每次new出一個對象。
static 方法內部的變量在執行時是沒有線程安全問題的。方法執行時,數據運行在棧裏面,棧的數據每個線程都是隔離開的,所以不會有線程安全的問題。

static 修飾方法塊

當 static 修飾方法塊時,我們叫做靜態代碼塊,靜態代碼塊常常用於在類啓動之前,初始化一些值。

final

final 的意思是不變的、最終的,一般來說用於以下三種場景:

  1. 被 final 修飾的類,表明該類是無法繼承的;
  2. 被 final 修飾的方法,表明該方法是無法覆寫的;
  3. 被 final 修飾的變量,說明該變量在聲明的時候,就必須初始化完成,而且以後也不能修改其內存地址


注意第三點,無法修改其內存地址,並沒有說無法修改其值。因爲對於 List、Map 這些集合類或傳址的對象來說,被 final 修飾後,是可以修改其內部值的,但卻無法修改其初始化時的內存地址。

try、catch、finally


這三個關鍵字常用於捕捉異常,try 用來確定代碼執行的範圍,catch 捕捉可能會發生的異常,finally 用來執行一定要執行的代碼塊,除了這些,我們還需要清楚,每個地方如果發生異常會怎麼樣,我們舉一個例子來演示一下:

public static void testError () {
    try {
        System.out.println("try code");
        if (true) {
            throw new RuntimeException("try exception");
        }
    } catch (Exception e) {
        System.out.println("catch code");
        if (true) {
            throw new RuntimeException("catch exception");
        }
    } finally {
        System.out.println("finally code");
    }
}

運行結果
image.png
可以看到兩點:

  1. finally 先執行後,再拋出 catch 的異常;
  2. 最終捕獲的異常是 catch 的異常,try 拋出來的異常已經被 catch 喫掉了。所以當我們遇見 catch 也有可能會拋出異常時,我們可以先打印出 try 的異常,這樣 try 的異常在日誌中就會有所體現。

default

default 關鍵字被很多源碼使用,它是在jdk8時,被引入的。default 關鍵字用在接口的方法上,意思是對於該方法,實現類是無需強制實現的,但自己必須有默認實現。

volatile

volatile在牛津詞典中,釋義爲易變的;無定性的;無常性的;不穩定的。在java中,它常用來修飾某個共享變量,意思是當共享變量的值被修改後,會及時通知到其它線程上,其它線程就能知道當前共享變量的值已經被修改了。個人理解它的語義:是在聲明時就告訴jvm,這個變量是容易改變的,需要時刻注意它的狀態。
背景知識:在多核 CPU 下,爲了提高效率,線程在拿值時,是直接和 CPU 緩存打交道的,而不是內存。主要是因爲 CPU 緩存執行速度更快。比如線程要拿值 C,會直接從 CPU 緩存中拿, CPU 緩存中沒有,就會從內存中拿,所以線程讀的操作永遠都是拿 CPU 緩存的值。
這時候會產生一個問題,CPU 緩存中的值和內存中的值可能並不是時刻都同步,導致線程計算的值可能不是最新的,共享變量的值有可能已經被其它線程所修改了,但此時修改是機器內存的值,CPU 緩存的值還是老的,導致計算會出現數據不一致的問題。
這時候有個機制,就是內存會主動通知 CPU 緩存。當前共享變量的值已經失效了,你需要重新來獲取一份,CPU 緩存就會重新從內存中拿取一份最新的值。
volatile 關鍵字就會觸發這種機制,加了 volatile 關鍵字的變量,就會被識別成共享變量,內存中值被修改後,會通知到各個 CPU 緩存,使 CPU 緩存中的值也對應被修改,從而保證線程從 CPU 緩存中拿取出來的值是最新的。
image.png
從圖中我們可以看到,線程 1 和線程 2 一開始都讀取了 C 值,CPU 1 和 CPU 2 緩存中也都有了 C 值,然後線程 1 把 C 值修改了,這時候內存的值和 CPU 2 緩存中的 C 值就不等了,內存這時發現 C 值被 volatile 關鍵字修飾,發現其是共享變量,就會使 CPU 2 緩存中的 C 值狀態置爲無效,CPU 2 會從內存中重新拉取最新的值,這時候線程 2 再來讀取 C 值時,讀取的已經是內存中最新的值了。

面試題

爲什麼使用了volatile變量i,而i++還是無法保證線程安全

答:volatile 可以保證可見性,但無法保證原子性,i++已經不是原子操作了,所以即使使用 volatile 修飾,也無法保證其線程安全,比較好的做法可以參考 AtomicInteger,值使用 volatile 修飾,保證多核下的可見性,數據修改使用 unsafe 方法,保證原子性。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章