2019秋招復習筆記--面試高頻知識點

第一模塊--併發與多線程

Java多線程方法:

實現Runnable接口, 繼承thread類, 使用線程池html

操做系統層面的進程與線程(對JAVA多線程和高併發有了解嗎?)

1.進程java

定義:進程是具備必定獨立功能的程序關於某個數據集合上的一次運行活動, 進程是系統進行資源分配和調度的一個獨立單位mysql

進程的三種基本狀態: linux

1.就緒狀態:除CPU外已分配全部資源,等待得到處理機執行
2.執行狀態:得到處理機,程序正在執行
3.阻塞狀態:因等待而沒法執行,放棄處理機,處於等待狀態。(等待I/O口完成,申請緩衝區不知足,等待信號等)android

2.線程:git

線程是進程的一個實體, 是CPU調度和分派的基本單位,它是比進程更小的能獨立運行的基本單位。程序員

3.進程和線程的區別:web

  1. 進程的內存空間是獨立的,有獨立的地址空間,不容許突破進程邊界的存取其餘進程的內存空間; 線程本身基本上不擁有系統資源,只擁有一點在運行中必不可少的資源(如程序計數器,一組寄存器和棧), 可是它可與同屬一個進程的其餘的線程共享進程所擁有的所有資源。Linux的COW技術能夠使得子線程只有在對數據段有改變行爲的時候才做一個本身的備份。(線程共享同一進程中的內存空間(共享的是進程代碼段、進程的公有數據(利用這些共享的數據,線程很容易的實現相互之間的通信)、進程打開的文件描述符、信號的處理器、進程的當前目錄和進程用戶ID與進程組ID)/(而且每一個線程擁有本身的棧內存))
  2. 線程的執行速度大於進程。

4. 隸屬關係:
線程屬於進程,進程退出時結束全部線程,線程佔用資源少於進程。正則表達式

多個執行:redis

兩個進程不能同時執行 (準確的說是在單個CPU上不能同時跑兩個進程,但多個CPU每一個核心能夠跑多個進程)
多個線程能夠同時執行 (所謂的同時指的是concurrent, 採用時間片輪轉的方式來給各個線程分配執行時間。)
多線程程序只要有一個線程死掉,整個進程也死掉

5. 線程通訊經常使用的幾種方式有:

通常來講由於同一進程下的線程間是共享內存資源,所以共享內存也成爲理所應當的線程通訊的方式。共享內存經常使用的數據結構有LRU和FIFO。

具體對JAVA來講,有以下的工具能夠實現線程通訊:

  • Object類的wait/notify 方法
  • Volatile 關鍵字將線程變量同步到主內存
  • Sychronized機制
  • CountDownLatch 、CyclicBarrier 、Semaphore等JUC下的併發工具

A.Wait/notify機制(JAVA特有);

方法wait()的做用是使當前執行代碼的線程進行等待,wait()方法只能在同步方法中或同步塊中調用,wait()方法執行後,當前線程釋放鎖,線程與其餘線程競爭從新獲取鎖。方法notify()也要在同步方法或同步塊中調用,該方法是用來通知那些可能等待該對象的對象鎖的其餘線程,對其發出通知notify,並使進入就緒態,等待獲取鎖。若是有多個線程等待,則有線程規劃器隨機挑選出一個呈wait狀態的線程。在notify()方法後,當前線程不會立刻釋放該對象鎖,要等到執行notify()方法的線程將程序執行完,也就是退出同步代碼塊中

B. 共享內存(下面是JAVA實現):

若是每一個線程執行的代碼相同,能夠使用同一個Runnable對象,這個Runnable對象中有那個共享數據,例如,賣票系統就能夠這麼作。 

若是每一個線程執行的代碼不一樣,這時候須要用不一樣的Runnable對象,例如,設計4個線程。其中兩個線程每次對j增長1,另外兩個線程對j每次減1,銀行存取款

6.進程的通信方式(本質上是不一樣進程的線程間通訊的方式):5種

  • 無名管道

數據只能在一個方向上流動
用於具備親緣關係的進程之間的通訊
特殊的文件,存在於內存

  • 2.命名管道

能夠在無關的進程之間交換數據
文件形式存在於文件系統

write_fifo的做用相似於客戶端,能夠打開多個客戶端向一個服務器發送請求信息,read_fifo相似於服務器,它適時監控着FIFO的讀端,當有數據時,讀出並進行處理,可是有一個關鍵的問題是,每個客戶端必須預先知道服務器提供的FIFO接口

  • 3.消息隊列

消息的鏈表,存放在內核中。一個消息隊列由一個標識符(即隊列ID)來標識。
消息隊列克服了信號傳遞信息少、管道只能承載無格式字節流以及緩衝區大小受限等缺點。
(消息隊列是面向記錄的,其中的消息具備特定的格式以及特定的優先級。
消息隊列獨立於發送與接收進程。進程終止時,消息隊列及其內容並不會被刪除。
消息隊列能夠實現消息的隨機查詢,消息不必定要以先進先出的次序讀取,也能夠按消息的類型讀取。)

  • 4.信號量

一個計數器,實現進程間的互斥與同步,而不是用於存儲進程間通訊數據。
常做爲一種鎖機制,防止某進程正在訪問共享資源時,其餘進程也訪問該資源。主要做爲進程間以及同一進程內不一樣線程之間的同步手段

  • 5.共享內存

兩個或多個進程共享一個給定的存儲區
共享內存是最快的一種 IPC,由於進程是直接對內存進行存取。
由於多個進程能夠同時操做,因此須要進行同步。
信號量+共享內存一般結合在一塊兒使用,信號量用來同步對共享內存的訪問

五種進程間通信方式總結

1.管道:速度慢,容量有限,只有父子進程能通信 。
2.FIFO文件(即命名管道):任何進程間都能通信,但速度慢 。
3.消息隊列:容量受到系統限制,且要注意第一次讀的時候,要考慮上一次沒有讀完數據的問題 ;信號傳遞信息較管道多。
4.信號量:不能傳遞複雜消息,只能用來同步 。
5.共享內存區:可以很容易控制容量,速度快,但要保持同步,好比一個進程在寫的時候,另外一個進程要注意讀寫的問題,至關於線程中的線程安全,固然,共享內存區一樣能夠用做線程間通信,不過沒這個必要,線程間原本就已經共享了同一進程內的一塊內存。

sleep()方法和wait()方法的區別

線程的資源有很多,但應該包含CPU資源和鎖資源這兩類。

sleep(long mills):讓出CPU資源,可是不會釋放鎖資源。Sleep方法退出CPU時間片的競爭,但鎖住通往某些數據的操做。

wait():讓出CPU資源和鎖資源。

鎖是用來線程同步的,sleep(long mills)雖然讓出了CPU,可是不會讓出鎖,其餘線程能夠利用CPU時間片了,但若是其餘線程要獲取sleep(long mills)擁有的鎖才能執行,則會由於沒法獲取鎖而不能執行,繼續等待。

可是那些沒有和sleep(long mills)競爭鎖的線程,一旦獲得CPU時間片便可運行了。

1. 這兩個方法來自不一樣的類, wait是Object類中的方法, sleep是Thread類中的方法。

2. sleep方法沒有釋放鎖(但釋放了CPU),而wait方法釋放了鎖(也釋放了CPU)。

3. wait,notify和notifyAll只能在同步控制方法或者同步控制塊裏面使用,而sleep能夠在任何地方使用。

4. sleep必須捕獲異常,而wait,notify和notifyAll不須要捕獲異常。

線程的狀態有幾種

Java中的線程的生命週期大致可分爲5種狀態。

1. 新建(NEW):新建立了一個線程對象。

2. 就緒態(RUNNABLE):線程對象建立後,其餘線程(好比main線程)調用了該對象的start()方法。該狀態的線程位於可運行線程池中,等待被線程調度選中,獲取cpu 的使用權 。

3. 運行態(RUNNING):可運行狀態(runnable)的線程得到了cpu 時間片(timeslice) ,執行程序代碼。
4. 阻塞態(BLOCKED):阻塞狀態是指線程由於某種緣由放棄了cpu 使用權,也即讓出了cpu timeslice,暫時中止運行。直到線程進入可運行(runnable)狀態,纔有機會再次得到cpu timeslice 轉到運行(running)狀態。阻塞的狀況分三種: 

(一). 等待阻塞:運行(running)的線程執行o.wait()方法,JVM會把該線程放入等待隊列(waitting queue)中。
(二). 同步阻塞:運行(running)的線程在獲取對象的同步鎖時,若該同步鎖被別的線程佔用,則JVM會把該線程放入鎖池(lock pool)中。
(三). 其餘阻塞:運行(running)的線程執行Thread.sleep(long ms)或t.join()方法,或者發出了I/O請求時,JVM會把該線程置爲阻塞狀態。當sleep()狀態超時、join()等待線程終止或者超時、或者I/O處理完畢時,線程從新轉入可運行(runnable)狀態。

5. 死亡態(DEAD):線程run()、main() 方法執行結束,或者因異常退出了run()方法,則該線程結束生命週期。死亡的線程不可再次復生

 

死鎖產生的緣由及解決方法

死鎖:兩個或兩個以上的進程在執行過程當中,因爲競爭資源或者因爲彼此通訊而形成的一種阻塞的現象,若無外力做用,它們都將沒法推動下去。

注意這裏產生死鎖的能夠是資源競爭,也有多是彼此的通訊等待。

資源競爭: 進程A, B必須同時持有資源1和2才能執行。 某一時刻進程A持有資源1並嘗試搶佔資源2, 進程B持有資源2並嘗試搶佔資源1。這時就形成了死鎖。

通訊等待: 進程A須要收到B的信號才能繼續往下執行(包括向其餘進程發信號),進程B須要收到進程A的信號才能繼續往下執行。這個時候進程A和B就會由於相互等待對方的信號而死鎖。

死鎖產生的四個必要條件

 

1. 互斥條件:線程對資源的佔有是排他性的,一個資源只能被一個線程佔有,直到資源被釋放。

 

2. 請求和保持條件:指進程已經保持至少一個資源,但又提出了新的資源請求,而該資源已被其它進程佔有,此時請求進程阻塞,但又對本身已得到的其它資源保持不放。

 

3. 不可剝奪條件:指進程已得到的資源,在未使用完以前,不能被剝奪,只能在使用完時由本身釋放。

 

4. 環路等待條件:指在發生死鎖時,存在一個進程——資源的環形鏈,即進程集合{P0,P1,P2,···,Pn}中的P0正在等待一個P1佔用的資源;P1正在等待P2佔用的資源,……,Pn正在等待已被P0佔用的資源。
全部 四個條件必須同時成立纔會出現死鎖。循環等待條件意味着佔有並等待條件,這樣四個條件並不徹底獨立。

避免死鎖的方法

首先互斥條件通常是沒法避免,不然也不會出線多線程的問題了。所以能夠從其餘三個條件出發,打破其中之一就能避免死鎖。

1. 破壞「請求和保持」條件

  當某個線程申請兩個及以上的資源時,讓它要麼能一次性申請成功全部資源,要麼就不申請任何資源(釋放申請的部分資源)。

2. 破壞「不可搶佔」條件

  容許進程進行資源搶佔。若是線程在持有部分資源的時候這部分資源發生了搶佔,則容許這部分資源被搶佔。更好的方法是容許操做系統搶佔資源,讓優先級大的線程能夠搶佔其餘線程佔有但未使用的資源。

3. 破壞「循環等待」條件

  a. 將系統中的全部資源統一編號,進程可在任什麼時候刻提出資源申請,但全部申請必須按照資源的編號順序提出

當多個線程須要相同的一些鎖,可是按照不一樣的順序加鎖,死鎖就很容易發生。

若是能確保全部的線程都是按照相同的順序得到鎖,那麼死鎖就不會發生。

若是一個線程(好比線程3)須要一些鎖,那麼它必須按照肯定的順序獲取鎖。它只有得到了從順序上排在前面的鎖以後,才能獲取後面的鎖。

例如,線程2和線程3只有在獲取了鎖A以後才能嘗試獲取鎖C (獲取鎖A是獲取鎖C的必要條件)。由於線程1已經擁有了鎖A,因此線程2和3須要一直等到鎖A被釋放。而後在它們嘗試對B或C加鎖以前,必須成功地對A加了鎖。

b. 爲線程設置加鎖時限

  在嘗試獲取鎖的時候加一個超時時間,在嘗試獲取鎖的過程當中若超過了這個時限該線程則放棄對該鎖請求。若一個線程沒有在給定的時限內成功得到全部須要的鎖,則會進行回退並釋放全部已經得到的鎖,而後等待一段隨機的時間再重試。這段隨機的等待時間讓其它線程有機會嘗試獲取相同的這些鎖。

4. 死鎖檢測及處理

  死鎖檢測是一個更好的死鎖預防機制,它主要是針對那些不可能實現按序加鎖、不可設置鎖超時、資源不可搶佔、破壞請求保持條件的時間效率不高(若是一次性申請到全部的資源的可能性不大,則會出現頻繁的資源申請、資源釋放操做)。

死鎖檢測的方法

主要須要爲全部的線程及資源創建一個資源分配圖,根據資源分配圖,如過資源分配圖沒有環,則系統沒有發生死鎖。(判斷圖是否成環使用拓撲排序算法)

檢測到死鎖了怎麼辦: 

1. 一個可行的作法是釋放全部鎖,回退,而且等待一段隨機的時間後重試雖然有回退和等待,可是若是有大量的線程競爭同一批鎖,它們仍是會重複地死鎖(緣由同超時相似,不能從根本上減輕競爭)。

2. 一個更好的方案是隨機選取若干個線程而不是所有線程,讓這些線程回退,剩下的線程就像沒發生死鎖同樣繼續保持着它們須要的鎖。

 

 

競態條件 & 臨界區

 

當兩個線程競爭同一資源時,若是對資源的訪問順序敏感,就稱存在競態條件。致使競態條件發生的代碼區稱做臨界區。在臨界區中使用適當的同步就能夠避免競態條件。

 如何開啓一個新線程

繼承Thread, 實現Runnable, 實現Callable(使用線程池, ExecutorService)

說一下線程池

A. 使用線程池的緣由

    • 利用線程池管理並複用線程、控制最大併發數等,節約資源,頻繁的建立銷燬線程對系統產生很大的壓力。
    • 實現任務線程隊列緩存策略和拒絕機制
    • 實現某些與時間相關的功能,如定時執行、週期執行等。
    • 隔離線程環境。好比,交易服務和搜索服務在同一臺服務器上,分別開啓兩個線程池,交易線程的資源消耗明顯要大;所以,經過配置獨立的線程池,將較慢的交易服務與搜索服務隔離開,避免各服務線程相互影響。

B. 建立線程池須要使用 ThreadPoolExecutor 類

該類的幾個核心的參數有:

corePoolSize表示常駐核心線程數。若是等於0,則任務執行完以後,沒有任何請求進入時銷燬線程池的線程;若是大於0,即便本地任務執行完畢,核心線程也不會被銷燬。這個值的設置很是關鍵,設置過大會浪費資源,設置太小會致使線程頻繁地建立或銷燬。(初始化的時候線程池中並非直接建立corePoolSize個線程,而是根據須要來建立。只不過在事務完成後向線程池交還線程的時候,若是線程池中線程的數量<=coolPoolSize, 則空閒的線程不會被消除,而是常駐在線程池中等待被取用。)

maximumPoolSize表示線程池可以容納同時執行的最大線程數。必須大於或等於1。若是待執行的線程數大於此值,須要藉助workQueue參數的幫助,緩存在隊列中。若是maximumPoolSize與corePoolSize相等,便是固定大小線程池。

keepAliveTime表示線程池中的線程空閒時間,當空閒時間達到keepAliveTime值時,線程會被銷燬,直到只剩下corePoolSize個線程爲止,避免浪費內存和句柄資源。在默認狀況下,當線程池的線程數大於corePoolSize時,keepAliveTime 纔會起做用。可是當ThreadPoolExecutor的allowCore Thread TimeOut變量設置爲true時,核心線程超時後也會被回收。

TimeUnit: 表示時間單位。keepAliveTime的時間單位一般是TimeUnit.SECONDS。

workQueue表示緩存隊列。當請求的線程數大於corePoolSize時,線程進入BlockingQee阻塞隊列。後續示例代碼中使用的LinkedBlockingQueue是單向鏈表,使用鎖來控制入隊和出隊的原子性,兩個鎖分別控制元素的添加和獲取,是一個生產消費模型隊列

threadFactory表示線程工廠。它用來生產一組相同任務的線程。線程池的命名是經過給這個ctoy增長組名前級來實現的。在虛擬機棧分析時,就能夠知道線程任務是由哪一個線程工廠產生的。

handler:表示執行拒絕策路的對象。當workQueue參數的任務緩有區到達上限後,而且活動線程數大於maximumPoolSize的時候,線程池經過該策略處理請求,這是一種簡單的限流保護。

handler:表示當拒絕處理任務時的策略,有如下四種取值:

ThreadPoolExecutor.AbortPolicy:丟棄任務並拋出RejectedExecutionException異常。 
ThreadPoolExecutor.DiscardPolicy:也是丟棄任務,可是不拋出異常。 
ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊列最前面的任務,而後從新嘗試執行任務(重複此過程)
ThreadPoolExecutor.CallerRunsPolicy:由調用線程處理該任務

C. JDK 爲咱們內置了五種常見線程池的實現

    1. 一個單線程的線程池。這個線程池只有一個線程在工做,也就是至關於單線程串行執行全部任務。此線程池保證全部任務的執行順序按照任務的提交順序執行。
    2. FixedThreadPool 的核心線程數和最大線程數都是指定值,也就是說當線程池中的線程數超過核心線程數後,任務都會被放到阻塞隊列中。
    3. CachedThreadPool 沒有核心線程,非核心線程數無上限,也就是所有使用外包,可是每一個外包空閒的時間只有 60 秒,超事後就會被回收。 
    4. ScheduledThreadPool 此線程池支持定時以及週期性執行任務的需求。

D. 講一下自定義線程池的工做流程

根據ThreadPoolExecutor源碼, 當試圖經過excute方法將一個Runnable任務添加到線程池中時,按照以下順序來處理:

    一、若是線程池中的線程數量少於corePoolSize,即便線程池中有空閒線程,也會建立一個新的線程來執行新添加的任務;

    二、若是線程池中的線程數量大於等於corePoolSize,但緩衝隊列workQueue未滿,則將新添加的任務放到workQueue中,按照FIFO的原則依次等待執行(線程池中有線程空閒出來後依次將緩衝隊列中的任務交付給空閒的線程執行);

   三、若是線程池中的線程數量大於等於corePoolSize,且緩衝隊列workQueue已滿,但線程池中的線程數量小於maximumPoolSize,則會建立新的線程來處理被添加的任務;

  四、若是線程池中的線程數量等於了maximumPoolSize,觸發拒絕策略。

    總結起來,也便是說,當有新的任務要處理時,先看線程池中的線程數量是否大於corePoolSize,再看緩衝隊列workQueue是否滿,最後看線程池中的線程數量是否大於maximumPoolSize。

    另外,當線程池中的線程數量大於corePoolSize時,若是裏面有線程的空閒時間超過了keepAliveTime,就將其移除線程池,這樣,能夠動態地調整線程池中線程的數量。

E. Executor 、ExecutorService 與 Executors 的區別與聯繫

    1. ExecutorService 接口繼承了 Executor 接口,是 Executor 的子接口
    2. Executor 接口定義了 execute()方法用來接收一個Runnable接口的對象;而 ExecutorService 接口中的 submit()方法能夠接受RunnableCallable接口的對象。
    3. Executor 中的 execute() 方法不返回任何結果,而 ExecutorService 中的 submit()方法能夠經過一個 Future 對象返回運算結果。
    4. 除了容許客戶端提交一個任務,ExecutorService 還提供用來控制線程池的方法。好比:調用 shutDown() 方法終止線程池。
    5. Executors 類提供五個靜態工廠方法用來建立不一樣類型的線程池。

 F.  任務排隊策略(WorkQueue)(存疑)

一、直接提交。緩衝隊列採用 SynchronousQueue,它將任務直接交給線程處理而不保持它們。若是不存在可用於當即運行任務的線程(即線程池中的線程都在工做),則試圖把任務加入緩衝隊列將會失敗,所以會構造一個新的線程來處理新添加的任務,並將其加入到線程池中。直接提交一般要求無界 maximumPoolSizes(Integer.MAX_VALUE) 以免拒絕新提交的任務。newCachedThreadPool採用的即是這種策略。

 二、無界隊列。使用無界隊列(典型的即是採用預約義容量的 LinkedBlockingQueue,理論上是該緩衝隊列能夠對無限多的任務排隊)將致使在全部 corePoolSize 線程都工做的狀況下將新任務加入到緩衝隊列中。這樣,建立的線程就不會超過 corePoolSize,也所以,maximumPoolSize 的值也就無效了。當每一個任務徹底獨立於其餘任務,即任務執行互不影響時,適合於使用無界隊列。newFixedThreadPool採用的即是這種策略。

三、有界隊列。當使用有限的 maximumPoolSizes 時,有界隊列(通常緩衝隊列使用ArrayBlockingQueue,並制定隊列的最大長度)有助於防止資源耗盡,可是可能較難調整和控制,隊列大小和最大池大小須要相互折衷,須要設定合理的參數。

E. Callable 與 Runnable

1. Runnable是一個接口,在它裏面只聲明瞭一個run()方法:

public interface Runnable { public abstract void run(); }

run()方法返回值爲void類型,因此在執行完任務以後沒法返回任何結果

2. Callable位於java.util.concurrent包下,它也是一個接口,在它裏面也只聲明瞭一個方法,只不過這個方法叫作call()

 

public interface Callable<V> { V call() throws Exception; }

 

這是一個泛型接口,該接口聲明瞭一個名稱爲call()的方法,同時這個方法能夠有返回值V,也能夠拋出異常。call()方法返回的類型就是傳遞進來的V類型

3. 如何使用Callable

通常狀況下配合ExecutorService來使用callable,在ExecutorService接口中聲明瞭若干個submit方法的重載版本:

<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);

  

第一個方法:submit提交一個實現Callable接口的任務,而且返回封裝了異步計算結果的Future

第二個方法:submit提交一個實現Runnable接口的任務,而且指定了在調用Future的get方法時返回的result對象。

第三個方法:submit提交一個實現Runnable接口的任務,而且返回封裝了異步計算結果的Future。

所以咱們只要建立好咱們的線程對象(實現Callable接口或者Runnable接口),而後經過上面3個方法提交給線程池去執行便可。

 Future就是對於具體的Runnable或者Callable任務的執行結果進行取消、查詢是否完成、獲取結果。必要時能夠經過get方法獲取執行結果,該方法會阻塞直到任務返回結果

F. Future

Future就是對於具體的Runnable或者Callable任務的執行結果進行取消、查詢是否完成、獲取結果。必要時能夠經過get方法獲取執行結果,get方法會阻塞直到任務返回結果

Future接口定義了下面5種方法:

  • cancel方法用來取消任務
  • isCancelled方法表示任務是否被取消成功
  • isDone方法表示任務是否已經完成
  • get()方法用來獲取執行結果,這個方法會產生阻塞,會一直等到任務執行完畢才返回
  • get(long timeout, TimeUnit unit)用來獲取執行結果,若是在指定時間內,還沒獲取到結果,就直接返回null。

也就是說Future提供了三種功能:

1)判斷任務是否完成;

2)可以中斷任務;

3)可以獲取任務執行結果。

由於Future只是一個接口,因此是沒法直接用來建立對象使用的,FutureTask是Future接口的一個惟一實現類。

 

Java併發包(JUC)相關

併發包主要分紅如下幾個類族:

  • 線程同步類  逐步淘汰了使用Object的wait()和notify()的同步方式,主要表明爲CountDownLatch, Semaphore,CyclicBarrier等
  • 併發集合類  最著名的是ConcurrentHashMap, 由剛開始的分段鎖到後來的CAS,不斷提高併發性能,還有ConcurrentSkipListMap, CopyOnWriteArrayList, BlockingQueue等
  • 線程管理類  線程池,如使用Executors靜態工廠或者使用ThreadPoolExecutor等,另外,使用ScheduledExecutorService來執行定時任務
  • 鎖相關類 以Lock接口爲核心,派生出一些類,最有名的是ReentrantLock

併發包中的鎖類

Lock是JUC包的頂層接口,它的實現邏輯並未用到synchronized, 而是利用了volatile的可見性和CAS

ReentrantLock對於Lock接口的實現主要依賴了Sync,而Sync繼承了AbstractQueuedSynchronizer(AQS),它是JUC包實現同步的基礎工具。在AQS中,定義了一個volatile int state變量做爲共享資源,若是線程獲取資源失敗,則進入同步FIFO隊列中等待;若是成功獲取資源就執行臨界區代碼。執行完釋放資源時,會通知同步隊列中的等待線程來獲取資源後出隊並執行。

AQS是抽象類,內置自旋鎖實現的同步隊列,封裝入隊和出隊的操做,提供獨佔、共享、中斷等特性的方法。AQS的子類能夠定義不一樣的資源實現不一樣性質的方法。

  1. 好比可重入鎖ReentrantLock,定義state爲0時能夠獲取資源並置爲1。若已得到資源,state 不斷加1,在釋放資源時state減1,直至爲0;
  2. CountDownLatch初始時定義了資源總量state=count,countDown()不斷將state減1,當state=0時才能得到鎖,釋放後state就一直爲0。全部線程調用await()都不會等待,因此CountDownLatch是一次性的,用完後若是再想用就只能從新建立一個;若是但願循環使用,推薦使用基於RentrantLock 實現的CyclicBarrier
  3. Semaphore 與CountDownLatch略有不一樣,一樣也是定義了資源總量state=permits,當state>0時就能得到鎖,並將state減1,當state=0時只能等待其餘線程釋放鎖,當釋放鎖時state加1,其餘等待線程又能得到這個鎖。

當Semphore的permits定義爲1時,就是互斥鎖,當permits>1就是共享鎖。總之,ReentrantLock, CountDownLatch, CyclicBarrier,Semaphore等工具都是基於AQS的不一樣配置來實現的。

自旋鎖是一種互斥鎖的實現方式而已,相比通常的互斥鎖會在等待期間放棄cpu,自旋鎖(spinlock)則是不斷循環並測試鎖的狀態,這樣就一直佔着cpu。與互斥量相似,它不是經過休眠使進程阻塞,而是在獲取鎖以前一直處於忙等(自旋)阻塞狀態。用在如下狀況:鎖持有的時間短,並且線程並不但願在從新調度上花太多的成本。"原地打轉"。

互斥鎖:用於保護臨界區,確保同一時間只有一個線程訪問數據。對共享資源的訪問,先對互斥量進行加鎖,若是互斥量已經上鎖,調用線程會阻塞,直到互斥量被解鎖。在完成了對共享資源的訪問後,要對互斥量進行解鎖。

 sychronized關鍵字與lock的區別

Volatile關鍵字

每一個線程都有獨佔的內存區域,如操做棧、本地變量表等。線程本地內存保存了引用變量在堆內存中的副本,線程對變量的全部操做都在本地內存區域中進行,執行結束後再同步到堆內存中去。這裏必然有一個時間差,在這個時間差內,該線程對副本的操做,對於其餘線程都是不可見的。
volatile的英文本義是「揮發、不穩定的」,延伸意義爲敏感的。當使用volatile修飾變量時,意味着任何對此變量的操做都會在內存中進行,不會產生副本,以保證共享變量的可見性,局部阻止了指令重排的發生。(從JDK5開始java加強了volatile的內存語義,除了線程讀取volatile變量要從主內存獲取和線程寫volatile變量要及時刷回到主內存之外,JDK5開始還嚴格限制編譯器和處理器對volatile變量和普通變量的重排序,從而確保volatiled的寫-讀和鎖的釋放-獲取具備相同的語義)

鎖也能夠確保變量的可見性,可是實現方式和volatile略有不一樣線程在獲得鎖時讀入副本,釋放時寫回內存
volatile解決的是多線程共享變量的可見性問題,相似於synchronized,但不具有synchronized的互斥性

由於全部的操做同須要同步給內存,所以volatile必定使線程的執行速度變慢

volatile變量與鎖的區別:

因爲volatile僅僅保證對對單個volatile變量的讀、寫具備原子性(複合操做不保證原子性如volatie i, i++),而鎖的互斥執行的特性能夠確保對整個臨界區代碼的執行具備原子性。在功能上,鎖比volatile更強大,在可伸縮性上和執行性能上,volatile更有優點。

信號量同步

信號量同步是指在不一樣的線程之間,經過傳遞同步信號量來協調線程執行的前後次序

CountDownLatch是基於執行時間的同步類。在實際編碼中,可能須要處理基於空閒信號的同步狀況。好比海關安檢的場景,任何國家公民在出國時,都要走海關的查驗通道。假設某機場的海關通道共有3個窗口,一批須要出關的人排成長隊,每一個人都是一個線程。當3個窗口中的任意一個出現空閒時,工做人員指示隊列中第一我的出隊到該空閒窗口接受查驗。對於上述場景,JDK中提供了一個Semaphore的信號同步類,只有在調用Semaphore對象的acquire()成功後,才能夠往下執行,完成後執行release()釋放持有的信號量,下一個線程就能夠立刻獲取這個空閒信號量進入執行

 

一、CountDownLatch end = new CountDownLatch(N); //構造對象時候 須要傳入參數N

 

  二、end.await()  可以阻塞線程 直到調用N次end.countDown() 方法才釋放線程

 

  三、end.countDown() 能夠在多個線程中調用  計算調用次數是全部線程調用次數的總和

 

還有其餘同步方式,如CyclicBarrier是基於同步到達某個點的信號量觸發機制。CyclicBarrier從命名上便可知道它是一個能夠循環使用(Cyclic)的屏障式(Barrier)多線程協做方式。採用這種方式進行剛纔的安檢服務,就是3我的同時進去,只有3我的都完成安檢,纔會放下一批進來。這是一種很是低效的安檢方式。但在某種場景下就是很是正確的方式,假設在機場排隊打車時,現場工做人員統一指揮,每次放3輛車進來,坐滿後開走,再放下一批車和人進來。經過CyclicBarrier的reset)來釋放線程資源。

 

ThreadLocal 變量

ThreadLocal是用來維護線程中的變量不被其餘線程干擾而出現的一個結構,內部包含一個ThreadLocalMap類,該類爲Thread類的一個局部變量,該Map存儲的key爲ThreadLocal對象所在線程對象,value爲咱們要存儲的對象,這樣一來,在不一樣線程中,持有的其實都是當前線程的變量副本,與其餘線程徹底隔離,以此來保證線程執行過程當中不受其餘線程的影響。利用ThreadLocal能夠實現讓全部的線程持有初始值相同的一個變量副本,但在初始化之後每一個線程都只能操做本身的那個變量副本,不一樣線程之間的變量副本互不干擾。

ThreadLocal可能會形成內存泄漏的問題:

ThreadLocal中的鍵值對中的鍵是一個弱引用,那麼在內存回收的時候,這個鍵極可能會被回收掉,而後鍵沒了,就沒法找到value的值,形成了內存泄漏;

咱們看下set方法的實現:

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

能夠看到,先經過Thread.currentThread()方法獲取到了當前線程,而後若是取不到map對象,就會建立,下面看下create方法

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

很簡單的方法,就是新建一個ThreadLocal的Map,這個map以ThreadLocal自身爲key,以咱們要設值的對象爲value,建立出來map以後,將對象賦值到線程的局部變量去。
看到這裏,就知道ThreadLocal主要目的就是將變量設值到當前的線程上,以此來保證線程安全。
那麼下面看下get方法:

public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
    ThreadLocalMap.Entry e = map.getEntry(this);
    if (e != null) {
        @SuppressWarnings("unchecked")
        T result = (T)e.value;
        return result;
    }
    }
    return setInitialValue();
}

能夠看出來,get方法就是拿的當前線程的局部變量threadLocals,而後從中取出map中存儲的對象,這樣每一個線程中獲取的必定是本身線程中存儲的對象了。
由上述分析可知,使用ThreadLocal是能夠保證線程安全的。
ThreadLocal其實是爲解決多線程程序的併發問題提供了一種新的思路(區別與sychronized關鍵字)。
ThreadLocal這個類提供線程本地的變量。這些變量與通常正常的變量不一樣,它們在每一個線程中都是獨立的。ThreadLocal實例最典型的運用就是在類的私有靜態變量中定義,並與線程關聯。 

而且ThreadLocal不會產生內存泄漏的問題
原文:https://blog.csdn.net/caoyishuai100/article/details/68946037

詳細解釋

Java引用類型和GC時機

對象在堆上建立以後所持有的用基實是一種變量類型,引用之間能夠經過賦值構成一條引用鏈。從GC Roots開始遍歷,判斷引用是否可達。引用的可達性是判斷可否被垃圾回收的基本條件。JMM會根據此自動管理內存的分配與回收,不須要開發工程師干預。但在某些場景下,即便引用可達,也但願可以根據語義的強弱進行有選擇的回收,以保證系統的正常運行。根據引用類型語義的強弱來決定垃圾回收的階段,咱們能夠把引用分爲強引用、軟引用、弱引用和虛引用四類。後三類引用,本質上是可讓開發工程師經過代碼方式來決定對象的垃圾回收時機。

  1. 強引用,即Strong Reference,最爲常見。如Object object=new Objecto);這樣的變量聲明和定義就會產生對該對象的強引用。只要對象有強引用指向,而且GC Roots可達,那麼Java內存回收時,即便瀕臨內存耗盡,也不會回收該對象。
  2. 軟引用,即Soft Reference,引用力度弱於「強引用」,是用在非必需對象的場景。在即將OMM以前,垃圾回收器會把這些軟引用指向的對象加入回收範圍,以得到更多的內存空間,讓程序可以繼續健康運行。主要用來緩存服務器中間計算結果及不須要實時保存的用戶行爲等。
  3. 弱引用,即Weak Reference,引用強度較前二者更弱,也是用來描述非必需對象的。若是弱引用指向的對象只存在弱引用這一條線路,則在下一次YGC時會被回收。因爲YGC時間的不肯定性,弱引用什麼時候被回收也具備不肯定性。弱引用主要用於指向某個易消失的對象,在強引用斷開後,此引用不會劫持對象。
  4. 虛引用,即Phantom Reference,是極弱的一種引用關係,定義完成後,就沒法經過該引用獲取指向的對象。爲一個對象設置虛引用的惟一目的就是但願能在這個對象被回收時收到一個系統通知。虛引用必須與引用隊列聯合使用,當垃圾回收時,若是發現存在虛引用,就會在回收對象內存前,把這個虛引用加入與之關聯的引用隊列中。

Static變量是線程安全的嗎?

 

JAVA對象鎖和方法鎖的區別

首先的明白Java中鎖的機制 synchronized 

在修飾代碼塊的時候須要一個reference對象做爲鎖的對象. 
在修飾方法的時候默認是當前對象做爲鎖的對象. 
在修飾類時候默認是當前類的Class對象做爲鎖的對象.

方法鎖(synchronized修飾方法時)

經過在方法聲明中加入 synchronized關鍵字來聲明 synchronized 方法。

synchronized 方法控制對類成員變量的訪問: 
每一個類實例對應一把鎖,每一個 synchronized 方法都必須得到調用該方法的類實例的鎖方能執行,不然所屬線程阻塞,方法一旦執行,就獨佔該鎖,直到從該方法返回時纔將鎖釋放,此後被阻塞的線程方能得到該鎖,從新進入可執行狀態。這種機制確保了同一時刻對於每個類實例,其全部聲明爲 synchronized 的成員函數中至多隻有一個處於可執行狀態,從而有效避免了類成員變量的訪問衝突。

對象鎖(synchronized修飾方法或代碼塊)

  當一個對象中有synchronized methodsynchronized block的時候調用此對象的同步方法或進入其同步區域時,就必須先得到對象鎖。若是此對象的對象鎖已被其餘調用者佔用,則須要等待此鎖被釋放。(方法鎖也是對象鎖)       

  java的全部對象都含有1個互斥鎖,這個鎖由JVM自動獲取和釋放。線程進入synchronized方法的時候獲取該對象的鎖,固然若是已經有線程獲取了這個對象的鎖,那麼當前線程會等待;synchronized方法正常返回或者拋異常而終止,JVM會自動釋放對象鎖。這裏也體現了用synchronized來加鎖的1個好處,方法拋異常的時候,鎖仍然能夠由JVM來自動釋放。 

類鎖(synchronized 修飾靜態的方法或代碼塊)

  因爲一個class不論被實例化多少次,其中的靜態方法和靜態變量在內存中都只有一份。因此,一旦一個靜態的方法被申明爲synchronized。此類全部的實例化對象在調用此方法,共用同一把鎖,咱們稱之爲類鎖。  

對象鎖是用來控制實例方法之間的同步,類鎖是用來控制靜態方法(或靜態變量互斥體)之間的同步。

總結:

1.類鎖是對靜態方法使用synchronized關鍵字後,不管是多線程訪問單個對象仍是多個對象的sychronized塊,都是同步的。

2.對象鎖是實例方法使用synchronized關鍵字後,若是是多個線程訪問同個對象的sychronized塊,纔是同步的,可是訪問不一樣對象的話就是不一樣步的。

3.類鎖和對象鎖是兩種不一樣的鎖,能夠同時使用,可是注意類鎖不要嵌套使用,這樣子容易發生死鎖。

 

類鎖和對象鎖區別

  • 類鎖全部對象一把鎖
  • 對象鎖一個對象一把鎖,多個對象多把鎖

同步是對同一把鎖而言的,同步這個概念是在多個線程爭奪同一把鎖的時候才能實現的,若是多個線程爭奪不一樣的鎖,那多個線程是不能同步的

  • 兩個線程一個取對象鎖,一個取類鎖,則不能同步
  • 兩個線程一個取a對象鎖,一個取b對象鎖,則不能同步

Sychronized方法加在方法上和加在代碼塊上有什麼區別?

           synchronize修飾方法的鎖對象只能是this當前對象

      synchronize修飾代碼塊能夠修改鎖對象(能夠是this對象,也能夠自行指定)

使用this對象鎖的好處:

這個鎖由JVM自動獲取和釋放。線程進入synchronized方法的時候獲取該對象的鎖,固然若是已經有線程獲取了這個對象的鎖,那麼當前線程會等待;synchronized方法正常返回或者拋異常而終止,JVM會自動釋放對象鎖。這裏也體現了用synchronized來加鎖的1個好處,方法拋異常的時候,鎖仍然能夠由JVM來自動釋放。

壞處:

鎖的粒度大,代碼效率下降;

synchronized的缺陷:當某個線程進入同步方法得到對象鎖,那麼其餘線程訪問這個對象的其他的同步方法時,也必須等待或者阻塞,這對高併發的系統是致命的,這很容易致使系統的崩潰。若是某個線程在同步方法裏面發生了死循環,那麼它就永遠不會釋放這個對象鎖,那麼其餘線程就要永遠的等待。這是一個致命的問題。

固然同步方法和同步代碼塊都會有這樣的缺陷,只要用了synchronized關鍵字就會有這樣的風險和缺陷。既然避免不了這種缺陷,那麼就應該將風險降到最低。這也是同步代碼塊在某種狀況下要優於同步方法的方面。例如在某個類的方法裏面:這個類裏面聲明瞭一個對象實例,SynObject so=new SynObject();在某個方法裏面調用了這個實例的方法so.testsy();可是調用這個方法須要進行同步,不能同時有多個線程同時執行調用這個方法。

這時若是直接用synchronized修飾調用了so.testsy();代碼的方法,那麼當某個線程進入了這個方法以後,這個對象其餘同步方法都不能給其餘線程訪問了。假如這個方法須要執行的時間很長,那麼其餘線程會一直阻塞,影響到系統的性能。

若是這時用synchronized來修飾代碼塊:synchronized(so){so.testsy();},那麼這個方法加鎖的對象是so這個對象,跟執行這行代碼的對象沒有關係,當一個線程執行這個方法時,這對其餘同步方法時沒有影響的,由於他們持有的鎖都徹底不同。

 

不過這裏還有一種特例,就是上面演示的第一個例子,對象鎖synchronized同時修飾方法和代碼塊,這時也能夠體現到同步代碼塊的優越性,若是test1方法同步代碼塊後面有很是多沒有同步的代碼,並且有一個100000的循環,這致使test1方法會執行時間很是長,那麼若是直接用synchronized修飾方法,那麼在方法沒執行完以前,其餘線程是不能夠訪問test2方法的,可是若是用了同步代碼塊,那麼當退出代碼塊時就已經釋放了對象鎖,當線程還在執行test1的那個100000的循環時,其餘線程就已經能夠訪問test2方法了。這就讓阻塞的機會或者線程更少。讓系統的性能更優越。

Synchronized 鎖的升級(膨脹)

Java1.6 中爲了減小得到鎖和釋放鎖帶來的性能消耗而引入的偏向鎖和輕量級鎖。鎖是按順序膨脹的,且鎖的膨脹是不可逆的。

偏向鎖:大多數狀況下,鎖不只不存在多線程競爭,並且老是由同一線程屢次得到,爲了讓線程得到鎖的代價更低而引入了偏向鎖。當一個線程A訪問加了同步鎖的代碼塊時,會在對象頭中存儲當前線程的id,後續這個線程進入和退出這段加了同步鎖的代碼塊時,不須要再次加鎖和釋放鎖。

輕量級鎖:在偏向鎖狀況下,若是線程B也訪問了同步代碼塊,比較對象頭的線程id不同,會升級爲輕量級鎖,而且經過自旋的方式來獲取輕量級鎖。

重量級鎖:若是線程A和線程B同時訪問同步代碼塊,則輕量級鎖會升級爲重量級鎖,線程A獲取到重量級鎖的狀況下,線程B只能入隊等待,進入BLOCK狀態。

多線程併發問題的技術選擇

  1. 當只有一個線程寫,其它線程都是讀的時候,能夠用 volatile 修飾變量
  2. 當多個線程寫,那麼通常狀況下併發不嚴重的話能夠用 Synchronized ,Synchronized並非一開始就是重量級鎖,在併發不嚴重的時候,好比只有一個線程訪問的時候,是偏向鎖;當多個線程訪問,但不是同時訪問,這時候鎖升級爲輕量級鎖;當多個線程同時訪問,這時候升級爲重量級鎖。因此在併發不是很嚴重的狀況下,使用Synchronized是能夠的。不過Synchronized有侷限性,好比不能設置鎖超時,不能經過代碼釋放鎖。
  3. ReentranLock 能夠經過代碼釋放鎖,能夠設置鎖超時。
  4. 高併發下,Synchronized、ReentranLock 效率低,由於同一時刻只有一個線程能進入同步代碼塊,若是同時有不少線程訪問,那麼其它線程就都在等待鎖。這個時候能夠使用併發包下的數據結構,例如 ConcurrentHashMap , LinkBlockingQueue ,以及原子性的數據結構如: AtomicInteger 。

volatile和synchronized特色

樂觀鎖和悲觀鎖

悲觀鎖
老是假設最壞的狀況,每次去拿數據的時候都認爲別人會修改,因此每次在拿數據的時候都會上鎖,這樣別人想拿這個數據就會阻塞直到它拿到鎖(共享資源每次只給一個線程使用,其它線程阻塞,用完後再把資源轉讓給其它線程)。傳統的關係型數據庫裏邊就用到了不少這種鎖機制,好比行鎖,表鎖等,讀鎖,寫鎖等,都是在作操做以前先上鎖。Java中synchronized和ReentrantLock等獨佔鎖就是悲觀鎖思想的實現。

樂觀鎖
老是假設最好的狀況,每次去拿數據的時候都認爲別人不會修改,因此不會上鎖,可是在更新的時候會判斷一下在此期間別人有沒有去更新這個數據,能夠使用版本號機制和CAS算法實現。樂觀鎖適用於多讀的應用類型,這樣能夠提升吞吐量,像數據庫提供的相似於write_condition機制,其實都是提供的樂觀鎖。在Java中java.util.concurrent.atomic包下面的原子變量類就是使用了樂觀鎖的一種實現方式CAS實現的。

CAS:

Compare and Swap(CAS)

CAS是項樂觀鎖技術,當多個線程嘗試使用CAS同時更新同一個變量時,只有其中一個線程能更新變量的值,而其它線程都失敗,失敗的線程並不會被掛起,而是被告知此次競爭中失敗,並能夠再次嘗試。CAS是一種非阻塞式的同步方式。

CAS 操做包含三個操做數 —— 內存位置(V)、預期原值(A)和新值(B)。若是內存位置的值與預期原值相匹配,那麼處理器會自動將該位置值更新爲新值。不然,處理器不作任何操做。不管哪一種狀況,它都會在 CAS 指令以前返回該位置的值。(在 CAS 的一些特殊狀況下將僅返回 CAS 是否成功,而不提取當前值。)CAS 有效地說明了「我認爲位置 V 應該包含值 A;若是包含該值,則將 B 放到這個位置;不然,不要更改該位置,只告訴我這個位置如今的值便可。」這其實和樂觀鎖的衝突檢查+數據更新的原理是同樣的。

兩種鎖的使用場景
從上面對兩種鎖的介紹,咱們知道兩種鎖各有優缺點,不可認爲一種好於另外一種,像樂觀鎖適用於寫比較少的狀況下(多讀場景),即衝突真的不多發生的時候,這樣能夠省去了鎖的開銷,加大了系統的整個吞吐量。但若是是多寫的狀況,通常會常常產生衝突,這就會致使上層應用會不斷的進行retry,這樣反卻是下降了性能,因此通常多寫的場景下用悲觀鎖就比較合適。

Java常見鎖

  • 公平鎖/非公平鎖
  • 可重入鎖
  • 獨享鎖/共享鎖
  • 互斥鎖/讀寫鎖
  • 樂觀鎖/悲觀鎖
  • 分段鎖
  • 偏向鎖/輕量級鎖/重量級鎖
  • 自旋鎖

重入鎖與不可重入鎖的區別

重入鎖是可重複得到資源的鎖,已經得到鎖的線程能夠對當前的資源重入加鎖而不會引發阻塞;不可重入鎖是不可重複得到資源的鎖,當已經得到鎖的線程對當前資源再次加鎖時,會把本身阻塞。這樣作的好處是能夠防止死鎖。

可重入性:

從名字上理解,ReenTrantLock的字面意思就是再進入的鎖,其實synchronized關鍵字所使用的鎖也是可重入的,二者關於這個的區別不大。同一個線程每進入一次,鎖的計數器都自增1,因此要等到鎖的計數器降低爲0時才能釋放鎖。

可重入鎖的設計思路

輕鬆學習java可重入鎖(ReentrantLock)的實現原理

第二模塊--計算機網路

TCP/IP結構有幾層

TCP/IP參考模型

分爲四個層次:應用層、傳輸層、網絡互連層和主機到網絡層。常見的協議以下:

基於UDP的常見的還有DNS、、RIP(路由選擇協議)、DHCP 動態主機設置協議

在TCP/IP參考模型中,去掉了OSI參考模型中的會話層和表示層(這兩層的功能被合併到應用層實現)。同時將OSI參考模型中的數據鏈路層和物理層合併爲主機到網絡層。

TCP/IP七層參考模型

物理層(Physical Layer)
數據鏈路層(Datalink Layer)
網絡層(Network Layer)
傳輸層(Transport Layer)
會話層(Session Layer)
表示層(Presentation Layer)
應用層(Application Layer)

經常使用網絡硬件設備分別工做在哪一層

1.網絡層:路由器、防火牆
2.數據鏈路層:網卡、網橋、交換機
3.物理層:中繼器、集線器

路由器Router),是鏈接因特網中各局域網廣域網的設備,它會根據信道的狀況自動選擇和設定路由,以最佳路徑,按先後順序發送信號。路由和交換機之間的主要區別就是交換機發生在OSI參考模型第二層(數據鏈路層),而路由發生在第三層,即網絡層。這一區別決定了路由和交換機在移動信息的過程當中需使用不一樣的控制信息,因此說二者實現各自功能的方式是不一樣的。

解釋TCP/IP 的三次握手

TCP創建鏈接的三次握手過程

TCP會話經過三次握手來初始化。三次握手的目標是使數據段的發送和接收同步。同時也向其餘主機代表其一次可接收的數據量(窗口大小),並創建邏輯鏈接。這三次握手的過程能夠簡述以下:

●源主機發送一個同步標誌位(SYN)置1的TCP數據段, 此段中同時包含一個初始值隨機的客戶端序列號,ClientISN;

●目標主機發回確認數據段,此段中的同步標誌位(SYN)一樣被置1,且確認標誌位(ACK)也置1,同時將ClientISN + 1,此外,此段中還包含一個初始值隨機的服務端序列號ServerISN;

●源主機收到目標主機確認數據段後,再回送一個數據段,包含ACK = 1, serverISN+1;

Source ---> Destination:   SYN = 1, Seq = ClientISN

Destination ---> Source:   SYN = 1, ACK =1, ClientISN + 1, Seq = ServerISN

Source ---> Destination:   ACK = 1, ServerISN+1

至此爲止,TCP會話的三次握手完成。接下來,源主機和目標主機能夠互相收發數據。整個過程可用下圖表示。

 

解釋TCP/IP四次揮手的過程

起初A和B處於ESTABLISHED狀態

A發出鏈接釋放報文段(Fin = 1,  Seq = u;)並處於FIN-WAIT-1狀態,A再也不有數據發送,但仍然能夠接受數據;

B發出確認報文段(ACK =  1, Seq = v, ack = u+1)且進入CLOSE-WAIT狀態, 並繼續發送剩下的數據, A收到確認後,進入FIN-WAIT-2狀態,等待B的鏈接釋放報文段;

B沒有要向A發出的數據,B發出鏈接釋放報文段(Fin = 1, ACK = 1, Seq = w, ack = u+1)且進入LAST-ACK狀態;

A發出確認報文段(ACK  = 1, Seq = w, ack = u+1)且進入TIME-WAIT狀態, B收到確認報文段後進入CLOSED狀態;

A通過等待計時器時間2MSL後,進入CLOSED狀態

A(FIN-WAIT-1) ---> B (ESTABLISHED):             Fin = 1,  Seq = u;

B(CLOSE-WAIT) ---> A:                                     ACK =  1, Seq = v, ack = u+1 (繼續發送剩餘數據); 

---> A (FIN-WAIT-2):                                          A 收到上一步數據,A等待B的Fin;

B(LAST-ACK) ---> A :                                        Fin = 1, ACK = 1, Seq = w, ack = u+1;

A(TIME-WAIT) ---> B:                                        收到上一步數據, 發送ACK  = 1, Seq = w, ack = u+1;

---> B(CLOSED):                                               收到上一步數據,ClOSED.

A(ClOSED):                                                       等待2MSL, 進入CLOSED.

 

TCP的鏈接的拆除須要發送四個包,所以稱爲四次揮手(four-way handshake)。客戶端或服務器都可主動發起揮手動做,在socket編程中,任何一方執行close()操做便可產生揮手操做。

 

爲何TCP/IP須要握手三次,揮手四次

  • 爲何須要三次握手呢?

兩個緣由:信息對等和防止超時

信息對等: 假設A對B 發起鏈接請求, 在第二次握手時,A機器可以確認本身的發報和收報能力正常而且B的發報收報能力正常,而B機器只能肯定本身的收報能力和對方的發報能力正常,不能肯定對方的收報能力和本身的發報能力是否正常,這些只能經過第三次握手確認;

防止超時 爲了防止已失效的鏈接請求報文段忽然又傳送到了服務端,浪費服務端資源。

好比:client發出的第一個鏈接請求報文段並無丟失,而是在某個網絡結點長時間的滯留了,以至延誤到鏈接釋放之後的某個時間纔到達server。原本這是一個早已失效的報文段,可是server收到此失效的鏈接請求報文段後,就誤認爲是client再次發出的一個新的鏈接請求,因而就向client發出確認報文段,贊成創建鏈接。假設不採用「三次握手」,那麼只要server發出確認,新的鏈接就創建了,因爲client並無發出創建鏈接的請求,所以不會理睬server的確認,也不會向server發送數據,但server卻覺得新的運輸鏈接已經創建,並一直等待client發來數據。因此沒有采用「三次握手」,這種狀況下server的不少資源就白白浪費掉了。

  • 爲何須要四次揮手呢?由於TCP是全雙工模式,一方發送FIN報文時只是單方面沒有信息發送了,可是另外一方能夠繼續發送報文,只有雙方都發送了FIN報文整個通訊過程才能結束。

TCP是全雙工模式,當client發出FIN報文段時,只是表示client已經沒有數據要發送了,client告訴server,它的數據已經所有發送完畢了;可是,這個時候client仍是能夠接受來server的數據;當server返回ACK報文段時,表示它已經知道client沒有數據發送了,可是server仍是能夠發送數據到client的;當server也發送了FIN報文段時,這個時候就表示server也沒有數據要發送了,就會告訴client,我也沒有數據要發送了,若是收到client確認報文段,以後彼此就會愉快的中斷此次TCP鏈接。

爲何A在TIME-WAIT狀態必須等待2MSL的時間?

MSL最長報文段壽命Maximum Segment Lifetime,MSL=2

兩個理由:

1)保證A發送的最後一個ACK報文段可以到達B

2)防止已失效的鏈接請求報文段出如今本鏈接中。

1)這個ACK報文段有可能丟失,使得處於LAST-ACK狀態的B收不到對已發送的FIN+ACK報文段的確認,B超時重傳FIN+ACK報文段,而A能在2MSL時間內收到這個重傳的FIN+ACK報文段,接着A重傳一次確認,從新啓動2MSL計時器,最後A和B都進入到CLOSED狀態,若A在TIME-WAIT狀態不等待一段時間,而是發送完ACK報文段後當即釋放鏈接,則沒法收到B重傳的FIN+ACK報文段,因此不會再發送一次確認報文段,則B沒法正常進入到CLOSED狀態。

2)A在發送完最後一個ACK報文段後,再通過2MSL,就能夠使本鏈接持續的時間內所產生的全部報文段都從網絡中消失,使下一個新的鏈接中不會出現這種舊的鏈接請求報文段。

 經過調整2MSL時間(能夠手動更改),能夠有效下降TIME_WAIT狀態的鏈接數目,達到系統調優的目的。建議將高併發服務器的TIME_WAIT超時調小。

TCP協議和UDP協議的區別是什麼

  1. TCP協議是有鏈接的,有鏈接的意思是開始傳輸實際數據以前TCP的客戶端和服務器端必須經過三次握手創建鏈接,會話結束以後也要結束鏈接,而UDP是無鏈接的。
  2. TCP協議保證數據按序發送,按序到達,提供超時重傳來保證可靠性,可是UDP不保證按序到達,甚至不保證到達,只是努力交付,即使是按序發送的序列,也不保證按序送到。
  3. TCP有流量控制和擁塞控制,UDP沒有,網絡擁堵不會影響發送端的發送速率
  4. TCP是一對一的鏈接,而UDP則能夠支持一對一,多對多,一對多的通訊。
  5. TCP面向的是字節流的服務,UDP面向的是報文的服務。
  6. TCP協議所需資源多,TCP首部需20個字節(不算可選項),UDP首部字段只需8個字節

TCP的特色是 連序控點流: 有鏈接、有序、流量控制、點對點、字節流

面向字節流 和麪向報文:

用UDP傳輸100個字節的數據:

面向數據報
若是發送端調用一次sendto, 發送100個字節, 那麼接收端也必須調用對應的一次recvfrom, 接收100個字節;
而不能循環調用10次recvfrom, 每次接收10個字節;

面向字節流
因爲緩衝區的存在, TCP程序的讀和寫不須要一一匹配, 例如:
寫100個字節數據時, 能夠調用一次write寫100個字節, 也能夠調用100次write, 每次寫⼀一個字節;
讀100個字節數據時, 也徹底不須要考慮寫的時候是怎麼寫的, 既能夠一次read 100個字節, 也能夠一次read一個字節, 重複100次;

應用層交給UDP多長的報文, UDP原樣發送, 既不會拆分, 也不會合並;
TCP有一個緩衝,當應用程序傳送的數據塊太長,TCP就能夠把它劃分短一些再傳送。若是應用程序一次只發送一個字節,TCP也能夠等待積累有足夠多的字節後再構成報文段發送出去。

常見的應用中有哪些是應用TCP協議的,哪些又是應用UDP協議的,爲何它們被如此設計?

基於TCP的應用層協議有: HTTP、FTP、SMTP、TELNET、
基於UDP的應用層協議: DNS、RIP(路由選擇協議)、DHCP、BOOTP(是DHCP的前身)、IGMP(Internet組管理協議)、TFTP(簡單文件傳輸協議)

 

如下應用通常或必須用udp實現

  • 多播的信息必定要用udp實現,由於tcp只支持一對一通訊。
  • 若是一個應用場景中大可能是簡短的信息,適合用udp實現,由於udp是基於報文段的,它直接對上層應用的數據封裝成報文段,而後丟在網絡中,若是信息量太大,會在鏈路層中被分片,影響傳輸效率。
  • 若是一個應用場景重性能甚於重完整性和安全性,那麼適合於udp,好比多媒體應用,缺一兩幀不影響用戶體驗,可是須要流媒體到達的速度快,所以比較適合用udp
  • 若是要求快速響應,那麼udp比較合適
  • 若是又要利用udp的快速響應優勢,又想可靠傳輸,那麼只能考上層應用本身制定規則了。
  • 常見的使用udp的例子:ICQ,QQ的聊天模塊。

UDP能不能實現可靠鏈接,怎麼實現

UDP它不屬於鏈接型協議,於是具備資源消耗小,處理速度快的優勢,因此一般音頻、視頻和普通數據在傳送時使用UDP較多,由於它們即便偶爾丟失一兩個數據包,也不會對接收結果產生太大影響。傳輸層沒法保證數據的可靠傳輸,只能經過應用層來實現了。實現的方式能夠參照tcp可靠性傳輸的方式,只是實現不在傳輸層,實現轉移到了應用層。

         實現確認機制、重傳機制、窗口確認機制。

        必須實現以下功能:

         發送:包的分片、包確認、包的重發

         接收:包的調序、包的序號確認

         目前有以下開源程序利用udp實現了可靠的數據傳輸。分別爲RUDP、RTP、UDT。

基於UDP的數據傳輸協議(UDP-basedData Transfer Protocol,簡稱UDT)是一種互聯網數據傳輸協議

解釋Server端受到SYN攻擊

服務器端的資源分配是在二次握手時分配的,而客戶端的資源是在完成三次握手時分配的,因此服務器容易受到SYN洪泛攻擊,SYN攻擊就是Client在短期內僞造大量不存在的IP地址,並向Server不斷地發送SYN包,Server則回覆確認包,並等待Client確認,因爲源地址不存在,所以Server須要不斷重發直至超時,這些僞造的SYN包將長時間佔用未鏈接隊列,致使正常的SYN請求由於隊列滿而被丟棄,從而引發網絡擁塞甚至系統癱瘓。

防範SYN攻擊措施:下降主機的等待時間使主機儘快的釋放半鏈接的佔用,短期受到某IP的重複SYN則丟棄後續請求

HTTP的八種 請求方法

根據HTTP標準,HTTP請求能夠使用多種請求方法。

HTTP1.0定義了三種請求方法: GET, POST 和 HEAD方法。

HTTP1.1新增了五種請求方法:OPTIONS, PUT, DELETE, TRACE 和 CONNECT 方法。

 

序號 方法 描述
1 GET 請求指定的頁面信息,並返回實體主體。
2 HEAD 相似於get請求,只不過返回的響應中沒有具體的內容,用於獲取報頭
3 POST 向指定資源提交數據進行處理請求(例如提交表單或者上傳文件)。數據被包含在請求體中。POST請求可能會致使新的資源的創建和/或已有資源的修改。
4 PUT 從客戶端向服務器傳送的數據取代指定的文檔的內容。
5 DELETE 請求服務器刪除指定的頁面。
6 CONNECT HTTP/1.1協議中預留給可以將鏈接改成管道方式的代理服務器。
7 OPTIONS 容許客戶端查看服務器的性能。
8 TRACE 回顯服務器收到的請求,主要用於測試或診斷。

 

GET和POST兩種基本請求方法的區別

「標準答案」:

瀏覽器行爲:

  • GET在瀏覽器回退時是無害的,而POST會再次提交請求
  • GET產生的URL地址能夠被Bookmark,而POST不能夠。
  • GET請求會被瀏覽器主動cache,而POST不會,除非手動設置。
  • GET請求只能進行url編碼,而POST支持多種編碼方式。
  • GET請求參數會被完整保留在瀏覽器歷史記錄裏,而POST中的參數不會被保留。
  • GET參數經過URL傳遞,且參數是有長度限制的,POST放在Request body中。
  • 對參數的數據類型,GET只接受ASCII字符,而POST沒有限制。
  • GET比POST更不安全,由於參數直接暴露在URL上,因此不能用來傳遞敏感信息。GET提交的數據會放在URL以後,以?分割URL和傳輸數據,參數之間以&相連,如EditPosts.aspx?name=test1&id=123456. POST方法是把提交的數據放在HTTP包的Body中.
  • GET產生一個TCP數據包;POST產生兩個TCP數據包

對於GET方式的請求,瀏覽器會把http header和data一併發送出去,服務器響應200(返回數據); 
而對於POST,瀏覽器先發送header,服務器響應100 continue,瀏覽器再發送data,服務器響應200 ok(返回數據)

GET和POST本質上沒有區別!

GET和POST是HTTP協議中的兩種發送請求的方法。

HTTP是是基於TCP/IP的關於數據如何在萬維網中如何通訊的協議。

HTTP的底層是TCP/IP。因此GET和POST的底層也是TCP/IP,也就是說,GET/POST都是TCP連接。GET和POST能作的事情是同樣同樣的。你要給GET加上request body,給POST帶上url參數,技術上是徹底行的通的。 

但不一樣的瀏覽器(發起http請求)和服務器(接受http請求)在技術層面上不容許 url中無限加參數。他們會限制單次運輸量來控制風險,數據量太大對瀏覽器和服務器都是很大負擔。業界不成文的規定是,(大多數)瀏覽器一般都會限制url長度在2K個字節,而(大多數)服務器最多處理64K大小的url。超過的部分,恕不處理。若是你用GET服務,在request body偷偷藏了數據,不一樣服務器的處理方式也是不一樣的,有些服務器會幫你卸貨,讀出數據,有些服務器直接忽略,因此,雖然GET能夠帶request body,也不能保證必定能被接收到。

 

GET和POST還有一個重大區別,簡單的說:

GET產生一個TCP數據包;POST產生兩個TCP數據包。

對於GET方式的請求,瀏覽器會把http header和data一併發送出去,服務器響應200(返回數據);

而對於POST,瀏覽器先發送header,服務器響應100 continue,瀏覽器再發送data,服務器響應200 ok(返回數據)。

但並非全部瀏覽器都會在POST中發送兩次包,Firefox就只發送一次。

HTTP和HTTPS

http協議與https協議的區別?

  1. http 是超文本傳輸協議,信息是明文傳輸,https 則是具備安全性的 ssl 加密傳輸協議
  2. http 的鏈接很簡單,是無狀態的;HTTPS 協議是由 SSL+HTTP 協議構建的可進行加密傳輸、身份認證的網絡協議,HTTPS爲了數據傳輸的安全,在HTTP的基礎上加入了SSL協議,SSL依靠證書來驗證服務器的身份,併爲瀏覽器和服務器之間的通訊加密,比 http 協議安全。
  3. http 和 https 使用的是徹底不一樣的鏈接方式,用的端口也不同,前者是 80,後者是 443
  4. https 協議須要到 ca 申請證書,通常免費證書較少,於是須要必定費用。。

HTTPS的優勢確保數據發送到正確的客戶機和服務器,確保數據的完整性,增長了中間人攻擊的成本)

  儘管HTTPS並不是絕對安全,掌握根證書的機構、掌握加密算法的組織一樣能夠進行中間人形式的攻擊,但HTTPS還是現行架構下最安全的解決方案,主要有如下幾個好處:

  (1)使用HTTPS協議可認證用戶和服務器,確保數據發送到正確的客戶機和服務器

  (2)HTTPS協議是由SSL+HTTP協議構建的可進行加密傳輸、身份認證的網絡協議,要比http協議安全,可防止數據在傳輸過程當中不被竊取、改變,確保數據的完整性

  (3)HTTPS是現行架構下最安全的解決方案,雖然不是絕對安全,但它大幅增長了中間人攻擊的成本。

HTTPS的缺點(效率低)

  雖說HTTPS有很大的優點,但其相對來講,仍是存在不足之處的:

  (1)HTTPS協議握手階段比較費時,會使頁面的加載時間延長近50%,增長10%到20%的耗電;

  (2)HTTPS鏈接緩存不如HTTP高效,會增長數據開銷和功耗,甚至已有的安全措施也會所以而受到影響;

Cookies和Session的區別

cookie和session的區別:

①存在的位置:

cookie 存在於客戶端,臨時文件夾中;  session存在於服務器的內存中,一個session域對象爲一個用戶瀏覽器服務

②安全性
cookie是以明文的方式存放在客戶端的,安全性低,能夠經過一個加密算法進行加密後存放;  session存放於服務器的內存中,因此安全性好

③網絡傳輸量
cookie會傳遞消息給服務器;  session自己存放於服務器,不會有傳送流量

④生命週期(以20分鐘爲例)
cookie的生命週期是累計的,從建立時,就開始計時,20分鐘後,cookie生命週期結束;
session的生命週期是間隔的,從建立時,開始計時如在20分鐘,沒有訪問session,那麼session生命週期被銷燬。可是,若是在20分鐘內(如在第19分鐘時)訪問過session,那麼,將從新計算session的生命週期。關機會形成session生命週期的結束,可是對cookie沒有影響

⑤訪問範圍
cookie爲多個用戶瀏覽器共享;  session爲一個用戶瀏覽器獨享
能夠看本身的筆記: https://www.cnblogs.com/greatLong/articles/11146045.html

Session:

服務器爲 每一個會話建立一個session對象,因此session中的數據可供當前會話中全部servlet共享。做用域: 會話從用戶打開瀏覽器開始,直到關閉瀏覽器才結束,一次會話期間只會建立一個session對象。session是服務器端對象,保存在服務器端,而且服務器 能夠將建立session後產生的 sessionid 經過一個 cookie 返回給客戶端,以便下次驗證。(session底層的一種實現依賴於cookie,session通常配合cookie來保存會話信息,但在不容許使用cookie的狀況下,也能夠使用url重寫的形式傳遞信息)

Cookie

cookie是http協議提供的,不是java獨有的;

cookie保存在客戶端;

能夠設置生命時長;

cookie有個屬性是路徑,但該路徑指的是服務端路徑;

cookie的形式是一個鍵值對,如uid:lxj;

http約定,單條cookie不超過4KB,單個會話最多保存20條cookie; 瀏覽器最多儲存300條cookie;但通常瀏覽器對這個規定會有超出,如容許單個會話超過20個cookie;

與之對應的,session是Java獨有的,全稱是httpsession, 每一個session 都有JSEESIONID;

session通常配合cookie來保存會話信息,但在不容許cookie的狀況下,也能夠使用url重寫的形式傳遞信息

此部分知識不徹底,須要進一步理解

中間人攻擊和重放攻擊

  • 重放攻擊是攻擊者獲取客戶端發送給服務器端的包,不作修改,原封不動的發送給服務器用來實現某些功能。好比說客戶端發送給服務器端一個包的功能是查詢某個信息,攻擊者攔截到這個包,而後想要查詢這個信息的時候,把這個包發送給服務器,服務器就會作相應的操做,返回查詢的信息。

防護方案: 加時間戳(須要時鐘同步),使用與當前事件有關的一次性隨機數N

  • 中間人攻擊就不同了,中間人攻擊是攻擊者把本身看成客戶端與服務器端的中間人,客戶端發送的信息會被攻擊者截取而後作一些操做再發送給服務器端,服務器端響應返回的包也會被攻擊者截取而後再發送給客戶端。你能夠看做是相對於客戶端來講攻擊者是服務器端,(攻擊者假冒客戶端須要證書,可是攻擊者本身製做的證書不是信任的證書,會彈警告,但每每人們對於這些警告大意或不懂就忽略了);相對於服務器段來講攻擊者是客戶端,兩邊都欺騙,從而獲取全部的信息。常見的有DNS劫持。

防護方案: 採用認證方式鏈接

轉發與重定向的區別

forward(轉發): 是服務器內部重定向,程序收到請求後從新定向到另外一個程序,客戶機並不知道。服務器直接訪問目標地址的URL,並把那個URL的相應內容讀取過來,而後把這些內容再發給瀏覽器,瀏覽器根本不知道服務器發送的內容是從哪裏來的,因此它的 地址欄中仍是原來的地址,轉發時並不通知客戶機對象能夠存儲在請求中,併發給下一個資源使用,而且徹底在服務器上面進行
 
redirect(重定向): 是服務器收到請求後發送一個狀態頭給客戶,客戶將再請求一次新的網址,這裏多了兩次網絡通訊的來往通常來講,瀏覽器會用剛纔請求的全部參數從新請求,因此session,request參數均可以獲取。 重定向致使瀏覽器發出了新的請求在重定向以前存儲爲請求屬性的任何對象都會消失,這也是二者最大的區別

HTTP狀態碼

  • 1**:請求收到,繼續處理

100——客戶必須繼續發出請求

101——客戶要求服務器根據請求轉換HTTP協議版本

  • 2**:操做成功收到,分析、接受

    200("OK") 通常來講,這是客戶端但願看到的響應代碼。它表示服務器成功執行了客戶端所請求的動做

  • 3XX 重定向

3XX系列響應代碼代表:客戶端須要作些額外工做才能獲得所須要的資源

                     301 redirect: 301 表明永久性轉移(Permanently Moved)

                     302 redirect: 302 表明暫時性轉移(Temporarily Moved )

  • 4**:客戶端錯誤, 請求包含一個錯誤語法或不能完成

   404——沒有發現文件、查詢或URl

   401——未受權

  • 5XX 服務端錯誤

這些響應代碼代表服務器端出現錯誤。通常來講,這些代碼意味着服務器處於不能執行客戶端請求的狀態,此時客戶端應稍後重試。

500("Internal Server Error")

  1. 這是一個通用的服務器錯誤響應。對於大多數web框架,若是在執行請求處理代碼時遇到了異常,它們就發送此響應代碼。

說一說HTTP請求頭的內容

 HTTP請求報文由3部分組成(請求行+請求頭+請求體): 

①是請求方法,GET和POST是最多見的HTTP方法,除此之外還包括DELETE、HEAD、OPTIONS、PUT、TRACE。

②爲請求對應的URL地址,它和報文頭的Host屬性組成完整的請求URL,③是協議名稱及版本號。 

④是HTTP的報文頭,報文頭包含若干個屬性,格式爲「屬性名:屬性值」,服務端據此獲取客戶端的信息。 

⑤是報文體,它將一個頁面表單中的組件值經過param1=value1&param2=value2的鍵值對形式編碼成一個格式化串,它承載多個請求參數的數據。不但報文體能夠傳遞請求參數,請求URL也能夠經過相似於「/chapter15/user.html? param1=value1&param2=value2」的方式傳遞請求參數。

常見的HTTP請求報文頭屬性

Accept客戶端接受什麼類型的響應,常見的如Accept:text/plain,Accept屬性的值能夠爲一個或多個MIME類型的值。

Cookie: 客戶端的Cookie就是經過這個報文頭屬性傳給服務端

Referer 表示這個請求是從哪一個URL過來的

Cache-Control 對緩存進行控制,控制是否緩存及緩存時間爲多久。

Accept-Language:接受的語言類型

User-Agent: 經過何種代理(瀏覽器)訪問

Content-type, Content-Length

Connection : 

Connection: keep-alive   當一個網頁打開完成後,客戶端和服務器之間用於傳輸HTTP數據的TCP鏈接不會關閉,若是客戶端再次訪問這個服務器上的網頁,會繼續使用這一條已經創建的鏈接

Connection: close  表明一個Request完成後,客戶端和服務器之間用於傳輸HTTP數據的TCP鏈接會關閉, 當客戶端再次發送Request,須要從新創建TCP鏈接。

 談談HTTP響應報文

HTTP的響應報文也由三部分組成(響應行+響應頭+響應體): 
 

如下是一個實際的HTTP響應報文: 

 

①報文協議及版本; 
②狀態碼及狀態描述; 
③響應報文頭,也是由多個屬性組成; 
④響應報文體,即咱們真正要的「乾貨」。 

 

 響應狀態碼 
和請求報文相比,響應報文多了一個「響應狀態碼」,它以「清晰明確」的語言告訴客戶端本次請求的處理結果。 
HTTP的響應狀態碼由5段組成: 

  • 1xx 消息,通常是告訴客戶端,請求已經收到了,正在處理,別急...
  • 2xx 處理成功,通常表示:請求收悉、我明白你要的、請求已受理、已經處理完成等信息.
  • 3xx 重定向到其它地方。它讓客戶端再發起一個請求以完成整個處理。
  • 4xx 處理髮生錯誤,責任在客戶端,如客戶端的請求一個不存在的資源,客戶端未被受權,禁止訪問等。
  • 5xx 處理髮生錯誤,責任在服務端,如服務端拋出異常,路由出錯,HTTP版本不支持等。

從瀏覽器發出請求開始,到服務端應用接受到請求返回結果顯示的過程

第一步,解析域名,找到ip

查本地緩存--查hosts文件--查ISP服務商的DNS緩存--從根域名開始遞歸搜索返回

瀏覽器會緩存DNS一段時間,通常2-30分鐘不等,若是有緩存,直接返回ip,不然下一步。
緩存中沒法找到ip,瀏覽器會進行一個系統調用,查詢hosts文件。若是找到,直接返回ip,不然下一步。
進行1 和2 本地查詢無果,只能藉助於網絡,路由器通常都會有本身的DNS緩存,ISP服務商DNS緩存,這時通常都可以獲得相應的ip,若是仍是無果,只能藉助於DNS遞歸解析了。
這時ISP的DNS服務器就會開始從根域名服務器開始遞歸搜索,從.com 頂級域名服務器,到baidu的域名服務器。 
到這裏,瀏覽器就得到網絡ip,在DNS解析過程當中,經常解析出不一樣的IP。

第二步,瀏覽器於網站創建TCP鏈接

瀏覽器利用ip直接網站主機通訊,瀏覽器發出TCP鏈接請求,主機返回TCP應答報文,瀏覽器收到應答報文發現ACK標誌位爲1,表示鏈接請求確認,瀏覽器返回TCP()確認報文,主機收到確認報文,三次握手,TCP鏈接創建完成。

第三步, 瀏覽器發起默認的GET請求

瀏覽器向主機發起一個HTTP-GET方法報文請求,請求中包含訪問的URL,也就是http://www.baidu.com/還有User-Agent用戶瀏覽器操做系統信息,編碼等,值得一提的是Accep-Encoding和Cookies項。Accept-Encoding通常採用gzip,壓縮以後傳輸html文件,Cookies若是是首次訪問,會提示服務器簡歷用戶緩存信息,若是不是,能夠利用Cookies對應鍵值,找到相應緩存,緩存裏面存放着用戶名,密碼和一些用戶設置項

第四步,顯示頁面或返回其餘

返回狀態碼200 OK,表示服務器能夠響應請求,返回報文,因爲在報頭中Content-type爲「text/html」,瀏覽器以HTML形式呈現,而不是下載文件。 
可是對於大型網站存在多個主機站點,每每不會直接返回請求頁面,而是重定向。返回的狀態碼就不是 200 OK, 而是301,302以3開頭的重定向嗎。瀏覽器在獲取了重定向響應後,在響應報文中Location項找到重定向地址,瀏覽器從新第一步訪問便可。 

第三模塊--操做系統、Git

Linux經常使用指令 (查看內存、進程之類的) 

文件處理

  • 文件查找:find
  • 文本搜索:grep
  • 排序:sort
  • 按列切分文本:cut
  • 統計行和字符:wc
  • 文本替換:sed
  • 數據流處理:awk
  • 性能分析
  • 進程查詢:ps
  • 進程監控:top
  • 打開文件查詢:lsof
  • 內存使用量:free
  • 監控性能指標:sar

監控CPU -u

監控內存 -r

網絡工具

  • 網卡配置:ifconfig
  • 查看當前網絡鏈接:netstat
  • 查看路由表:route
  • 檢查網絡連通性:ping
  • 轉發路徑:traceroute
  • 命令行抓包:tcpdump
  • 域名解析工具:dig
  • 網絡請求:curl

其餘

  • 終止進程:kill
  • 修改文件權限:chmod
  • 建立連接:ln
  • 顯示文件尾:tail
  • 版本控制:git
  • 設置別名:alias

根據程序名稱結束該程序

以殺掉tomcat爲例子

ps -ef|grep tomcat|grep -v|awk -F '{print $2}'|xargs kill -9

等同於

kill -9 `ps -ef | grep 'tomcat' | awk '{print $2}'`

xargs接收管道前面傳過來的字符;

awk '{print $2}'的意思是選取並輸出第二列的數據

kill 與kill -9的區別

  1. kill 的默認參數是 -15, kill pid的效果等同於kill -15 pid。  執行kill命令,系統會發送一個關閉信號給對應的程序。當程序接收到該信號後,將有機會釋放資源、處理善後工做後再中止;
  2. kill -9命令,系統給對應程序發送的信號是SIGKILL,即exit。exit信號不會被系統阻塞,因此kill -9能順利殺掉進程。至關於強制關閉程序。

在使用 kill -9 前,應該先使用 kill -15,給目標進程一個清理善後工做的機會。若是沒有,可能會留下一些不完整的文件或狀態,從而影響服務的再次啓動。

查詢日誌

1. 按行號查看---過濾出關鍵字附近的日誌

首先: cat -n test.log |grep "keyword"  獲得關鍵日誌的行號

 而後,獲得"keyword"關鍵字所在的行號是102行. 此時若是想查看這個關鍵字前10行和後10行的日誌:

cat -n test.log |tail -n +92|head -n 20

2. 刷新顯示日誌變更

tail -f test.log 查看日誌的尾部,並刷新顯示日誌變更。此方法適合在調試程序的時候查看日誌,日誌變更會實時刷新顯示到終端。

top指令能查看進程的哪些信息 

包括進程的相關信息,包括進程ID,內存佔用率,CPU佔用率

待補充

守護進程瞭解嗎

 線程是調度的基本單位,進程是資源分配的基本單位。。

守護進程是生存期長的一種進程。它們獨立於控制終端而且週期性的執行某種任務或等待處理某些發生的事件。他們經常在系統引導裝入時啓動,在系統關閉時終止。linux系統有不少守護進程,大多數服務器都是用守護進程實現的。linux上的守護進程相似於windows上的服務。

殭屍進程、孤兒進程

父進程在調用fork接口以後和子進程已經能夠獨立開,以後父進程和子進程就以未知的順序向下執行(異步過程)。因此父進程和子進程都有可能先執行完。當父進程先結束,子進程此時就會變成孤兒進程,不過這種狀況問題不大,孤兒進程會自動向上被init進程收養,init進程完成對狀態收集工做。並且這種過繼的方式也是守護進程可以實現的因素。若是子進程先結束,父進程並未調用wait或者waitpid獲取進程狀態信息,那麼子進程描述符就會一直保存在系統中,這種進程稱爲殭屍進程。

Git常見指令

fetch和merge和pull的區別
 pull至關於git fetch 和 git merge,即更新遠程倉庫的代碼到本地倉庫,而後將內容合併到當前分支。
 git fetch:至關因而從遠程獲取最新版本到本地,不會自動merge
 git merge :  將內容合併到當前分支
 git pull:至關因而從遠程獲取最新版本並merge到本地

經常使用命令
git show # 顯示某次提交的內容 git show $id
git add <file> # 將工做文件修改提交到本地暫存區
git rm <file> # 從版本庫中刪除文件
git reset <file> # 從暫存區恢復到工做文件
git reset HEAD^ # 恢復最近一次提交過的狀態,即放棄上次提交後的全部本次修改
git diff <file> # 比較當前文件和暫存區文件差別 git diff
git log -p <file> # 查看每次詳細修改內容的diff
git branch -r # 查看遠程分支
git merge <branch> # 將branch分支合併到當前分支
git stash # 暫存
git stash pop #恢復最近一次的暫存
git pull # 抓取遠程倉庫全部分支更新併合併到本地
git push origin master # 將本地主分支推到遠程主分支

第四模塊--JAVA語言特性

Integer緩存數據的範圍

java定義:在自動裝箱時對於值從–128到127之間的值,它們被裝箱爲Integer對象後,會存在內存中被重用,始終只存在一個對象。這歸結於java對於Integer與int的自動裝箱與拆箱的設計,是一種模式:享元模式(flyweight)

而若是超過了從–128到127之間的值,被裝箱後的Integer對象並不會被重用,即至關於每次裝箱時都新建一個 Integer對象;以上的現象是因爲使用了自動裝箱所引發的,若是你沒有使用自動裝箱,而是跟通常類同樣,用new來進行實例化,就會每次new就都一個新的對象。

http://www.javashuo.com/article/p-uqjyldot-cg.html

面向對象編程和麪向過程編程的優缺點

面向過程

優勢:性能比面向對象高,由於類調用時須要實例化,開銷比較大,比較消耗資源;好比單片機、嵌入式開發、 Linux/Unix等通常採用面向過程開發,性能是最重要的因素。 
缺點:沒有面向對象易維護、易複用、易擴展

面向對象

優勢:易維護、易複用、易擴展,因爲面向對象有封裝、繼承、多態性的特性,能夠設計出低耦合的系統,使系統更加靈活、更加易於維護 
缺點:性能比面向過程低

JAVA提供了幾個類加載器?分別是?怎麼對類進行加載的?

根加載器、擴展類加載器、系統類加載器

 

Bootstrap 是JVM在啓動時建立的,一般由與操做系統相關的本地代碼實現,是最根基的類加載器,負責裝載最核心的JAVA類,如Object, System, String;

而後第二層在JDK9中稱爲Platform 加載器, JDK9以前叫Extension加載器,加載一些擴展的系統類,如XML, 加密, 壓縮相關的功能類;

第三層是Application 加載器, 主要加載用戶定義的CLASSPATH路徑下的類。

類加載的雙親委任模型

低層次的當前類加載器,不能覆蓋更高層次類加載器已經加載的類若是低層次的類加載器想加載一個未知類,要很是禮貌地向上逐級詢問:「請問,這個類已經加載了嗎?」被詢問的高層次類加載器會自問兩個問題:第一,我是否已加載過此類?第二,若是沒有,是否能夠加載此類?只有當全部高層次類加載器在兩個問題上的答案均爲「否」時,纔可讓當前類加載器加載這個未知類。如圖4-6所示,左側綠色箭頭向上逐級詢問是否已加載此類,直至Bootstrap ClassLoader,而後向下逐級嘗試是否可以加載此類,若是都加載不了,則通知發起加載請求的當前類加載器,准予加載。

JAVA類的加載過程

在JVM中,類從被加載到虛擬機內存中開始,到卸載出內存爲止,它的整個生命週期包括:加載、驗證、準備、解析、初始化5個階段。而解析階段便是虛擬機將常量池內的符號引用替換爲直接引用的過程。

  1. 加載:程序運行以前jvm會把編譯完成的.class二進制文件加載到內存,供程序使用,用到的就是類加載器classLoader 二、
  2. 鏈接:分爲三步  驗證  》準備  》解析  

驗證:確保類加載的正確性。

準備:爲類的靜態變量分配內存,將其初始化爲默認值 。

解析:把類中的符號引用轉化爲直接引用。

3. 初始化:爲類的靜態變量賦予正確的初始值,上述的準備階段爲靜態變量賦予的是虛擬機默認的初始值,此處賦予的纔是程序編寫者爲變量分配的真正的初始值

如今java程序的執行就能夠分爲

  

類成員初始化順序總結:先靜態,後父類普通構造,再子類普通構造,同級看書寫順序

1.先執行父類靜態變量和靜態代碼塊,再執行子類靜態變量和靜態代碼塊 2.先執行父類普通變量和代碼塊,再執行父類構造器(static方法) 3.先執行子類普通變量和代碼塊,再執行子類構造器(static方法) 4.static方法初始化先於普通方法,靜態初始化只有在必要時刻才進行且只初始化一次。

反射機制

  • 定義: 在運行狀態中,對於任意一個類,都可以獲取到這個類的全部屬性和方法,對於任意一個對象,都可以調用它的任意一個方法和屬性(包括私有的方法和屬性),這種動態獲取的信息以及動態調用對象的方法的功能就稱爲java語言的反射機制。
  • 使用: 想要使用反射機制,就必需要先獲取到該類的字節碼文件對象(.class),經過字節碼文件對象,就可以經過該類中的方法獲取到咱們想要的全部信息(方法,屬性,類名,父類名,實現的全部接口等等),每個類對應着一個字節碼文件也就對應着一個Class類型的對象,也就是字節碼文件對象。
  • Java反射的三種實現方式

Foo foo = new Foo();

第一種:經過Object類的getClass方法

Class cla = foo.getClass();

第二種:經過對象實例方法獲取對象

Class cla = foo.class;

第三種:經過Class.forName方式

Class cla = Class.forName("xx.xx.Foo");

HashCode 是幹什麼用的?若是重寫HashCode 有哪些注意點?

hashCode方法的主要做用是爲了配合基於散列的集合一塊兒正常運行,這樣的散列集合包括HashSet、HashMap以及HashTable。當向集合中插入對象時,如何判別在集合中是否已經存在該對象了?(注意:集合中不容許重複的元素存在)也許大多數人都會想到調用equals方法來逐個進行比較,這個方法確實可行。可是若是集合中已經存在一萬條數據或者更多的數據,若是採用equals方法去逐一比較,效率必然是一個問題。此時hashCode方法的做用就體現出來了,當集合要添加新的對象時,先調用這個對象的hashCode方法,獲得對應的hashcode值,實際上在HashMap的具體實現中會用一個table保存已經存進去的對象的hashcode值,若是table中沒有該hashcode值,它就能夠直接存進去,不用再進行任何比較了;若是存在該hashcode值, 就調用它的equals方法與新元素進行比較,相同的話就不存了,不相同就散列其它的地址,因此這裏存在一個衝突解決的問題,這樣一來實際調用equals方法的次數就大大下降了。

 

在每一個類中,在重寫 equals 方法的時侯,必定要重寫 hashcode 方法。若是不這樣作,就違反了hashCode的通用約定,這會阻止它在HashMap和HashSet這樣的集合中正常工做。

  1. 若是兩個對象根據equals(Object)方法比較是相等的,那麼在兩個對象上調用hashCode就必須產生的結果是相同的整數。
  2. 若是兩個對象根據equals(Object)方法比較並不相等,則不要求在每一個對象上調用hashCode都必須產生不一樣的結果。 可是,程序員應該意識到,爲不相等的對象生成不一樣的結果可能會提升散列表(hash tables)的性能。

JAVA集合類的常見問題

HashMap底層是怎麼實現的

數組加鏈表的形式

在JAVA 8 中,改進爲,當鏈表長度達到8及以上,轉爲紅黑樹來實現; 紅黑樹的數據數量將爲6的時候又會自動轉爲鏈表。

(由於長度爲6的鏈表平均查詢次數是3, 查找樹的話,3次查詢能查到8個數據)

HashMap 是一個用於存儲Key-Value 鍵值對的集合,每個鍵值對也叫作Entry。這些個Entry 分散存儲在一個數組當中,這個數組就是HashMap 的主幹。 每一個Entry都是鏈表的結構,含有能夠指向一個Entry的next指針;
HashMap 數組每個元素的初始值都是Null。 
這裏寫圖片描述

1. Put 方法的原理

調用Put方法的時候發生了什麼呢? 根據鍵計算哈希值,肯定放在哪一個Entry裏,若是計算出index位置的entry已經有值,就將新加入的鍵值對掛在這個entry的鏈表尾部。
好比調用 hashMap.put(「apple」, 0) ,插入一個Key爲「apple」的元素。這時候咱們須要利用一個哈希函數來肯定Entry的插入位置(index): 
index = Hash("apple") 
假定最後計算出的index是2,那麼結果以下: 
這裏寫圖片描述 
可是,由於HashMap的長度是有限的,當插入的Entry愈來愈多時,再完美的Hash函數也不免會出現index衝突的狀況。好比下面這樣: 
這裏寫圖片描述 
這時候該怎麼辦呢?咱們能夠利用鏈表來解決。 
HashMap數組的每個元素不止是一個Entry對象,也是一個鏈表的頭節點。每個Entry對象經過Next指針指向它的下一個Entry節點。當新來的Entry映射到衝突的數組位置時,只須要插入到對應的鏈表便可: 
這裏寫圖片描述 
新來的Entry節點插入鏈表時,使用的是頭插法

2. Get方法的原理

使用Get方法根據Key來查找Value的時候,發生了什麼呢? 
首先會把輸入的Key作一次Hash映射,獲得對應的index: 
index = Hash(「apple」) 
因爲剛纔所說的Hash衝突,同一個位置有可能匹配到多個Entry,這時候就須要順着對應鏈表的頭節點,一個一個向下來查找。假設咱們要查找的Key是「apple」: 
這裏寫圖片描述

第一步,咱們查看的是頭節點Entry6,Entry6的Key是banana,顯然不是咱們要找的結果。 
第二步,咱們查看的是Next節點Entry1,Entry1的Key是apple,正是咱們要找的結果。 
之因此把Entry放在頭節點,是由於HashMap的發明者認爲,後插入的Entry被查找的可能性更大。

3. HashMap的初始長度

初始長度爲16,且每次自動擴容或者手動初始化的時候必須是2的冪。 

對於新插入的數據或者待讀取的數據,HashMap將Key的哈希值對數組長度取模,結果做爲該Entry在數組中的index。在計算機中,取模的代價遠高於位操做的代價,所以HashMap要求數組的長度必須爲2的N次方。此時將Key的哈希值對2^N-1進行與運算,其效果即與取模等效。HashMap並不要求用戶在指定HashMap容量時必須傳入一個2的N次方的整數,而是會經過Integer.highestOneBit算出比指定整數大的最小的2^N值。

如何進行位運算呢?有以下的公式(Length是HashMap的長度): 
以前說過,從Key映射到HashMap數組的對應位置,會用到一個Hash函數: 
index = Hash(「apple」) 
如何實現一個儘可能均勻分佈的Hash函數呢?咱們經過利用Key的HashCode值來作某種運算。 
index = HashCode(Key) & (Length - 1) 
下面咱們以值爲「book」的Key來演示整個過程:

  1. 計算book的hashcode,結果爲十進制的3029737,二進制的101110001110101110 1001
  2. 假定HashMap長度是默認的16,計算Length-1的結果爲十進制的15,二進制的1111
  3. 把以上兩個結果作與運算,101110001110101110 1001 & 1111 = 1001,十進制是9,因此 index=9。 
    能夠說,Hash算法最終獲得的index結果,徹底取決於Key的Hashcode值的最後幾位。這裏的位運算實際上是一種快速取模算法。

HashMap 的size爲何必須是2的冪?。這是由於2的冪用二進制表示時全部位都爲1,例如16-1=15 的二進制就是1111B。咱們說了Hash算法是爲了讓hash 的分佈變得均勻。其實咱們能夠把1111當作四個通道,表示跟1111 作&運算後分布是均勻的。假如默認長度取10,二進制表示爲1010,這樣就至關於有兩個通道是關閉的,因此計算出來的索引重複的概率比較大。

HashMap的擴容條件:

(size>=threshold) && (null != table[bucketIndex]) 即達到閾值,而且當前須要存放對象的slot上已經有值

解決hash衝突的辦法

  1. 開放定址法(線性探測再散列,二次探測再散列,僞隨機探測再散列)
  2. 再哈希法
  3. 鏈地址法
  4. 創建一個公共溢出區

Java中hashmap的解決辦法就是採用的鏈地址法

解決Hash衝突的開放地址檢測是怎麼實現的

這個方法的基本思想是:當發生地址衝突時,按照某種方法繼續探測哈希表中的其餘存儲單元,直到找到空位置爲止。這個過程可用下式描述: 
H i ( key ) = ( H ( key )+ d i ) mod m ( i = 1,2,…… , k ( k ≤ m – 1)) 
其中: H ( key ) 爲關鍵字 key 的直接哈希地址, m 爲哈希表的長度, di 爲每次再探測時的地址增量。

增量 d 能夠有不一樣的取法,並根據其取法有不一樣的稱呼: 
( 1 ) d i = 1 , 2 , 3 , …… 線性探測再散列; 
( 2 ) d i = 1^2 ,- 1^2 , 2^2 ,- 2^2 , k^2, -k^2…… 二次探測再散列; 
( 3 ) d i = 僞隨機序列 僞隨機再散列;

從哈希表中刪除一個元素,再加入元素時剛好與原來的那個哈希衝突,這個元素會放在哪

放在衝突Entry最開始的地方,由於哈希表在衝突時採用的是頭插法,之因此把Entry放在頭節點,是由於HashMap的發明者認爲,後插入的Entry被查找的可能性更大。

併發容器, hashtable和concurrenthashmap的區別

 HashMap爲何線程不安全?

主要有死鏈高併發下的數據用丟失兩個問題

  • 死鏈

                https://www.jianshu.com/p/e2f75c8cce01

主要發生在多線程狀況下rezise時遷移數據的transfer()方法,put(), get()方法。多個線程同時擴容時, hashmap會可能產生環,形成死循環。

  • 高併發下的數據丟失

新增對象丟失的緣由通常有:

    • 併發賦值時被覆蓋
    • resize線程已遍歷區間新增元素會丟失
    • 多個線程同時resize「新表「被覆蓋
    • 遷移丟失

1. 數據丟失場景: 在表擴容時,當前線程遷移數據的過程當中,其餘線程新增的元素有可能落在遷移線程已經遍歷過的哈希槽上;在遍歷完成以後,table數組引用指向了newTable, 這時剛纔新增的元素就會失去引用,被GC回收。

2. 數據覆蓋場景:

put的時候致使的多線程數據不一致:有兩個線程A和B,首先A但願插入一個key-value對到HashMap中,首先計算記錄所要落到的桶的索引座標,而後獲取到該桶裏面的鏈表頭結點,此時線程A的時間片用完了,而此時線程B被調度得以執行,和線程A同樣執行,只不過線程B成功將記錄插到了桶裏面,假設線程A插入的記錄計算出來的桶索引和線程B要插入的記錄計算出來的桶索引是同樣的,那麼當線程B成功插入以後,線程A再次被調度運行時,它依然持有過時的鏈表頭可是它對此一無所知,以致於它認爲它應該這樣作,如此一來就覆蓋了線程B插入的記錄,這樣線程B插入的記錄就憑空消失了,形成了數據不一致的行爲。

 

或者多個線程同時執行resize,也會形成table的覆蓋問題。

ConcurrentHashMap

Hashtable 在JDK1.0引入,以全互斥方式處理併發問題,性能極差;

HashMap在JDK1.2引入,非線程安全,在併發寫的情形下,容易出現死鏈的問題;

ConcurrentHashMap在JDK5中引入,是線程安全的哈希式集合,JDK1.8以前採用分段鎖的設計理念,至關於Hashtable和HashMap的折衷版。分段鎖由內部類Segment實現,繼承於ReentrantLock, 用它來管轄轄區每一個HashEntry。JDK11對JDK7作了以下優化:

  • 取消分段鎖機制,進一步下降衝突的機率;
  • 引入紅黑樹結構,同一個哈希槽上的元素個數超過必定閾值後(8),將單向鏈表轉爲紅黑樹結構(在轉化中,使用同步塊鎖住當前槽的首元素,防止其餘線程對當前槽進行修改,轉化完成後利用CAS替換原有鏈表);當某個槽的元素減小至6個時,由紅黑樹從新轉回鏈表;
  • 使用更加優化的方式統計集合內的元素數量

 

待補充:

快排

 

索引

LinkedHashMap

LinkedHashMap是有序的HashMap

map = new LinkedHashMap<K, V>(capacity, DEFAULT_LOAD_FACTORY, false)

  • 第三個參數設置爲true,表明linkedlist按訪問順序排序,可做爲LRU緩存 ;
  • 第三個參數設置爲false,表明按插入順序排序,可做爲FIFO緩存 ;

關於使用LinkedHashMap實現LRU和FIFO

JAVA建立對象的方法有哪些

Java中有5種建立對象的方式,下面給出它們的例子還有它們的字節碼

使用new關鍵字 } → 調用了構造函數
使用Class類的newInstance方法 } → 調用了構造函數
使用Constructor類的newInstance方法 } → 調用了構造函數
使用clone方法 } → 沒有調用構造函數
使用反序列化 } → 沒有調用構造函數
  • 使用Class類的newInstance方法:咱們也能夠使用Class類的newInstance方法建立對象,這個newInstance方法調用無參的構造器建立對象
  • 使用Constructor類的newInstance方法咱們能夠經過這個newInstance方法調用有參數的和私有的構造函數。這兩種newInstance的方法就是你們所說的反射,事實上Class的newInstance方法內部調用Constructor的newInstance方法。
  • 調用一個對象的clone方法,JVM就會建立一個新的對象,將前面的對象的內容所有拷貝進去,用clone方法建立對象並不會調用任何構造函數。要使用clone方法,咱們必須先實現Cloneable接口並實現其定義的clone方法。
  • 使用反序列化:當咱們序列化和反序列化一個對象,JVM會給咱們建立一個單獨的對象,在反序列化時,JVM建立對象並不會調用任何構造函數。爲了反序列化一個對象,咱們須要讓咱們的類實現Serializable接口。

Object 類有哪些方法?

九個方法:clone, getclass, toString, finalize, equals, hashcode, wait, notify, notifyAll

什麼是JAVA序列化

把對象轉換爲字節序列的過程稱爲對象的序列化。
把字節序列恢復爲對象的過程稱爲對象的反序列化。
  對象的序列化主要有兩種用途:
  1) 把對象的字節序列永久地保存到硬盤上,一般存放在一個文件中;
  2) 在網絡上傳送對象的字節序列。

String, stringbuilder, stringbuffer區別

  • 首先說運行速度(特指修改的操做),在這方面運行速度快慢爲:StringBuilder > StringBuffer > String

  String最慢的緣由:

String爲字符串常量,String對象一旦建立以後該對象是不可更改的,Java中對String對象進行的更改操做其實是一個不斷建立新的對象而且將舊的對象回收的一個過程,因此執行速度很慢。而StringBuilder和StringBuffer均爲字符串變量,對變量進行操做就是直接對該對象進行更改,而不進行建立和回收的操做,因此速度要比String快不少。

  • 在線程安全上,StringBuilder是線程不安全的,而StringBuffer是線程安全的。

若是一個StringBuffer對象在字符串緩衝區被多個線程使用時,StringBuffer中不少方法能夠帶有synchronized關鍵字,因此能夠保證線程是安全的,但StringBuilder的方法則沒有該關鍵字,因此不能保證線程安全。

String:適用於少許的字符串操做的狀況

  StringBuilder:適用於單線程下在字符緩衝區進行大量操做的狀況

  StringBuffer:適用多線程下在字符緩衝區進行大量操做的狀況

string的不可變如何實現

一般狀況下,在java中經過如下步驟實現不可變

  1. 對於屬性不提供設值方法
  2. 全部的屬性定義爲private final
  3. 類聲明爲final不容許繼承

若是讀了String 的源碼的話,在Java中String類其實就是對字符數組的封裝, JDK6中, value是String封裝的數組,offset是String在這個value數組中的起始位置,count是String所佔的字符的個數。會發現String 類的全部變量(value,offset和count等)都是 private final 聲明的,且沒有對外提供任何set方法。因此在String類的外部沒法修改String。也就是說一旦初始化就不能修改, 而且在String類的外部不能訪問這三個成員。一旦String初始化了, 也不能被改變。因此能夠認爲String對象是不可變的了。

string爲什麼設計爲不可變

 1. 字符串常量池的須要

字符串常量池(String pool, String intern pool, String保留池) 是Java堆內存中一個特殊的存儲區域, 當建立一個String對象時,假如此字符串值已經存在於常量池中,則不會建立一個新的對象,而是引用已經存在的對象。倘若字符串對象容許改變,那麼將會致使各類邏輯錯誤,好比改變一個對象會影響到另外一個獨立對象. 嚴格來講,這種常量池的思想,是一種優化手段。

2. 容許String對象緩存HashCode

Java中String對象的哈希碼被頻繁地使用, 好比在hashMap 等容器中。字符串不變性保證了hash碼的惟一性,所以能夠放心地進行緩存.這也是一種性能優化手段,意味着沒必要每次都去計算新的哈希碼. 

3. 安全性

String被許多的Java類(庫)用來當作參數,例如 網絡鏈接地址URL,文件路徑path,還有反射機制所須要的String參數等, 倘若String不是固定不變的,將會引發各類安全隱患。

整體來講, String不可變的緣由包括 設計考慮,效率優化問題,以及安全性這三大方面。

 

爲何String被設計爲不可變?

  • 安全:首要緣由是安全,不只僅體如今你的應用中,並且在JDK中,Java的類裝載機制經過傳遞的參數(一般是類名)加載類,這些類名在類路徑下,想象一下,假設String是可變的,一些人經過自定義類裝載機制分分鐘黑掉應用。若是沒有了安全,Java不會走到今天。
  • 性能: string不可變的設計出於性能考慮,固然背後的原理是string pool,固然string pool不可能使string類不可變,不可變的string更好的提升性能。
  • 線程安全: 當多線程訪問時,不可變對象是線程安全的,不須要什麼高深的邏輯解釋,若是對象不可變,線程也不能改變它。

異常

 

Java把異常看成對象來處理,並定義一個基類java.lang.Throwable做爲全部異常的超類。Throwable 下又分爲 Error Exception

  • Error 用來表示 JVM 沒法處理的錯誤

ErrorError類對象由 Java 虛擬機生成並拋出,大多數錯誤與代碼編寫者所執行的操做無關。

最多見的erro是當JVM再也不有繼續執行操做所需的內存資源時,將出現 OutOfMemoryError。這些異常發生時,Java虛擬機(JVM)通常會選擇線程終止;

  • Exception 分爲兩種:
    1. 受檢異常 :須要用 try...catch... 語句捕獲並進行處理,而且能夠從異常中恢復;
    2. 非受檢異常 :是程序運行時錯誤,例如除 0 會引起 Arithmetic Exception,此時程序崩潰而且沒法恢復。

設計模式有哪些

單例模式: (懶漢模式、飢漢模式)
一、構造方法私有化,除了本身類中能建立外其餘地方都不能建立

二、在本身的類中建立一個單實例(飽漢模式是一出來就建立建立單實例,而飢漢模式須要的時候才建立)

三、提供一個方法獲取該實例對象(建立時須要進行方法同步)

餓漢模式

餓漢模式就是當即加載,在方法調用前,實例就已經被建立了,因此是線程安全的。

public class MyObject1 { private static MyObject1 myObject1 = new MyObject1(); private MyObject1() {} public static MyObject1 getInstance() { return myObject1; } }

懶漢模式

懶漢就是延遲化加載,當須要使用的時候才進行實例化。

線程不安全方式:

public class MyObject2 { private static MyObject2 myObject2; private MyObject2() {} public static MyObject2 getInstance() { if (myObject2 == null) { myObject2 = new MyObject2(); } return myObject2; } }

線程安全可是效率低下的方式:

public class MyObject3 { private static MyObject3 myObject3; private MyObject3() {} synchronized public static MyObject3 getInstance() { if (myObject3 == null) { myObject3 = new MyObject3(); } return myObject3; } }

使用DCL雙檢查鎖(雙重檢查鎖定),線程安全並且效率獲得提升,只將進行實例化的代碼進行加鎖:

public class MyObject4 { private volatile static MyObject4 myObject4; private MyObject4() {} public static MyObject4 getInstance() { if (myObject4 == null) { synchronized (MyObject4.class) { if (myObject4 == null) { myObject4 = new MyObject4(); } } } return myObject4; } }

但要注意實例化的對象必定要聲明位volatile型,以禁止指令重排序,不然仍然出現線程安全問題,分析見此

還有其餘初始化的手段,見本博客

工廠模式: Spring IOC就是使用了工廠模式.
       對象的建立交給一個工廠去建立。在工廠方法模式中,核心的工廠類再也不負責全部的產品的建立,而是將具體建立的工做交給子類去作。

抽象工廠模式:抽象工廠模式能夠向客戶端提供一個接口,使客戶端在沒必要指定產品的具體的狀況下,建立多個產品族中的產品對象。

代理模式: Spring AOP就是使用的動態代理。

觀察者模式:在對象之間定義了一對多的依賴,這樣一來,當一個對象改變狀態,依賴它的對象會收到通知並自動更新。

裝飾器模式:在不影響其餘對象的狀況下,以動態、透明的方式給單個對象添加職責,在Java源碼中典型的裝飾器模式就是java I/O, 其實裝飾者模式也有其缺點,就是產生了太多的裝飾者小類,有類爆炸的趨勢。

第五模塊--數據結構:

BST 二叉搜索樹

1 首先它也是一個二叉樹,故知足遞歸定義;

2 需知足 左子樹值<=根值<=右子樹 ,BST的中序遍歷一定是嚴格遞增的

3. 任意節點的左右子樹也分別是二叉查找樹.

4. 沒有鍵值相等的節點.

5. 對於某些狀況,二叉查找樹會退化成一個有n個節點的線性鏈

AVL樹

1. 是帶有平衡條件的二叉查找樹,通常是用平衡因子差值判斷是否平衡並經過旋轉來實現平衡,

2. 左右子樹樹高不超過1,和紅黑樹相比,它是嚴格的平衡二叉樹,平衡條件必須知足(全部節點的左右子樹高度差不超過1).

3. 無論咱們是執行插入仍是刪除操做,只要不知足上面的條件,就要經過旋轉來保持平衡,而旋轉是很是耗時的,由此咱們能夠知道AVL樹適合用於插入刪除次數比較少,但查找多的狀況。 

紅黑樹的六大特徵

本質上是一個平衡搜索二叉樹,和Btree不同,能夠說是AVL樹的變種,犧牲了必定查詢效率,減小了維護平衡的時間。log(n) 中序排序都能獲得遞增序列。

1)每一個結點要麼是紅的,要麼是黑的。(沒有其餘顏色)

2)根結點是黑的

3)每一個葉結點(葉結點即指樹尾端NIL(Nothing In the Leaf)指針或NULL結點)是黑的。(葉子節點即爲樹尾端的NIL指針,或者說NULL節點。)

4)一條路徑上不能出現相鄰的兩個紅色節點

5)對於任一結點而言,其到葉結點(樹尾端NIL指針)的每一條路徑都包含相同數目的黑結點。(最長路徑是最小路徑的2倍)

6)最多三次旋轉,能夠達到平衡

上述條件保證紅黑樹的新增、查找、刪除的最壞時間複雜度均爲O(logN)

經過對任何一條從根到葉子的路徑上各個節點着色的方式的限制,紅黑樹確保沒有一條路徑會比其它路徑長出兩倍.它是一種弱平衡二叉樹(因爲是弱平衡,能夠推出,相同的節點狀況下,AVL樹的高度低於紅黑樹),相對於要求嚴格的AVL樹來講,它的旋轉次數變少,因此對於搜索,插入,刪除操做多的狀況下,咱們就用紅黑樹.

紅黑樹相比於BST和AVL樹有什麼優勢?        

紅黑樹是犧牲了嚴格的高度平衡的優越條件爲代價,它只要求部分地達到平衡要求,下降了對旋轉的要求,從而提升了性能。紅黑樹可以以O(logN)的時間複雜度進行搜索、插入、刪除操做。此外,因爲它的設計,任何不平衡都會在三次旋轉以內解決。固然,還有一些更好的,但實現起來更復雜的數據結構可以作到一步旋轉以內達到平衡,但紅黑樹可以給咱們一個比較「便宜」的解決方案。

       相比於BST,由於紅黑樹能夠能確保樹的最長路徑不大於兩倍的最短路徑的長度,因此能夠看出它的查找效果是有最低保證的。在最壞的狀況下也能夠保證O(logN)的,這是要好於二叉查找樹的。由於二叉查找樹最壞狀況可讓查找達到O(N)。紅黑樹的算法時間複雜度和AVL相同,但統計性能比AVL樹更高,因此在插入和刪除中所作的後期維護操做確定會比紅黑樹要耗時好多

在插入時,紅黑樹和AVL樹都能在至多2次旋轉內恢復平衡;在刪除時,紅黑樹因爲只追求大體上的平衡,能在至多3次旋轉內恢復平衡,而追求絕對平衡的AVL樹至多須要O(logN)次旋轉。

B-樹(B樹、平衡多路樹)

B-tree是一種平衡多路搜索樹(並不必定是二叉的)B-tree樹即B樹

B樹,通常用於外存儲索引結構。系統從磁盤讀取數據到內存時是以磁盤塊(block)爲基本單位的。二叉樹、紅黑樹 [複雜度O(h)]致使樹高度很是高(平衡二叉樹一個節點只能有左子樹和右子樹),邏輯上很近的節點(父子)物理上可能很遠,沒法利用局部性,IO次數多查找慢,效率低

特色:

(1)根節點的左子樹和右子樹的深度最多相差1.(確保了不會出現上圖右邊的極端現象)

(2)是一個平衡多路搜索樹,單個節點能放多個子節點

(3)全部結點都有存儲關鍵字(數據)

(4)不太穩定,可能走不到葉子節點

B+TREE

B+樹是B-樹的變體,也是一種平衡多路搜索樹,非葉子節點只作索引,全部數據都保存在葉子節點中

和B樹區別

1:根節點和分支節點中不保存數據,只用於索引  。數據和索引分離

2:全部數據都保存在葉子節點中

B+的搜索與B-樹也基本相同,區別是B+樹只有達到葉子結點才命中(B-樹能夠在非葉子結點命中),其性能也等價於在關鍵字全集作一次二分查找;

B+Tree內節點去掉了data域,只作索引,減小了io次數,所以能夠擁有更大的出度,擁有更好的性能。只利用索引快速定位數據索引範圍,先定位索引再經過索引高效快速定位數據。

B+樹和B樹的區別

一篇詳盡的解析

爲何用B+樹做爲數據庫索引,而不是平衡樹或者B樹?

  • 爲何平衡二叉樹或者紅黑樹不適合做爲索引

索引是存在於索引文件中,是存在於磁盤中的。由於索引一般是很大的,所以沒法一次將所有索引加載到內存當中,所以每次只能從磁盤中讀取一個磁盤頁的數據到內存中。而這個磁盤的讀取的速度較內存中的讀取速度而言是差了好幾個級別。注意,咱們說的平衡二叉樹結構,指的是邏輯結構上的平衡二叉樹,其物理實現是數組。因爲在邏輯結構上相近的節點在物理結構上可能會差很遠。所以,每次讀取的磁盤頁的數據中有許可能是用不上的。所以,查找過程當中要進行許屢次的磁盤讀取操做。而適合做爲索引的結構應該是儘量少的執行磁盤IO操做,由於執行磁盤IO操做很是的耗時。所以,平衡二叉樹並不適合做爲索引結構。另一點,平衡二叉樹和紅黑樹都是二叉樹,數據量較大的時候,樹的高度也比較高,所以會產生較多的磁盤IOB樹的查詢,因爲每層能夠儲存多個節點,所以能夠做磁盤預讀,將相關的數據一次性讀進內存裏,所以他的查找主要發生在內存中,而平衡二叉樹的查詢,則是發生在磁盤讀取中。所以,雖然B樹查詢查詢的次數不比平衡二叉樹的次數少,可是相比起磁盤IO速度,內存中比較的耗時就能夠忽略不計了。所以,B樹更適合做爲索引。

  • 爲何B+樹比B樹更適合做索引

B樹:有序數組+平衡多叉樹; 
B+樹:有序數組鏈表+平衡多叉樹;

B+樹的關鍵字所有存放在葉子節點中,非葉子節點用來作索引,而葉子節點中有一個指針指向一下個葉子節點。作這個優化的目的是爲了提升區間訪問的性能。而正是這個特性決定了B+樹更適合用來存儲外部數據。

B樹必須用中序遍歷的方法按序掃庫,而B+樹直接從葉子結點挨個掃一遍就完了,B+樹支持範圍查找很是方便,而B樹不支持。這是數據庫選用B+樹的最主要緣由。 好比要查 5-10之間的,B+樹一次找到到5這個標記,再一次找到10這個標記,而後串起來就好了,B樹就很是麻煩。

舉個例子來對比。 
B樹: 

好比說,咱們要查找關鍵字範圍在3到7的關鍵字,在找到第一個符合條件的數字3後,訪問完第一個關鍵字所在的塊後,得遍歷這個B樹,獲取下一個塊,直到遇到一個不符合條件的關鍵字。遍歷的過程是比較複雜的。

B+樹: 

相比之下,B+樹的基於範圍的查詢簡潔不少。因爲葉子節點有指向下一個葉子節點的指針,所以從塊1到塊2的訪問,經過塊1指向塊2的指針便可。從塊2到塊3也是經過一個指針便可。

數據庫索引採用B+樹的主要緣由是B樹在提升了磁盤IO性能的同時並無解決元素遍歷的效率低下的問題。正是爲了解決這個問題,B+樹應運而生。B+樹只要遍歷葉子節點就能夠實現整棵樹的遍歷。並且在數據庫中基於範圍的查詢是很是頻繁的,而B樹不支持這樣的操做(或者說效率過低)

B+樹因爲非葉子節點也有數據,這樣在作範圍查找的時候有可能某個範圍的數據散落在葉子節點和非葉子節點(各層),這樣就須要屢次掃描B樹才能找全數據,會產生屢次的磁盤IO;而B+樹全部的數據都在葉子節點,且葉子節點的數據先後用指針相連,只要經過索引找到範圍數據在葉子節點中的起始或結束位置,就能夠順着葉子節點鏈表把這個範圍內的數據全都讀出來,避免了再次掃描,從而提升範圍查找的效率。

八大排序的特色

選擇排序、快速排序、希爾排序、堆排序不是穩定的排序算法(快選希堆),

冒泡排序、插入排序、歸併排序和基數排序是穩定的排序算法。

希爾排序,也稱遞減增量排序算法,是插入排序的一種更高效的改進版本。但希爾排序是非穩定排序算法。希爾排序的基本思想是:先將整個待排序的記錄序列分割成爲若干子序列分別進行直接插入排序,待整個序列中的記錄「基本有序」時,再對全體記錄進行依次直接插入排序。

鏈表的倒數第K個節點(快排)

樹的深度優先遍歷和廣度優先遍歷

搜索方法有哪些,穩定性怎麼樣

第六模塊--數據庫

Reids 支持的數據類型

Redis支持五種數據類型:

  • string(字符串)
  • hash(哈希)是一個鍵值(key=>value)對集合。
  • list(列表)是簡單的字符串列表,按照插入順序排序
  • set(集合),Set是string類型的無序集合。集合是經過哈希表實現的
  • zset(sorted set:有序集合),是string類型元素的集合,且不容許重複的成員,不一樣的是每一個元素都會關聯一個double類型的分數。redis正是經過分數來爲集合中的成員進行從小到大的排序。

redis的替換策略

替換對象分爲 allkeys 和 volatile,

替換方式分爲LRU、random 和ttl

總的策略 = 2*3 = 6

LRU(Least recently used,最近最少使用)算法根據數據的歷史訪問記錄來進行淘汰數據,其核心思想是「若是數據最近被訪問過,那麼未來被訪問的概率也更高」。

騰訊一面後端問了LRU的算法原理,本身怎麼實現LRU

  • volatile-lru : 對具備生存週期的key(設置失效(expire set)的key)進行LRU算法置換.
  • volatile-random : 對具備生存週期的key進行隨機置換.
  • volatile-ttl : 對具備生存週期的key隨機進行抽樣, 置換出抽樣中生存週期最短的.
  • allkeys-lru : 對整個db進行LRU算法置換.
  • allkeys-random : 對整個db進行隨機置換.
  • noeviction : 不進行置換.

Redis 持久化方法

redis提供兩種方式進行持久化:

  • 一種是RDB(快照)持久化(原理是將Reids在內存中的數據庫記錄定時dump到磁盤上的RDB持久化)
  • 另一種是AOF(append only file)持久化(原理是將Reids的操做日誌以追加的方式寫入文件)

RDB持久化是指在指定的時間間隔內將內存中的數據集快照寫入磁盤,實際操做過程是fork一個子進程,先將數據集寫入臨時文件,寫入成功後,再替換以前的文件,用二進制壓縮存儲。

 

AOF持久化以日誌的形式記錄服務器所處理的每個寫、刪除操做,查詢操做不會記錄,以文本的方式記錄,能夠打開文件看到詳細的操做記錄。

何時用RDB何時用AOF:

備份、全量複製時使用RDB;須要實時備份的時候採用AOF

RDB的優勢

  • RDB是一個緊湊壓縮的二進制文件, 表明Redis在某個時間點上的數據快照。 很是適用於備份, 全量複製等場景。 好比每6小時執行bgsave備份,並把RDB文件拷貝到遠程機器或者文件系統中(如hdfs) , 用於災難恢復。
  • Redis加載RDB恢復數據遠遠快於AOF的方式。

 

RDB的缺點·

  • RDB方式數據沒辦法作到實時持久化/秒級持久化。 由於bgsave每次運行都要執行fork操做建立子進程, 屬於重量級操做, 頻繁執行成本太高。
  • RDB文件使用特定二進制格式保存, Redis版本演進過程當中有多個格式RDB版本, 存在老版本Redis服務沒法兼容新版RDB格式的問題。

AOF: 經過追加寫命令到文件實現持久化, 經過appendfsync參數能夠控制實時/秒級持久化。 由於須要不斷追加寫命令, 因此AOF文件體積逐漸變大,

         須要按期執行重寫操做來下降文件體積。 Redis執行AOF恢復數據遠遠慢於加載RDB的方式。

Redis其餘知識:

1)Redis提供5種數據結構,每種數據結構都有多種內部編碼實現
2)純內存存儲、IO多路複用技術、單線程架構是造就Redis高性能的三個因素
3)因爲Redis的單線程架構,因此須要每一個命令能被快速執行完,不然會存在阻塞Redis的可能,理解Redis單線程命令處理機制是開發和運維Redis的核心之一。
4)批量操做(例如mget、mset、hmset等)可以有效提升命令執行的效率,但要注意每次批量操做的個數和字節數。
5)瞭解每一個命令的時間複雜度在開發中相當重要,例如在使用keys、hgetall、smembers、zrange等時間複雜度較高的命令時,須要考慮數據規模對於Redis的影響。
6)persist命令能夠刪除任意類型鍵的過時時間,可是set命令也會刪除字符串類型鍵的過時時間,這在開發時容易被忽視。
7)move、dump+restore、migrate是Redis發展過程當中三種遷移鍵的方式,其中move命令基本廢棄,migrate命令用原子性的方式實現了dump+restore,而且支持批量操做,是Redis Cluster實現水平擴容的重要工具。
8)scan命令能夠解決keys命令可能帶來的阻塞問題,同時Redis還提供了hscan、sscan、zscan漸進式地遍歷hash、set、zset。

Redis應用場景:

1. 緩存功能

Redis做爲緩存層,MySQL做爲存儲層,絕大部分請求的熱點數據都從Redis中獲取。因爲Redis具備支撐高併發的特性,因此緩存一般能起到加速讀寫和下降後端壓力的做用。

2. 限速

爲了短信接口不被頻繁訪問,會限制用戶每分鐘獲取驗證碼的頻率,例如一分鐘不能超過5次。此功能能夠使用Redis來實現,利用Redis能夠設置鍵過時的功能,在用戶首次訪問時爲其新建一個帶有過時時間的key(如60秒),在這個key過時以前,都不容許用戶再次申請短信驗證碼。

3. 消息隊列

Redis的lpush+brpop命令組合便可實現阻塞隊列,生產者客戶端使用lrpush從列表左側插入元素,多個消費者客戶端使用brpop命令阻塞式的「搶」列表尾部的元素,多個客戶端保證了消費的負載均衡和高可用性。

4. 用戶標籤系統

集合類型比較典型的使用場景是標籤(tag)。例如一個用戶可能對娛樂、體育比較感興趣,另外一個用戶可能對歷史、新聞比較感興趣,這些興趣點就是標籤。能夠經過redis set找出集合的交集並集等

5. 排行榜系統

有序集合比較典型的使用場景就是排行榜系統。例如視頻網站須要對用戶上傳的視頻作排行榜,榜單的維度多是多個方面的:按照時間、按照播放數量、按照得到的贊數。

 

http://www.javashuo.com/article/p-bzhisjxt-bn.html

數據庫事物特性(acid)

數據庫事務必須具有ACID特性,ACID是Atomic原子性,Consistency一致性,Isolation隔離性,Durability持久性

MySQL的架構

  • MySQL 主要分爲 Server 層引擎層,Server 層主要包括鏈接器、查詢緩存、分析器、優化器、執行器,同時還有一個日誌模塊(binlog),這個日誌模塊全部執行引擎均可以共用,redolog 只有 InnoDB 有。

鏈接器: 身份認證和權限相關(登陸 MySQL 的時候)。
查詢緩存: 執行查詢語句的時候,會先查詢緩存(MySQL 8.0 版本後移除,由於這個功能不太實用)。
分析器: 沒有命中緩存的話,SQL 語句就會通過分析器,分析器說白了就是要先看你的 SQL 語句要幹嗎,再檢查你的 SQL 語句語法是否正確。
優化器: 按照 MySQL 認爲最優的方案去執行。
執行器: 執行語句,而後從存儲引擎返回數據。

  • 引擎層是插件式的,目前主要包括,MyISAM,InnoDB,Memory 等。

一條SQL語句的執行過程

SQL 等執行過程分爲兩類

    • 對於查詢等過程以下:權限校驗—》查詢緩存—》分析器—》優化器—》權限校驗—》執行器—》引擎
    • 對於更新等語句執行流程以下:分析器----》權限校驗----》執行器—》引擎—redo log prepare—》binlog—》redo log commit

內鏈接、外鏈接 與 左鏈接、右鏈接

驅動表與被驅動表:

在兩錶鏈接查詢中,驅動表只須要訪問一次,被驅動表可能被訪問屢次。

內鏈接與外鏈接:

  • 對於內鏈接的兩個表,驅動表中的記錄在被驅動表中找不到匹配的記錄,該記錄不會加入到最後的結果集,咱們上邊提到的鏈接都是所謂的內鏈接
  • 對於外鏈接的兩個表,驅動表中的記錄即便在被驅動表中沒有匹配的記錄,也仍然須要加入到結果集。在MySQL中,根據選取驅動表的不一樣,外鏈接仍然能夠細分爲2種:
    • 左外鏈接     選取左側的表爲驅動表
    • 右外鏈接     選取右側的表爲驅動表

過濾條件where 和 on:

  • WHERE子句中的過濾條件

    WHERE子句中的過濾條件就是咱們平時見的那種,不管是內鏈接仍是外鏈接,凡是不符合WHERE子句中的過濾條件的記錄都不會被加入最後的結果集。

  • ON子句中的過濾條件

    對於外鏈接的驅動表的記錄來講,若是沒法在被驅動表中找到匹配ON子句中的過濾條件的記錄,那麼該記錄仍然會被加入到結果集中,對應的被驅動表記錄的各個字段使用NULL值填充。

    須要注意的是,這個ON子句是專門爲外鏈接驅動表中的記錄在被驅動表找不到匹配記錄時應不該該把該記錄加入結果集這個場景下提出的,因此若是把ON子句放到內鏈接中,MySQL會把它和WHERE子句同樣對待,也就是說:內鏈接中的WHERE子句和ON子句是等價的。

通常狀況下,咱們都把只涉及單表的過濾條件放到WHERE子句中,把涉及兩表的過濾條件都放到ON子句中,咱們也通常把放到ON子句中的過濾條件也稱之爲鏈接條件

左鏈接與右鏈接:

左鏈接和右鏈接是左外鏈接和右外鏈接簡稱, 只有外鏈接才分左右,且外鏈接必定會分左右

能夠據此來分辨內鏈接和外鏈接:

若是join語句前面有left或者right, 則必定是外鏈接,此時on的語義與where的語義不一樣;

若是join語句前面沒有left或者right, 則必定是內鏈接,此時on的語義與where相同。

標識內鏈接和外鏈接的關鍵字inner|cross 和 outer都是能夠省略的。

什麼是事務?

事務指的是邏輯上的一組操做,這組操做要麼所有發生,要麼所有失敗

舉例 : 張三和李四 進行 轉帳的操做  

張三向轉帳李四 1000元  張三餘額-1000元  李四餘額+1000元

不該該出現的是  在轉帳過程當中因爲一些意外,使張三的餘額減去了1000元, 而李四並無收到這筆錢。  使用事務來進行管理。  必須一塊兒成功或者一塊兒失敗

事務四大特性(ACID)

一、原子性:事務包含的全部數據庫操做要麼所有成功,要不所有失敗回滾

二、一致性:一個事務執行以前和執行以後都必須處於一致性狀態。拿轉帳來講,假設用戶A和用戶B二者的錢加起來一共是5000,那麼無論A和B之間如何轉帳,轉幾回帳,事務結束後兩個用戶的錢相加起來應該還得是5000,這就是事務的一致性。

三、隔離性:一個事務未提交的業務結果是否對於其它事務可見。級別通常有:read_uncommit,read_commit,read_repeatable,串行化訪問。

四、持久性:一個事務一旦被提交了,那麼對數據庫中數據的改變就是永久性的,即使是在數據庫系統遇到故障的狀況下也不會丟失提交事務的操做。

什麼是髒數據,髒讀,不可重複讀,幻覺讀

一、髒數據所指的就是未提交的數據。。也就是說,一個事務正在對一條記錄作修改,在這個事務完成並提交以前,這條數據是處於待定狀態的(可能提交也可能回滾),這時,第二個事務來讀取這條沒有提交的數據,並據此作進一步的處理,就會產生未提交的數據依賴關係。一個事務中訪問到了另一個事務未提交的數據,這種現象被稱爲髒讀

二、不可重複讀(Non-Repeatable Reads):一個事務前後讀取同一條記錄,而事務在兩次讀取之間該數據被其它事務所修改,則兩次讀取的數據不一樣,咱們稱之爲不可重複讀。所謂不可重複讀是指在一個事務內根據同一個條件對行記錄進行屢次查詢,可是搜出來的結果卻不一致。發生不可重複讀的緣由是在屢次搜索期間查詢條件覆蓋的數據被其餘事務修改了。

三、幻讀(Phantom Reads):一個事務按相同的查詢條件從新讀取之前檢索過的數據,卻發現其餘事務插入了知足其查詢條件的新數據,這種現象就稱爲幻讀。

四、所謂幻讀是指同一個事務內屢次查詢返回的結果集不同(好比增長了或者減小了行記錄)。好比同一個事務A內第一次查詢時候有n條記錄,可是第二次同等條件下查詢卻又n+1條記錄,這就好像產生了幻覺,爲啥兩次結果不同那。其實和不可重複讀同樣,發生幻讀的緣由也是另一個事務新增或者刪除或者修改了第一個事務結果集裏面的數據。不一樣在於不可重複讀是同一個記錄的數據內容被修改了,幻讀是數據行記錄變多了或者少了。

數據庫隔離級別:

數據庫事務的隔離級別有4個,由低到高依次爲Read uncommitted 、Read committed 、Repeatable read 、Serializable ,這四個級別能夠逐個解決髒讀 、不可重複讀 、幻讀 這幾類問題。

√: 可能出現    ×: 不會出現

 

髒讀

不可重複讀

幻讀

Read uncommitted

Read committed

×

Repeatable read

×

×

Serializable

×

×

×

當隔離級別設置爲Read uncommitted 時,就可能出現髒讀;

當隔離級別設置爲Read committed 時,避免了髒讀,可是可能會形成不可重複讀。大多數數據庫的默認級別就是Read committed,好比Sql Server , Oracle。

當隔離級別設置爲Repeatable read 時,能夠避免不可重複讀。

Mysql的默認隔離級別就是Repeatable read。

Serializable 是最高的事務隔離級別,同時代價也花費最高,性能很低,通常不多使用,在該級別下,事務順序執行,不只能夠避免髒讀、不可重複讀,還避免了幻讀。

MVCC的原理 | MySQL 的事務是怎樣實現的?

http://www.javashuo.com/article/p-nylgqcxo-bu.html

1. 未提交讀隔離級別老是讀取最新的數據行,無需使用 MVCC;

2. 提交讀和可重複讀這兩種隔離級別, MySQL 的 InnoDB 存儲引擎用 多版本併發控制(Multi-Version Concurrency Control, MVCC) 實現;

3. 可串行化隔離級別須要對全部讀取的行都加鎖。

MVCC的原理

 MVCC經過版本號記錄隱藏的兩個列(建立版本號、刪除版本號)和Undo日誌(經過回滾指針將全部的快照鏈接成版本鏈)

版本號

  • 系統版本號:是一個遞增的數字,每開始一個新的事務,系統版本號就會自動遞增。
  • 事務版本號:事務開始時的系統版本號。

隱藏的列

MVCC 在每行記錄後面都保存着兩個隱藏的列,用來存儲兩個版本號:

  • 建立版本號:指示建立一個數據行的快照時的系統版本號;
  • 刪除版本號:若是該快照的刪除版本號大於當前事務版本號表示該快照有效,不然表示該快照已經被刪除了。

Undo 日誌

每次對記錄進行改動,都會記錄一條undo日誌。MVCC 使用到的快照存儲在 Undo 日誌中,該日誌經過回滾指針把一個數據行(Record)的全部快照鏈接起來。

 

對於使用READ COMMITTEDREPEATABLE READ隔離級別的事務來講,都必須保證讀到已經提交了的事務修改過的記錄,也就是說假如另外一個事務已經修改了記錄可是還沒有提交,是不能直接讀取最新版本的記錄的,核心問題就是:須要判斷一下版本鏈中的哪一個版本是當前事務可見的。爲此,InnoDB提出了一個ReadView的概念。

還須要讀掘金小冊的相關部分

MySQL 主從複製的原理以及流程

複製基本原理流程

1. 主:binlog線程——記錄下全部改變了數據庫數據的語句,放進master上的 binlog中;
2. 從:io線程——在使用start slave 以後,負責從master上拉取 binlog 內容,放進 本身的 relay log中;
3. 從:sql執行線程——執行relay log中的語句;

解釋數據庫設計三大範式

爲了創建冗餘較小、結構合理的數據庫,設計數據庫時必須遵循必定的規則。在關係型數據庫中這種規則就稱爲範式。

1.第一範式(確保每列保持原子性)

第一範式是最基本的範式。若是數據庫表中的全部字段值都是不可分解的原子值,就說明該數據庫表知足了第一範式。

第一範式的合理遵循須要根據系統的實際需求來定。好比某些數據庫系統中須要用到「地址」這個屬性,原本直接將「地址」屬性設計成一個數據庫表的字段就行。可是若是系統常常會訪問「地址」屬性中的「城市」部分,那麼就非要將「地址」這個屬性從新拆分爲省份、城市、詳細地址等多個部分進行存儲,這樣在對地址中某一部分操做的時候將很是方便。

2.第二範式(確保表中的每列都和主鍵相關)

第二範式在第一範式的基礎之上更進一層。第二範式須要確保數據庫表中的每一列都和主鍵相關,而不能只與主鍵的某一部分相關(主要針對聯合主鍵而言)。也就是說在一個數據庫表中,一個表中只能保存一種數據,不能夠把多種數據保存在同一張數據庫表中。

3.第三範式(確保每列都和主鍵列直接相關,而不是間接相關)

第三範式須要確保數據表中的每一列數據都和主鍵直接相關,而不能間接相關。

好比在設計一個訂單數據表的時候,能夠將客戶編號做爲一個外鍵和訂單表創建相應的關係。而不能夠在訂單表中添加關於客戶其它信息(好比姓名、所屬公司等)的字段。

數據庫索引

索引是關係型數據庫中給數據庫表中一列或多列的值排序後的存儲結構,彙集索引以及非彙集索引用的是B+樹索引。

MySQL 索引類型有:惟一索引,主鍵(彙集)索引,非彙集索引,全文索引

彙集(clustered)索引,也叫聚簇索引,MySQL裏主鍵就是彙集索引

定義:數據行的物理順序與列值(通常是主鍵的那一列)的邏輯順序相同,一個表中只能擁有一個彙集索引。查詢方面,彙集索引的速度每每會更佔優點。

數據行的物理順序與列值的順序相同,若是咱們查詢id比較靠後的數據,那麼這行數據的地址在磁盤中的物理地址也會比較靠後。並且因爲物理排列方式與彙集索引的順序相同,因此也就只能創建一個彙集索引了。

非彙集(unclustered)索引。

定義:該索引中索引的邏輯順序與磁盤上行的物理存儲順序不一樣,一個表中能夠擁有多個非彙集索引。

非彙集索引的二次查詢問題

非彙集索引葉節點仍然是索引節點,只是有一個指針指向對應的數據塊,此若是使用非彙集索引查詢,而查詢列中包含了其餘該索引沒有覆蓋的列,那麼他還要進行第二次的查詢,查詢節點上對應的數據行的數據。 

SQL語句查詢太慢怎麼找緣由、優化?

http://www.javashuo.com/article/p-fqpbkjqn-ca.html

分類:

一、大多數狀況是正常的,只是偶爾會出現很慢的狀況。

二、在數據量不變的狀況下,這條SQL語句一直以來都執行的很慢。

偶發性:

a. 數據庫在刷新髒頁。例如 redo log 寫滿了須要同步到磁盤。

b. 拿不到鎖。要執行的這條語句涉及到的表,別人在用,而且加鎖了,咱們拿不到鎖,只能慢慢等待別人釋放鎖。要判斷是否真的在等待鎖,咱們能夠用 show processlist這個命令來查看當前的狀態

常常性:

a. 沒用到索引

b. 索引失效 (能夠用explain 看一下這條語句,查看possible-key字段看一下可能用到的索引,key字段查看實際用到的索引)

c. 數據庫索引選擇錯誤。MySQL在執行一條語句的時候會經過分析器估計各類查詢計劃的查詢代價,而後在其中選擇代價最小的執行。若是分析器估計走索引的代價比較大,可能就放棄索引而走全表掃描。但分析器的代價估計有多是不許確的(涉及到一個索引區分度的概念,這個區分度是用採樣來統計的,採樣會有誤差)。  解決方法, 能夠強制走索引force index(a); 能夠強制數據庫從新統計索引區分度: analyze table t.

能夠解釋一下explain 一個查詢語句後的一些字段嗎?

一條查詢語句在通過MySQL查詢優化器的各類基於成本和規則的優化會後生成一個所謂的執行計劃。

explain 就是用來查看一條查詢語句的執行計劃的。

查詢計劃由以下幾個關鍵的字段:

id : 在一個大的查詢語句中每一個SELECT關鍵字都對應一個惟一的id

table: 該查詢語句涉及的表名

type: 針對單表訪問的方法 (有 const[主鍵或者惟一二級索引列與常數進行等值匹配]、ref[普通的二級索引列與常量進行等值匹配]、all[全表掃描]、index[能夠使用索引覆蓋,但須要掃描所有的索引記錄]等)。

possible_key:該查詢可能用到的索引。

key : 該查詢實際用到的索引。

rows: 執行該查詢計劃須要掃描的行數。(若是查詢優化器決定使用全表掃描的方式對某個表執行查詢時,執行計劃的rows列就表明預計須要掃描的行數,若是使用索引來執行查詢時,執行計劃的rows列就表明預計掃描的索引記錄行數)

如何查看執行計劃的執行成本?

EXPLAIN單詞和真正的查詢語句中間加上FORMAT=JSON

這樣咱們就能夠獲得一個json格式的執行計劃,裏邊兒包含該計劃花費的成本

聯合索引和單列索引的區別是什麼?

https://www.cnblogs.com/greatLong/articles/11573588.html

聯合索引本質:

是對多個列創建一個索引,稱爲聯合索引。當建立(a,b,c)聯合索引時,至關於建立了(a)單列索引(a,b)聯合索引以及(a,b,c)聯合索引。因此只有查詢條件知足a 或者 (a,b) 或者 (a,b,c)的時候,纔會用上聯合索引。這就是所謂的最左匹配原則。意思是 以最左邊的爲起點任何連續的索引都能匹配上。

單列索引只是對一個字段創建索引。

MySQL索引失效的狀況有哪些?

1.若是條件中有or,假如or鏈接的兩個查詢條件字段中有一個沒有索引的話,引擎會放棄索引而產生全表掃描。

2.對於多列索引,不是使用的第一部分(第一個),則不會使用索引

3.like查詢是以%開頭(以%結尾的狀況能夠使用)

4. 查詢時,採用is null條件時,不能利用到索引,只能全表掃描

5. 若是列類型是字符串,那必定要在條件中將數據使用引號引用起來,不然不使用索引

6.若是mysql估計使用全表掃描要比使用索引快,則不使用索引

7. 查詢條件使用函數在索引列上,或者對索引列進行運算,運算包括(+,-,*,/,! 等) 錯誤的例子:select * from test where id-1=9; 正確的例子:select * from test where id=10;

8. 使用IN 關鍵字進行範圍查找,有可能不走索引(IN 的 範圍裏面有超過200個單點區間的時候會放棄索引,由於這個時候優化器對這200個單點區間做成本估計的成本很高。MySQL 5.7.3以前這個單點區間的上限是10, 5.7.3以後上限是200)

如何提升數據庫查詢效率

a. 對查詢進行優化,應儘可能避免全表掃描,首先應考慮在 where 及 order by 涉及的列上創建索引;

b. 應儘可能避免在 where 子句中對字段進行 null 值判斷,不然將致使引擎放棄使用索引而進行全表掃描;

c. 索引並非越多越好,索引當然能夠提升相應的 select 的效率,但同時也下降了 insert 及 update 的效率,由於 insert 或 update 時有可能會重建索引,因此怎樣建索引須要慎重考慮,視具體狀況而定。

d. 應儘可能避免在 where 子句中使用!=或<>操做符,不然將引擎放棄使用索引而進行全表掃描

數據庫能夠有幾個彙集索引

一個,主鍵是彙集索引,數據行的物理順序與列值(通常是主鍵的那一列)的邏輯順序相同

哪些引擎支持彙集索引

InnoDB索引MyISAM索引的區別:

一是主索引的區別,InnoDB的數據文件自己就是索引文件。而MyISAM的索引和數據是分開的。

二是輔助索引的區別:InnoDB的輔助索引data域存儲相應記錄主鍵的值而不是地址。而MyISAM的輔助索引和主索引沒有多大區別。 

簡單的SQL語句(更新)

如何建立索引

MySQL中innodb表主鍵設計原則

InnoDB主鍵設計的原則:

1. 必定要顯式定義主鍵
2. 採用與業務無關的單獨列
3. 採用自增列
4. 數據類型採用int,並儘量小,能用tinyint就不用int,能用int就不用bigint
5. 將主鍵放在表的第一列


這樣設計的緣由:
1. 在innodb引擎中只能有一個彙集索引,咱們知道,彙集索引的葉子節點上直接存有行數據,因此彙集索引列儘可能不要更改,而innodb表在有主鍵時會自動將主鍵設爲彙集索引,若是不顯式定義主鍵,會選第一個沒有null值的惟一索引做爲彙集索引,惟一索引涉及到的列內容不免被修改引起存儲碎片且可能不是遞增關係,存取效率低,因此最好顯式定義主鍵且採用與業務無關的列以免修改;若是這個條件也不符合,就會自動添加一個不可見不可引用的6byte大小的rowid做爲彙集索引

2. 需採用自增列來使數據順序插入,新增數據順序插入到當前索引的後面,符合葉子節點的分裂順序,性能較高;若不用自增列,數據的插入近似於隨機,插入時須要插入到如今索引頁的某個中間位置,須要移動數據,形成大量的數據碎片,索引結構鬆散,性能不好

3. 在主鍵插入時,會判斷是否有重複值,因此儘可能採用較小的數據類型,以減少比對長度提升性能,且能夠減少存儲需求,磁盤佔用小,進而減小磁盤IO和內存佔用;並且主鍵存儲佔用小,普通索引的佔用也相應較小,減小佔用,減小IO,且存儲索引的頁中能包含較多的數據,減小頁的分裂,提升效率

本身的項目中:取三維模型的名稱+描述+標籤組成的字符串,用MD5或者SHA1計算哈希碼做爲主鍵。哈希碰撞的機率極小。或者直接使用自增的序號做爲主鍵,索引的效率比較高,可是也存在兩個問題: 1. 若是要從庫中刪除一條記錄,會引發整個索引的從新計算;2. 表中的其餘列不能和主鍵直接相關,不符合數據庫設計的第三範式;

數據庫主鍵和外鍵

數據庫

InnoDB, MyISAM和Memory, 默認InnoDB, 支持事務;memory徹底在內存中,除了大小限制還有斷電丟失;

  • redishash算法用的是啥

一致性哈希算法

  • nosql爲啥比sql

nosql不須要知足sql關係數據庫數據一致性等複雜特性,非關係型通常是緩存數據庫,數據加載到內存中天然更快。redis是單線程執行的,任務都放到隊列中。

  • 常見關係型數據庫:

Oracle、Microsoft Access、MySQL

  • 常見非關係型數據庫:

MongoDb、redis、HBase

  • 關係型數據庫和非關係型數據庫的區別與聯繫:

非關係型數據庫中,咱們查詢一條數據,結果出來一個數組;關係型數據庫中,查詢一條數據結果是一個對象。

數據庫

類型

特性

優勢

缺點

關係型數據庫

SQLite、Oracle、mysql

一、關係型數據庫,是指採用了關係模型來組織

數據的數據庫;

二、關係型數據庫的最大特色就是事務的一致性

三、簡單來講,關係模型指的就是二維表格模型,

而一個關係型數據庫就是由二維表及其之間的聯繫所組成的一個數據組織。

一、容易理解:二維表結構是很是貼近邏輯世界一個概念,關係模型相對網狀、層次等其餘模型來講更容易理解;

二、使用方便:通用的SQL語言使得操做關係型數據庫很是方便;

三、易於維護:豐富的完整性(實體完整性、參照完整性和用戶定義的完整性)大大減低了數據冗餘和數據不一致的機率;

四、支持SQL,可用於複雜的查詢。

一、爲了維護一致性所付出的巨大代價就是其讀寫性能比較差;

二、固定的表結構;

三、高併發讀寫需求;

四、海量數據的高效率讀寫;

非關係型數據庫

MongoDb、redis、HBase

一、使用鍵值對存儲數據;

2、分佈式;

3、通常不支持ACID特性;

4、非關係型數據庫嚴格上不是一種數據庫,應該是一種數據結構化存儲方法的集合。

一、無需通過sql層的解析,讀寫性能很高;

二、基於鍵值對,數據沒有耦合性,容易擴展;

三、存儲數據的格式:nosql的存儲格式是key,value形式、文檔形式、圖片形式等等,文檔形式、圖片形式等等,而關係型數據庫則只支持基礎類型。

一、不提供sql支持,學習和使用成本較高;

二、無事務處理,附加功能bi和報表等支持也很差;

 

注1:數據庫事務必須具有ACID特性,ACID是Atomic原子性,Consistency一致性,Isolation隔離性,Durability持久性

注2:數據的持久存儲,尤爲是海量數據的持久存儲,仍是須要一種關係數據庫。

 Myisam 和innodb的區別

  1. MyISAM:它是基於傳統的ISAM類型,ISAM是Indexed Sequential Access Method (有索引的順序訪問方法) 的縮寫,它是存儲記錄和文件的標準方法。不是事務安全的,並且不支持外鍵若是執行大量的select,insert MyISAM比較適合。
  2. InnoDB:支持事務安全的引擎,支持外鍵、行鎖、事務是他的最大特色。若是有大量的update和insert,建議使用InnoDB,特別是針對多個併發和QPS較高的狀況。
  3. MyISAM索引實現:MyISAM索引文件和數據文件是分離的,索引文件僅保存數據記錄的地址。

  4. 在InnoDB中,表數據文件自己就是按B+Tree組織的一個索引結構,這棵樹的葉節點data域保存了完整的數據記錄。這個索引的key是數據表的主鍵,所以InnoDB表數據文件自己就是主索引

第七模塊 高併發與分佈式

什麼是負載均衡?有哪些經常使用的負載均衡策略?

負載均衡

由一個獨立的統一入口來收斂流量(接收請求),再由作二次分發的過程就是「負載均衡」。

負載均衡實現方法的分類

根據實現技術不一樣,可分爲DNS負載均衡,HTTP負載均衡,IP負載均衡,反向代理負載均衡、鏈路層負載均衡等。

HTTP重定向負載均衡

當用戶向服務器發起請求時,請求首先被集羣調度者截獲;調度者根據某種分配策略,選擇一臺服務器,並將選中的服務器的IP地址封裝在HTTP響應消息頭部的Location字段中,並將響應消息的狀態碼設爲302,最後將這個響應消息返回給瀏覽器。

當瀏覽器收到響應消息後,解析Location字段,並向該URL發起請求,而後指定的服務器處理該用戶的請求,最後將結果返回給用戶。

缺點:

調度服務器只在客戶端第一次向網站發起請求的時候起做用。當調度服務器向瀏覽器返回響應信息後,客戶端此後的操做都基於新的URL進行的(也就是後端服務器),此後瀏覽器就不會與調度服務器產生關係。

  • 因爲不一樣用戶的訪問時間、訪問頁面深度有所不一樣,從而每一個用戶對各自的後端服務器所形成的壓力也不一樣。而調度服務器在調度時,沒法知道當前用戶將會對服務器形成多大的壓力,所以這種方式沒法實現真正意義上的負載均衡,只不過是把請求次數平均分配給每臺服務器罷了
  • 若分配給該用戶的後端服務器出現故障,而且若是頁面被瀏覽器緩存,那麼當用戶再次訪問網站時,請求都會發給出現故障的服務器,從而致使訪問失敗。作不到高可用。

DNS負載均衡

當用戶向咱們的域名發起請求時,DNS服務器會自動地根據咱們事先設定好的調度策略選一個合適的IP返回給用戶,用戶再向該IP發起請求。它的做用與HTTP重定向相似。問題是DNS會緩存IP,若是某一IP不可用(如機器故障)會致使部分用戶沒法正常訪問,此時能夠用動態DNS解決。

反向代理負載均衡

用戶發來的請求都首先要通過反向代理服務器,服務器根據用戶的請求要麼直接將結果返回給用戶,要麼將請求交給後端服務器處理,再返回給用戶。

優勢

  • 隱藏後端服務器。與HTTP重定向相比,反向代理可以隱藏後端服務器,全部瀏覽器都不會與後端服務器直接交互,從而可以確保調度者的控制權,提高集羣的總體性能。
  • 故障轉移。與DNS負載均衡相比,反向代理可以更快速地移除故障結點。當監控程序發現某一後端服務器出現故障時,可以及時通知反向代理服務器,並當即將其刪除。
  • 合理分配任務 。HTTP重定向和DNS負載均衡都沒法實現真正意義上的負載均衡,也就是調度服務器沒法根據後端服務器的實際負載狀況分配任務。但反向代理服務器支持手動設定每臺後端服務器的權重。咱們能夠根據服務器的配置設置不一樣的權重,權重的不一樣會致使被調度者選中的機率的不一樣。
  • 像安全防禦、故障轉移等都是反向代理纔有的好處。

缺點

  • 調度者壓力過大 。因爲全部的請求都先由反向代理服務器處理,那麼當請求量超過調度服務器的最大負載時,調度服務器的吞吐率下降會直接下降集羣的總體性能。
  • 制約擴展。當後端服務器也沒法知足巨大的吞吐量時,就須要增長後端服務器的數量,可沒辦法無限量地增長,由於會受到調度服務器的最大吞吐量的制約。

負載均衡的做用

1.解決併發壓力,提升應用處理性能(增長吞吐量,增強網絡處理能力);

2.提供故障轉移,實現高可用

3.經過添加或減小服務器數量,提供網站伸縮性(擴展性);

4.安全防禦;(負載均衡設備上作一些過濾,黑白名單等處理)

常見的負載均衡策略

1. 輪詢 

2. 加權輪詢

3. 最少鏈接次數

4. 最快響應

5. 源地址Hash法

第七模塊--算法題

LRU原理及其實現

 JAVA實現LRU 

LRU(Least recently used,最近最少使用)算法根據數據的歷史訪問記錄來進行淘汰數據,其核心思想是「若是數據最近被訪問過,那麼未來被訪問的概率也更高」。

實現

最多見的實現是使用一個鏈表保存緩存數據,詳細算法實現以下: 
這裏寫圖片描述 
1. 新數據插入到鏈表頭部; 
2. 每當緩存命中(即緩存數據被訪問),則將數據移到鏈表頭部; 
3. 當鏈表滿的時候,將鏈表尾部的數據丟棄。 
分析 
【命中率】 
當存在熱點數據時,LRU的效率很好,但偶發性的、週期性的批量操做會致使LRU命中率急劇降低,緩存污染狀況比較嚴重。 
【複雜度】 
實現簡單。 
【代價】 
命中時須要遍歷鏈表,找到命中的數據塊索引,而後須要將數據移到頭部。

證實一個數是2的N次方

(value & (value -1)) == 0;

 Top K 問題

找出一個大數組裏面前K個最大數,如1億個數字中找出最大或最小的前100個數字(考慮判重和內存空間限制)?

        若是不判重,能夠將前100個數字作成最大堆或最小堆的數據結構(找最大的Top100用最小堆, 找最小的Top100用最大堆), 而後依次遍歷全部數字, 符合條件時替換根節點後並從新構建堆。堆的缺點是容許有重複數字!!!

        若是要判重,則建立一個100個空間的空數組, 遍歷1億個數字依次插入值(按照升序), 使用二分查找算法判斷新值在數組中的位置並插入, 該數組最多容納100個值。 當有101個值時先判重, 若是重複繼續向後遍歷, 若是值大於數組最小值則插入到指定位置,數組第一個元素移出數組, 由於數組是連續的,全部能夠用內存拷貝方式賦值,只有新插入的值要單獨賦值到對應的下標(原理相似於android.util.SparseArray)。   因內存拷貝可認爲不佔用時間, 該思路的總會時間複雜度是O(1億*log100), log100是二分查找的複雜度。

關於大數據處理的相關問題這個博客講的很是好

熱詞統計問題

搜索引擎會經過日誌文件把用戶每次檢索使用的全部檢索串都記錄下來,每一個查詢串的長度爲1-255字節。假設目前有一千萬個記錄(這些查詢串的重複度比較高,雖然總數是1千萬,但若是除去重複後,不超過3百萬個。一個查詢串的重複度越高,說明查詢它的用戶越多,也就是越熱門。),請你統計最熱門的10個查詢串,要求使用的內存不能超過1G

若是隻有一臺設備:

第一步:Query統計 (統計出每一個Query出現的次數)         Query統計有如下兩個個方法

一、直接排序法

 首先咱們最早想到的的算法就是排序了,首先對這個日誌裏面的全部Query都進行排序,而後再遍歷排好序的Query,統計每一個Query出現的次數了。

可是題目中有明確要求,那就是內存不能超過1G,一千萬條記錄,每條記錄是255Byte,很顯然要佔據2.375G內存,這個條件就不知足要求了。

讓咱們回憶一下數據結構課程上的內容,當數據量比較大並且內存沒法裝下的時候,咱們能夠採用外排序的方法來進行排序,這裏咱們能夠採用歸併排序,由於歸併排序有一個比較好的時間複雜度O(NlgN)。排完序以後咱們再對已經有序的Query文件進行遍歷,統計每一個Query出現的次數,再次寫入文件中。綜合分析一下,排序的時間複雜度是O(NlgN),而遍歷的時間複雜度是O(N),所以該算法的整體時間複雜度就是O(N+NlgN)=O(NlgN)。

二、Hash Table法  (這種方法統計字符串出現的次數很是好)  在第1個方法中,咱們採用了排序的辦法來統計每一個Query出現的次數,時間複雜度是NlgN,那麼能不能有更好的方法來存儲,而時間複雜度更低呢?

       題目中說明了,雖然有一千萬個Query,可是因爲重複度比較高,所以事實上只有300萬的Query,每一個Query 255Byte,所以咱們能夠考慮把他們都放進內存中去,而如今只是須要一個合適的數據結構,在這裏,Hash Table絕對是咱們優先的選擇,由於Hash Table的查詢速度很是的快,幾乎是O(1)的時間複雜度。

那麼,咱們的算法就有了:

維護一個Key爲Query字串,Value爲該Query出現次數的HashTable,每次讀取一個Query,若是該字串不在Table中,那麼加入該字串,而且將Value值設爲1;若是該字串在Table中,那麼將該字串的計數加一便可。最終咱們在O(N)的時間複雜度內完成了對該海量數據的處理。

 本方法相比算法1:在時間複雜度上提升了一個數量級,爲O(N),但不只僅是時間複雜度上的優化,該方法只須要IO數據文件一次,而算法1的IO次數較多的,所以該算法2比算法1在工程上有更好的可操做性。

第二步:找出Top 10 (找出出現次數最多的10個)      

算法一:普通排序 (咱們只用找出top10,因此所有排序有冗餘)      我想對於排序算法你們都已經不陌生了,這裏不在贅述,咱們要注意的是排序算法的時間複雜度是NlgN,在本題目中,三百萬條記錄,用1G內存是能夠存下的。

算法二:部分排序  題目要求是求出Top 10,所以咱們沒有必要對全部的Query都進行排序,咱們只須要維護一個10個大小的數組,初始化放入10個Query,按照每一個Query的統計次數由大到小排序,而後遍歷這300萬條記錄,每讀一條記錄就和數組最後一個Query對比,若是小於這個Query,那麼繼續遍歷,不然,將數組中最後一條數據淘汰(仍是要放在合適的位置,保持有序),加入當前的Query。最後當全部的數據都遍歷完畢以後,那麼這個數組中的10個Query即是咱們要找的Top10了。

      不難分析出,這樣,算法的最壞時間複雜度是N*K, 其中K是指top多少。

算法三:堆在算法二中,咱們已經將時間複雜度由NlogN優化到N*K,不得不說這是一個比較大的改進了,但是有沒有更好的辦法呢

       分析一下,在算法二中,每次比較完成以後,須要的操做複雜度都是K,由於要把元素插入到一個線性表之中,並且採用的是順序比較。這裏咱們注意一下,該數組是有序的,一次咱們每次查找的時候能夠採用二分的方法查找,這樣操做的複雜度就降到了logK,但是,隨之而來的問題就是數據移動,由於移動數據次數增多了。不過,這個算法仍是比算法二有了改進。

基於以上的分析,咱們想一想,有沒有一種既能快速查找,又能快速移動元素的數據結構呢?

       回答是確定的,那就是堆。藉助堆結構,咱們能夠在log量級的時間內查找和調整/移動。所以到這裏,咱們的算法能夠改進爲這樣,維護一個K(該題目中是10)大小的小根堆,而後遍歷300萬的Query,分別和根元素進行對比。思想與上述算法二一致,只是在算法三,咱們採用了最小堆這種數據結構代替數組,把查找目標元素的時間複雜度有O(K)降到了O(logK)。那麼這樣,採用堆數據結構,算法三,最終的時間複雜度就降到了N*logK,和算法二相比,又有了比較大的改進。

10億個數中找出最大的10000個數(top K問題)

先拿10000個數建堆,而後一次添加剩餘元素,若是大於堆頂的數(10000中最小的),將這個數替換堆頂,並調整結構使之仍然是一個最小堆,這樣,遍歷完後,堆中的10000個數就是所需的最大的10000個。建堆時間複雜度是O(mlogm),算法的時間複雜度爲O(nmlogm)(n爲10億,m爲10000)。

針對top K類問題,一般比較好的方案是分治+Trie樹/hash+小頂堆(就是上面提到的最小堆),即先將數據集按照Hash方法分解成多個小數據集,而後使用Trie樹活着Hash統計每一個小數據集中的query詞頻,以後用小頂堆求出每一個數據集中出現頻率最高的前K個數,最後在全部top K中求出最終的top K。
---------------------

總結:

至此,算法就徹底結束了,通過上述第一步、先用Hash表統計每一個Query出現的次數,O(N);而後第二步、採用堆數據結構找出Top 10,N*O(logK)。因此,咱們最終的時間複雜度是:O(N) + N'*O(logK)。(N爲1000萬,N’爲300萬)。

優化的方法:能夠把全部10億個數據分組存放,好比分別放在1000個文件中。這樣處理就能夠分別在每一個文件的10^6個數據中找出最大的10000個數,合併到一塊兒在再找出最終的結果。

若是有多臺設備:

可參考Map-Reduce 的思想

核心是「分治」、「歸併」和哈希,  第一次遍歷將關鍵詞散列到不一樣的文件中(散列算法是性能關鍵,哈希函數的性能直接影響散列的結果, 儘可能避免「數據傾斜」), 同一個關鍵詞必定會散列到同一個文件, 理想狀況是全部關鍵詞均勻散列到不一樣的文件中(即分治思想,將大文件分解爲小問題)。

       讀取每一個文件並記錄各關鍵詞的次數, 作個排序, 從每一個文件中排序出前100的關鍵詞;

       取第一個文件的記錄結果, 和第二個文件作「合併」, 即200個結果中排序出前100個關鍵詞, 而後依次合併第三個、第四個。。。。第N個文件(將子結果合併爲總結果)

第八模塊--JVM

JVM執行模式

主流的JVM是Oracle的HotSpot JVM, 採用解釋和編譯混合執行的模式,JIT技術採用分層編譯,極大的提升了Java的執行速度。

三種執行模式:

1. 解釋執行

2.JIT編譯執行

3.JIT編譯與解釋混合執行

混合執行的優點在於解釋器在啓動時先解釋執行,省去編譯時間。隨着時間推動,JVM經過熱點代碼統計分析,識別高頻的代碼,基於強大的JIT動態編譯技術,將熱點代碼轉換成機器碼,直接交給CPU執行。JIT的做用是將Java字節碼動態地編譯成能夠直接發送給處理器指令執行地機器碼。

內存佈局:

程序計數器(Program Counter Register)線程私有的,記錄當前線程的行號指示器,爲線程的切換提供保障

虛擬機棧(JVM Stacks)//本地方法棧: 線程私有的,主要存放局部變量表,操做數棧,動態連接和方法出口等;

堆區(Heap): 堆是全部線程共享的,主要用來存儲對象。其中,堆可分爲:年輕代和老年代兩塊區域。使用NewRatio參數來設定比例。對於年輕代,一個Eden區和兩個Suvivor區,使用參數SuvivorRatio來設定大小。

元數據區 (Metaspace): JDK8纔有,保存JDK8以前永久代中的類的元信息

本地方法棧(Native Method Stacks)

永久代和方法區的區別

HotSpot使用永久代來實現方法區。永久代是HotSpot對方法區的實現,方法區是Java虛擬機規範中的定義,是一種規範。而永久代是一種實現,一個是標準一個是實現。在Java虛擬機(JVM)內部,class文件中包括類的版本、字段、方法、接口等描述信息,還有運行時常量池,用於存放編譯器生成的各類字面量和符號引用。在過去(自定義類加載器還不是很常見的時候),類大可能是」static」的,不多被卸載或收集,所以被稱爲「永久的(Permanent)」

對於Java8, HotSpots取消了永久代,那麼是否是也就沒有方法區了呢?固然不是,方法區是一個規範,規範沒變,它就一直在。那麼取代永久代的就是元空間。

永久代和元空間的區別

1. 存儲位置不一樣,永久代物理上是堆的一部分,和新生代,老年代地址是連續的,而元空間屬於本地內存

2. 存儲內容不一樣,元空間存儲類的元信息,靜態變量和常量池等併入堆中。至關於永久代的數據被分到了堆和元空間中。

http://www.javashuo.com/article/p-eijjpfdr-ng.html

爲何使用 元空間+堆 取代 永久代

1. 字符串存在永久代中,容易出現性能問題和內存溢出。

2. 類及方法的信息等比較難肯定其大小,所以對於永久代的大小指定比較困難,過小容易出現永久代溢出,太大則容易致使老年代溢出。永久代的垃圾收集是和老年代(old generation)捆綁在一塊兒的,所以不管誰滿了,都會觸發永久代和老年代的垃圾收集。

3. 永久代會爲 GC 帶來沒必要要的複雜度,而且回收效率偏低

http://www.javashuo.com/article/p-cfyrfgtp-kh.html

何時會發生堆溢出何時會發生棧溢出?

1. 棧溢出

棧是線程私有的,他的生命週期與線程相同,每一個方法在執行的時候都會建立一個棧幀,用來存儲局部變量表,操做數棧,動態連接,方法出口燈信息。局部變量表又包含基本數據類型,對象引用類型(局部變量表編譯器完成,運行期間不會變化)

棧溢出就是方法執行是建立的棧幀超過了棧的深度。那麼最有可能的就是方法遞歸調用產生棧溢出

2. 堆溢出

heap space表示堆空間,堆中主要存儲的是對象。若是不斷的new對象則會致使堆中的空間溢出。

3. 永久代溢出(OutOfMemoryError: PermGen space)

永久代物理上是堆的一部分,和新生代,老年代地址是連續的,永久代溢出的表現就是堆溢出。(永久代的GC是和老年代(old generation)捆綁在一塊兒的,所以不管誰滿了,都會觸發永久代和老年代的垃圾收集。)

因爲JDK七、8移除永久帶,因此只有JDK1.6及如下會出現永久帶溢出的現象

堆區(Heap):

堆區儲存着幾乎全部的實例對象,堆由垃圾收集器自動回收,堆區由各子線程共享使用,能夠經過-Xms設置最小堆容量, 和-Xmx來設置最大堆容量。

堆分紅兩大塊:新生代和老年代。

對象產生之初在新生代,步入暮年時進入老年代,可是老年代也接納在新生代沒法容納的超大對象

新生代=1個Eden區+2個Survior區。絕大部分對象在Eden區生成,當Eden區裝填滿的時候,會觸發Young Garbage Collection,即YGC。垃圾回收的時候,在Eden區實現清除策略,沒有被引用的對象則直接回收。依然存活的對象會被移送到Suvivor區。Suvivor區分爲S0和S1兩塊內存空間,送到哪塊空間呢?每次YGC的時候,它們將存活的對象複製到未使用的那塊空間,而後將當前正在使用的空間徹底清除,交換兩塊空間的使用狀態。若是YGC要移送的對象大於Survivor區容量的上限,則直接移交給老年代。若是老年代也沒法放下,則會觸發Full Garbage Collection, FGC。 若是依然沒法放下,則拋出OOM。

假如一些沒有進取心的對象覺得能夠一直在新生代的Survivor區交換來交換去,那就錯了。每一個對象都有一個計數器,每次YGC都會加1。 -XXiMax Tenuring Threshold參數能配置計數器的值到達某個閾值的時候,對象重新生代晉升至老年代。若是該參數配置爲1,那麼重新生代的Eden區直接移至老年代。默認值是15,能夠在Survivor區交換14次以後,晉升至老年代。

JVM Stack(虛擬機棧):

棧(Stack)是一個先進後出的數據結構。
JVM中的虛擬機棧是描述Java方法執行的內在區域,它線程私有的。棧中的元素用於支持虛擬機進行方法調用,每一個方法從開始調用到執行完成的過程,就是棧幀從入棧到出棧的過程。在活動線程中,只有位於棧頂的幀纔是有效的,稱爲當前棧幀。正在執行的方法稱爲當前方法,棧幀是方法運行的基本結構。
在執行引擎運行時,全部指令都只能針對當前棧施進行操做。而StackOverflowError表示請求的棧溢出,致使內存耗盡,一般出如今遞歸方法中。

 

虛擬機棧經過壓棧和出棧的方式,對每一個方法對應的活動棧幀進行運算處理,方法正常執行結束,確定會跳轉到另外一個棧幀上。在執行的過程當中,若是出現異常,會進行常回溯,返回地址經過異常處理表肯定。棧幀在整個JVM體系中的地位頗高,包括局部變量表、操做棧、動態鏈接、方法返回地址等。
(1)局部變量表
  局部變量表是存放方法參數和局部變量的區域。

(2)操做棧
  操做棧是一個初始狀態爲空的桶式結構棧。在方法執行過程當中,會有各類指令往棧中寫入和提取信息。

  (3) 動態鏈接

  每一個棧幀中包含一個在常量池中對當前方法的引用,目的是支持方法調用過程的動態鏈接。

  (4) 方法返回地址

  方法執行遇到退出狀況,將返回至方法當前被調用的位置

Native Method Stacks(本地方法棧)

本地方法棧在JVM內存佈局中,也是線程對象私有的,但虛擬機「主內」,本地方法棧「主外」。這個「內外」是針對JVM來講的,線程調用本地方法時,會進入一個再也不受JVM約束的世界。本地方法棧能夠經過JNI(Java Native Interface)來訪問虛擬機運行時的數據區,甚至能夠調用寄存器,具備和JVM相同的能力和權限。最著名的本地方法應該是System.currentTimeMillis(), JNI使Java深度使用操做系統的特性功能,複用非Java代碼。

Program Counter Register(程序計數寄存器)

在程序計數寄存器(Program Counter Register,PC)中,Register的命名源於CPU的寄存器,CPU只有把數據裝載到寄存器纔可以運行。寄存器存儲指令相關的現場信息,因爲CPU時間片輪限制,衆多線程在併發執行過程當中,任何一個肯定的時刻,一個處理器或者多核處理器中的一個內核,只會執行某個線程中的一條指令。這樣必然致使常常中斷或恢復,如何保證分毫無差呢?每一個線程在建立後,都會產生本身的程序計數器和棧幀,程序計數器用來存放執行指令的偏移量和行號指示器等,線程執行或恢復都要依賴程序計數器。程序計數器在各個線程之間互不影響,此區域也不會發生內存溢出異常。
最後,從線程共享的角度來看,堆和元空間是全部線程共享的,而虛擬機棧、本地方法棧、程序計數器都是線程內部私有的。從這個角度看一下Java的內存結構:

GC(垃圾回收)

  • GC如何判斷對象是否能夠回收

爲了判斷對象是否存活,JVM 引入了GC Roots若是一個對象與GC Roots之間沒有直接或間接的引用關係,好比某個失去任何引用的對象,或者兩個互相環島狀循環引用的對象等,判決這些對象「死緩」,是能夠被回收的。能夠做爲GC Roots對象能夠是:類靜態屬性中引用的對象、常量引用的對象、虛擬機棧中引用的對象、本地方法棧中引用的對象等。

  • 垃圾回收算法

有了判斷對象是否存活的標準後,再瞭解一下垃圾回收的相關算法。

  1. 標記--清除算法」,該算法會從每一個GC Roots出發,依次標記有引用關係的對象,最後將沒有被標記的對象清除。可是這種算法會帶來大量的空間碎片,致使須要分配一個較大連續空間時容易觸發FGC。典型的例子是CMS回收器。
  2. 標記--整理算法」,該算法相似計算機的磁盤整理,首先會從GC Roots出發標記存活的對象,而後將存活對象整理到內存空間的一端,造成連續的已使用空間,最後把已使用空間以外的部分所有清理掉,這樣就不會產生空間碎片的問題。典型的例子是老年代的FullGC, G1回收器。
  3. 複製算法,爲了可以並行地標記和整理將空間分爲兩塊,每次只激活其中一塊,垃圾回收時只需把存活的對象複製到另外一塊未激活空間上,將未激活空間標記爲已激活,將已激活空間標記爲未激活,而後清除原空間中的原對象。堆內存空間分爲較大的Eden和兩塊較小的Survivor,每次只使用Eden和Survivor區的一塊。這種情形下的「Mark-Copy」減小了內存空間的浪費。典型的例子是年輕代的minGC。

垃圾回收器(Garbage Collector)

是實現垃圾回收算法並應用在JVM環境中的內存管理模塊。當前實現的垃圾回收器有數十種,常見的有Serial、CMS、G1三種。

  1. Serial回收器是一個主要應用於YGC的垃圾回收器,採用串行單線程的方式完成GC任務,其中「Stop The World」簡稱STW,即垃圾回收的某個階段會暫停整個應用程序的執行。FGC的時間相對較長,頻繁FGC會嚴重影響應用程序的性能
  2. CMS回收器(Concurrent Mark Sweep Collector)是回收停頓時間比較短、目前比較經常使用的垃圾回收器。它經過初始標記(Initial Mark)、併發標記(ConcurrentMark)、從新標記(Remark)、併發清除(Concurrent Sweep)四個步驟完成垃圾回收工做。第一、3步的初始標記和從新標記階段依然會引起STW,而第二、4步的併發標記和併發清除兩個階段能夠和應用程序併發執行,也是比較耗時的操做,但並不影響應用程序的正常執行。因爲CMS採用的是「標記一清除算法」,所以產生大量的空間碎片。爲了解決這個問題,CMS能夠經過配置-XX:+UseCMSCompactAtFullCo lection參數,強制JVM在FGC完成後對老年代進行壓縮,執行一次空間碎片整理,可是空間碎片整理階段也會引起STW。爲了減小STW次數,CMS還能夠經過配置一XX:+CMSFullGCsBeforeCompaction=n參數,在執行了n次FGC後,JVM再在老年代執行空間碎片整理。
  3. G1回收器 是HotSpot在JDK7中推出的新一代G1垃圾回收器,在JDK11中,G1是默認的回收器。G1採用的是」Mark-Copy「,有較好的空間整理能力,不會產生大量空間碎片。G1的優點是具備可預測的停頓時間,可以儘快在指定時間完成垃圾回收任務。
  4. 另外還有在JDK11中引入的實驗性的ZGC,是一個可伸縮的低延遲垃圾收集器。

怎樣分析查找OOM的緣由

  1. 使用jmap將當前的內存 Dump成一個 hprof格式的文件,MAT 讀取這個文件後會給出方便閱讀的信息,配合它的查找,對比功能,就能夠定位內存泄漏的緣由
  2. 用的最多的功能是 Histogram,它按類名將全部的實例對象列出來,能夠點擊表頭進行排序,在表的第一行能夠輸入正則表達式來匹配結果

舉例一個典型的分析內存泄漏的過程:

1.  使用 Heap查看當前堆大小爲 23.00M

2.  添加一個頁後堆大小變爲 23.40M

3.  將添加的一個頁刪除,堆大小爲 23.40M

4.  屢次操做,結果仍類似,說明添加/刪除頁存在內存泄漏 (也應注意排除其它因素的影響)

5.  Dump 出操做先後的 hprof 文件 (1.hprof,2.hprof),用 mat打開,並獲得 histgram結果

6.  使用 HomePage字段過濾 histgram結果,並列出該類的對象實例列表,看到兩個表中的對象集合大小不一樣,操做後比操做前多出一個 HomePage,說明確實存在泄漏

7.  將兩個列表進行對比,找出多出的一個對象,用查找 GC Root的方法找出是誰串起了這條引用線路,定位結束

PS :

·        不少時候堆增大是 Bitmap引發的,Bitmap在 Histogram中的類型是 byte [],對比兩個 Histogram中的 byte[] 對象就能夠找出哪些 Bitmap有差別

·        多使用排序功能,對找出差別頗有用

2 內存泄漏的緣由分析

總結出來只有一條: 存在無效的引用! 
良好的模塊設計以及合理使用設計模式有助於解決此問題。

參考:

https://blog.csdn.net/liao0801_123/article/details/82900874

http://www.javashuo.com/article/p-skcqcyfq-ho.html

http://blog.sina.com.cn/s/blog_73b4b91f0102wze4.html

內存溢出的緣由及解決方法

1. 內存溢出緣由: 

1.內存中加載的數據量過於龐大,如一次從數據庫取出過多數據; 
2.集合類中有對對象的引用,使用完後未清空,使得JVM不能回收; 
3.代碼中存在死循環或循環產生過多重複的對象實體; 
4.使用的第三方軟件中的BUG; 
5.啓動參數內存值設定的太小

2. 內存溢出的緣由及解決方法:

  1. 修改JVM啓動參數,直接增長內存。(-Xms,-Xmx參數必定不要忘記加。)
  2. 檢查錯誤日誌,查看「OutOfMemory」錯誤前是否有其 它異常或錯誤。
  3. 對代碼進行走查和分析,找出可能發生內存溢出的位置。
  4.  使用內存查看工具動態查看內存使用狀況

對代碼分析找出可能發生內存溢出的位置, 可能出現的幾種狀況:

一、檢查對數據庫查詢中,是否有一次得到所有數據的查詢。通常來講,若是一次取十萬條記錄到內存,就可能引發內存溢出。這個問題比較隱蔽,在上線前,數據   庫中數據較少,不容易出問題,上線後,數據庫中數據多了,一次查詢就有可能引發內存溢出。所以對於數據庫查詢儘可能採用分頁的方式查詢。

二、檢查代碼中是否有死循環或遞歸調用。

三、檢查是否有大循環重複產生新對象實體。

四、檢查List、MAP等集合對象是否有使用完後,未清除的問題。List、MAP等集合對象會始終存有對對象的引用,使得這些對象不能被GC回收。

第九模塊--我的

 

項目總結

關於三維模型搜索引擎項目相關度排序算法是怎麼作的:

以文字搜模型:

基於Lucene文本搜索引擎,查找最匹配的;

以圖片搜模型:

計算圖片特徵,對圖片特徵計算HashCode, 搜索的時候匹配HashCode;

以模型搜模型:

計算模型的特徵獲得n維特徵矩陣, 對特徵矩陣計算HashCode, 搜索的時候匹配HashCode;

去重和檢測url有效性是怎麼作的:

對外網數據去重:

一開始直接使用條件逐個字段比較判斷是否重複;

後來對關鍵字段鏈接創建聯合哈希值保存,用這個哈希值去重;

後來想到其實外網的url是惟一的,直接對url創建哈希值來去重;後來設想直接用url哈希以後做爲主鍵保存,創建彙集索引;

有效性檢測比較簡單:

使用java.net 下的類來實現,主要用到了 URL和HttpURLConnection :

剛開始使用openStream()方法,這樣使用卻是能夠,可是速度慢;

最後使用了getResponseCode()方法,能夠獲得請求的響應狀態,該方法返回一個 int 分別是 200 and 404 如沒法從響應中識別任何代碼則返回 -1, 若是對該url發起的5次請求都沒有應答則認爲連接失效;

你的項目用了哪些技術?

Lucene, Solr 

MySQL, 

Redis,

Java多線程

遇到過什麼問題?你是怎麼解決的?

去重的過程經歷了屢次迭代:

剛開始直接對記錄逐個字段比較判斷是否重複 ——>而後對關鍵字段創建HashCode做爲標識,對比該Hash字段——>再是對外網URL創建HashCode對比;

有什麼能夠改進的地方?

能夠對URL使用布隆過濾器作去重;(位圖+多個哈希函數)

使用緩存數據庫來提升併發訪問;(緩存穿透(查詢一個數據庫必定不存在的數據),緩存擊穿(一個key很是熱點,在不停的扛着大併發,大併發集中對這一個點進行訪問,當這個key在失效的瞬間,持續的大併發就穿破緩存),緩存雪崩(緩存集中過時失效))

使用Elesticsearch來替代Solr(Elasticsearch是分佈式的, 不須要其餘組件,分發是實時的;solr須要結合依賴其餘分佈式組件來實現分佈式);

第九模塊--待整理問題

阿里:

一面

全局惟一有序ID:

Snowflake, timestamp, 機器id等

馮諾伊曼體系:

shell命令的執行體系:

信息熵:

程序運行中的棧式結構

TCP/IP, TCP傳輸層加端口號,IP網絡層加ip地址;路由器主要工做在IP網絡層

各層常見的協議有哪些

同步與阻塞

並行與併發

java線程的本質、內核線程與用戶進程,線程調度,並行級別

內核態與用戶態,中斷

CPU與內存與硬盤

緩存行與僞共享

內存分配管理,段業式 jemalloc

二面:

java程序的運行原理:

緩存行與僞共享

一個線程忙碌,多個線程閒置怎麼解決

Java多線程引起的性能問題以及調優策略

多重繼承會帶來哪些問題

單點登錄

正向代理與反向代理

反爬機制,爬蟲模擬瀏覽器行爲

cglib方法攔截

動態代理

依賴注入

servlet的本質

TCP長鏈接 心跳包 websocket

Netty 百萬級長鏈接優化

DSL解析到AST

JVM 相關(gc的源碼)

代碼規範,包命名規範

如今流行的線程調度算法是什麼(時間片輪轉法)

 

項目用到了數據庫,談談對事物的理解

假設你要作一個銀行app,有可能碰到多我的同時向一個帳戶打錢的狀況,有可能碰到什麼問題,如何解決(還有可能出現重複提交的問題,保證服務的冪等性)

排序算法

給定一個文件名,如何在d盤找出這個文件

java對象頭

知道哪些排序算法

快排怎麼實現

堆排怎麼實現

找出兩個有序數組中的相同元素

經常使用集合框架

介紹下hashtable

快排如何實現

一個集合裏有1000萬個隨機元素,快速求和(多線程)

 

排他鎖的改進策略

 

map怎麼實現

 

紅黑樹有什麼特性

 

快排的思路講一下

 

給大量的qq號,怎麼排序(數據庫外排),問算法時間複雜度

 

代碼:

 

數組裏搜索第K大的數,非遞歸二分查找,鏈表相加

相關文章
相關標籤/搜索