1. 程式人生 > >JAVA基礎的高併發與資料結構

JAVA基礎的高併發與資料結構

1.列出你瞭解的實現結合的介面(Collection)的類,並說明他們的作用和區別
    List
        保證元素的儲存順序,而且元素可以重複
    ArrayList
        基於陣列,預設初始容量是10,每次擴容一半,記憶體空間連續,增刪改查慢,查詢相對比較快,是一個執行緒不安全的集合
    vector
        基於陣列,預設初始容量是10,每次擴容一倍,記憶體空間連續,增刪改查慢,查詢相對比較
    快,是一個執行緒安全-----java中最早的集合
    stack
        棧---先進後出
    LinkedList
        基於連結串列,記憶體空間不連續,增刪改查較快,查詢較慢,線性不安全集合
    set
        元素不可重複,並不保證元素的儲存順序
        Hashset---預設初始化容量是16,載入因子是0.75f,每次擴容一倍,是一個線性不安全集合
    Queue
        佇列---先進後出

2.抽象類和介面有什麼區別,分別在什麼情況下使用?
    構造方法:介面沒有構造方法,抽象類有構造方法
    成員變數:介面只有final修飾的常量,final修飾的常量在常量池。抽象類有普通變數和常量
    成員方法:介面只有抽象方法,抽象類有抽象方法和普通方法
    使用區別:介面部分子類必須完成的操作,抽象類所有的子類都必須完成的操作
3.建構函式
    作用:建立物件
        在建立物件的時候實際上是呼叫對應形式的建構函式。建構函式的函式名要去與類名一致並且沒有返回值型別,當一個類沒有建構函式的時候,JVM會在編譯的時候會自動新增一個無參的構造,建構函式可以過載
4.程序和執行緒
    程序:程式載入到記憶體之中之後CPU所計算的過程----程序是計算機資源分配、任務排程的最小單位
        、
         三個維度:
            1.實體記憶體維度:每一個程序都要分配一塊連續的記憶體空間----首地址,尾地址
            2.執行角度/邏輯角度:每一個程序都能被CPU計算,每一個程序都能掛起然後讓另外的程序被CPU計算---對於單核而言,每一個時刻只能計算一個程序。對於Windows而言,預設是隻用一個核處理,對於LINUX作業系統,有幾個核就用幾個核。-----從微觀上,計算機是序列處理程序,從巨集觀上而言,多個程序來並行執行-----多道程式設計
            3.時間角度:每一個時間段上,程序一定是向前撲進的
        為什麼引入程序模型?
            1.減少響應時間,提高使用效率
            2.提高CPU的利用率
        程序的產生事件?
            1.系統啟動的時候會建立系統程序
            2.使用者請求建立程序---例如開啟一個exe檔案
            3.父程序自動啟動子程序---例如開啟QQ的時候附帶啟動QQ的安全防護程序
        程序的消亡事件?
            1.程序任務執行完成,自然死亡
            2.程序的執行過程中出現錯誤或者異常,意外身亡
            3.一個程序被另外的程序強制關閉,他殺
    執行緒:是程序中執行的任務。執行緒本質上是簡化版的程序。一個程序中至少有一個執行緒。---執行緒是任務執行的最小的單位
    程序的任務排程演算法:1.時間片輪詢演算法2.優先順序排程演算法3.短任務優先演算法4.FICS

5.NIO
    NIO---NEWIO---NomBlockingIO---非阻塞式IO---基於通道和緩衝區(為所有的原始資料【boolean型別除外】提供快取支援的資料容器,使用它可以提供非阻塞式的高延伸縮性網路)
    通道
        Channel是一個物件,可以通過它讀取和寫入資料。拿 NIO 與原來的 I/O 做個比較,通道就像是流,而且他們面向緩衝區的。
  正如前面提到的,所有資料都通過 Buffer 物件來處理。您永遠不會將位元組直接寫入通道中,相反,您是將資料寫入包含一個或者多個位元組的緩衝區。同樣,您不會直接從通道中讀取位元組,而是將資料從通道讀入緩衝區,再從緩衝區獲取這個位元組。
    通道與流的不同之處在於通道是雙向的。而流只是在一個方向上移動(一個流必須是 InputStream 或者 OutputStream 的子類), 而 通道 可以用於讀、寫或者同時用於讀寫。

    因為它們是雙向的,所以通道可以比流更好地反映底層作業系統的真實情況。特別是在 UNIX 模型中,底層作業系統通道是雙向的。


    緩衝區.
        是一個固定資料量的指定基本型別的資料容器。除內容之外,緩衝區還具有位置 和界限,其中位置是要讀寫的下一個元素的索引,界限是第一個應該讀寫的元素的索引。基本 Buffer 類定義了這些屬性以及清除、反轉 和重繞 方法,用以標記 當前位置,以及將當前位置重置 為前一個標記處。

    每個非布林基本型別都有一個緩衝區類。每個類定義了一系列用於將資料移出或移入緩衝區的 get 和 put 方法,用於壓縮、複製 和切片 緩衝區的方法,以及用於分的異類或同類二進位制資料序列),訪問要麼是以 big-endian位元組順序進行,要麼是以 little-endian 位元組順序進行。
    特點:
        1.能夠進行資料的雙向傳輸---減少了流的數量降低了伺服器的記憶體消耗
        2.由於資料是儲存在緩衝區的,所以可以針對緩衝區的資料做定向操作
        3.能夠利用一個或者少量的伺服器來完成大量的使用者的請求的處理---適用於短任務場景

6.BIO
    BIO--BlockingIO--阻塞式IO---阻塞在一些場景會相對影響效率;由於流有方向性,所以在資料傳輸的時候往往要建立多個流物件;如果一些流長時間不適用卻依然會保持連線的話會造成資源的大量浪費;無法從流中準確的抽取一點資料
7.ByteBuffer
    底層是依靠位元組陣列來儲存資料的
    當建立好這個緩衝區的時候,就有了這麼幾個屬性:
        capacity:容量位---表示緩衝區的容量的
        position:操作位---表示要操作的位置---當緩衝區剛剛建立的時候,操作位預設為0
        limit:限制位---表示position所能到達的最大位置---當緩衝區剛剛建立的時候,limit預設為capacity
        put()---向緩衝區新增資料,每新增一個位元組的資料,position就會向後挪動一位
        在讀取資料之前往往要做一次flip操作----反轉緩衝區---先將限制位設定為當前的操作位,然後將操作位歸零
        重繞緩衝區---將操作位歸零
8.Selector---選擇器
    每一個客戶端或者伺服器端都需要註冊到選擇器身上,讓這個選擇器進行管理。選擇器在管理的時候需要監聽事件
    對應的通道必須註冊到對應的選擇器的身上,並且得申請對應的事件的許可權,後面的選擇器才會管理選擇對應的事件

9.資料黏包問題的處理
    1.定長---如果資料長度不夠,填充無用資料
    2.約定結尾符號---結尾符號可能會和實際資料的內容衝突
    3.約定了起始和結束的協議
10.ConcurrentHashMap---分段鎖
        ConcurrentHashMap---非同步執行緒安全的對映---引入了分段鎖(分桶鎖)
        ConcurrentHashMap 的主幹是Segment陣列
        Segment繼承了ReentrantLock,所以他就是一種可重入鎖(ReentarntLock)。在ConcurrentHashMap,一個segment就是一個子雜湊表,Segment裡維護了一個HashEntry陣列,併發環境下,對不同的Segment的資料進行操作是不用考慮鎖競爭的。(就按預設的ConcurrentLeve為16來講,理論上就允許16個執行緒併發執行)
11.ConcurrentNavigableMap
    ConcurrentNavigableMap---併發導航對映---允許從指定的位置開始擷取一個子對映
    基於跳躍表---  B-tree
        跳躍表是一種隨機化的資料結構,目前開源軟體Redis和LevelDB都有用到它,它的效率和紅黑樹以及AVL樹不相上下,但跳躍表的原理相當簡單,只要能熟練操作連結串列就能輕鬆實現一個SkipList
        性質:
            1.由很多層結構組成
            2.最底層的連結串列包含所有的元素
            3.每一層都是一個有序的連結串列
            4.如果一個元素出現在這一層的連結串列中,則它在這一層之下的連結串列也會出現
            5.每個節點包括兩個指標,一個指向同一連結串列中的下一個元素,一個指向下一層的元素

12.BlockingQueue
    ArrayBlocking---底層是基於陣列。遵循佇列的先進先出(FIFO)的原則---有界
    LinkedBlockingQueue---底層是基於節點的---有界
    PriorityBlockingQueue---無界;不允許元素為null;佇列中的元素對應的類必須實現介面---Comparable,為了重寫介面中的compareTo方法來進行排序----自然排序---一般來說自然排序就是升序排序---如果迭代遍歷,不保證排序;但是如果是逐個取出的話,保證元素的排序順序

13.CountDownLatch
    閉鎖/執行緒遞減鎖----在構造的時候需要傳入計數。在計數的執行緒之後可以進行await操作,直到計數的執行緒全部執行完成(進行countDown操作來減少一次計數)才會執行await之後的程式碼---適用於不通的執行緒進行計數
14.CyclicBarrier
    柵欄。也需要在構造的時候進行計數,在需要阻塞的地方進行await操作,每await一次,計數就減少一次,直到減為0的位置,計數結束,放開阻塞。-----適用於同一執行緒類產生的多個執行緒進行計數阻塞

15.Exchanger
    交換機----適用於交換連個執行緒的資訊
16.Semaphore
    訊號量---用於限制某段程式碼在某個時間段內最多隻有n個執行緒進入訪問。每個執行緒進入的時候需要進行acquire操作,會使訊號量減少1,直到到了0為止,如果還有新的執行緒繼續過來訪問,則會在acquire處阻塞住,直到有執行緒歸還了訊號量(release)才能訪問
17.ExecutorService
    先交給核心執行緒處理,如果核心執行緒已經用完,在來的請求放入工作佇列中,如果工作佇列已經放滿,則建立臨時執行緒來處理請求
    corePoolSize:執行緒池的大小----執行緒池中的核心執行緒的數量---核心執行緒一旦建立不在銷燬
    maximumPoolSize:允許存在的最大執行緒數量、
    keepAliveTime:存活時間
    unit:時間單位
    workQueue:工作佇列----阻塞式佇列----在核心執行緒都被使用的情況下,再來的請求就會放到工作佇列中儲存
    ExecutorService es = new ThreadPoolExecutor(5, 10, 3000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(5));
    快取執行緒池
    沒有核心執行緒池;臨時執行緒池存活時間比較短
    小佇列大池子的執行緒池
    能夠相對比較好的應對高併發場景
    不適合於長任務場景
    ExecutorService es = Executors.newCachedThreadPool();
    沒有臨時執行緒,全部都是核心執行緒
    大佇列小池子的執行緒池
    降低伺服器的併發壓力----適用於長任務場景
    ExecutorService es = Executors.newFixedThreadPool();

18.sleep和wait有什麼區別?
    sleep方法需要指定睡眠時間,到點自然醒。釋放執行權,不釋放鎖,被設計在Thread類上,是一個靜態方法
    wait方法可以指定等待時間也可以不指定,如果不指定時間需要喚醒,釋放執行權,釋放鎖。被設計在了Object類上,是一個普通的方法,wait方法必須結合鎖來使用

19.守護執行緒
    守護其他執行緒的執行。當守護的執行緒結束之後,守護執行緒無論完成與否都會隨之結束,只要程式碼中出現了守護程序,要麼這個程序是守護程序,要麼就是被守護執行緒-----如果出現了多個被守護的執行緒,那麼最後的一個被守護的執行緒作為結束的標誌
20.Lock
    鎖-----和synchronized機制類似,但是比synchronized更加靈活
    所有公平策略和非公平策略
        非公平鎖和公平鎖在獲取鎖的方法上,流程是一樣的;他們的區別主要表現在“嘗試獲取鎖的機制不同”,簡單點說“公平鎖”在每次嘗試獲取鎖的時候,都是採用公平策略(根據等待佇列依次排序等待);而“非公平鎖”在每次嘗試獲取鎖的時候,都是採用的非公平策略(無視等待佇列,直接嘗試獲取鎖,如果鎖是空閒的,即可獲取狀態,則獲取鎖)
    synchronized是非公平策略的
    ReentrantLock---預設是非公平策略,可以設定為公平策略。
    通過lock()上鎖,通過unlock()方法解鎖。
    synchronized和lock的區別
        1.ReentrantLock擁有synchronized相同的併發性和記憶體語句,此外還多了鎖投票,定時鎖等候和中斷鎖等待
        2.synchronized是在JVM層面上實現的,不但可以通過一些監控工具監控synchronized的鎖定,而且在程式碼執行時出現異常,JVM會自動釋放鎖定,但是lock不行,lock是通過程式碼實現的,要保證鎖定一定被釋放,就必須把unlock()放到finally()中
        3.在資源競爭不是很激烈的情況下,synchronized的效能要優於ReentrantLock(編譯程式通常會盡可能的進行優化synchronize,另外可讀性好),但是在競爭激烈的情況下,synchronized的效能會下降幾十倍,但是ReentrantLock的效能能維持常態
    ReentrantLock獲取鎖定與三種方式:
        1.lock(),如果獲取了鎖立即返回,如果別的執行緒持有鎖,當前執行緒一直處於休眠狀態,直到獲取鎖
        2.tryLock(),如果獲取了鎖立即返回true,如果別的執行緒持有鎖,立即返回false
        3.tryLock(long timeout, TimeUnit  unit),如果獲取了鎖定立即返回true,如果別的執行緒正持有鎖,會等待引數給定的時間,在等待的過程中,如果獲取了鎖定,就返回true,如果等待超時,返回false
        4.lockInterruptibly:如果獲取了鎖定立即返回,如果沒有獲取鎖定,當前執行緒處於休眠狀態,直到或者鎖定,或者當前執行緒被別的執行緒中斷

21.tcp三次握手
    第一次握手:建立連線時,客戶端傳送syn(syn=j)包到伺服器,並且進入SYN-SENT狀態,等待伺服器確認,SYN同步序列編號
    第二次握手:伺服器收到syn包,必須確認客戶的SYN(ack=j+1),同時自己也傳送一個SYN(syn=k)包,即SYN+ACK包,此時伺服器進入SYN-RECV狀態
    第三次握手:客戶端收到伺服器的SYN+ACK包,向伺服器傳送確認包ACK(ack=k+1),此包傳送完畢,客戶端和伺服器進入ESTABLISHED(TCP連線狀態),完成三次握手
    完成三次握手之後,客戶端與伺服器開始傳送資料

22.網路七層模型
    OSI:應用層    檔案傳輸,電子郵件,檔案服務,虛擬終端 
         表示層  資料格式化,程式碼轉換,資料加密
         會話層  解除或建立與其他節點的聯絡
         傳輸層    提供端對端的介面
         網路層  為資料包選擇路由
         資料鏈路層  傳輸有地址的幀,錯誤檢測功能
         物理層  以二進位制資料形式在物理媒體上傳輸資料
    TCP/IP層
         應用層    
         傳輸層    四層交換機,也有工作在四層的路由器
         網路層    路由器,三層交換
         資料鏈路層    網橋,網絡卡(一半工作在物理層,一半工作在資料鏈路層)
         物理層    中繼器,集線器,雙絞線
23.get和post請求
    1.get請求指示單純的想伺服器獲取資訊,不會改變資料,post請求可以修改資料,也就是說get只能用於獲取資訊,post可以獲取資訊也可以修改、增加資料
    2.get適用於小資料的互動,如果資料量較大就必須用post請求
    3.安全性:get請求安全性低,post請求安全性高,post會把請求引數隱藏,get請求會暴露引數建議:1.get方式請求的安全性較Post方式要差些,包含機密資訊的話,建議用Post資料的提交方式2.在做資料查詢的時候,建議用Get方式;而在做資料新增、修改、下載或刪除的時候,建議用Post方式,

24.JDBC的流程
    1.註冊資料庫
    2.獲取資料庫連線
    3.獲取傳輸器
    4.利用傳輸器,傳送sql到資料庫執行,返回執行結果
    5.處理結果
    6.釋放資源

25.悲觀鎖和樂觀鎖
    悲觀鎖:顧名思義,就是很悲觀,每次去拿資料的時候都認為被人會修改,所以每次在拿資料的時候都會上鎖,這樣別人想拿資料就會block直到它拿到鎖。傳統的關係型資料庫裡面就用到了這種鎖機制,比如說行鎖,表鎖等,讀鎖,寫鎖等,都是在做這些鎖操作之前先上鎖
    樂觀鎖:顧名思義,就是很樂觀,每次去拿資料的時候都是認為別人不會修改,所以不會上鎖,但是在更新的時候會判斷一下在此期間比別人有沒有去更新這個資料,可以使用版本號等機制。樂觀鎖適用於多讀的應用型別。這樣可以提高吞吐量,想資料庫如果提供類似於write_condition機制的其實都是提供的樂觀鎖
    兩種鎖各有優點,不可認為一種好於另一種,像樂觀鎖適用於寫比較少的情況,即衝突真的很少發生的時候,這樣可以省去鎖的開銷,加大了系統的整個吞吐量。但如果經常發生衝突,上層應用會不斷的進行retry,這樣反倒是降低了效能,所以這種情況下用悲觀鎖就比較合適

26.ReadWriteLock和readLock和writeLock(讀寫鎖和讀鎖和寫鎖)
    ReadWriteLock也是一個介面,提供了readLock和writeLock兩種鎖的操作機制,一個資源可以被多個執行緒同時讀,或者被一個執行緒寫,但是不能同時存在讀和寫的執行緒
    使用場合
        假設在程式中定義一個共享資料結構用作快取,它大部分時間提供讀服務(例如:查詢和搜尋),而寫操作佔有的時間很少,但是寫操作完成之後的更新需要對後續的讀服務可見。
27.記憶體溢位
    記憶體溢位out of memory,是指程式在申請記憶體的時候,沒有足夠的記憶體空間供其使用,出現out of memory;比如申請了一個integer,但給它存了long才能存下的數,那就是記憶體溢位
    記憶體洩露memory leak,是指程式在申請記憶體後,無法釋放已申請的記憶體空間,一次記憶體洩漏危害可以忽略不計,但是記憶體洩露堆積後果很嚴重,無論記憶體多少遲早會被佔光
    memory leak最終會導致out of memory
28.棧記憶體溢位(StackOverflowError)
    程式所需要的棧深度大導致,可以寫一個死遞迴程式觸發
    如果執行緒請求的棧容量超過棧允許的最大容量的話,Java 虛擬機器將丟擲一個StackOverflow異常;如果Java虛擬機器棧可以動態擴充套件,並且擴充套件的動作已經嘗試過,但是無法申請到足夠的記憶體去完成擴充套件,或者在新建立執行緒的時候沒有足夠的記憶體去建立對應的虛擬機器棧,那麼Java虛擬機器將丟擲一個OutOfMemory 異
29.堆記憶體溢位(OutOfMemoryError:java heap space)
    需要分清記憶體溢位還是記憶體洩露
    1.如果是記憶體溢位,則通過調大-Xms,-Xmx引數
    2.如果是記憶體洩露,則看物件如何被GC Root引用
    物件大於新生代剩餘記憶體的時候,將直接放入老年代,當老年代剩餘記憶體還是無法放下的時候,觸發垃圾收集,收集後還是不能放下就會丟擲記憶體溢位異常了
30.持續帶記憶體溢位(OutOfMemoryError:PermGen space)
    持續帶中包含方法區,方法區包含常量池
    因此持久帶溢位可能是1.執行時常量池溢位2.方法區中儲存了Class物件沒有被及時回收掉或者Class資訊佔用的記憶體超過了我們的配置
    用String.intern()觸發常量池溢位
    Class物件未被釋放,Class物件佔用資訊過多,有過多的Class物件。可以導致持久帶記憶體溢位
    •使用一些應用伺服器的熱部署的時候,我們就會遇到熱部署幾次以後發現記憶體溢位了,這種情況就是因為每次熱部署的後,原來的Class沒有被解除安裝掉。
    •如果應用程式本身比較大,涉及的類庫比較多,但是我們分配給持久帶的記憶體(通過-XX:PermSize和-XX:MaxPermSize來設定)比較小的時候也可能出現此種問題。
    •一些第三方框架,比如spring、hibernate都是通過位元組碼生成技術(比如CGLib)來實現一些增強的功能,這種情況可能需要更大的方法區來儲存動態生成的Class檔案。
    •我們知道Java中字串常量是放在常量池中的,String.intern()這個方法執行的時候,會檢查常量池中是否存和本字串相等的物件,如果存在直接返回常量池中物件的引用,不存在的話,先把此字串加入常量池,然後再返回字串的引用。


31.無法建立本地執行緒
    系統記憶體的容量不變,堆記憶體、非堆記憶體設定過大,會導致能給執行緒分配的記憶體不足
     •程式建立的執行緒數超過了作業系統的限制。對於Linux系統,我們可以通過 ulimit -u 來檢視此限制。
    •給虛擬機器分配的記憶體過大,導致建立執行緒的時候需要的native記憶體太少。
32.HashSet是如何保證元素不重複的
     1,如果hash碼值不相同,說明是一個新元素,存; 
     如果沒有元素和傳入物件(也就是add的元素)的hash值相等,那麼就認為這個元素在table中不存在,將其新增進table;
     2(1),如果hash碼值相同,且equles判斷相等,說明元素已經存在,不存;
     2(2),如果hash碼值相同,且equles判斷不相等,說明元素不存在,存;
     如果有元素和傳入物件的hash值相等,那麼,繼續進行equles()判斷,如果仍然相等,那麼就認為傳入元素已經存在,不再新增,結束,否則仍然新增;
33.為什麼HashSet是執行緒不安全的?
     HashMap底層是一個Entry陣列,當發生hash衝突時,hashMap是採用連結串列的方式來解決的,在對應的陣列的位置存放連結串列的頭結點。對於連結串列而言,新加入的節點會從頭節點加入
     1.此實現不是同步的,如果多個執行緒同時訪問一個雜湊對映,而其中至少一個執行緒從結構上修改了該對映,則它必須保證外部同步(結構上的修改是指新增或刪除一個或者多個對映關係的任何操作;僅改變與例項已經包含的鍵關聯的值不是結構上的修改)當兩個執行緒同時寫入同一個陣列的時候,兩個想成會同時得到現在的頭節點,一個寫入之後,第二個寫入操作的寫入就會覆蓋第一個寫入操作,早成第一個寫入操作的丟失
     2.刪除鍵值對:當多個執行緒同時操作一個數組位置的時候,也都會取得現在的狀態下該位置儲存的頭結點,然後各自去進行計算操作,之後再把結構寫到該陣列位置去,其實寫回的時候可能其他執行緒已經修改過這個位置,就會覆蓋其他執行緒的修改
     3.addEntry中當加入一個新的鍵值對後鍵值對總數量超過門限值的時候會呼叫一個resize操作
     當多個執行緒同時檢測到總數量超過門限值的時候就會同時呼叫resize操作,各自生成新的陣列並rehash後賦給該map底層的陣列table,結果最終只有最後一個執行緒生成的新陣列被賦給table變數,其他執行緒的均會丟失。而且當某些執行緒已經完成賦值而其他執行緒剛開始的時候,就會用已經被賦值的table作為原始陣列
34.java執行緒棧
     執行緒的每個方法被執行的時候,都會同時建立一個幀(Frame)用於儲存本地變量表、操作棧、動態連結、方法出入口等資訊、每一個方法的呼叫至完成,就意味著一個幀在VM棧中的入棧至出棧的過程。如果執行緒請求的棧深度大於虛擬機器鎖允許的深度,將丟擲StackOverflowError異常;如果VM棧可以動態擴充套件,當擴充套件時無法申請到足夠記憶體則丟擲OutOfMemoryError異常。
35.JMM(java記憶體模型)
     JMM定義了java虛擬機器在計算機記憶體中的工作方式
     java的併發採用的是共享記憶體
     這裡的共享記憶體就是JMM,JMM決定了一個執行緒對共享變數的寫入何時對另一個執行緒可見。從抽象的角度來說,JMM定義了執行緒和主記憶體之間的抽象關係:執行緒之間的共享變數儲存在主記憶體之中,每個執行緒都有一個私有的本地記憶體,本地記憶體中儲存了該執行緒以讀/寫共享變數的副本
     執行緒A在向執行緒B傳送訊息,而且這個通訊過程必須要經過主記憶體。JMM通過控制主記憶體與每個執行緒的本地記憶體之間的互動,來為java程式設計師提供記憶體可見性保證。
     JMM記憶體模型的基礎原理
        指令重排序
             在執行程式時,為了提高效能,編譯器和處理器會對指令做重排序。但是JMM通過插入特定型別的Memory Barrier來禁止特定型別的編譯器重排序和處理器重排序,為上一層提供一致的記憶體可見性保障
             1.編譯器優化重排序:編譯器在不改變單執行緒程式語義的前提下,可以重新安排語句的執行順序
             2.指令級並行的重排序:如果不存在/在資料依賴性,處理器可以改變語句對應機器指令的執行順序
             3.記憶體系統的重排序:處理器使用快取和讀寫緩衝區,這使得載入和儲存操作看上去可能是亂序執行
        資料的依賴性
             如果兩個操作同時訪問一個變數,其中一個為寫操作,此時這兩個操作之間存在資料依賴性。編譯器和處理器不會改變存在資料依賴性關係的兩個操作的執行順序
        as-if-serial
             不管怎麼重排序,單執行緒下的執行結果不能被改變,編譯器、runtime和處理器都必須遵循as-if-serial語義 
     記憶體屏障(Memory Barrier )
         通過記憶體屏障可以禁止特定型別處理器的重排序,從而讓程式按預想的流程去執行。記憶體屏障,又稱記憶體柵欄,是一個CPU指令:
         1.保證特定操作的執行順序
         2.影響某些資料(或則是某條指令的執行結果)記憶體的可見性
         如果一個變數是volatile修飾的,JMM會在寫入這個欄位之後插進一個Write-Barrier指令,並在讀這個欄位之前插入一個Read-Barrier指令。這意味著,如果寫入一個volatile變數,就可以保證:
         1.一個執行緒寫入變數a後,任何執行緒訪問該變數都會拿到最新值。
         2.在寫入變數a之前的寫入操作,其更新的資料對於其他執行緒也是可見的。因為Memory Barrier會刷出cache中的所有先前的寫入。
     happens-before
         從jdk5開始,java使用新的JSR-133記憶體模型,基於happens-before的概念來闡述操作之間的記憶體可見性。
         在JMM中,如果一個操作的執行結果需要對另一個操作可見,那麼這兩個操作之間必須要存在happens-before關係,這個的兩個操作既可以在同一個執行緒,也可以在不同的兩個執行緒中。
         與程式設計師密切相關的happens-before規則如下:
             1.程式順序規則:一個執行緒中的每個操作,happens-before於該執行緒中任意的後續操作。
             2.監視器鎖規則:對一個鎖的解鎖操作,happens-before於隨後對這個鎖的加鎖操作。
             3.volatile域規則:對一個volatile域的寫操作,happens-before於任意執行緒後續對這個volatile域的讀。 
             4.傳遞性規則:如果 A happens-before B,且 B happens-before C,那麼A happens-before C。
         注意:兩個操作之間具有happens-before關係,並不意味前一個操作必須要在後一個操作之前執行!僅僅要求前一個操作的執行結果,對於後一個操作是可見的,且前一個操作按順序排在後一個操作之前。


36.java反射
     Java反射機制是在執行狀態下,對於任何一個類,都能夠知道這個類的所有屬性和方法;對於任意一個物件,都能呼叫它的任意一個方法和屬性;這種動態獲取資訊以及動態呼叫物件的方法的功能稱之為java語言的反射機制
     要剖析一個類,必須先要獲取該類的位元組碼檔案物件。而剖析使用的就是Class類中的方法。所以先要獲取到每一個位元組碼對應的Class型別的物件
37.HashMap和TreeMap的區別
     HashMap:陣列方式儲存key/value,執行緒非安全,允許null作為key和value,key不可以重複,value允許重複,不保證元素迭代順序是按照插入時的順序,key的hash值是先計算key的hashcode值,然後在進行計算,每次擴容都會重新計算所有的key的hash值,會消耗資源,要求key必須重寫equals和hashcode方法
     預設初始容量是16,載入因子是0.75,擴容為舊容量乘2,查詢元素快,如果key一樣則比較value,如果value不一樣,則按照連結串列結構儲存value,就是一個key後面有多個value
     TreeMap:基於紅黑二叉樹的NavigableMap的實現,執行緒安全,不允許null,key不可以重複,value允許重複,存入TreeMap的元素應當實現comparable介面或者實現Comparator介面,會按照排序後的順序迭代元素,兩個相對比較的key不得丟擲classCastException。主要用於存入元素的時候對元素進行自動排序,迭代輸出的時候就按照順序輸出
38.java多執行緒的實現方式,及之間的區別,使用場景