喪心病狂的面試知識

設計模式

  1. 單例模式(線程安全的對象發佈寫法)
  2. 工廠模式
  3. 代理模式(實現代理模式的方式有哪些 jdk的invocationHandler,基於接口 ,cglib,(MethodInterceptor)基於方法)
  4. 享元模式:主要用於減少創建對象的數量,以減少內存佔用和提高性能。嘗試重用現有的同類對象。
  5. 迭代器模式(Iterator):對外不暴露內部的數據結構,提供統一的接口,實現next,hashNext的訪問數據的基本功能
設計模式 設計模式應用場景
單例模式 連接池,線程池,工廠類
工廠模式
代理模式 功能增強,比如日誌處理,權限判斷等等
享元模式 java 中的string,如果有就返回,否則創建並保存到字符串緩存池;數據庫的數據池
迭代器模式 隱藏內部數據結構,對外提供統一的數據訪問方式

面向對象的特點

  1. 封裝,繼承,多態

軟件設計原則:

  1. 開閉原則:對擴展開放,對修改關閉,需要增加新功能,增加一個實現接口的具體類就好了
    比如僞代碼: 判斷武器類型,給出傷害值
  2. 里氏代換原則: 開閉原則的補充,(保障基類的可複用性)
  3. 迪米特原則:類與類的少了解,即類只需要知道依賴類必須知道的方法,減少類之間的聯繫,只需要知道關心的方法,不要過多的依賴
  4. 依賴倒置:調用者不依賴被調用者(具體實現),而是依賴抽象,被調用者的具體實現發生更改,後續,被調用者可以被無感替換
  5. 接口隔離: 接口定義要最小化,就是接口只專注一個領域,不要大而全,要小而細。Cloneable 接口只負責克隆

數據結構

  1. 數組與鏈表(連續內存與非連續內存,隨機訪問效率,刪除效率不一樣)

  2. 排序算法實現以及時間,空間複雜度,改良分析

  3. 堆和棧

  4. 二叉樹,搜索樹,完全二叉時,滿二叉樹,哈夫曼樹(最優樹)

  5. 二叉樹的遞歸遍歷,藉助棧的非遞歸遍歷

  6. 字典樹(原理以及應用場景,可能的優化角度)

  7. B 樹,B+樹,特點,以及應用場景(數據庫索引)

  8. 鄰接矩陣與鄰接表

  9. 查找

  10. hash法,hash衝突的解決方式(可能會考慮到redis集羣時的hash算法)
    衝突解決方法:開放地址法(線性探測(刪除比較麻煩,只能標記爲刪除)
    隨機探測(步長隨機數序列))
    拉鍊法(指針數組+鏈表)

  11. 深度優先於廣度優先
    廣度優先:類比成樹中 層次遍歷
    深度優先:類比樹中序遍歷

  12. 圖的基本概念
    無向圖:
    有向圖:
    表示方式:
    鄰接表:
    鄰接矩陣:

  13. 最短路徑(算法)
    **單源最短路徑:dj(貪心算法),**使用一維數組D[i] 記錄起點到i的最短距離,
    如果起點v0 到i需要經過 k,則D[i]=min{d[i],D[k]+e(k,i)}
    多源最短路徑:floyd(dp)
    d[i] [j] 表示i與j之間的最短距離
    遞推公式: d[i][j]=min(d[i][j],d[i][k]+d[k][j]);

  14. 最小生成樹
    可使用貪心算法:

算法名 描述 使用場景,複雜度
prims 設點集爲S,已選取點爲S1,未選取點集爲S-S1, 1,初始化,選取第一個點2, 遍歷 已選取點和未選取點之間最小邊,並將該點加入S1,直到S-S1爲空,完成 時間複雜度:o(n2^2),與邊數目無關,適用於稠密圖
cruskal 1.初始化 將邊按照從小到大排列,所有點看成是孤立的;2,依次選取邊, 如果該邊的兩個端點在同一個連通圖中,則跳過;否選取該邊;3,重複2直到只剩一個連通圖,這就得到最小生成樹 複雜度:o(elge) ,e是邊的條數,這裏主要是排序來體現的,適合於稀疏圖
查找方式 數據結構 時間複雜度
順序查找 順序存儲,鏈式存儲 順序存儲(o(1)),鏈式存儲o(n)
隨機查找 順序存儲(o(1)),鏈式存儲o(n)
二分查找 有序順序存儲結構 o(lgn)
hash查找 o(1)
索引查找 o(lgn)+o(k) 先使用二分查找找到塊,然後在塊中順序查找

spring

  1. servlet
    Servlet對象只會創建、初始化一次,如果有多個請求同時訪問,會從線程池中獲取一個線程還是用這個Servlet對象處理用戶請求,算是單例多線程,這就會帶來一個問題,線程安全問題,訪問特別是修改Servlet類中的全局變量時會導致數據錯誤,所以儘量不使用在Servlet類中聲明全局變量。實在不行就需要給線程加鎖。
  2. springMvc 的流程

請求-》前端控制器(dispatchServlet)->調用處理映射器(HandlerMapping),生成處理器對象以及處理攔截器->DispatchServlet(前端控制器)->調用處理適配器進一步調用處理器-》執行處理器( Controller)->返回ModelAndView給dispatchServlet,傳遞modelAndView -》ViewResolver(視圖解析器)-》返回具體View ->DispatchServlet對View 進行渲染-》響應用戶
在這裏插入圖片描述

  1. springmvc 的控制器是單例,線程問題怎麼解決?(用同步會影響性能,不要寫字段)
  2. 簡述IOC,DI
    IOC:是控制反轉

創建對象的權限:由開發者new 交由spring 去創建(需要將類的全限定名配置到xml文件中,spring底層根據反射去創建對象)
不使用ioc時:
A a=new A();
使用ioc後 A a=SpringContext.getBean("");

DI:是依賴注入(注入方式:構造+settter,一般使用後者)
3. spring中的bean有些什麼類型?
4. spring 線程安全嗎?
大多數bean 是單例的,而且spring並沒有考慮線程安全,但是大多數bean是無狀態的
每個bean自身的設計。不要在bean中聲明任何有狀態的實例變量或類變量,如果必須如此,那麼就使用ThreadLocal把變量變爲線程私有的,如果bean的實例變量或類變量需要在多個線程之間共享,那麼就只能使用synchronized、lock、CAS等這些實現線程同步的方法了。
下面將通過解析ThreadLocal的源碼來了解它的實現與作用,ThreadLocal是一個很好用的工具類,它在某些情況下解決了線程安全問題(在變量不需要被多個線程共享時)。
5. FactoryBean 與BeanFactory 的區別
BeanFactory,以Factory結尾,表示它是一個工廠類(接口),用於管理Bean的一個工廠。在Spring中,BeanFactory是IOC容器的核心接口,它的職責包括:實例化、定位、配置應用程序中的對象及建立這些對象間的依賴。
 FactoryBean 以Bean結尾,表示它是一個Bean,不同於普通Bean的是:它是實現了FactoryBean接口的Bean,根據該Bean的ID從BeanFactory中獲取的實際上是FactoryBean的getObject()返回的對象,而不是FactoryBean本身,如果要獲取FactoryBean對象,請在id前面加一個&符號來獲取。

  1. 聲明式事務的類型(xml+註解)
    優點:避免侵入業務代碼,配置方便,便於管理
  2. 事務的特性
特性 描述
原子性 將事務中所做的操作捆綁成一個原子單元,即對於事務所進行的數據修改等操作,要麼全部執行,要麼全部不執行。
隔離性 由併發事務所做的修改必須與任何其他事務所做的修改相隔離。事務查看數據時數據所處的狀態,要麼是被另一併發事務修改之前的狀態,要麼是被另一併發事務修改之後的狀態,即事務不會查看由另一個併發事務正在修改的數據。這種隔離方式也叫可串行性
持久性 事務完成之後,它對系統的影響是永久的,即使出現系統故障也是如此
一致性 事務在完成時,必須使所有的數據都保持一致狀態,而且在相關數據中,所有規則都必須應用於事務的修改,以保持所有數據的完整性。事務結束時,所有的內部數據結構都應該是正確的

9. 如何實現分佈式事務

沒有應用場景,個人分析,分佈式事務的產生是由於某個業務操作需要在
本地事務的實現當然很簡單,加上Transactional 註解
如果是分佈式事務:
方案一:2PC 提交
前提:
存在一個節點作爲協調者,其他節點作爲參與者,節點間可以相互網絡通信
所有節點採用預寫日誌,並且日誌保存在可靠的存儲設備上,即使節點損壞也不會導致日誌丟失
所有節點都不會永久性損壞(損壞後可以恢復)

階段 說明
投票 本地執行事務成功(各節點事務信息寫入redo,undo日誌)的節點發消息給協調者,內容是"同意",如果某個節點執行失敗,返回消息"終止"
提交執行階段 如果協調者收到的回覆全部是"同意",協調者發起消息"正式提交",節點正式完成操作,參與者節點返回"完成"信息,協調者如果收到全部都回復爲"完成"時,完成事務; 如果出現一個"終止",協調者向所有參與節點發起"回滾"請求,參與者節點利用之前的undo日誌,執行回滾操作,然後發送給協調者"回滾完成",收到所有參與者的回滾完成消息後,協調者取消事務

  1. 如何實現以下場景; 一個業務處理,包含兩個操作,如何實現操作b 失敗時,a操作事務不回滾,只是回滾b操作
//1. 獲取連接
conn=getConn();
SavePoint savePoint=null;
// 2. 關閉自動提交事務  conn.setAutoCommit(false)
try{

A(發工資);
savePoint=conn.setSavePoint();

B(工資發放通知短信)(出現異常,如果A也回滾,那麼就沒工資了)
}catch(Exception e){
if(savePoint!=null){
// 說明 A 發工資是沒有異常,那麼就是B 發短信出現了異常
A 需要提交
conn.rollBack(savePoint);
conn.commit();
}
// A 操作出錯,全部回滾
else{
conn.roolBack();
}
}
  1. spring aop 的實現方式
    基於註解,基於配置
-  核心是定義切點:需要被增強的方法
 -  定義通知:具體要增加的功能
-    定義切面:切點與通知組合形成切面
-  定義代理工廠對象(ProxyFactoryBean):將目標對象,切面組合,目標對象接口組合到一起用於製造代理對象

    <!--1,配置切點(需要被增強的方法) 攔截doSth 結尾的方法-->
    <bean id="myPointCut" class="org.springframework.aop.support.JdkRegexpMethodPointcut">
        <property name="pattern" value=".*study"/>
    </bean>
    <!-- 2,創建通知-->
    <bean id="myAdvice" class="top.forethought.framework.aop.xml.StudyAdvice">
    </bean>
    <!--3,創建切面(通知與切點組合在一起成爲切面-->
    <bean id="myAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
        <property name="advice"  ref="myAdvice"/>
        <property name="pointcut" ref="myPointCut"/>
    </bean>
    <!--4,製造代理對象-->
    <bean id="studentProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="target" ref="studentObj"/>
        <property name="interceptorNames" value="myAdvisor" />
        <property name="proxyInterfaces" value="top.forethought.framework.aop.BaseInterface" />
    </bean>
   <!--配置原對象:代理實際是將原對象與通知組合在一起而形成的-->
    <bean id="studentObj" class="top.forethought.framework.aop.xml.Student"></bean>
問題 解答
什麼是AOP 面向切面編程,是oop的補充
使用場景,用途 不改變原有代碼,橫向抽取(相對於繼承而言)添加功能,比如事務管理,緩存,日誌(可能是手寫代碼記錄日誌,有些日誌是不需要記錄的)
spring aop java 編寫,運行期通過代理方式向目標類織入增強代理(cglib ,不依賴接口類,使用字節碼增強),查看字節碼發現是extends
jdk 的invocationhandler 依賴接口,反射生成動態代理類,查看生成的class文件發現是implements

起初,jdk 動態代理運行效率低於cglib,創建效率高,但隨着jdk版本的提高,1.8版本jdk動態代理的運行效率已經高於cglib
jdk 動態代理基於接口 (可以觀察生成的字節碼)
cglib 動態代理基於繼承

spring 也可以配置使用jdk動態代理

 proxy-target-class=true表示使用CGLib代理,false 則使用jdk 動態代理
  1. aop 的幾個術語示意圖
  • [目標類]需要被aop作用的類,也就是需要被增強的類(初始類)
  • [ 連接點] :可能被增強的的方法
  • [ 切入點] :被增強的方法
  • [通知] : before(),after() ,具體實現切入點需要增強的功能
  • [織入] weaving:將原始類和切入點組合在一起生成代理類的過程
  • [代理類] (proxy):是原始類和切入點組合的類(擁有與原始類一樣的方法,只是方法中使用了通知)
  • [切面] aspect:通知+切點構成一個"面"

在這裏插入圖片描述
13. 什麼是微服務
單體應用,功能很多,使用的技術也較爲統一,難以升級新技術,時間推移,代碼維護難度比較大。擴展比較難,耦合可能比較高。
微服務:將某些相對獨立的功能以服務的形式抽離出來,單獨開發,不同的服務可以使用不同的技術開發,通過http通信的方式給整個應用提供支持,從外部看,是一個完整的整體。

hibernate

  1. 鎖機制有什麼用?簡述Hibernate的悲觀鎖和樂觀鎖機制
    鎖機制:有些業務邏輯在操作數據時,需要保持排他性,不希望數據被其他操作修改

悲觀鎖:悲觀的認爲極有可能存在修改數據的併發事務(包括本系統的其他事務或來自外部系統的事務),於是將處理的數據設置爲鎖定狀態。悲觀鎖必須依賴數據庫本身的鎖機制才能真正保證數據訪問的排他性

樂觀鎖:樂觀的認爲併發訪問時很少的,使用版本號來標識,每次操作將版本號+1, 讀取數據時獲取一次數據庫版本號,寫入數據時,如果新的版本號大於數據庫版本號,允許操作;否則認爲是過期數據無法更新。Hibernate中通過Session的get()和load()方法從數據庫中加載對象時可以通過參數指定使用悲觀鎖;而樂觀鎖可以通過給實體類加整型的版本字段再通過XML或@Version註解進行配置
2. hibernate 與mybatis 對比
3. hibernate 對象的三態(暫時態(新創建的),持久態(在數據庫中有相應的記錄,與session關聯),遊離態(與session 緩存失去關聯),)

jvm

1. 類加載過程

**1,加載**:是類加載的一個步驟,主要是
      a,通過全限定名獲取定義此類的二進制字節流
      b,將字節流所代表的的靜態數據結構轉爲方法區的運行時數據結構
      c,在內存中生成一個代表此類額java.lang.Class 對象,作爲方法區這個類的各個數據訪問的入口
       
**2, 驗證**: 確保Class文件的字節流中包含的信息是否符合當前虛擬機的要求,不對當前虛擬機造成危害
**3, 準備**:正式爲**類變量**(static 修飾的,不包含成員變量)分配內存並設置類變量的初始值階段(這裏的初始值
通常指零值,但是如果是static final ,則會生成Constant Value 屬性,值爲java代碼指定的值)
**4, 解析**:機將常量池中的符號引用替換爲直接引用
**5, 初始化**:執行類中定義的代碼(執行程序員代碼的主觀代碼初始化)

2. 對象內存分佈

:創建的實例對象在此,線程共享(由於是GC的主要發生場所,又叫GC堆)
:(本地方法棧+虛擬機棧(hotspot 虛擬機已經合併) :
對象的內存佈局:

含義
對象頭 運行時數據(hash,GC分代年齡,鎖狀態標誌,線程持有的鎖,偏向線程id,偏向時間戳)+類型指針
實例數據 程序代碼中定義的各種類型字段內容,包括從父類繼承下來的
對齊填充 非必須,佔位填充

3. 垃圾回收算法

算法名 描述 特點(適用場景)
標記-清除 通過GCRoot 對象,引用鏈標記,被標記的表示存活,未標記對象被清除 會產生大不連續內存碎片,如果後序出現大對象,會提前觸發垃圾回收
複製 內存對半分,只能使用其中一半,將存活對象拷貝到另一半內存,然後清除之前的內存 無內存碎片,空間利用率低,如果存活率高,效率較低
標記-整理 標記,然後將存活對象往某一端移動,然後抹去邊界之外的空間 無內存碎片,無空間浪費
分代收集 根據對象的存活率來使用不同的回收算法 少量存活,選用複製算法;大量存活,則選擇標記-清除或者標記-整理
  1. jvm 參數
  2. io
    a. InputStream,OutputStream

4 NIO

NIO(NEW IO),可以通過本地方法在堆外開闢內存(直接內存),減少在jvm定義的內存中複製,提高效率(基於緩衝與管道)

5. 內存分配與回收策略

相關參數
優先在Eden分配 Eden區沒足夠空間分配時,虛擬機將發起MinorGC(發生在新生代的GC
大對象直接進入老年代 大對象是指需要連續分配內存的對象,比如長字符串以及數組 參數-XX:pREtenureSzieSizeThreshold,大於此值的對象直接在老年代分配
超期存活的對象進入老年代 對象年齡(Age)計數器,在Eden區出生,經過一次MinorGC 之後,仍然存活並且能被survivor區收納,移到survivor區,年齡設置爲1,每熬過一次MinorGC,年齡+1,默認是到達15時,進入到老年代 稱爲老年代的閾值可以使用-XX:MaxTenuringThreshold
動態對象年齡判定 如果survivor區相同年齡對象的內存總和大於survivor區內存的一半,則將年齡>=該年齡的對象直接進入老年代
空間分配擔保 在發生MinorGC之前,檢查老年代最大可用連續空間是否大於新生代所有對象總空間,如果成立,MinorGc 就確保是安全的。如果不成立,檢查HandlerPromotionFailure 是否設置爲允許擔保失敗,如果是允許擔保失敗,繼續檢查老年代最大連續可用空間是否大於歷次晉升到老年代對象的平均大小,如果大於,則嘗試經進行這次有風險的MinorGC。 如果不允許冒險,使用FULLGC

雙親委派模型優缺點

類加載器:用來實現類的加載動作
常見類加載器:

加載器名 描述
啓動類加載器(Bootstrap ClassLoader) c++實現,加載java_home\lib 下的類
擴展類加載器(Extension ClassLoader) java 實現,加載java_home\lib\ext 目錄下
應用類加載器(ApplicationClassLoader) 負責加載ClassPath 上指定的類庫 ,(如果沒有自定義類加載器,那麼程序中默認是使用這個類加載器)

雙親委派模型::指java虛擬機只存在兩種不同的類加載器,意識c++ 實現的BootstrapClassLoader,另一種則是其他用java實現的類加載器
委派:是指類加載器加載對象時,總是嘗試讓自己的父類加載器去完成,如父類加載器不能完成,再自己完成類加載
優點: java 類隨着他的類加載器一起具備了一種帶有優先級的層次關係. 例如java.lang.Object,存在不於rt.jar,這樣在各種類加載器環境中都是同一個類

redis

1. 爲什麼速度快

單線程(沒有上下文切換,操作大多基於內存),數據結構簡單(hash的使用),

2. 底層的數據結構

string,hash(適合對象 ,hset 對象名 屬性名 值),zSet()有序

redis的動態字符串:
不以’\0’ 結束
字符串定義屬性有:

屬性 含義
int free 剩餘可用空間長度
int len 當前字符串內容長度
char [] buf 字符串數組
相比c中傳統的字符串有啥優點呢?
1. 不使用'\0'結尾,讀取內容是o(1)  :起始地址到len
2. 杜絕緩衝區溢出,c字符串只能通過'\0'來計算長度,
比如:連續的存放兩個字符串  a='redis' b='memcache'
 如果將a 修改爲'redisclauster',那麼會導致內存覆蓋到字符串b,導致b 的內容發生改變
 通過free 屬性,可以判斷出剩餘的空閒內存能否容納修改後的字符串值
如果不能容納:free=修改後字符串長度(預分配內存)
len=修改後字符串長度
以後的修改可以減少內存的分配次數(應爲預先分配了)
 3. 減少內存分配次數
 4. 可以保存二進制數據(因爲不再以'\0'作爲字符串結尾判斷,而是通過len)

3. 與memecached 對比

比較項 redis memcache
持久化 aof(append ),rdb日誌
數據類型 string ,list,set,hash,zset
分佈式 支持(redis clauster)) 不支持(可以通過在客戶端使用一致性hash來實現分佈式存儲,在存儲和查詢時,需要現在客戶端計算一次數據所在節點)
內存管理機制 長久未使用的valu交換到磁盤 一直在內存,使用特定長度的塊存儲數據,完全解決內存碎片問題,但是內存利用率不高
鍵過期時間 每個鍵可設置過期時間,到期自動刪除
內存淘汰策略 可設置內存最大使用量,超過時,執行數據淘汰策略(6中)

4. redis持久化機制

aof:appendonly file

機制:redis 每收到一個寫命令,都通過write 函數追加到文件中(默認是appendonly.aof)

appendonly yes              //啓用aof持久化方式
# appendfsync always      //每次收到寫命令就立即強制寫入磁盤,最慢的,但是保證完全的持久化,不推薦使用
appendfsync everysec     //每秒鐘強制寫入磁盤一次,在性能和持久化方面做了很好的折中,推薦
# appendfsync no    //完全依賴os,性能最好,持久化沒保證

如何壓縮aof 文件?提供bgrewriteaof 命令,收到此命令,redis 將內存中的數據已命令的方式保存到臨時文件中,最後替換原來的文件

  • redis 調用fork,現在有父子連個進程
  • 子進程根據內存中的數據庫快照,往臨時文件中寫入重建數據庫狀態的命令
  • 父進程繼續處理client請求,除了把寫命令寫入到原來的aof文件中,同時將寫命令緩存.這是爲了保證如果子進程重寫失敗而不出現問題
  • 當子進程把快照內容以命令的方式寫入到臨時文件中後,子進程發信號通知父進程,然後父進程把緩存的命令也寫入到臨時文件
  • 最後父進程使用臨時文件替換老的aof文件,並且重名名,後面收到的寫命令也開始往新的aof文件追加
    注:重寫aof文件,並沒有讀取舊的aof文件,而是將整個內存中的數據庫內容以命令的方式重寫了一個新的aof文件,類似快照

rdb:定時將內存中數據快照寫入到二進制文件.rdb中

 save 900 1     #900秒內如果超過1個key被修改,則發起快照保存
   save 300 10    #300秒內容如超過10個key被修改,則發起快照保存
   save 60 10000

5. redis 淘汰策略

策略 描述
volatile-lru 從已設置過期時間的數據集中挑選最近最少使用的數據淘汰
volatile-ttl 從已設置過期時間的數據集中挑選將要過期的數據淘汰
volatile-random 從已設置過期時間的數據集中任意選擇數據淘汰
allkeys-lru 從所有數據集中選擇使用lru
allkeys-random
noeviction 禁止驅逐數據
使用 Redis 緩存數據時,爲了提高緩存命中率,需要保證緩存數據都是熱點數據。可以將內存最大使用量設置爲熱點數據佔用的內存量,然後啓用 allkeys-lru 淘汰策略,將最近最少使用的數據淘汰。

Redis 4.0 引入了 volatile-lfu 和 allkeys-lfu 淘汰策略,LFU 策略通過統計訪問頻率,將訪問頻率最少的鍵值對淘汰

6使用場景

a. 消息隊列(由於Redis的列表是使用雙向鏈表實現的,保存了頭尾節點,所以在列表頭尾兩邊插取元素都是非常快的)
b. 緩存
5. 緩存穿透:查詢數據庫不存在的數據:緩存null值(過期時間設置較小)
6. 緩存雪崩:緩存集中失效,不同類型產品設置不同過期時間,並且加上隨機因子
7. 緩存擊穿(單個點):某個緩存數據成爲熱點,失效時,大量的併發查詢數據庫(可以考慮將爆款設置爲永不過期)
8. 緩存一致性

1.更新服務器時,立即更新緩存,或者刪除對應緩存
2.讀取緩存是,判斷是否爲最新緩存,若不是,需要衝到服務器讀取

6. 分佈式緩存: Master-slave

slave 發送請求讓master傳遞最新的內存快照給自己
在傳輸過程中,master 正在接受的數據,放到與slave建立的連接的緩存裏(list),快照傳輸完成後,發送緩存中的數據給slave
藉助redis,memched等緩存工具,將服務器本地緩存同步到分佈式緩存服務器,需要時間,實現難度較大

7. 分佈式鎖

命令 含義
setNX set not exist:如果key不存在,就設置上,返回1,否則返回0
getset 返回就值並且設置新值(相當於是原子操作,是單獨使用get ,再set所不能達到的效果)
分佈式鎖的實現:
現在有併發線程 c1,c2,鎖被C3持有,並且C3 已經掛掉
c1,c2 分別使用 setNX("LOCK");//顯然是失敗(返回0)的,而且鎖也沒被釋放
c1 del鎖,加鎖成功
c2 del 鎖,加鎖成功(但是這是錯誤的)

解決:
加鎖時,將加鎖時間戳,以及過期時間(即持有鎖的最長時間)
併發線程 c1,c2,鎖被C3持有,並且C3 已經掛掉
c4 setNX("LOCK")// 返回0
如果獲取失敗:
c4  get  命令,檢查是否過期,
        如果沒過期,繼續等待/ /檢查上次加鎖時間以及過期時間算出當前鎖是否應該被釋放  (lockTime+expirTime<currentTime)
         如果過期,使用getset 更新獲得鎖時間以及過期時間
         

// 如果線程c5先於獲取到鎖,那麼c4在檢查過期時間時,得到的是未過期,需要等待(嘗試次數可以做一下記錄,超過多少次,就返回失敗)

7. 事物的實現(隊列,其實出錯後,不會回滾沒出錯的指令)

命令multi  開啓事務

set key1 val1
set key2 val2
....

命令:exec 提交事務
你會發現一旦一條指令出錯,其餘是**不會回滾的。**
redis 是將多個命令放入一個隊列,輸入exec 命令,依次執行隊列中的命令

在這裏插入圖片描述

rabbitmq

1. 協議

2. 交換機模式

direct:
單發多接收:

3. 使用場景

化同步爲異步(應用內部,流量削峯,比如下單秒殺,用戶比較活躍,同步會導致計算機性能下降),松耦合(多個應用之間)

場景 描述
單發單接收 簡單的發送與接收,沒有特別的處理
單發多接收 一個發送端,多個接收端,如分佈式的任務派發。爲了保證消息發送的可靠性,不丟失消息,使消息持久化了。同時爲了防止接收端在處理消息時down掉,只有在消息處理完成後才發送ack消息
Publish/Subscribe 發佈、訂閱模式,發送端發送廣播消息,多個接收端接收
Routing (按路線發送接收 發送端按routing key發送消息,不同的接收端按不同的routing key接收消息

4. 持久化

默認是沒有開啓持久化的,如果在產生消息時,指定消息時持久消息,那麼消息會發送到持久化交換機,然後交給持久化隊列,(會將信息記錄在持久化日誌中),如果消息被路由到非持久隊列,那麼持久化日誌中就會刪掉持久化記錄

多線程

1. 線程與進程對比

進程是指運行中的程序,有獨立的內存空間
線程是進程中最小的執行單元,共享進程的內存空間,以及上下文,當然也有自己

2. 線程的狀態(生命週期)

新建(new 創建)
就緒(調用了start)
運行中(running)
阻塞(由於鎖導致),
無限等待(需要其他線程喚醒),
有限等待(時間到,就繼續),
終止(線程的run 執行完就結束)

3. 線程的實現方式(繼承thread,實現runnable 接口,或者實現Callable 接口)

4. 鎖的概念

5. synchronized 與lock 接口對比

synhronized lock 描述 方法
隱示獲取鎖,釋放鎖(先獲取,再釋放,簡化同步,但是缺少擴展) 顯式獲取鎖,釋放鎖
可中斷的獲取鎖 在獲取鎖的過程,可以響應線程中斷,當獲取到鎖的的線程被中斷時,鎖可以被釋放) lockInterruptibly()
嘗試非阻塞式的獲取鎖 當前線程嘗試獲取鎖,如果這一時刻鎖沒有被其他線程佔有,則成功獲取鎖 tryLock()
超時獲取鎖 在指定的截止時間之前返回(1.獲得鎖,返回true 2.被中斷,拋出異常 3. 超時時間結束,返回false) boolean tryLock(long time,TimeUnit unit)

6. 鎖分類

名稱 概念 特點
偏向鎖 如果對象頭的偏向鎖標誌位指向當前線程,那麼當前線程不需要通過cas 獲取鎖,直接進入同步塊
自旋鎖 線程獲取鎖失敗,通過循環嘗試獲取鎖,佔用cpu,避免線程切換帶來開銷 需要注意自旋的次數以及間隔時間,不要無限自旋下去(jvm默認自旋次數是10,可以使用-XX:PreBlockSpin更改;除此之外,1.6引入自適應自旋鎖,自旋時間取決於當前線程上次獲得鎖用時長度,如果上次獲取鎖是在短時間內獲取,那麼這次自旋也很可能成功獲取鎖,那麼jvm可以給出較長的時間,給他;如果上一次獲取鎖耗時很長,那麼這次也認爲他很難通過自旋獲取到鎖,那麼就直接省略自旋操作)
可重入鎖 當前線程如果已經獲取到鎖,可以重複加鎖 比如遞歸調用某種需要加鎖的方法時,不會被自己阻塞
排它鎖(獨佔鎖) 只能由一個線程獲取到鎖,如果鎖已經被其他線程獲取,當前線程被阻塞 比如synchronized,ReentrantLock
共享鎖 可多個線程同時獲取到鎖 比如semphore,信號量,可以控制併發的線程數(連接池可以使用)
讀寫鎖 使用鎖分離的思想,讀鎖時共享可重入鎖,寫鎖是獨佔可重入鎖 寫鎖加鎖過程:如果當前線程是已經獲取到鎖的線程,可以直接獲取鎖(可重入),如果寫鎖狀態爲0,表示沒有線程獲取到寫鎖,當前線程獲取鎖,寫鎖狀態+1。 寫鎖釋放:可以將寫鎖狀態置爲0,讀鎖加鎖過程:如果當前線程是持有寫鎖的線程,或者是寫鎖狀態爲0,則可以獲取讀鎖,讀鎖狀態+1;否則需要被阻塞;鎖降級:持有寫鎖的線程,可以直接獲取讀鎖,然後釋放持有的寫鎖
公平鎖 線程最終獲取到鎖的順序和請求鎖的順序一致(也就是FIFO)
非公平鎖 同一個線程可能連續多次獲取到鎖 可減少上下文切換,提高吞吐量(也是鎖的默認實現方式)

7. AQS=AbstractQueueSynchronized

併發同步器,使用來實現Lock接口定義方法功能的基礎
同步隊列:雙向鏈表, 加鎖時,如果同步隊列爲空,使用cas將其設置到隊列頭
如果同步隊列不空,使用cas將其設置到隊列尾部
釋放鎖:(頭節點是獲取同步狀態成功的線程節點),釋放鎖時,會喚起後繼節點
由於某些節點發生中斷以及爲null的情況,從隊列尾部向前找,找到所謂的後繼節點,然後由獲取同步狀態成功的線程去將頭結點設置爲自己所在節點(這個過程不需要cas)
每個非頭部節點自旋檢測自己的前驅結點是否爲頭節點,如果是尾頭節點,那麼該節點可以嘗試獲取同步狀態;除此之外,所有節點之間基本沒有數據通信

狀態隊列:

9. synchonized 加鎖過程

幾個概念:

概念 解釋
mark word 用於存儲對象自身的運行時數據,比如哈希碼,GC分代年齡,鎖狀態標誌,線程持有的鎖,偏向線程id,偏向時間
對象頭
監視器 用來實現同步,與每一個java對象關聯,每個java對象都有一個監視器與之對應,是synchronized實現內置鎖的基礎
自旋鎖 (忙人) 讓不滿足條件的線程,等待一段時間,看能不能獲取鎖,通過佔用處理器,避免線程切換帶來開銷(注意自旋時間和次數應該有一定限制,到達限制,任然沒有獲取鎖,則需要掛起等待)
偏向鎖(熟人) 大多數情況下,同一個對象的鎖總是由同一個線程多次獲得。當一個線程訪問同步塊,並且獲取到鎖,會在對象頭和棧幀中的鎖記錄裏存儲鎖偏向(偏愛)的線程id,偏向鎖是可重入鎖(可以想象帶上synchronized修飾的遞歸方法,就會同一個線程多次進入同步代碼塊,但不會每次每次進入去獲取鎖,第一次就獲取到了)。如果鎖的對象頭中的markword存儲着指向當前線程的偏向鎖,則不需要重新通過cas 加鎖,解鎖,持有偏向鎖的線程(不處於活動狀態)時,纔會釋放鎖。偏向鎖無法使用自旋鎖優化,因爲有其他線程來競爭鎖,就破壞了偏向鎖的假設前提
輕量級鎖 相對重量級鎖而言,輕量級鎖是爲了減少在無實際競爭情況下,使用重量級鎖帶來的性能消耗。jvm 在當前線程的棧幀中創建用於存儲鎖記錄的空間,並且將對象頭中的mark word 複製到鎖記錄中,然後嘗試CAS 將對象頭的mark word 替換爲指向鎖記錄的指針,如果成功,則表示當前線程獲取鎖成功,失敗,則表示當前存在其他線程在競爭鎖。當前線程採用自旋的方式獲取,自旋失敗,則升級爲重量級鎖
重量級鎖 通過對象內部的監視器實現,底層依賴操作系統的Mutex Lock 實現,操作系統實現線程的切換需要從用戶態到內核態的切換,成本很高。線程競爭不使用自旋,不消耗cpu。線程進入阻塞,等待被喚醒。

7. object 的notify (notifyall)方法與wait方法的作用

(必須使用在synchronized修飾的{} 內部)
wait 將持有該對象的線程進入waiting狀態,同時會釋放鎖,只有其他線程獲取到該對象的鎖,執行對象的notify方法,然後釋放鎖,waiting狀態纔會轉變爲runnable

9. 什麼是線程安全?

 線程安全就是說多線程訪問同一代碼,不會產生不確定的結果
 (主存與內存,線程是讀取主存的變量的拷貝,操作完之後將結果寫入主內存,多個線程操作可能導致,讀取的雖然是同個數據,各自對自己的副本進行操作,寫入主內存時,可能出現只保存其中一個線程的結果)

10. 線程安全的集合類,不安全的類的對比,線程安全的實現方式

方式1:同步集合類,Vector,HashTable,
方式2:不可變容器類,unmodifiableXXX
方式3:讀寫分離,CopyOnWriteArrayList(對寫加鎖,讀不加鎖)
方式4:鎖細化:1.7 的concurrentHashMap 分片鎖, 1.8的頭節點加鎖
concurrentHashMap也有樂觀鎖的用法,在doPut 方法中,
方式5:skipList,無鎖併發,利用概率算法計算出元素層(level),可實現o(lgn) 查找
插入節點時,需要判斷後繼節點之前插入marker,隔離防止併發操作
插入之後,新節點的next 指向marker 的next

方式5:跳躍表:ConcurrentSkipListMap,
a . hashmap (不安全)與hashTable(synchonized ,全表鎖定) ,currentHashMap(分片鎖,(1.7)(1.8 使用鎖分離,只對頭結點加鎖)
b. ArrayList 與Vector
11. hashmap線程不安全,有線程安全的map嗎?(併發包的currentHashMap(1.7分片鎖),或者是Collections 下的unmodifierableMap,所有的寫操作都是直接拋出異常)
10.hashmap與hashtable
後者是同步(速度就會降低)
迭代器:前者是fail-fast 類型,遍歷過程不允許刪除,後者(enumerator迭代器)允許
12. 保證線程安全的方式有哪些

  • [線程封閉] 將對象封裝到一個線程裏,只有這一個線程看到這個對象,實現方式
    a. 堆棧封閉,局部變量,無併發問題
    b. ThreadLoacl,根本就麼有併發問題,看查看set 方法
    在這裏插入圖片描述
  • [使用不可變對象],比如Collections.unmodifiablexxx,final 關鍵字修飾變量
  • 加同步鎖,代碼層面synchronized關鍵字,數據庫層面是樂觀鎖和悲觀鎖
  1. ThreadLocal 的作用
    可以當做是線程的本地變量處理,是與調用的線程所綁定的
  2. synchronized 關鍵字底層實現原理
  3. 什麼是cas?
  4. cas 面臨的問題,以及如何解決
可能面臨的問題 解決方式
ABA cas 在操作值的時候,會檢查值是否發生變化,如果沒有發生變化,則更新,但是如果數據由A ->B->A ,檢查發現A 沒有改變,(事實上已經改變),解決思路使用 版本號,1A->2B->3A
循環時間長,開銷大 CAS 如果循環時間過長,會對cpu造成較大的開銷,解決思路:1. 如果jvm支持處理器提供的pause指令,可以延遲流水線執行指令,也可以避免在退出循環的時候因內存順序衝突引起cpu流水線被清空
  1. 併發編程的最佳實踐

1,使用本地變量
2.使用不可邊量
3.最小化鎖的作用與範圍
4. 使用線程池的Excutor,而不是直接new Thread 執行
5. 寧可使用同步,也不要使用現成的wait 與notify
6.使用BlockingQueueingQueue 實現生產-消費模式
7. 使用併發集合而不是使用加了鎖的同步集合
8. 使用Semaphore 創建有界的訪問
9. 寧可使用同步代碼塊,也不實用同步的方法
10. 避免使用靜態變量(在併發情況下,使用final,使用只讀集合)

  1. 線程調度方式:
線程調度方式 含義
協同式調度 當前線程執行完,通知cpu切換到另一個線程(可能長時間阻塞,如果當前線程時長時間任務)
搶佔式調度 每個線程由系統來分配時間,線程切換不由線程自身決定,java 就是使用這種方式

注意:雖然java中使用搶佔式調度,線程還是可以"建議"系統給自己分配多一些時間,設置線程優先級(java 有10個)
但是操作系統有可能自己來改變線程的優先級,比如windows,優先級推進器,發現某些工作效率很高的線程,那麼會額外的給他更多的時間

計算機網絡

  1. 三次握手與四次揮手

  2. 三次握手(爲了確認雙方發送與接受數據是沒問題的)能保證一定建立連接嗎?
        a與b建立連接
        第一次握手(syn=1,seq=client_isn) a說我想和你建立聯繫,你能不能收到我的消息啊(確認b能收到自己的消息)
        第二次握手:(syn=1,seq=server_isn,ack=client_isn+1)b 回覆可以啊,但是你能不能收到我發送的消息啊
        第三次握手:(syn=0,seq=client_isn+1,ack=server_isn+1)a說,老弟,我能收到你的消息,趕緊發數據給我吧,老闆催我幹活呢
    三次握手:保證了雙方都信任對方能接受到自己的消息,那麼就可以發正式的數據了
    注:
    ack 字段是爲了告訴對方自己已經收到了你的ack-1 那條信息
    seq 字段是爲了標識這條記錄是自己這邊的編號,對方返回的ack 是基於我這個seq來的

四次揮手:假設A發起斷開連接請求
A:“喂,我不說了。”A->FIN_WAIT1
B:“我知道了。等下,上一句還沒說完。Balabala……”B->CLOSE_WAIT | A->FIN_WAIT2
B:”好了,說完了,我也不說了。”B->LAST_ACK
A:”我知道了。”A->TIME_WAIT | B->CLOSED

A等待2MSL,保證B收到了消息,否則重說一次”我知道了”,A->CLOSED

  1. tcp與udp 的區別
     
    1.基於連接與無連接;
    2.對系統資源的要求(TCP較多,UDP少);
    3.UDP程序結構較簡單;
    4.流模式與數據報模式 ;
    5.TCP保證數據正確性,UDP可能丟包,TCP保證數據順序,UDP不保證。
    UDP 用於實時性要求比較高,比如直播,tcp適用於通信可靠性比較高的情景
  2. 常見狀態碼含義
  3. ip地址分類
  4. URI 與URL

操作系統

  1. 什麼是死鎖
    所有申請的資源都被其他等待進程佔有,那麼該等待進程有可能在無法改變其狀態,這種情況稱爲死鎖(deadlock)
  2. 造成死鎖條件
條件(全部滿足發生死鎖) 描述 解決方式
互斥 至少有一個資源處於非共享模式(即一次只有一個進程使用)如果另一個進程申請該資源,那麼申請進程必須等到該資源被釋放爲止
佔有並等待 一個進程必須佔有至少一個資源,並且等待另一個資源,而該資源爲其他進程所佔有
非搶佔 資源不能被搶佔(即資源只能在進程完成任務後自動釋放
循環等待 一組等待進程,p0,pn ,p0 等待p1,p1等待p2,…pn 等待p1
  1. 解決或者是避免死鎖的算法

  2. 銀行家算法

  3. 哲學家喫飯問題

  4. rpc(socket+反射)
    rpc 是一種協議
    實現此協議的有tomcat,dubbo 等等

  5. 什麼是CPU流水線(將指令分成多步,不同指令各步操作重疊,從而實現幾條指令並行處理,加速程序運行)

 以洗車爲例:A,B,C,D四輛車,都需要噴水->洗潔劑->擦洗->烘乾四個步驟
   如果不將洗車這個任務成多個小步驟,那麼B 車必須等到A 烘乾後才能開始洗車(有些步驟的場所就是空閒的)
   如果使用流水線:
   如果A 車到了洗潔劑,那麼B 車可以前往噴水(這樣很多步驟不會閒着,等其他步驟完成,提高了效率)
  1. 進程間通信的方式
    父,子進程間共享狀態信息,共享文件表,但是不共享用戶地址空間。 進程有自己的獨立的地址空間,不會擔心進程的虛擬內存相互覆蓋
    但是獨立的地址空間,使得進程間通信變得更加困難
    (需要使用顯式的IPC 通信)
進程間通信的方式 描述
套接字 不同主機上的進程間通信,交換任意的字節流
  1. 內存交換技術
PCB:進程控制塊,常駐內存,如果內存緊張,會將進程數據對換到磁盤,控制塊掛起,當內存不緊張時,停止交換
控制塊被調度時,將磁盤中進程讀取到內存
  1. 內存覆蓋技術
  2. 內存分配算法
名稱 描述 分區排列順序 特點 缺點
首次適應 從頭到尾到合適的分區 空閒分區以地址遞增次序排列 綜合看性能最好,算法開銷小,回收分區後一般不需要對分區隊列重新排序
最佳適應 優先使用更小的分區,已保留更大的分區 空閒分區以容量遞增次序排列 會有更多的大分區被保留,滿足大進程使用 會產生很多太小、難以利用的碎片:算法開銷大,回收分區可能需要對空閒分區隊列重新排序
最壞適應 優先使用更大的分區,以防產生太小的不可用的內存碎片 空閒分區以容量遞減排列 可以減少難以利用的小碎片 大分區容易被用完,不利於大進程:算法開銷大,理由同上
臨近適應 由首次適應演變而來,每次從上次查找結束位置開始查找 空閒分區以地址遞增 不用每次從低地址的小分區開始檢索,算法開銷小,原因同首次適應算法 會使高分區的大分區也被用完

觀察可以知道;首次適應,最佳適應,最壞適應,以及鄰近適應中,首次適應是最佛系的,沒有特別要求說是優先使用大,小空閒塊,而是遇到合適的,就使用;不適合就繼續往下找
首次適應於鄰近適應的區別是:首次適應每次都是從頭開始查找,鄰近適應是從上次查找結束的位置開始
最佳適應與最壞適應區別是:前者是優先使用較小的空閒塊,後者是優先使用大空閒塊

mysql

  1. mysql 支持的存儲引擎

innodb(事務性) vs Myisam(不支持事務)

innodb Myisam
B+
支持事務 不支持事務
支持表級鎖和行級鎖 只支持表級鎖

redo log和undo log都屬於InnoDB的事務日誌

innodb 事務持久性的實現原理:使用redo log

數據存儲在磁盤中,但是io速度很慢,於是有了緩衝(buffer pool),緩衝中保存磁盤中部分數據頁的映射,
查詢數據時,先到緩衝查詢,,如果沒有,讀取磁盤,加入到緩衝,這提高了查詢效率.
寫數據也是先寫入緩衝,定時將緩衝中數據刷到磁盤中(刷髒)
但是問題來了,如果突然斷電,或者宕機,緩存中的數據就消失了,會導致丟失數據,無法保證事務持久性.
redo log 出現了,
1. 在日誌將一個事務中需要執行的sql記錄下來,
2. 接着往緩存寫數據
3. 事物提交時,調用fsync 接口將redo log中的sql 執行刷入到磁盤
4. 如果宕機,只會丟失緩存中的數據,但是緩存中的數據是在記錄到redolog 之後才添加的,也就是redo log還保留着數據的記錄,讀取redo log,執行操作,這就保證了事物的持久性

至於  redolog 也是磁盤io,爲什麼速度會比緩存中刷髒速度快呢?
5. 緩存刷髒數隨機io,redolog是追加,屬於順序io
6. 刷髒是以數據頁(Page)爲單位的,MySQL默認頁大小是16KB,
一個Page上一個小修改都要整頁寫入;而redo log中只包含真正需要寫入的部分,無效IO大大減少

原子性,隔離性實現原理:undo log(原子性,單元要麼都成功,要麼都不成功一個出現失敗,就需要回滾)

InnoDB實現回滾,靠的是undo log:當事務對數據庫進行修改時,InnoDB會生成對應的undo log;
如果事務執行失敗或調用了rollback,導致事務需要回滾,便可以利用undo log中的信息將數據
回滾到修改之前的樣子。

undo log屬於邏輯日誌,它記錄的是sql執行相關的信息。當發生回滾時,InnoDB會根據undo log的內容
做與之前相反的工作:對於每個insert,回滾時會執行delete;對於每個delete,回滾時會執行insert;
對於每個update,回滾時會執行一個相反的update,把數據改回去。
  1. 引擎的對比以及使用場景

1. 
讀多寫少:使用myisam
寫多讀少:(需要保證事物).使用innodb


  1. 索引的實現原理
    底層使用B+樹實現
  2. 爲什麼不使用hash,AVL等數據結構呢?
    B,B+ 是自平衡的多叉樹,相同數量的節點,B,B+ 樹高度會低於二叉樹
    如果使用avl,有序序列是中序遍歷,也就是說得到有序序列,需要往根回退
    hash 對於查找單個數據很快,但是對於範圍查找就顯得無能爲力了
B樹 B+樹
非葉子節點有數據 葉子節點無數據,只有key (節省空間,可存放更多的索引值),每個節點大小一致(一個界節點可以對應上磁盤上的一個數據頁,避免了跨頁查找) ,減少io,提高性能
葉子節點有數據 葉子節點有數據,並且有指針相連,成有序單鏈表(方便順序查找)
  1. 併發版本控制 MVCC
    通過在數據表中添加一列隱藏列記錄該條記錄版本號之類的東西,
    假設讀取數據時:版本號是 A
    更新:事務開始時版本號設置爲A+1,但是提交失誤時,發現數據庫中版本號不再是A+1了,就是說明數據庫記錄被另一個線程修改了,而當前的修改就是在舊記錄上修改而成的,顯然是不滿住要求的,所以重新取出數據庫的記錄,執行操作
    併發版本控制實現了無鎖操作數據,提高了併發性能
每行存儲版本號,而不是存儲事件實際發生的時間。
  每次事物的開始這個版本號都會增加。自記錄時間開始,每個事物都會保存記錄的系統版本號。

秒殺系統順序:

化創建訂單,減庫存並行爲串行
一般是:數據褲查庫存,大於0,創建訂單
但是如果此時是並行訪問,可能只有1件商品,但是卻創建了兩件訂單

改進:使用redis的 原子減操作,如果返回結果<0,表示沒有庫存,反回秒殺結束
但是又有另一個場景redis減庫存成功,但是數據庫創建訂單失敗
改進:化並行爲串行,原子減操作>=0,
redis 設置 userid-goodsid 這樣的記錄,表示已經提交過秒殺訂單,不能重複秒殺.
將秒殺請求放入消息隊列
消費者取出消息隊列中的訂單請求數據,創建訂單,減數據庫秒殺庫存

  1. 預加載庫存到redis
  2. 下單請求
  3. 查詢redis是否存在userId-goodsId(避免重複單)
  4. 非重複單,查詢(redis,原子減)庫存(減庫存)(庫存小於0,則返回秒殺結束)
  5. 減庫存成功,請求入隊,返回排隊中
  6. 消費者,取隊列消息,查詢數據庫中庫存,如果爲0,該請求不能創建訂單
  7. 創建訂單:先查數據庫庫存,(減數據庫庫存),再建立訂單(可以在同一個事務中提交))

電話一面補漏

  1. 爲什麼要重寫equal方法?
    答案:因爲Object的equal方法默認是兩個對象的引用的比較,意思就是指向同一內存,地址則相等,否則不相等;如果你現在需要利用對象裏面的值來判斷是否相等,則重載equal方法。
Note that it is generally necessary to override the {@code hashCode}
     * method whenever this method is overridden, so as to maintain the
     * general contract for the {@code hashCode} method, which states
     * that equal objects must have equal hash codes.
  1. 爲什麼重寫hashCode方法?
    因爲規定;equals 成立的對象必須hashcode相同
    hashMap 中:key 的位置計算是通過hash ,key 的是否存在是通過equals //如果equals 成立,而hash 不等,那麼在hashMap 這些結構中是找不到放入的key 的
    答案:一般的地方不需要重載hashCode,只有當類需要放在HashTable、HashMap、HashSet等等hash結構的集合時纔會 重載hashCode,那麼爲什麼要重載hashCode呢?就HashMap來說,好比HashMap就是一個大內存塊,裏面有很多小內存塊,小內存塊 裏面是一系列的對象,可以利用hashCode來查找小內存塊hashCode%size(小內存塊數量),所以當equal相等時,hashCode必 須相等,而且如果是object對象,必須重載hashCode和equal方法。

hasmap 的普通方法:

key1,key2 如果equals 成立,但是沒有重寫hashcode,那麼就能同時存在key1和key2 在hashmap中
在通過key1執行get方法時,通過equals 成立的key去獲取數據,會出現獲取到不同的數據

 public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

hashMap 的get 方法:
要求hash相同並且 (key == 或者equeals)

 final Node<K,V> getNode(int hash, Object key) {
 
 if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                        }
  1. http 協議
    超文本傳輸協議,基於tcp實現

  2. 微信綁定登錄時是如何實現的
    如何獲取openId 的??
    在登錄攔截器中,判斷當前用戶是否登錄;
    如果未登錄,
    調用微信api提供的一個接口(獲取openid),主要參數:appid+回調接口地址(自己開發的)
    微信api收到請求後,會回調你的接口
    在自定義的回調接口中,需要調用weixin api的另一個接口,這時才能夠返回openid

大致流程是這樣的:
登錄攔截器->
調用微信api接口1(帶上appid,自定義回調接口1)
->微信調用自定義回調接口1(微信返回一個code)
->自定義回調接口1 處理邏輯:接收微信返回的code,然後再帶上參數appid,請求微信api的2號接口
->微信api接口2返回openid

(有點像是三次握手,客戶端發起請求,表明身份,服務端返回code,表示同意你請求,然後客戶端帶着code 去請求服務端獲取數據)
5. 閱讀計數怎麼實現的
閱讀記錄表: userid-文章id生成一條記錄(按照文章發佈月份建立表,避免判斷某篇文章是否被某人閱讀還需要跨表查詢的問題)
文章表 添加字段 :閱讀次數,點贊數

點贊與閱讀計數 具體場景處理;

前提:
文章表(文章id,title,content,點贊數,閱讀數)
閱讀記錄表:(id,文章id,用戶id,閱讀時間,是否點贊)

閱讀次數:同一用戶閱讀同一篇文章,重複閱讀只算一次閱讀
8 .如何獲得閱讀總數(某一篇文章)

先查緩存,存在,則返回
如果不存在,查數據庫,並且存入緩存,返回
  1. 閱讀次數怎麼處理?是用select count(*) 嗎?
文章表添加字段記錄總閱讀數,總點贊數類似

10.閱讀次數怎麼更新,實時嗎?

閱讀次數 實時在緩存中更新,定時將緩存更新到數據庫
  1. 用戶當天反覆閱讀,怎麼辦?(如何避免重複查詢用戶是否閱讀過這篇文章)
       查詢文章詳情時
       1. 取緩存,是否存在用戶閱讀記錄,
                1.1 若存在,返回
        2若不存在,則查數據庫
                     2.1若依然不存在,存入閱讀記錄,緩存也存入一條記錄,緩存中 總閱讀次數+1
                     2.2如果數據庫存在,也是存入緩存,返回(這裏避免了短時間內反覆閱讀時,反覆查詢數據庫,去判斷是否需要插入閱讀記錄)
  1. 點贊次數怎麼處理?(同閱讀次數)
  2. 如何處理用戶的頻繁點贊,取消點贊
  1. 查詢緩存中是否存在點贊記錄,
           1.1如果存在,更新緩存中點贊狀態;
  2 如果不存在,取數據庫點贊記錄,
               2.1 數據庫存在,修改點贊狀態(只是修改緩存中的點贊狀態,數據庫中此時還不修改)
               2.2  如果不存在,數據庫插入點贊記錄; 
   2. 更新(或插入)緩存中點贊狀態;  更新緩存中點贊總數
   3. 定時器同步緩存中的點贊總數到數據庫;同步各用戶的點贊狀態到數據庫
  1. 如果隨着時間推移,閱讀記錄表變得很大怎麼處理?
    可以考慮分表:
 1. 將按照文章發佈年月來建立閱讀記錄表,一個月內發佈的文章的閱讀記錄全部在同一張表,通過文章發佈年月定位到閱讀記錄表名
 2.  或者按照文章發佈的日期,1-31 建立31張表,每月同一天發佈的文章的閱讀記錄都放到同一張表

電話2,3面知識點

  1. http 協議

  2. 如何分佈式架構下的秒殺系統,如何設計,比如如何限流(令牌筒,擴容(負載均衡))

  3. 如何保證集羣(主-從庫的高可用,如果主庫崩掉了咋辦)
    可投票選舉新的主節點
    崩掉的節點是有log的,redolog,重啓會自動執行

  4. 消息隊列分佈式下面如何使用

  5. 線程池的配置方法(配置的理論依據,比如配置多少合適啊,)
    需要考慮是cpu 密集型還是io密集型,如果是cpu密集,則表示cpu空閒時間少,線程數可以設置爲N(cpu)+1

  6. 線程池的好處
    a. 可以即時響應,任務到達時,不必創建線程後再執行
    b. 提高線程的可管理性,線程是不能無限制創建的,線程池可以統一分配,調優,和監控
    c. 降低資源消耗避免創建,銷燬帶來的消耗

  7. 線程池的實現原理

這裏的是書上原話,感覺有問題!!

當一個任務到來時
a. 線程池查看核心線程是否都在工作
 1. 如果不是,創建一個新的工作線程來執行任務
 2. 否則,進入下一個步驟
b. 線程池判斷工作隊列是否已滿
 1. 如果沒滿,將新提交的任務儲存在工作隊列
 2. 否則,進入下個流程
 
 c. 線程池判斷線程池中線程是否都處於工作狀態
 1. 如果沒有,創建一個新的工作線程來執行任務
 2. 否則交給飽和策略處理這個任務 
 

線程池參數:

參數名 含義
corePoolSize 線程池基本大小,即使當前線程足夠完成任務,但是線程數量小於該值,會繼續創建線程
  1. 系統中用到了redis,有沒有過性能對比
  2. 有沒有看過日誌(redis,mysql)
  3. 看的書籍(併發藝術,jvm 需要再看看,一定要隨時隨地可以說出類加載,垃圾回收,線程池,鎖機制,synchronized與lock接口, ReentrantLock,CountDownLatch, 特點以及使用場景)
  4. 併發包,底層實現以及特點
  5. 存儲引擎沒問

自我總結:不善於引導面試官去往自己擅長的(瞭解的方面引導)

比如:

  • 問:問道爲什麼使用redis
  • 答:因爲redis是得益於底層的獨特的數據結構,單線程,以及數據基本是存在於內存中,效率奇高,適用場景之一是用作緩存,減少查詢對數據庫的壓力,提高查詢速度
(暗示了了四個可提問點,
1,數據結構實現(string,zMap,Hash,),可能還有事務等等
2,單線程有啥好處,
3,內存那就相對於磁盤了,數據存儲方式
4,緩存可以延伸出適用場景)
  • 1 問:既然你說到了使用場景,那除了緩存之外還有其他什麼場景嗎?
  • 答:redis除了用作緩存外,數據也可以持久化到磁盤,主要是aop與rdb兩種方式,也就是說可以當做數據庫使用,他本本身也是key-val型數據庫,(類似的還有memache);
    除此之外,redis自帶了訂閱-發佈功能,可以實現普通的消息隊列的類似的功能
暗示了其他提問點:
1. aop 與rdb有啥區別,具體說說(前者是基於日誌(也就是文件),後者是基於二進制文件),持久化的效率不一樣,
而且 配置方式也不一樣,
2. memched也是key-val 型,這兩者有啥區別?  (可支持的數據類型,緩存淘汰機制,持久化,是否支持分佈式,
等方面區別)
3. 訂閱發佈,與中間件,比如rabbitmq有啥區別?(可能需要從穩定性,分佈式,使用場景等等方面去比較)
  • 2問:我看你項目使用了rabbitmq,爲什麼使用rabbitmq
  • 答:秒殺場景,是典型的高併發場景,衆多用戶同時搶購同一件爲數不多的商品,可能會出現重複訂單,以及超賣現象,而mq的一種應用場景則是化同步爲異步(化並行爲串行),然後巴拉巴拉
暗示:
4. 消息隊列的其它使用場景
5. 消息隊列使用到的設計模式(訂閱-發佈模式)
  • 3問: 這樣用戶會不會由於遲遲不能得知是否秒殺成功,體驗不好
  • 答:用戶請求一放入消息隊列,就返回給前端排隊中,然後前端輪詢查詢用戶訂單是否創建成功(或者是咋在訂單創建成功後,給客戶端推送一條消息,或者發送一條短信,商品搶購成功,請前往支付)
  • 4問:可以不使用消息隊列實現秒殺這一要求嗎
  • 可以,併發場景所有的問題都是歸根於對於某些資源,或者數據的能否線程安全的操作,也就是讀和寫
    讀需要保證各線程看到的是一致的,寫需要保證不能統一時刻寫,寫結果而且需要給其他線程可見
    這裏的秒殺線程併發的問題無疑是在以下幾個地方:
1.查詢庫存: (查數據庫,多個線程查詢數據庫,對數據庫造成壓力,可以使用redis預減庫存
,查庫存可在redis 中進行,因爲redis是單進程單線程的,併發的請求自然而然的變成了串行的方式)
2. 創建訂單:如果是併發的創建訂單,刪減也可能會出現問題,當然數據庫也有行級鎖,表級鎖,
還有MVCC,這些東西可以被暗示提出來
 這可以使用redis 實現分佈式鎖,主要實現思路:
   1.setNx
   2.設置鎖超時時間(比如鎖最長持有10秒,這需要根據業務處理的時間來定,不能太長也不能太短),比如客戶端掛了
   鎖沒有釋放,那麼其他客戶端一段時間可以獲取到鎖
   3. 自旋獲取鎖,配置嘗試次數
   4. 限時獲取鎖,這指定時間內獲取不到鎖,就返回
   5. 針對獲取到鎖的客戶端沒釋放鎖就掛掉的情況,其他客戶端獲取鎖之前可以先setbnX,
如果失敗,自旋獲取當前鎖超時時間,如果超時時間已經過了,刪除鎖,然後setNx,獲取到鎖

  • 5問:這兩者推薦使用哪一種呢?
  • 個人建議使用消息隊列,可以立刻給前端以響應,加鎖獲取鎖都需要時間,因爲秒殺的商品不一定是一件,假設是1000種秒殺商品,使用鎖,那麼會有1000個鎖,極端點也就是1000個併發線程,各自操作各自的商品,這樣對數據庫還是有衝擊的(因爲數據庫連接池對連接數是有限制的,也就是除了這個分佈式鎖之外,請求也可能阻塞在獲取數據連接上),但是使用消息隊列完全就是異步了,不是簡單的串行,對數據庫基本就沒有什麼壓力
  • 6問:消息隊列還有其他適用場景嗎
    1. 應用解耦(高內聚,低耦合\迪米特原則)(比如訂單系統與庫存系統之間的,不能因爲庫存系統減庫存失敗,導致訂單創建失敗,庫存是可以機動的調整的,而不僅僅是字面上的數字)
    1. 流量削峯(秒殺)
    1. 異步處理:非必要業務邏輯異步處理,比如註冊短信發送,傳統響應時間是=寫入數據庫+發送短信
      異步處理後:只要寫入數據庫成功,就可以響應用戶,短信可以在之後發送也是可以的
  • 7問:平時喜歡看一些技術書籍,那麼你看了哪些書籍
  • 看了jvm,和java併發藝術:
    jvm 讓我瞭解到虛擬機內部的一些列巧妙的設計(分代回收),還有一個類從.class 文件到對象的全部過程;
    併發藝術打開了我對多線程學習的大門,還有一些jdk中一些經典數據結構的具體應用(hashmap,紅黑樹,跳躍表,雙向隊列併發容器),還有鎖機制以及實現,lock接口,線程池,隊列等等,纔開始發現數據結構是如此的重要
  • N 問,談談鎖機制,線程安全,紅黑樹,synchronized與Lock 接口(或者是reentrentLock)區別,鎖降級(讀寫鎖),鎖分離,鎖優化(自旋,鎖粒度擴張,鎖粒度細化),類加載機制,雙親委派模型,同步容器與併發容器對比(數據結構,加鎖方式)

N面被虐

  1. 兩個有序數組求交集
    需要給出時間複雜度分析和方法對比
  2. 設計排行榜,千萬用戶,得分實時變化, (id,得分,排名)
    需要的功能:
    根據id獲取得分
    根據id獲取排名
    獲取topN
    思路:使用redis實現,key=id,val=score
    zSet
    根據得分範圍分段,比如得分範圍是1-100萬
    則可以0-10,10-20, 使用10個redis,各自負責10萬數據的存儲
    當分數發生變化時:檢查新得分是否還在當前redis範圍,如果不在就刪除,然後插入到合適的redis中
    根據id獲取得分,則需要在這10個reddis 桶中去查找
    可以在某個redis記錄id對應的redis編號,方便快速去準確的redis中查找數據
    根據id獲取排名:這點需要注意,每個同存儲的記錄需要單獨記錄,比如10號redis當前存儲1000個
    9號當前存儲2000個,那麼第8個redis的某個用戶的排名=1000+2000+當前桶中排名
    獲取topN: 檢查n落在哪一個桶內,如果落在9號redis,則需要取出10號全部數據+9號的前 n-10號筒數據量數

spring 爲什麼要使用工廠模式?

直觀的感受是代替了new 操作
更多的是由於某些對象創建需要很多參數
工廠通過反射創建對象,其餘參數配置放到配置文件

volatile 爲什麼使用?

volitaile 變量在修改後,立刻將值刷回主內存,會將其他線程持有的該變量在本地內存置爲失效,使得其他線程使用前必須從主內存讀取
happens before :讀在其他線程寫之後(可以將其看成是一把鎖,來理解)

事務的隔離級別

hasemap 擴容原理,爲什麼選擇2的n次冪去擴容?

因爲在確定key的鏈表頭結點位置時,是通過hash&size
如果不是2的n次冪
會出現:
比如 15
1111,全部是用上了
如果是10
0110 會有幾位是浪費掉的

海量數據,千萬或者是億萬級別,兩兩成對的數據,

加入一個不一樣的數,找出這個數
使用位運算異或:相同的數據異或結束坑定是0,0^任何數=數本身

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