本文已參與好文召集令活動,點擊查看:後端、大前端雙賽道投稿,2萬元獎池等你挑戰!前端
- 考查對Java設計的掌握程度
- Java的private修飾符並非爲了絕對安全性設計的,更多的是對用戶常規使用Java的一種約束
- 從外部對對象進行常規調用時,能夠清晰瞭解類結構
- Java類的初始化順序:
- 基類靜態代碼塊,基類靜態成員變量(並列優先級,按照代碼中出現的前後順序執行,而且只有第一次加載時執行)
- 派生類靜態代碼塊,派生類靜態成員變量(並列優先級,按照代碼中出現的前後順序,而且只有第一次加載時執行)
- 基類普通代碼塊,基類普通成員變量(並列優先級,按照代碼中出現的前後順序執行)
- 基類構造函數
- 派生類普通代碼塊,派生類普通成員變量(並列優先級,按照代碼中出現的前後順序執行)
- 派生類構造函數
- 成員變量:
- 能夠不經初始化,在類的加載過程當中的準備階段能夠賦予默認值
- 賦值和取值訪問的前後順序具備不肯定性
- 成員變量能夠在一個方法調用前賦值,也能夠在方法調用後進行賦值. 這是在運行時發生的,編譯器肯定不了,全部交給JVM來賦值
- 局部變量:
- 在使用以前須要顯式賦予初始值
- 局部變量的賦值和訪問順序是肯定的
- 這樣設計是一種約束,盡最大可能減小使用者犯錯:
- 假使局部變量可使用默認值,可能總會無心間忘記賦值,進而致使不可預期的狀況發生
- 從結構實現上來說:
- HashMap實現是數組+鏈表+紅黑樹(紅黑樹部分是JDK 1.8以後增長的)
- HashMap最多容許一條記錄的鍵爲null,容許多條記錄的值爲null
- HashMap是非線程安全的
- 可變與不可變:
- String不可變,每一次執行 "+" 都會新生成一個新對象,因此頻繁改變字符串的狀況下不用String,以節省內存
- 是否多線程安全:
- StringBuilder並無對方法進行加同步鎖,因此是非線程安全的.StringBuffer和String都是線程安全的
- ArrayList在內存不夠時默認擴展是50%+1個,Vector默認是擴展1倍
- Vector是屬於線程安全級別的,可是大多數狀況下不使用Vector,由於線程安全須要更大的系統開銷
- HashTable繼承Dictionary類,HashMap繼承AbstrctMap類
- HashTable不容許空鍵值對,而HashMap容許空鍵值對,但最多隻有一個空對象
- HashTable同步,而HashMap不一樣步,效率上比HashTable要高
- ConcurrentHashMap融合了HashTable和HashMap兩者的優點:
- HashTable是作了同步的,是線程安全的,而HashMap未考慮同步,因此HashMap在單線程狀況下效率比較高
- HashTable在多線程的狀況下,同步操做能保證程序執行的正確性
- 可是HashTable是阻塞的,每次同步執行的時候都要鎖住整個結構
- ConcurrentHashMap正好解決了效率和阻塞問題:
- ConcurrentHashMap容許多個修改操做併發進行,技術的關鍵是使用了鎖分離,即一個Array保存多個Object,使用這些對象的鎖做爲分離鎖,get或者put的時候隨機使用任意一個
- ConcurrentHashMap使用了多個鎖來控制對Hash表的不一樣部分進行的修改
- 從JDK 1.6開始,在HashEntry結構中,每次插入將新添加節點做爲鏈的頭節點,這與HashMap實現相同.
- 每次刪除一個節點時,會將刪除節點以前的全部節點拷貝一份組成一個新的鏈,而將當前節點的上一個節點的next指向當前節點的下一個節點.從而在刪除之後會有兩條鏈存在
- 所以能夠保證即便在同一條鏈中,有一個線程在刪除,而另外一個線程在遍歷,都能工做良好.由於遍歷的線程能繼續使用原有的鏈
- 在Java 8中,使用volatile HashEntry保存數據,table元素做爲鎖.從Table數組+單向鏈表又加上了紅黑樹
- 紅黑樹是一種特別的二叉查找樹,紅黑樹的特性:
- 節點爲紅或黑
- 根節點爲黑
- 葉節點爲黑
- 一節點爲紅,則一節點爲黑
- 一節點到其子孫節點全部路徑上的黑節點數目相同
- ArrayList底層的數據結構是數組,支持隨機訪問.LinkedList的底層數據結構是鏈表,不支持隨機訪問
- 使用下表訪問一個元素:
- ArrayList的時間複雜度是O(1)
- LinkedList的時間複雜度是O(n). LinkedList是雙向鏈表
- Comparable接口用於定義對象的天然順序,是排序接口
- Comparator一般用於定義用戶定製的順序,是比較接口
- 若是須要控制某個類的次序,而該類自己不支持排序,即沒有實現Comparable接口,就能夠創建一個"該類的比較器"來進行排序
- Comparable老是隻有一個,可是能夠有多個Comparator來定義對象的順序
- 抽象類是不容許被實例化的類,一個類只能使用一次繼承關係,可是一個類能夠實現多個接口
- 抽象類和接口所反映出的設計理念不一樣:
- 抽象類表示的是 "is - a"
- 接口表示的是 "like - a"
- 實現抽象類和接口的類必須實現其中的全部方法.抽象類能夠有非抽象方法,接口中則不能有實現方法,可是在Java 8中容許接口中有靜態默認方法
- 接口中定義的變量默認是public static final型,且必須給出初值,因此實現類中不能從新定義,也不能改變這個值
- 抽象類中定義的變量默認是friendly型,這個變量的值能夠在子類中從新定義,也能夠從新賦值
- 子類中實現父類中的抽象方法時.可見性能夠大於等於父類中的
- 接口實現類類中的接口方法的可見性只能與接口中的相同,即爲public
- 使用抽象類是爲了重用,減小編碼量,下降耦合性
- 重載和重寫都是使用相同的名稱實現不一樣的功能,可是重載是編譯時活動,重寫是運行時活動
- 能夠在同一個類中重載方法,但只能在子類中重寫方法,重寫必需要有繼承
- 重載:
- 重載的時候,方法名要同樣,可是參數類型和參數個數不同,返回值類型能夠相同也能夠不一樣
- 沒法以返回型別做爲重載函數的區分標準
- 重寫:
- 在子類中能夠根據須要對從基類中繼承的方法進行重寫
- 重寫的方法和被重寫的方法必須具備相同的方法名稱,參數列表和返回類型
- 重寫方法不能使用比被重寫方法更嚴格的訪問權限
- Collection< E >是Java集合框架中的基本接口
- Collections是Java集合框架提供的一個工具類,其中包含了大量用於操做和返回集合的靜態方法
- 多態指的是父類引用指向子類的對象,調用方法時會調用子類的實現而不是父類的實現
- 多態的實現關鍵在於動態綁定
- clone()
- equals()
- hashCode()
- toString()
- notify()
- notifyAll()
- wait()
- finalize()
- getClass()
- 泛型即參數化類型,在建立集合時,指定集合元素的類型,此集合只能傳入該類型的參數
- 類型擦除:Java編譯器生成的字節碼不包括泛型信息,因此在編譯時擦除
- 泛型用最頂級的父類替換
- 移除
- Lambda表達式
- 容許像對象同樣傳遞匿名函數Stream API,充分利用現代多核CPU,能夠寫出很簡潔的代碼
- Date與Time API,有一個穩定簡單的日期和時間庫可供使用
- 接口中能夠有靜態,默認方法
- 重複註解,能夠將相同的註解在同一類型上使用屢次
- protected可在包內及包外子類訪問
- default只能在同一包內訪問
- private只能在同一個類中訪問
- 集合
- 線性結構
- 數組
- 隊列
- 鏈表
- 棧
- 樹形結構
- 圖狀結構
Java中的TreeMap是使用紅黑樹實現的java
- 匿名內部類就是沒有名字的內部類,匿名內部類只能使用一次,一般用來簡化代碼編寫
- 匿名內部類只能訪問外部類的final變量
- 在Java 8中,若是局部變量被匿名內部類訪問,那麼該局部變量至關於自動使用了final修飾
- 經過枚舉
- 經過靜態內部類
- 也能夠經過雙重檢查建立單例模式,可是這種單例模式是線程不安全的
- poll()和remove都是從隊列中取出一個元素
- poll()在獲取元素失敗時會返回空
- remove()在獲取元素失敗時會拋出異常
- 使用迭代器
Iterator it = list.iterator(); while (it.hasNext()) { if (...) { it.remove(); } } 複製代碼
- GPL: GNU General Public License,GNU通用公共許可協議
- LGPL: GNU Lesser General Public License, GNU寬通用公共許可協議
- BSD: Berkeley Software Distribution, 伯克利軟件分發許可協議
- MIT: Massachusetts Institute of Technology
- Apache: Apache Licence, Apache許可協議
- MPL: Mozilla Public Licence, Mozilla公共許可協議
- 線程同步與否和阻塞非阻塞沒有關係
- 同步是一個過程,阻塞是線程的一種狀態
- 多個線程操做共享變量時會出現競爭
- 須要使用同步來防止兩個以上的線程同時進入臨界區內,在這個過程當中,後進入臨界區的線程將阻塞,等待先進入的線程走出臨界區
- 同步和異步最大的區別是: 一個須要等待,一個不須要等待
- 同步能夠避免出現死鎖,讀髒數據的發生,通常共享某一資源的時候使用
- 若是每一個人都有修改權限,同時修改一個文件,有可能使一我的讀取另外一我的已經刪除的內容,就會出錯
- 同步就會按照順序來修改
- 線程池的做用是根據系統自身的狀況,有效的限制執行線程的數量,使得運行效果達到最佳
- 線程池主要執行的是:
- 控制執行線程的數量
- 超出數量的線程排隊等候
- 等待有任務執行完畢
- 再從隊列中最前面取出任務執行
- wait()方法應該在循環中調用:
- 由於當線程獲取到CPU開始執行的時候,其餘條件可能尚未知足
- 因此在處理前,循環檢測條件是否知足更好
- wait(),notify()和notifyAll()方法是java.lang.Object類爲線程提供的用於實現線程間通訊的同步控制的等待和喚醒方法
- 實現線程的方法:
- 繼承Thread類,重寫run函數
- 實現Runnable接口,重寫run函數
- 實現Callable接口,重寫call函數
- 僞共享是多線程系統(這個系統的每隔處理器都有本身的局部緩存)中一個廣泛存在的性能問題
- 緩存系統中是以緩存行(cache line)爲單位存儲的
- 緩存行是2的整數冪個連續字節,通常爲32 - 256字節
- 最多見的緩存行是64個字節
- 當多線程修改相互獨立的變量時,若是這些變量共享同一個緩存行,就會影響彼此的性能,這就是僞共享
- java.util.concurrent
- java.util.concurrent.atomic
- java.util.concurrent.lock
- ReadWriteRock讀寫鎖的使用場景:
- 讀 - 讀
- 讀 - 寫
- 寫 - 寫
- 除了讀 - 讀之間是共享的,其他都是互斥的
- 考查對AQS, CAS的掌握程度
- Semaphore
- CountDownLatch
- CyclicBarrier
- Exchanger
- ReadWriteLock讀寫鎖的使用場景:
- 讀,讀
- 讀,寫
- 寫,寫
- 除了讀和讀之間是共享的,其餘都是互斥的
這樣以後會討論怎樣實現互斥鎖和同步鎖的,瞭解對AQS,CAS的掌握程度,技術學習深度node
- Semaphore拿到執行權的線程之間是互斥的
- Semaphore, CountDownLatch, CyclicBarrier, Exchanger是Java併發編程中的4個輔助類,瞭解CountDownLatch和CyclicBarrier之間的區別
- Semaphore可能有多把鎖,能夠容許多個線程同時擁有執行權,這些有執行權的線程若是併發訪問同一對象,會產生線程安全問題
- Semaphore:
- 能夠有多把鎖,容許多個線程同時擁有執行權
- 這些有執行權的線程若是併發訪問同一對象,會產生線程安全問題
- 單例模式是最常遇到的設計模式之一,考查對常常碰到的問題的理解的深度
- 單例一共有5種實現方式:
- 餓漢
- 懶漢
- 靜態內部類
- 雙檢鎖
- 枚舉
- 要是寫了簡單的懶漢式可能會問: 要是多線程狀況下怎樣保證線程安全呢?
- 使用雙檢鎖能夠保證線程安全.
- 爲何要兩次校驗?光是雙檢鎖還會有什麼問題?
- 對象在定義的時候加上volatile關鍵字
- 引伸討論原子性和可見性,Java內存模型,類的加載過程
- 枚舉方式,靜態內部類,雙檢鎖均可以實現單例模式. 雙檢鎖的單例模式:
public Class Singleton { private Singleton() { } private volatile static Singleton instance; public static Singleton getInstance() { if (null == instance) { synchronized (Singleton.class) { if (null == instance) { instance = new Singleton(); } } } return instance; } } 複製代碼
- 死鎖的四個條件:
- 示例: 定義兩個ArrayList,都加上鎖A,B.線程1,2. 線程1獲取到鎖A,請求鎖B. 線程2獲取到鎖B,請求鎖A. 在等待對方釋放鎖的過程當中都不會讓出已得到的鎖
public class DeadLock { public static void main(String[] args) { final List<Integer> list1 = Arrays.asList(1, 2, 3); final List<Integer> list2 = Arrays.asList(4, 5 ,6); new Thread(new Runnable() { @Override public void run() { synchronized (list1) { for (Integer i : list1) { System.out.println(i); } try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (list2) { for (Integer i : list2) { System.out.println(i); } } } } }).start(); new Thread(new Runnable() { @Override public void run() { synchronized (list2) { for (Integer i : list2) { System.out.println(i); } try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (list1) { for (Integer i : list1) { System.out.println(i); } } } } }).start(); } } 複製代碼
- 相等
- new一個對象賦給變量
- 這行表達式建立了幾個對象
- 是
- 不可使用for循環直接刪除ArrayList中的特定元素:
- 不一樣的for循環會發生不一樣的異常
- 泛型for會拋出ConcurrentModificationException
- 普通的for想要刪除集合中重複且連續的元素,只能刪除第一個
- 緣由:
- JDK中的ArrayList源碼
- ArrayList中的remove有兩個同名方法,只是入參不一樣:
- 入參爲Object的實現:
- 通常狀況下程序的執行路徑走到else路徑下最終調用faseRemove() 方法,會執行System.arraycopy() 方法,致使刪除元素時涉及到數組元素的移動
- 普通for循環,在 遍歷第一個符合刪除條件的字符串時將該元素從數組中刪除,而且將後一個元素即第二個元素移動到當前位置,致使下一次遍歷時後一個字符串並無遍歷成功,因此沒法刪除. 這種可使用倒序刪除的方式來避免
- 解決方法: 使用迭代器Iterator
List<String> list = new ArrayList(Arrays.asList("a", "b", "b", "c", "d")); Iterator<String> iterator = list.iterator(); while (iterator.hasNext()) { String element = iterator.next(); if ("b".equals(element)) { iterator.remove(); } } 複製代碼
- 第一步: 線程池判斷核心線程池裏的線程是否都在執行任務. 若是不是,則建立一個新的工做線程來執行任務. 若是核心線程池裏的線程都在執行任務,則執行第二步
- 第二步: 線程池判斷工做隊列是否已經滿了. 若是工做隊列沒有滿,則將新提交的任務存儲在這個工做隊列中等待. 若是工做隊列滿了,則執行第三步
- 第三步: 線程池判斷線程池的線程是否都處於工做狀態. 若是沒有,則建立一個新的工做線程來執行任務. 若是已經滿了,則交給飽和策略來處理這個任務
- 抽象隊列同步器AQS: AbstractQueuedSychronizer
- 若是說java.util.concurrent的基礎是CAS的話,那麼AQS就是整個Java併發包的核心
- ReentrantLock, CountDownLatch, Semaphore都用到了AQS
- AQS實際上以雙向隊列的形式鏈接全部的Entry:
- ReentrantLock: 全部等待的線程都被放在一個Entry中並連成雙向隊列,前面一個線程使用ReentrantLock好了,則雙向隊列實際上的第一個Entry開始運行
- AQS定義了對雙向隊列全部的操做,而且只開放了tryLock和tryRelease方法給開發者使用.開發者能夠根據本身的實現重寫tryLock和tryRelease方法來實現本身的併發功能
- 比較並替換CAS: Compare and Swap
- 假設有三個操做數:
- 內存之V
- 舊的預期值A
- 要修改的值B
- 當且僅當預期值A和內存值V相同時,纔會將內存值修改成B並返回true. 不然什麼都不作並返回false.
- 整個比較並替換的操做是一個原子操做
- CAS必需要volatile變量配合,這樣才能保證每次拿到的變量是主內存中最新的響應值. 不然舊的預期值A對某條線程來講,永遠是一個不會變的值A. 只要某次CAS操做失敗,則CAS操做永遠不會成功
- CAS高效地解決了原子操做的問題,但仍然存在三大問題:
- 循環時間長開銷很大
- 只能保證一個共享變量的原子操做
- ABA問題
- synchronized(this)原理:
- 兩條指令: monitorenter和monitorexit
- 同步方法: 從同步方法的反編譯的結果中能夠看出 - 方法的同步並無經過指令monitorenter和monitorexit來實現,相對於普通方法,在常量池中多了ACC_SYNCHRONIZED標識符
- JVM就是根據ACC_SYNCHRONIZED標識符來實現方法同步的:
- 當方法被調用時,調用指令將會檢查方法的ACC_SYNCHRONIZED訪問標誌是否被設置
- 若是設置了,執行線程將先獲取monitor,獲取成功以後才能執行方法體,方法執行完以後再釋放monitor
- 在方法執行期間,其他任何線程都沒法再得到同一個monitor對象
- 理解volatile關鍵字的做用的前提是要理解Java的內存模型
- volatile關鍵字的做用主要有兩點:
- 多線程主要圍繞可見性和原子性兩個特性展開.使用volatile關鍵字修飾的變量,保證了在多線程之間的可見性.即每次讀取到volatile變量,必定是最新的數據
- 底層代碼的執行: Java代碼 -> 字節碼 -> 根據字節碼執行對應的C/C++代碼 -> C/C++代碼被編譯成彙編語言 -> 和硬件電路交互.現實中,爲了獲取更好的性能,JVM可能會對指令進行重排序,多線程下可能會出現意想不到的問題.使用volatile則會禁止對語義重排序,不過也會必定程度上下降代碼的執行效率
- 從實踐角度而言,volatile的一個重要做用就是和CAS結合,保證了原子性. 好比AtomicInteger
- B樹和B+樹,既考查MySQL索引的實現原理,也考查數據結構基礎
- 首先從二叉樹提及:
- 由於會產生退化現象,提出平衡二叉樹
- 再提出怎麼樣讓每一層放的節點多一些來減小遍歷高度,引伸出m叉樹
- m叉搜索樹一樣會有退化現象,引出m叉平衡樹,即B樹
- 這個時候每一個節點既放了key又放了value.怎樣使每一個節點放盡量多的key值,以減小遍歷高度也就是訪問磁盤的次數
- 能夠將每一個節點只放key值,將value值放在葉子節點,在葉子節點的value值增長指向相鄰節點的指針,這就是優化後的B+樹
- 而後談談數據庫索引失效的狀況:
- 爲何給離散度低的字段,好比性別創建索引是不可取的?查詢數據反而更慢
- 若是將離散度高的字段和離散度低的字段,好比性別創建聯合索引會怎樣,有什麼須要注意的?
- AOP和IOC是Spring的精華部分
- AOP:
- AOP能夠看做是對OOP的補充,對代碼進行橫向擴展
- 經過代理模式實現.代理模式有靜態代理和動態代理.
- Spring利用的是動態代理,在程序運行過程當中將加強代碼織入源代碼中
- IOC: 控制反轉
- 將對象的控制權交給Spring框架,用戶使用對象無需建立,直接使用便可
- AOP和IOC重點要了解設計思想
- Spring的循環依賴問題:
- 什麼是循環依賴?
- 怎樣檢測出循環依賴?
- Spring循環依賴有幾種方式,使用基於setter屬性的循環依賴爲何不會出現問題?
- 1. 用戶發送請求 -> DispatcherServlet: 前端控制器收到請求後本身不進行處理,而是委託給其他解析器進行處理,做爲統一的訪問點,進行全局的流程控制
- 2. DispatcherServlet -> HandlerMapping: HandlerMapping將會把請求映射爲HandlerExecutionChain對象.HandlerExecutionChain包含一個Hander處理器,多個HandlerInterceptor攔截器
- 3. DispatcherServlet -> HandlerAdapter: HandlerAdapter將會將處理器包裝爲適配器,從而支持多種類型的處理器
- 4. HandlerAdapter -> 處理器功能方法的調用: HandlerAdapter將會根據適配的結果調用真正的處理器的功能處理方法,完成功能處理,並返回一個ModelAndView對象. ModelAndView對象包含模型數據.邏輯視圖名
- 5. ModelAndView的邏輯視圖名 -> ViewResolver: ViewResolver將邏輯的視圖名解析爲具體的View
- 6. View -> 渲染: View會根據傳進來的Model模型數據進行渲染,這裏的Model是一個Map數據結構
- 7. 返回控制權給DispatcherServlet,由DispatcherServlet返回響應給用戶
- 第一範式: 數據庫中的表的全部字段值都是不可分割的原子數據項
- 第二範式: 數據庫表中的每一列都和主鍵相關,而不能只和主鍵的某一部分相關
- 第三範式: 數據庫表中每一列數據都和主鍵直接相關,不能間接相關
- 範式是爲了減小數據冗餘
- 重複性強的字段,不適合添加索引
- MySQL給離散度低的字段,好比性別設置索引,再以性別做爲條件查詢反而會更慢
- 一個表可能會涉及兩個數據結構:
- 數據表: 存放表中的數據
- 索引
- 索引:
- 將一個或幾個字段(組合索引)按規律排列起來,再附加上該字段所在行數據的物理地址(位於表中)
- 好比有個字段是年齡,若是須要選取某個年齡段的全部行,那麼通常狀況下可能須要進行一次全表掃描
- 可是若是以這個年齡段創建一個索引,那麼索引會按照年齡值根據特定的數據結構建一個排列,這樣在索引中就能迅速定位,不須要進行全表掃描
- 爲何性別不適合創建索引呢?
- 由於訪問索引須要有額外的IO開銷,從索引中拿到的只是地址,要想真正訪問到數據仍是要對錶進行一次IO
- 若是要從表中的100萬行數據中取幾個數據,那麼利用索引迅速定位,訪問索引的IO開銷就能夠忽略不計
- 若是要從標中的100萬行數據取50萬行數據,再訪問50萬次表,加起來的開銷並不會比對錶進行一次完整的掃描小
- 若是將性別字段設爲聚焦索引,那麼確定能加快大約一半該字段的查詢速度
- 聚焦索引:
-所以聚焦索引要用到搜索最頻繁的字段上
- 指的是表自己數據按照哪一個字段的值來進行排序
- 聚焦索引不會付出額外IO開銷
- 聚焦索引只能有一個
- 能夠根據業務場景須要,將性別和其他的字段創建聯合索引. 好比時間戳,要將時間戳字段放在性別前面
- undo log:
- redo log:
- binlog:
- 數據庫中的索引的結構是一種排序的數據結構,數據庫的索引是經過B樹和變形的B+樹實現的
- 什麼狀況下不適合創建索引:
- 對於在查詢過程當中不多使用或者參考的列
- 對於只有不多數據值的列
- 對於定義爲image,text和bit數據類型的列
- 當修改性能遠遠大於檢索性能時
- 根據系統自身的環境狀況,有效限制線程數量,使得運行效果達到最佳
- 線程主要是經過控制執行線程的數量,超出數量的線程排隊等候,等待有任務執行完畢,再從隊列最前面取出任務執行
- SQL優化
- 表結構優化
- 索引優化
- 緩存參數優化
- 生產者消費者模式:
- synchronized鎖住一個LinkedList:
- 生產者: 只要隊列不滿,生產後往裏存
- 消費者: 只要隊列不空,消費後往外取
- 二者經過wait() 和notify() 進行協調
- 要考慮怎麼樣提升效率
- 熟悉消息隊列設計精要思想及使用
- 異步處理: 相對於傳統的串行,並行方式,提升了系統的吞吐量
- 應用解耦: 系統間經過消息通訊,不用關心其餘系統的處理
- 流量削峯: 能夠經過消息隊列長度控制請求量,能夠緩解短期內高併發請求
- 日誌處理: 解決大量日誌傳輸
- 消息通信: 消息隊列通常都內置了高效的通訊機制,所以能夠用在純的消息通信. 好比實現點對點消息隊列,聊天室等
- 將全部Broker和待分配的Partition排序
- 將第i個Partion分配到第 (i mod n) 個Broker上
- 將第i個Partion的第j個Replica分配到第 ((i+j) mod n) 個Broker上
- 消息隊列的順序問題
- 消息有序指的是能夠按照消息的發送順序來消費
- 假定生產者產生了2條消息:M1,M2.假定M1發送到S1,M2發送到S2.若是要保證M1優先於M2被消費,如何保證:
- 解決方案:
- 保證生產者 - MQSever - 消費者是一對一對一的關係
- 缺陷:
- 並行度會成爲系統的瓶頸,吞吐量不夠
- 會出現更多的異常處理問題: 只要消費者出現問題,就會致使整個流程堵塞,不得不解決阻塞的問題
- 能夠經過合理的設計或者將問題分解來規避:
- 不關注亂序的應用實際大量存在
- 隊列無序並不意味着消息無序
- 消息的重複問題:
- 形成消息重複的根本緣由: 網絡不可達
- 因此解決這個問題的方法就是繞過這個問題.也就是: 若是消費端收到兩條同樣的消息,應該怎樣處理?
- 解決方案:
- 消費端處理消息的業務邏輯保持冪等性
- 只要保持冪等性,無論來多少條重複消息,最後處理的結果都同樣
- 保證每條消息都有惟一編號且保證消息處理成功與去重表的日誌同時出現
- 利用一張日誌表來記錄已經處理成功的消息的ID,若是新到的消息ID已經在日誌表中,那麼就再也不處理這條消息
- 服務容器負責啓動,加載,運行服務提供者
- 服務提供者在啓動時,向註冊中心註冊本身提供的服務
- 服務消費者在啓動時,向註冊中心訂閱本身所需的服務
- 註冊中心返回服務提供者地址列表給消費者,若是有變動,註冊中心將基於長鏈接推送變動數據給消費者
- 服務消費者,從提供者地址列表中,基於軟負載均衡算法,選擇一臺提供者進行調用.若是調用失敗,再選擇另外一臺進行調用
- 服務消費者和服務提供者,在內存中累計調用次數和調用時間,定時每分鐘發送統計數據到監控中心
- Random:
- 隨機負載均衡策略,按權重設置隨機機率
- 在一個截面上的碰撞機率高,但調用量越大分佈越均勻,並且按機率使用權重後也比較均勻,有利於動態調整提供者權重
- RoundRobin:
- 輪循負載均衡策略,按公約後的權重設置輪循比率
- 存在慢的提供者累積請求的問題
- 好比: 第二臺機器很慢,但沒有宕機,當請求到第二臺機器就會卡住,長此以往,全部的請求都會卡在 調到第二臺機器的時候
- LeastActive:
- 最少活躍調用數負載均衡策略,相同活躍數的隨機調用.活躍數指的是調用先後計數差
- 使慢的提供者收到更少的請求,由於越慢的提供者的調用先後計數差會越大
- ConsistentHash:
- 一致性Hash負載均衡策略,相同的參數請求老是發到同一提供者
- 當某臺提供者宕機時,本來發往該提供者的請求,基於虛擬節點,平攤到其餘提供者,不會引發劇烈變更
- 缺省只對第一個參數Hash,若是要修改,須要修改 < dubbo:parameter key="hash.arguments" value="0,1" />
- 缺省使用160份虛擬節點,若是要修改,須要修改< dubbo:parameter key="hash.nodes" value="320" >
- Failover: 失敗自動切換,當出現失敗,重試其餘服務器. 一般用於讀操做,但重試會帶來更長延遲. 能夠經過設置retries="2" 來設置重試次數,不包含第一次
- Failfast: 快速失敗,只發起一次調用,失敗當即報錯. 一般用於非冪等性的寫操做,好比新增記錄
- Failsafe: 失敗安全,出現異常時,直接忽略. 一般用於寫入審計日誌等操做
- Failback: 失敗自動恢復,後臺記錄失敗請求,定時重發. 一般用於消息通知操做
- Forking: 並行調用多個服務器,只要一個成功即返回. 一般用於實時性要求比較高的讀操做,但須要浪費更多服務資源,能夠經過設置 forks="2"來設置最大並行數
- Broadcast: 廣播調用全部提供者,逐個調用,任意一臺報錯即報錯. 一般用於通知全部提供者更新緩存或日誌等本地資源信息
- Dubbo做爲RPC框架,首先要完成的就是跨系統,跨網絡的服務調用
- 消費方和提供方遵循統一的接口定義
- 消費方調用接口時,Dubbo將其轉換爲統一格式的數據結構
- 經過網絡傳輸,提供方根據規則找到接口實現,經過反射完成調用
- 消費方獲取的是對遠程服務的一個代理 Proxy, 提供方由於要支持不一樣的接口實現,須要一個包裝層Wrapper
- 調用過程:
- 消費方的Proxy和提供方的Wrapper得以讓Dubbo構建出複雜,統一的體系
- 這種動態代理與包裝是經過SPI的插件方式實現的,接口就是ProxyFactory:
@SPI("javassist") public interface ProxyFactory { @Adaptive({Constants.PROXY_KEY}) <T> T getProxy(Invoker<T> invoker) throws RpcException; @Adaptive({Constants.PROXY_KEY}) <T> invoker<T> getInvoker(T proxy, Class<T> type, URL url) throws RpcException; } 複製代碼
- ProxyFactor有兩種實現方式:
- 基於JDK的代理實現
- 基於javassist的實現
- ProxyFactory接口上定義了 @SPI("javassist"), 默認爲javassist的實現
- Dubbo序列化: 阿里基於Java的序列化實現
- Hessian2序列化: Hessian是一種跨語言的高效二進制的序列化方式. 這裏實際不是原生的Hessian2序列化,而是阿里修改過的Hessian Lite,是Dubbo默認啓用的序列化方式
- Json序列化: 目前有兩種實現:
- 採用阿里的fastjson庫
- 採用Dubbo自身實現的簡單Json庫
- 通常狀況下,json這種文本序列化性能不如二進制序列化
- Kryo和FST: Kryo和FST的性能廣泛優於Hessian和Dubbo序列化
- Hessian是一個輕量級的remoting on http工具,採用Binary RPC協議,很適合發送二進制數據,同時又具備防火牆穿透能力
- Hessian支持跨語言串行
- Hessian序列化比Java默認的序列化具備更好的性能和易用性
- Hessian序列化支持的語言比較多
- Protoco Buffer是谷歌出品的一種輕量而且高效的結構化數據存儲格式,性能比Json,XML強大得多
- Protoco的序列化和反序列化簡單而且速度快. 緣由在於:
- 編碼和解碼方式簡單,只須要簡單的數學運算=位移等等
- 採用Protoco Buffer自身的框架代碼和編譯器共同完成
- Protoco Buffer的數據壓縮效果好,即序列化後數據量的體積小. 緣由在於:
- 採用獨特的編碼方式,好比Varint,Zigzag編碼方式等等
- 採用 T - L - V 數據存儲方式,減小了分隔符的使用而且數據存儲得緊湊
- 能夠
- Dubbo消費者在應用啓動時會從註冊中心拉取已註冊的生產者的地址接口,並緩存在本地. 每次調用時,按照本地存儲的地址進行調用
- ZooKeeper是一個分佈式應用協調系統,已經應用到了許多分佈式項目中,用來完成
- 統一命名服務
- 狀態同步服務
- 集羣管理
- 分佈式應用配置項的管理
- 每一個Server在內存中存儲了一份數據
- ZooKeeper啓動時,將從實例中選舉一個leader(Paxo協議)
- Leader負責處理數據更新等操做(Zab協議)
- 當且僅當大多數Server在內存中成功修改數據時,一個更新操做成功
- Netty是一個網絡通訊框架
- Netty進行事件處理的流程:
- Channel是鏈接的通道,是ChannelEvent的產生者
- ChannelPipeline能夠理解爲ChannelHandler的集合
- IO的方式一般分爲:
- 同步阻塞的BIO
- 同步非阻塞的NIO
- 異步非阻塞的AIO
- 在使用同步阻塞的BIO的網絡應用:
- 若是要同時處理多個客戶端請求,或者是在客戶端要同時和多個服務器進行通信,就必須使用多線程來處理
- 同步非阻塞的NIO基於Reactor:
- 當socket有流可讀或者可寫入socket時,操做系統會相應的通知引用程序進行處理,應用再將流讀取到緩衝區或者寫入操做系統
- 這個時候,不是一個鏈接就要對應一個處理線程了.而是有效的請求,對應一個線程,當鏈接沒有數據時,是沒有工做線程來處理的
- 異步非阻塞的AIO與NIO不一樣:
- 當進行讀寫操做時,只須要直接調用API的read或者write方法便可
- 這兩種方法均爲異步的:
- 對於讀操做而言, 當有流可讀取時,操做系統會將可讀的流傳入read方法的緩衝區,並通知應用程序
- 對於寫操做而言,當操做系統將write方法傳遞的流寫入完畢時,操做系統主動通知應用程序
- read或者write方法都是異步的,完成後會主動調用回調函數
- 系統拆分的分類:
- 從資源角度:
- 應用拆分
- 數據庫拆分
- 從採用的前後順序:
- 水平擴展
- 垂直拆分
- 業務拆分
- 水平拆分
- 是否使用Dubbo依據實際業務場景來決定:
- 當垂直應用愈來愈多,應用之間的交互不可避免,將核心業務抽取出來,做爲獨立的服務,逐漸造成穩定的服務中心,使前端應用能更快速的響應多變的市場需求. 此時,用於提升業務複用以及整合的分佈式框架RPC是關鍵
- 當服務愈來愈多,容量的評估,小服務資源的浪費等問題逐漸顯現,此時須要增長一個調度中心基於訪問壓力實時管理集羣容量,提升集羣的利用率. 此時,用於提升機器利用率的資源調度和治理中心SOA是關鍵
- Dubbo支持服務治理,而Thrift不支持
- Thrift是跨語言RPC框架
setNX key value value保證惟一性,避免線程A釋放線程B拿到的鎖面試
設置過時時間算法
set命令提供了相應的原子操做命令來保證set key value和設置過時時間的原子操做數據庫
Redis集羣使用的是多主多從,當一半以上的主節點set成功,纔算成功編程
先Delete緩存,再更新DB,延時一段時間再Delete緩存 或者先更新DB,延時一段時間再Delete緩存json
由於若是線程A先Delete緩存,此時線程B發現緩存中沒有數據,則從DB中讀出老數據並reload到緩存,線程A更新數據庫以後,則緩存與數據庫數據庫中的數據不一致,所以須要延時一段時間執行刪除後端
重試機制設計模式
- 併發編程中的問題:
- 原子性問題
- 可見性問題
- 有序性問題
- volatile:
- volatile關鍵字能保證可見性,只能禁止指令重排序,不能保證原子性
- 可見性只能保證每次讀取的是最新的值,可是volatile沒法保證對變量的操做的原子性
- 在生成的會變語句中加入Lock關鍵字和內存屏障
- Lock:
- Lock實現提供了比使用synchronized方法和語句可得到的更普遍的鎖定操做,可以使用更優雅的方式解決線程同步問題
- 用synchronized修飾的方法或者語句塊在代碼執行完以後鎖自動釋放,然而使用Lock修飾的方法或者語句須要手動釋放鎖
熱部署
- 管道: Pipe
- 命名管道: Named Pipe
- 信號: Signal
- 消息隊列: Message
- 共享內存
- 內存映射: Mapped Memory
- 信號量: Semaphore
- 套接口: Socket
Synchronized修飾靜態方法,鎖定自己不是實例.非靜態方法鎖定實例
- 死鎖: 指多個進程在運行過程當中因爭奪資源而形成的一種僵局
- 產生緣由: 競爭資源
- 當系統中多個進程使用共享資源,而且資源不足以知足須要,會引發進程對資源的競爭而產生死鎖
- 進程間推動的順序不當
- 請求和釋放資源的順序不當,一樣也會產生進程死鎖
- 互斥條件: 進程獨佔資源
- 請求與保持: 進程因請求資源而阻塞時,對已得到的資源保持不放
- 不剝奪條件: 進程已經得到資源,在未使用完以前,不能強行剝奪
- 循環等待: 若干進程之間造成頭尾相接的循環等待資源關係
線上服務器是分佈式多臺部署的,常常會面臨解決分佈式場景下數據一致性問題,這是就要利用分佈式鎖來解決這些問題
- Java類的初始化順序:
- 基類靜態代碼塊,基類靜態成員變量. 並列優先級,按照代碼中出現的前後順序執行,而且只有第一次加載時執行
- 派生類靜態代碼塊,派生類靜態成員變量. 並列優先級,按照代碼中出現的前後順序執行,而且只有第一次加載時執行
- 基類普通代碼塊,基類普通成員變量. 並列優先級,按照代碼塊中出現的前後順序執行
- 基類構造函數.
- 派生類普通代碼塊,派生類普通成員變量. 並列優先級,按照代碼塊中出現的前後順序執行
- 派生類構造函數.
- 方法區是JVM規範中要求的 ,永久區是Hotspot虛擬機對方法區的具體實現
- 方法區是規範,永久區是實現方式(JDK 1.8之後作了改變)
- 文件中有幾個類,編譯後就有幾個class文件
- 成員變量是能夠不經初始化的,在類加載過程的準備階段便可以給成員變量賦予默認值.
- 局部變量在使用以前須要顯式賦予初始值
- javac不是推斷不出不能夠這樣作,對於成員變量而言,其賦值和取值訪問的前後順序具備不肯定性,對於一個成員變量能夠在一個方法調用前賦值,也能夠在方法調用後進行賦值,這是運行時發生的,編譯器肯定不了,交給JVM作比較合適
- 對於局部變量而言,局部變量的賦值和訪問順序是肯定的,這樣設計是一種約束,盡最大程度減小使用者犯錯的可能性:
- 假使局部變量可使用默認值,可能總會無心間忘記賦值,進而致使不可預期的狀況出現
- 類加載過程:
- 加載
- 驗證: 驗證階段做用是保證Class文件的字節流包含的信息符合JVM規範,不會給JVM形成傷害
- 準備: 準備階段爲變量分配內存並設置類變量的初始化
- 解析: 解析過程是將常量池內的符號引用替換成直接引用
- 初始化
- 雙親委派模型中的方法: 雙親委派是指若是一個類收到類加載請求,不會本身先嚐試加載,先找父類加載器完成.當頂層啓動類加載器表示沒法加載這個類的時候,子類纔會本身去加載.當回到最開始的發起者加載器還沒法加載時,並不會向下找,而是拋出ClassNotFound異常
- 啓動(Bootstrap)類加載器
- 標準擴展(Extension)類加載器
- 應用程序(Application)類加載器
- 上下文(Custom)類加載器
- 意義是防止內存中出現多份一樣的字節碼
- JVM如何判斷一個對象已經變成可回收的垃圾:
- 引用計數器法: 引用計數器沒法解決循環引用的問題
- 根搜索算法: 從一系列的GC Roots對象開始向下搜索,搜索的路徑稱爲引用鏈.當一個對象到GC Roots之間沒有引用鏈時稱爲引用不可達.引用不可達的對象被認爲是可回收對象
- 幾種垃圾回收器:
- Serial New或者Serial Old: 串行
- Parrallel New: 並行
- Parrallel Scavenge
- Parrallel Old
- G1: 一款並行與併發收集器,而且可創建可預測的停頓時間模型,總體上是基於標記清理,局部採用複製
- CMS
- CMS收集器是一個以得到最短回收停頓時間爲目標的收集器,是一種併發收集器,採用的是Mark - Sweep算法
- 方法區(Method): 被全部線程共享,方法區包含全部的類信息和靜態變量
- 堆(Heap): 被全部的線程共享,存放對象實例以及數組,Java堆是GC的主要區域
- 棧(Stack): 每個線程包含一棧區,棧中保存一些局部變量
- 程序計數器: 當前線程執行的字節碼行指示器
- 新生代存放全部新生成的對象
- 老年代存放的都是一些生命週期較長的對象
- 持久代主要存放的是Java類的類信息,與垃圾收集要收集的Java對象關係不大
- 內存溢出: out of memory,程序申請內存時,沒有足夠的內存
- 內存泄露: 垃圾對象沒法回收,但是使用memory analyzer工具查看泄露
- 進程: 運行中的程序,具備獨立性,動態性,併發性
- 線程: 指進程中的順序執行流
- 進程與線程的區別:
- 進程間不共享內存
- 建立進程進行資源分配的代價要大得多,因此多線程在高併發的環境中效率高
- 序列化: 將Java對象轉化爲字節序列
- 反序列化: 將字節序列轉化爲Java對象
- 序列化和反序列化主要是爲了Java線程間的通信,實現對象傳遞.只有實現了Serializable或者Externalizable接口類對象纔可被序列化
在JVM中,int類型的變量的長度是一個固定值,與平臺無關,4個字節,長度爲32位
- Java中一共有四種類型的引用:
- StrongReference
- SoftReference
- WeakReference
- PhantomReference
- StrongReference是Java的默認引用實現,會盡量長時間的存活於JVM內,當沒有任何對象指向時將會被GC回收
- SoftReference會盡量長的保留引用直到JVM內存不足時纔會被回收,經過虛擬機保證.這一特性使得SofeReference很是適合緩存應用
- WeakReference是一個弱引用,當所引用的對象在JVM內再也不有強引用時,將被GC回收
- WeakReference和SoftReference的區別:
- WeakReference與SoftReference都有利於提升GC和內存的效率
- WeakReference一旦失去最後一個強引用,就會被GC回收
- SoftReference會盡量長的保留引用直到JVM內存不足時纔會被回收,經過虛擬機保證
- Java中的堆和棧屬於不一樣的內存區域,使用目的也不一樣
- 棧一般用於保存方法幀和局部變量.而對象老是在堆上分配
- 棧一般比堆小,也不會在多個線程之間共享,而堆是被整個JVM全部線程共享
- Java堆空間:
- 當經過Java命令啓動Java進程的時候,會分配內存,內存的一部分用於建立堆空間
- 當程序中建立對象的時候,就從堆空間中分配內存
- GC:
- GC是JVM內部的一個進程,回收無效對象的內存用於未來的分配
- 在TCP鏈接中,數據流必須以正確的順序送達對方
-TCP可靠性:
- 經過順序編碼和確認(ACK) 來實現的
- TCP鏈接是經過三次握手進行初始化的,三次握手的目的是同步鏈接雙方序列號和確認號並交換TCP窗口大小信息:
- 第一次: 客戶端發起鏈接
- 第二次: 表示服務器收到了客戶端請求
- 第三次: 表示客戶端收到了服務器反饋
- 在cookie中存儲的session id
- cd: 用來改變所在目錄. cd / - 轉到根目錄, cd ~ - 轉到用戶目錄
- ls: 用來查看目錄的內容
- cp: 用來拷貝文件. cp sourceFileName targetFileName
- mv: 移動文件. mv t.txt Document
- 加法Hash: 所謂的加法Hash就是把輸入元素一個一個加起來構成最後的結果
- 位運算Hash: 這種類型的Hash函數經過利用各類位運算,好比移位或者異或來充分的混合輸入元素
- 乘法Hash: 33*hash + key.charAt(i)
- 一致性Hash的設計目標是爲了解決因特網中的熱點(Hot spot)問題,一致性Hash算法提出了在動態變化的Cache環境中,斷定Hash算法好壞的四個定義:
- 平衡性 :Balance
- 單調性 :Monotonicity
- 分散性 :Spread
- 負載 :Load
- get是從服務器獲取信息, post是向服務器傳信息
- get傳送的數據量比較小, post傳遞的數據量能夠比較大
- get的安全性比post低
- TCP: Tranfer Control Protocol, 是一種面向鏈接的保證傳輸協議,在傳輸數據流以前,雙方會創建一條虛擬的通訊道,能夠極少差錯傳輸數據
- UDP: User DataGram Protocol,是一種無鏈接的協議,使用UDP時,每個數據段都是一個獨立的信息,包括完整的源地址和目的地,在網絡上以任何可能的路徑到達目的地.所以,可否到達目的地,以及到達目的地的時間和內容的完整性都不能保證
- TCP比UDP多了創建鏈接的時間.相對UDP而言,TCP具備更高的安全性和可靠性
- TCP協議傳輸的大小不限制,一旦鏈接被創建,雙方能夠按照吧必定的格式傳輸大量的數據,而UDP是一個不可靠協議,大小有限制,每次不能超過64K