點贊再看,養成習慣,微信搜索【三太子敖丙】關注這個互聯網茍且偷生的工具人,
本文 GitHub https://github.com/JavaFamily 已收錄,有一線大廠面試完整考點、資料以及我的系列文章,
前言
前段時間敖丙不是在復習嘛,很多小伙伴也想要我的復習路線,以及我自己筆記里面的一些知識點,好了,丙丙花了一個月的時間,整整一個月啊,給大家整理出來了,
一上來我就放個大招好吧,我的復習腦圖,可以說是全得不行,為了防止被盜圖,我加了水印哈,
這期看下去你會發現很硬核,而且我會持續更新,啥也不說了,看在我熬夜一個月滿臉痘痘的份上,你可以點贊了哈哈,

注:如果圖被壓縮了,可以去公眾號【三太子敖丙】回復【復習】獲取原圖
Spring
Spring框架的七大模塊
Spring Core:框架的最基礎部分,提供 IoC 容器,對 bean 進行管理,
Spring Context:繼承BeanFactory,提供背景關系資訊,擴展出JNDI、EJB、電子郵件、國際化等功能,
Spring DAO:提供了JDBC的抽象層,還提供了宣告性事務管理方法,
Spring ORM:提供了JPA、JDO、Hibernate、MyBatis 等ORM映射層.
Spring AOP:集成了所有AOP功能
Spring Web:提供了基礎的 Web 開發的背景關系資訊,現有的Web框架,如JSF、Tapestry、Structs等,提供了集成
Spring Web MVC:提供了 Web 應用的 Model-View-Controller 全功能實作,
Bean定義5種作用域
singleton(單例) prototype(原型) request session global session
spring ioc初始化流程?
resource定位 即尋找用戶定義的bean資源,由 ResourceLoader通過統一的介面Resource介面來完成 beanDefinition載入 BeanDefinitionReader讀取、決議Resource定位的資源 成BeanDefinition 載入到ioc中(通過HashMap進行維護BD) BeanDefinition注冊 即向IOC容器注冊這些BeanDefinition, 通過BeanDefinitionRegistery實作
BeanDefinition加載流程?
定義BeanDefinitionReader決議xml的document BeanDefinitionDocumentReader決議document成beanDefinition
DI依賴注入流程? (實體化,處理Bean之間的依賴關系)
程序在Ioc初始化后,依賴注入的程序是用戶第一次向IoC容器索要Bean時觸發
如果設定lazy-init=true,會在第一次getBean的時候才初始化bean, lazy-init=false,會容器啟動的時候直接初始化(singleton bean);
呼叫BeanFactory.getBean()生成bean的;
生成bean程序運用裝飾器模式產生的bean都是beanWrapper(bean的增強);
依賴注入怎么處理bean之間的依賴關系?
其實就是通過在beanDefinition載入時,如果bean有依賴關系,通過占位符來代替,在呼叫getbean時候,如果遇到占位符,從ioc里獲取bean注入到本實體來
Bean的生命周期?
實體化Bean: Ioc容器通過獲取BeanDefinition物件中的資訊進行實體化,實體化物件被包裝在BeanWrapper物件中 設定物件屬性(DI):通過BeanWrapper提供的設定屬性的介面完成屬性依賴注入; 注入Aware介面(BeanFactoryAware, 可以用這個方式來獲取其它 Bean,ApplicationContextAware):Spring會檢測該物件是否實作了xxxAware介面,并將相關的xxxAware實體注入給bean BeanPostProcessor:自定義的處理(分前置處理和后置處理) InitializingBean和init-method:執行我們自己定義的初始化方法 使用 destroy:bean的銷毀
IOC:控制反轉:將物件的創建權,由Spring管理. DI(依賴注入):在Spring創建物件的程序中,把物件依賴的屬性注入到類中,
Spring的IOC注入方式
構造器注入 setter方法注入 注解注入 介面注入
怎么檢測是否存在回圈依賴?
Bean在創建的時候可以給該Bean打標,如果遞回呼叫回來發現正在創建中的話,即說明了回圈依賴了,
Spring如解決Bean回圈依賴問題?
Spring中回圈依賴場景有:
構造器的回圈依賴 屬性的回圈依賴 singletonObjects:第一級快取,里面放置的是實體化好的單例物件; earlySingletonObjects:第二級快取,里面存放的是提前曝光的單例物件; singletonFactories:第三級快取,里面存放的是要被實體化的物件的物件工廠 創建bean的時候Spring首先從一級快取singletonObjects中獲取,如果獲取不到,并且物件正在創建中,就再從二級快取earlySingletonObjects中獲取,如果還是獲取不到就從三級快取singletonFactories中取(Bean呼叫建構式進行實體化后,即使屬性還未填充,就可以通過三級快取向外提前暴露依賴的參考值(提前曝光),根據物件參考能定位到堆中的物件,其原理是基于Java的參考傳遞),取到后從三級快取移動到了二級快取完全初始化之后將自己放入到一級快取中供其他使用, 因為加入singletonFactories三級快取的前提是執行了構造器,所以構造器的回圈依賴沒法解決, 構造器回圈依賴解決辦法:在建構式中使用@Lazy注解延遲加載,在注入依賴時,先注入代理物件,當首次使用時再創建物件說明:一種互斥的關系而非層次遞進的關系,故稱為三個Map而非三級快取的緣由 完成注入;
Spring 中使用了哪些設計模式?
工廠模式: spring中的BeanFactory就是簡單工廠模式的體現,根據傳入唯一的標識來獲得bean物件; 單例模式: 提供了全域的訪問點BeanFactory; 代理模式: AOP功能的原理就使用代理模式(1、JDK動態代理,2、CGLib位元組碼生成技術代理,) 裝飾器模式: 依賴注入就需要使用BeanWrapper; 觀察者模式: spring中Observer模式常用的地方是listener的實作,如ApplicationListener, 策略模式: Bean的實體化的時候決定采用何種方式初始化bean實體(反射或者CGLIB動態位元組碼生成)
AOP 核心概念
1、切面(aspect):類是對物體特征的抽象,切面就是對橫切關注點的抽象
2、橫切關注點:對哪些方法進行攔截,攔截后怎么處理,這些關注點稱之為橫切關注點,
3、連接點(joinpoint):被攔截到的點,因為 Spring 只支持方法型別的連接點,所以在Spring 中連接點指的就是被攔截到的方法,實際上連接點還可以是欄位或者構造器,
4、切入點(pointcut):對連接點進行攔截的定義
5、通知(advice):所謂通知指的就是指攔截到連接點之后要執行的代碼,通知分為前置、后置、例外、最終、環繞通知五類,
6、目標物件:代理的目標物件
7、織入(weave):將切面應用到目標物件并導致代理物件創建的程序
8、引入(introduction):在不修改代碼的前提下,引入可以在運行期為類動態地添加方法或欄位,
解釋一下AOP
傳統oop開發代碼邏輯自上而下的,這個程序中會產生一些橫切性問題,這些問題與我們主業務邏輯關系不大,會散落在代碼的各個地方,造成難以維護,aop思想就是把業務邏輯與橫切的問題進行分離,達到解耦的目的,提高代碼重用性和開發效率;
AOP 主要應用場景有:
記錄日志 監控性能 權限控制 事務管理
AOP原始碼分析
@EnableAspectJAutoProxy給容器(beanFactory)中注冊一個AnnotationAwareAspectJAutoProxyCreator物件;
AnnotationAwareAspectJAutoProxyCreator對目標物件進行代理物件的創建,物件內部,是封裝JDK和CGlib兩個技術,實作動態代理物件創建的(創建代理物件程序中,會先創建一個代理工廠,獲取到所有的增強器(通知方法),將這些增強器和目標類注入代理工廠,再用代理工廠創建物件);
代理物件執行目標方法,得到目標方法的攔截器鏈,利用攔截器的鏈式機制,依次進入每一個攔截器進行執行
AOP應用場景
日志記錄 事務管理 執行緒池關閉等
AOP使用哪種動態代理?
當bean的是實作中存在介面或者是Proxy的子類,---jdk動態代理;不存在介面,spring會采用CGLIB來生成代理物件; JDK 動態代理主要涉及到 java.lang.reflect 包中的兩個類:Proxy 和 InvocationHandler, Proxy 利用 InvocationHandler(定義橫切邏輯) 介面動態創建 目標類的代理物件,
jdk動態代理
通過bind方法建立代理與真實物件關系,通過Proxy.newProxyInstance(target)生成代理物件 代理物件通過反射invoke方法實作呼叫真實物件的方法
動態代理與靜態代理區別
靜態代理,程式運行前代理類的.class檔案就存在了; 動態代理:在程式運行時利用反射動態創建代理物件<復用性,易用性,更加集中都呼叫invoke>
CGLIB與JDK動態代理區別
Jdk必須提供介面才能使用; C不需要,只要一個非抽象類就能實作動態代理
SpringMVC
springMVC流程:
(1):用戶請求發送給DispatcherServlet,DispatcherServlet呼叫HandlerMapping處理器映射器;
(2):HandlerMapping根據xml或注解找到對應的處理器,生成處理器物件回傳給DispatcherServlet;
(3):DispatcherServlet會呼叫相應的HandlerAdapter;
(4):HandlerAdapter經過適配呼叫具體的處理器去處理請求,生成ModelAndView回傳給DispatcherServlet
(5):DispatcherServlet將ModelAndView傳給ViewReslover決議生成View回傳給DispatcherServlet;
(6):DispatcherServlet根據View進行渲染視圖;
->DispatcherServlet->HandlerMapping->Handler ->DispatcherServlet->HandlerAdapter處理handler->ModelAndView ->DispatcherServlet->ModelAndView->ViewReslover->View ->DispatcherServlet->回傳給客戶
Mybatis
Mybatis原理
sqlsessionFactoryBuilder生成sqlsessionFactory(單例) 工廠模式生成sqlsession執行sql以及控制事務 Mybatis通過動態代理使Mapper(sql映射器)介面能運行起來即為介面生成代理物件將sql查詢到結果映射成pojo
sqlSessionFactory構建程序
決議并讀取配置中的xml創建Configuration物件 (單例) 使用Configruation類去創建sqlSessionFactory(builder模式)
Mybatis一級快取與二級快取
默認情況下一級快取是開啟的,而且是不能關閉的,
一級快取是指 SqlSession 級別的快取 原理:使用的資料結構是一個 map,如果兩次中間出現 commit 操作 (修改、添加、洗掉),本 sqlsession 中的一級快取區域全部清空 二級快取是指可以跨 SqlSession 的快取,是 mapper 級別的快取; 原理: 是通過 CacheExecutor 實作的,CacheExecutor其實是 Executor 的代理物件
Zookeeper+eureka+springcloud
SpringBoot啟動流程
new springApplication物件,利用spi機制加載applicationContextInitializer, applicationLister介面實體(META-INF/spring.factories);
調run方法準備Environment,加載應用背景關系(applicationContext),發布事件 很多通過lister實作
創建spring容器, refreshContext() ,實作starter自動化配置,spring.factories檔案加載, bean實體化
SpringBoot自動配置的原理
@EnableAutoConfiguration找到META-INF/spring.factories(需要創建的bean在里面)組態檔 讀取每個starter中的spring.factories檔案
Spring Boot 的核心注解
核心注解是@SpringBootApplication 由以下三種組成
@SpringBootConfiguration:組合了 @Configuration 注解,實作組態檔的功能, @EnableAutoConfiguration:打開自動配置的功能, @ComponentScan:Spring組件掃描,
SpringBoot常用starter都有哪些
spring-boot-starter-web - Web 和 RESTful 應用程式; spring-boot-starter-test - 單元測驗和集成測驗; spring-boot-starter-jdbc - 傳統的 JDBC; spring-boot-starter-security - 使用 SpringSecurity 進行身份驗證和授權; spring-boot-starter-data-jpa - 帶有 Hibernate 的 Spring Data JPA; spring-boot-starter-data-rest - 使用 Spring Data REST 公布簡單的 REST 服務
Spring Boot 的核心組態檔
(1):Application.yml 一般用來定義單個應用級別的,如果搭配 spring-cloud-config 使用
(2).Bootstrap.yml(先加載) 系統級別的一些引數配置,這些引數一般是不變的
Zuul與Gateway區別
(1):zuul則是netflix公司的專案集成在spring-cloud中使用而已, Gateway是spring-cloud的 一個子專案;
(2):zuul不提供異步支持流控等均由hystrix支持, gateway提供了異步支持,提供了抽象負載均衡,提供了抽象流控; 理論上gateway則更適合于提高系統吞吐量(但不一定能有更好的性能),最終性能還需要通過嚴密的壓測來決定
(3):兩者底層實作都是servlet,但是gateway多嵌套了一層webflux框架
(4): zuul可用至其他微服務框架中,內部沒有實作限流、負載均衡;gateway只能用在springcloud中;
Zuul原理分析
(1):請求給zuulservlet處理(HttpServlet子類) zuulservlet中有一個zuulRunner物件,該物件中初始化了RequestContext(存盤請求的資料),RequestContext被所有的zuulfilter共享;
(2): zuulRunner中有 FilterProcessor(zuulfilter的管理器),其從filterloader 中獲取zuulfilter;
(3):有了這些filter之后, zuulservelet執行的Pre-> route-> post 型別的過濾器,如果在執行這些過濾器有錯誤的時候則會執行error型別的過濾器,執行完后把結果回傳給客戶端.
Gateway原理分析
(1):請求到達DispatcherHandler, DispatchHandler在IOC容器初始化時會在容器中實體化HandlerMapping介面
(2):用handlerMapping根據請求URL匹配到對應的Route,然后有對應的filter做對應的請求轉發最終response回傳去
Zookeeper 作業原理(待查)
Zookeeper 的核心是原子廣播,這個機制保證了各個 server 之間的同步,實作這個機制的協議叫做 Zab 協議,Zab 協議有兩種模式,它們分別是恢復模式和廣播模式,
zoo與eur區別
zookeeper保證cp(一致性) eureka保證ap(可用性) zoo在選舉期間注冊服務癱瘓,期間不可用 eur各個節點平等關系,只要有一臺就可保證服務可用,而查詢到的資料可能不是最新的,可以很好應對網路故障導致部分節點失聯情況 zoo有leader和follower角色,eur各個節點平等 zoo采用半數存活原則(避免腦裂),eur采用自我保護機制來解決磁區問題 eur本質是個工程,zoo只是一個行程 ZooKeeper基于CP,不保證高可用,如果zookeeper正在選主,或者Zookeeper集群中半數以上機器不可用,那么將無法獲得資料, Eureka基于AP,能保證高可用,即使所有機器都掛了,也能拿到本地快取的資料,作為注冊中心,其實配置是不經常變動的,只有發版(發布新的版本)和機器出故障時會變,對于不經常變動的配置來說,CP是不合適的,而AP在遇到問題時可以用犧牲一致性來保證可用性,既回傳舊資料,快取資料, 所以理論上Eureka是更適合做注冊中心,而現實環境中大部分專案可能會使用ZooKeeper,那是因為集群不夠大,并且基本不會遇到用做注冊中心的機器一半以上都掛了的情況,所以實際上也沒什么大問題,
Hystrix原理(待查)
通過維護一個自己的執行緒池,當執行緒池達到閾值的時候,就啟動服務降級,回傳fallback默認值
為什么需要hystrix熔斷
防止雪崩,及時釋放資源,防止系統發生更多的額級聯故障,需要對故障和延遲進行隔離,防止單個依賴關系的失敗影響整個應用程式;
微服務優缺點
每個服務高內聚,松耦合,面向介面編程; 服務間通信成本,資料一致性,多服務運維難度增加,http傳輸效率不如rpc
eureka自我保護機制
eur不移除長時間沒收到心跳而應該過期的服務 仍然接受新服務注冊和查詢請求,但是不會同步到其它節點(高可用) 當網路穩定后,當前實體新注冊資訊會同步到其它節點(最終一致性)
MQ對比
ActiveMQ:Apache出品,最早使用的訊息佇列產品,時間比較長了,最近版本更新比較緩慢, RabbitMQ:erlang語言開發,支持很多的協議,非常重量級,更適合于企業級的開發,性能較好,但是不利于做二次開發和維護, RocketMQ:阿里開源的訊息中間件,純Java開發,具有高吞吐量、高可用性、適合大規模分布式系統應用的特點,分布式事務, ZeroMQ:號稱最快的訊息佇列系統,尤其針對大吞吐量的需求場景,采用 C 語言實作, 訊息佇列的選型需要根據具體應用需求而定,ZeroMQ 小而美,RabbitMQ 大而穩,Kakfa 和 RocketMQ 快而強勁
JAVA基礎
AVL樹與紅黑樹(R-B樹)的區別與聯系
AVL是嚴格的平衡樹,因此在增加或者洗掉節點的時候,根據不同情況,旋轉的次數比紅黑樹要多; 紅黑樹是用非嚴格的平衡來換取增刪節點時候旋轉次數的降低開銷; 所以簡單說,查詢多選擇AVL樹,查詢更新次數差不多選紅黑樹 AVL樹順序插入和洗掉時有20%左右的性能優勢,紅黑樹隨機操作15%左右優勢,現實應用當然一般都是隨機情況,所以紅黑樹得到了更廣泛的應用 索引為B+樹 Hashmap為紅黑樹
為啥redis zset使用跳躍鏈表而不用紅黑樹實作
skiplist的復雜度和紅黑樹一樣,而且實作起來更簡單, 在并發環境下紅黑樹在插入和洗掉時需要rebalance,性能不如跳表,
JAVA基本資料型別
(1個位元組是8個bit) 整數型:byte(1位元組)、short(2位元組)、int(4位元組)、long(8位元組) 浮點型:float(4位元組)、double(8位元組) 布爾型:boolean(1位元組) 字符型:char(2位元組)
IO與NIO
包括 類File,outputStream,inputStream,writer,readerseralizable(5類1介面)
NIO三大核心內容 selector(選擇器,用于監聽channel),channel(通道),buffer(緩沖區)
NIO與IO區別,IO面向流,NIO面向緩沖區;io阻塞,nio非阻塞
例外類
throwable為父類,子為error跟exception,exception分runtime(空指標,越界等)跟checkexception(sql,io,找不到類等例外)
LVS(4層與7層)原理
由前端虛擬負載均衡器和后端真實服務器群組成; 請求發送給虛擬服務器后其根據包轉發策略以及負載均衡調度演算法轉發給真實服務器 所謂四層(lvs,f5)就是基于IP+埠的負載均衡;七層(nginx)就是基于URL等應用層資訊的負載均衡
StringBuilder與StringBuffer
StringBuilder 更快; StringBuffer是執行緒安全的
interrupt/isInterrupted/interrupt區別
interrupt() 呼叫該方法的執行緒的狀態為將被置為"中斷"狀態(set操作) isinterrupted() 是作用于呼叫該方法的執行緒物件所對應的執行緒的中斷信號是true還是false(get操作),例如我們可以在A執行緒中去呼叫B執行緒物件的isInterrupted方法,查看的是A interrupted()是靜態方法:內部實作是呼叫的當前執行緒的isInterrupted(),并且會重置當前執行緒的中斷狀態(getandset)
sleep與wait區別
sleep屬于執行緒類,wait屬于object類;sleep不釋放鎖
CountDownLatch和CyclicBarrier區別
con用于主執行緒等待其他子執行緒任務都執行完畢后再執行,cyc用于一組執行緒相互等待大家都達到某個狀態后,再同時執行; CountDownLatch是不可重用的,CyclicBarrier可重用
終止執行緒方法
使用退出標志,說執行緒正常退出; 通過判斷this.interrupted() throw new InterruptedException()來停止 使用String常量池作為鎖物件會導致兩個執行緒持有相同的鎖,另一個執行緒不執行,改用其他如new Object()
ThreadLocal的原理和應用
原理:
執行緒中創建副本,訪問自己內部的副本變數,內部實作是其內部類名叫ThreadLocalMap的成員變數threadLocals,key為本身,value為實際存值的變數副本
應用:
用來解決資料庫連接,存放connection物件,不同執行緒存放各自session; 解決simpleDateFormat執行緒安全問題; 會出現記憶體泄漏,顯式remove..不要與執行緒池配合,因為worker往往是不會退出的;
threadLocal 記憶體泄漏問題
如果是強參考,設定tl=null,但是key的參考依然指向ThreadLocal物件,所以會有記憶體泄漏,而使用弱參考則不會; 但是還是會有記憶體泄漏存在,ThreadLocal被回收,key的值變成null,導致整個value再也無法被訪問到; 解決辦法:在使用結束時,呼叫ThreadLocal.remove來釋放其value的參考;
如果我們要獲取父執行緒的ThreadLocal值呢
ThreadLocal是不具備繼承性的,所以是無法獲取到的,但是我們可以用InteritableThreadLocal來實作這個功能,InteritableThreadLocal繼承來ThreadLocal,重寫了createdMap方法,已經對應的get和set方法,不是在利用了threadLocals,而是interitableThreadLocals變數,
這個變數會在執行緒初始化的時候(呼叫init方法),會判斷父執行緒的interitableThreadLocals變數是否為空,如果不為空,則把放入子執行緒中,但是其實這玩意沒啥鳥用,當父執行緒創建完子執行緒后,如果改變父執行緒內容是同步不到子執行緒的,,,同樣,如果在子執行緒創建完后,再去賦值,也是沒啥鳥用的
執行緒狀態
執行緒池有5種狀態:running,showdown,stop,Tidying,TERMINATED,
running:執行緒池處于運行狀態,可以接受任務,執行任務,創建執行緒默認就是這個狀態了
showdown:呼叫showdown()函式,不會接受新任務,但是會慢慢處理完堆積的任務,
stop:呼叫showdownnow()函式,不會接受新任務,不處理已有的任務,會中斷現有的任務,
Tidying:當執行緒池狀態為showdown或者stop,任務數量為0,就會變為tidying,這個時候會呼叫鉤子函式terminated(),
TERMINATED:terminated()執行完成,
在執行緒池中,用了一個原子類來記錄執行緒池的資訊,用了int的高3位表示狀態,后面的29位表示執行緒池中執行緒的個數,
Java中的執行緒池是如何實作的?
執行緒中執行緒被抽象為靜態內部類Worker,是基于AQS實作的存放在HashSet中; 要被執行的執行緒存放在BlockingQueue中; 基本思想就是從workQueue中取出要執行的任務,放在worker中處理;
如果執行緒池中的一個執行緒運行時出現了例外,會發生什么
如果提交任務的時候使用了submit,則回傳的feature里會存有例外資訊,但是如果數execute則會列印出例外堆疊,但是不會給其他執行緒造成影響,之后執行緒池會洗掉該執行緒,會新增加一個worker,
執行緒池原理
提交一個任務,執行緒池里存活的核心執行緒數小于corePoolSize時,執行緒池會創建一個核心執行緒去處理提交的任務 如果執行緒池核心執行緒數已滿,即執行緒數已經等于corePoolSize,一個新提交的任務,會被放進任務佇列workQueue排隊等待執行, 當執行緒池里面存活的執行緒數已經等于corePoolSize了,并且任務佇列workQueue也滿,判斷執行緒數是否達到maximumPoolSize,即最大執行緒數是否已滿,如果沒到達,創建非核心執行緒執行提交的任務, 如果當前的執行緒數達到了maximumPoolSize,還有新的任務過來的話,直接采用拒絕策略處理,
拒絕策略
AbortPolicy直接拋出例外阻止執行緒運行; CallerRunsPolicy如果被丟棄的執行緒任務未關閉,則執行該執行緒; DiscardOldestPolicy移除佇列最早執行緒嘗試提交當前任務 DiscardPolicy丟棄當前任務,不做處理
newFixedThreadPool (固定數目執行緒的執行緒池)
阻塞佇列為無界佇列LinkedBlockingQueue 適用于處理CPU密集型的任務,適用執行長期的任務
newCachedThreadPool(可快取執行緒的執行緒池)
阻塞佇列是SynchronousQueue 適用于并發執行大量短期的小任務
newSingleThreadExecutor(單執行緒的執行緒池)
阻塞佇列是LinkedBlockingQueue 適用于串行執行任務的場景,一個任務一個任務地執行
newScheduledThreadPool(定時及周期執行的執行緒池)
阻塞佇列是DelayedWorkQueue 周期性執行任務的場景,需要限制執行緒數量的場景
java鎖相關
synchronized實作原理
contentionList(請求鎖執行緒佇列) entryList(有資格的候選者佇列) waitSet(wait方法后阻塞佇列) onDeck(競爭候選者) ower(競爭到鎖執行緒) !ower(執行成功釋放鎖后狀態); Synchronized 是非公平鎖,
Synchronized 在執行緒進入 ContentionList 時,等待的執行緒會先嘗試自旋獲取鎖,如果獲取不到就進入 ContentionList,這明顯對于已經進入佇列的執行緒是不公平的,還有一個不公平的事情就是自旋獲取鎖的執行緒還可能直接搶占 OnDeck 執行緒的鎖資源,
底層是由一對monitorenter和monitorexit指令實作的(監視器鎖)
每個物件有一個監視器鎖(monitor),當monitor被占用時就會處于鎖定狀態,執行緒執行monitorenter指令時嘗試獲取monitor的所有權,程序:
如果monitor的進入數為0,則該執行緒進入monitor,然后將進入數設定為1,該執行緒即為monitor的所有者, 如果執行緒已經占有該monitor,只是重新進入,則進入monitor的進入數加1. 如果其他執行緒已經占用了monitor,則該執行緒進入阻塞狀態,直到monitor的進入數為0,再重新嘗試獲取monitor的所有權,
ReentrantLock 是如何實作可重入性的 ?
內部自定義了同步器 Sync,加鎖的時候通過CAS 演算法 ,將執行緒物件放到一個雙向鏈表 中,每次獲取鎖的時候 ,看下當前維 護的那個執行緒ID和當前請求的執行緒ID是否一樣,一樣就可重入了;
ReentrantLock如何避免死鎖?
回應中斷lockInterruptibly() 可輪詢鎖tryLock() 定時鎖tryLock(long time)
tryLock 和 lock 和 lockInterruptibly 的區別
(1):tryLock 能獲得鎖就回傳 true,不能就立即回傳 false,
(2):tryLock(long timeout,TimeUnit unit),可以增加時間限制,如果超過該時間段還沒獲得鎖,回傳 false
(3):lock 能獲得鎖就回傳 true,不能的話一直等待獲得鎖
(4):lock 和 lockInterruptibly,如果兩個執行緒分別執行這兩個方法,但此時中斷這兩個執行緒, lock 不會拋出例外,而 lockInterruptibly 會拋出例外,
CountDownLatch和CyclicBarrier的區別是什么
CountDownLatch是等待其他執行緒執行到某一個點的時候,在繼續執行邏輯(子執行緒不會被阻塞,會繼續執行),只能被使用一次,最常見的就是join形式,主執行緒等待子執行緒執行完任務,在用主執行緒去獲取結果的方式(當然不一定),內部是用計數器相減實作的(沒錯,又特么是AQS),AQS的state承擔了計數器的作用,初始化的時候,使用CAS賦值,主執行緒呼叫await()則被加入共享執行緒等待佇列里面,子執行緒呼叫countDown的時候,使用自旋的方式,減1,知道為0,就觸發喚醒,
CyclicBarrier回環屏障,主要是等待一組執行緒到底同一個狀態的時候,放閘,CyclicBarrier還可以傳遞一個Runnable物件,可以到放閘的時候,執行這個任務,CyclicBarrier是可回圈的,當呼叫await的時候如果count變成0了則會重置狀態,如何重置呢,CyclicBarrier新增了一個欄位parties,用來保存初始值,當count變為0的時候,就重新賦值,還有一個不同點,CyclicBarrier不是基于AQS的,而是基于RentrantLock實作的,存放的等待佇列是用了條件變數的方式,
synchronized與ReentrantLock區別
都是可重入鎖; R是顯示獲取和釋放鎖,s是隱式; R更靈活可以知道有沒有成功獲取鎖,可以定義讀寫鎖,是api級別,s是JVM級別; R可以定義公平鎖;Lock是介面,s是java中的關鍵字
什么是信號量Semaphore
信號量是一種固定資源的限制的一種并發工具包,基于AQS實作的,在構造的時候會設定一個值,代表著資源數量,信號量主要是應用于是用于多個共享資源的互斥使用,和用于并發執行緒數的控制(druid的資料庫連接數,就是用這個實作的),信號量也分公平和非公平的情況,基本方式和reentrantLock差不多,在請求資源呼叫task時,會用自旋的方式減1,如果成功,則獲取成功了,如果失敗,導致資源數變為了0,就會加入佇列里面去等待,呼叫release的時候會加一,補充資源,并喚醒等待佇列,
Semaphore 應用
acquire() release() 可用于物件池,資源池的構建,比如靜態全域物件池,資料庫連接池; 可創建計數為1的S,作為互斥鎖(二元信號量)
可重入鎖概念
(1):可重入鎖是指同一個執行緒可以多次獲取同一把鎖,不會因為之前已經獲取過還沒釋放而阻塞;
(2):reentrantLock和synchronized都是可重入鎖
(3):可重入鎖的一個優點是可一定程度避免死鎖
ReentrantLock原理(CAS+AQS)
CAS+AQS佇列來實作
(1):先通過CAS嘗試獲取鎖, 如果此時已經有執行緒占據了鎖,那就加入AQS佇列并且被掛起;
(2): 當鎖被釋放之后, 排在隊首的執行緒會被喚醒CAS再次嘗試獲取鎖,
(3):如果是非公平鎖, 同時還有另一個執行緒進來嘗試獲取可能會讓這個執行緒搶到鎖;
(4):如果是公平鎖, 會排到隊尾,由隊首的執行緒獲取到鎖,
AQS 原理
Node內部類構成的一個雙向鏈表結構的同步佇列,通過控制(volatile的int型別)state狀態來判斷鎖的狀態,對于非可重入鎖狀態不是0則去阻塞;
對于可重入鎖如果是0則執行,非0則判斷當前執行緒是否是獲取到這個鎖的執行緒,是的話把state狀態+1,比如重入5次,那么state=5, 而在釋放鎖的時候,同樣需要釋放5次直到state=0其他執行緒才有資格獲得鎖
AQS兩種資源共享方式
Exclusive:獨占,只有一個執行緒能執行,如ReentrantLock Share:共享,多個執行緒可以同時執行,如Semaphore、CountDownLatch、ReadWriteLock,CyclicBarrier
CAS原理
記憶體值V,舊的預期值A,要修改的新值B,當A=V時,將記憶體值修改為B,否則什么都不做;
CAS的缺點:
(1):ABA問題; (2):如果CAS失敗,自旋會給CPU帶來壓力; (3):只能保證對一個變數的原子性操作,i++這種是不能保證的
CAS在java中的應用:
(1):Atomic系列
公平鎖與分公平鎖
(1):公平鎖指在分配鎖前檢查是否有執行緒在排隊等待獲取該鎖,優先分配排隊時間最長的執行緒,非公平直接嘗試獲取鎖 (2):公平鎖需多維護一個鎖執行緒佇列,效率低;默認非公平
獨占鎖與共享鎖
(1):ReentrantLock為獨占鎖(悲觀加鎖策略) (2):ReentrantReadWriteLock中讀鎖為共享鎖 (3): JDK1.8 郵戳鎖(StampedLock), 不可重入鎖 讀的程序中也允許獲取寫鎖后寫入!這樣一來,我們讀的資料就可能不一致,所以,需要一點額外的代碼來判斷讀的程序中是否有寫入,這種讀鎖是一種樂觀鎖, 樂觀鎖的并發效率更高,但一旦有小概率的寫入導致讀取的資料不一致,需要能檢測出來,再讀一遍就行
4種鎖狀態
無鎖
偏向鎖 會偏向第一個訪問鎖的執行緒,當一個執行緒訪問同步代碼塊獲得鎖時,會在物件頭和堆疊幀記錄里存盤鎖偏向的執行緒ID,當這個執行緒再次進入同步代碼塊時,就不需要CAS操作來加鎖了,只要測驗一下物件頭里是否存盤著指向當前執行緒的偏向鎖 如果偏向鎖未啟動,new出的物件是普通物件(即無鎖,有稍微競爭會成輕量級鎖),如果啟動,new出的物件是匿名偏向(偏向鎖) 物件頭主要包括兩部分資料:Mark Word(標記欄位, 存盤物件自身的運行時資料)、class Pointer(型別指標, 是物件指向它的類元資料的指標)
輕量級鎖(自旋鎖) (1):在把執行緒進行阻塞操作之前先讓執行緒自旋等待一段時間,可能在等待期間其他執行緒已經 解鎖,這時就無需再讓執行緒執行阻塞操作,避免了用戶態到內核態的切換,(自適應自旋時間為一個執行緒背景關系切換的時間)
(2):在用自旋鎖時有可能造成死鎖,當遞回呼叫時有可能造成死鎖
(3):自旋鎖底層是通過指向執行緒堆疊中Lock Record的指標來實作的
重量級鎖
輕量級鎖與偏向鎖的區別
(1):輕量級鎖是通過CAS來避免進入開銷較大的互斥操作
(2):偏向鎖是在無競爭場景下完全消除同步,連CAS也不執行
自旋鎖升級到重量級鎖條件
(1):某執行緒自旋次數超過10次;
(2):等待的自旋執行緒超過了系統core數的一半;
讀寫鎖了解嘛,知道讀寫鎖的實作方式嘛
常用的讀寫鎖ReentrantReanWritelock,這個其實和reentrantLock相似,也是基于AQS的,但是這個是基于共享資源的,不是互斥,關鍵在于state的處理,讀寫鎖把高16為記為讀狀態,低16位記為寫狀態,就分開了,讀讀情況其實就是讀鎖重入,讀寫/寫讀/寫寫都是互斥的,只要判斷低16位就好了,
zookeeper實作分布式鎖
(1):利用節點名稱唯一性來實作,加鎖時所有客戶端一起創建節點,只有一個創建成功者獲得鎖,解鎖時洗掉節點,
(2):利用臨時順序節點實作,加鎖時所有客戶端都創建臨時順序節點,創建節點序列號最小的獲得鎖,否則監視比自己序列號次小的節點進行等待
(3):方案2比1好處是當zookeeper宕機后,臨時順序節點會自動洗掉釋放鎖,不會造成鎖等待;
(4):方案1會產生驚群效應(當有很多行程在等待鎖的時候,在釋放鎖的時候會有很多行程就過來爭奪鎖),
(5):由于需要頻繁創建和洗掉節點,性能上不如redis鎖
volatile變數
(1):變數可見性
(2):防止指令重排序
(3):保障變數單次讀,寫操作的原子性,但不能保證i++這種操作的原子性,因為本質是讀,寫兩次操作
volatile如何保證執行緒間可見和避免指令重排
volatile可見性是有指令原子性保證的,在jmm中定義了8類原子性指令,比如write,store,read,load,而volatile就要求write-store,load-read成為一個原子性操作,這樣子可以確保在讀取的時候都是從主記憶體讀入,寫入的時候會同步到主記憶體中(準確來說也是記憶體屏障),指令重排則是由記憶體屏障來保證的,由兩個記憶體屏障:
一個是編譯器屏障:阻止編譯器重排,保證編譯程式時在優化屏障之前的指令不會在優化屏障之后執行, 第二個是cpu屏障:sfence保證寫入,lfence保證讀取,lock類似于鎖的方式,java多執行了一個“load addl $0x0, (%esp)”操作,這個操作相當于一個lock指令,就是增加一個完全的記憶體屏障指令,
JVM
jre、jdk、jvm的關系:
jdk是最小的開發環境,由jre++java工具組成,
jre是java運行的最小環境,由jvm+核心類別庫組成,
jvm是虛擬機,是java位元組碼運行的容器,如果只有jvm是無法運行java的,因為缺少了核心類別庫,
JVM記憶體模型
(1):堆<物件,靜態變數,共享
(2):方法區<存放類資訊,常量池,共享>(java8移除了永久代(PermGen),替換為元空間(Metaspace))
(3):虛擬機堆疊<執行緒執行方法的時候內部存區域變數會存堆中物件的地址等等資料>
(4):本地方法堆疊<存放各種native方法的區域變數表之類的資訊>
(5):程式計數器<記錄當前執行緒執行到哪一條位元組碼指令位置>
物件4種參考
(1):強(記憶體泄露主因)
(2):軟(只有軟參考的話,空間不足將被回收),適合快取用
(3):弱(只,GC會回收)
(4):虛參考(用于跟蹤GC狀態)用于管理堆外記憶體
物件的構成:
一個物件分為3個區域:物件頭、實體資料、對齊填充
物件頭:主要是包括兩部分,1.存盤自身的運行時資料比如hash碼,分代年齡,鎖標記等(但是不是絕對哦,鎖狀態如果是偏向鎖,輕量級鎖,是沒有hash碼的,,,是不固定的)2.指向類的元資料指標,還有可能存在第三部分,那就是陣列型別,會多一塊記錄陣列的長度(因為陣列的長度是jvm判斷不出來的,jvm只有元資料資訊)
實體資料:會根據虛擬機分配策略來定,分配策略中,會把相同大小的型別放在一起,并按照定義順序排列(父類的變數也會在哦)
對齊填充:這個意義不是很大,主要在虛擬機規范中物件必須是8位元組的整數,所以當物件不滿足這個情況時,就會用占位符填充
如果判斷一個物件是否存活:
一般判斷物件是否存活有兩種演算法,一種是參考計數,另外一種是可達性分析,在java中主要是第二種
java是根據什么來執行可達性分析的:
根據GC ROOTS,GC ROOTS可以的物件有:虛擬機堆疊中的參考物件,方法區的類變數的參考,方法區中的常量參考,本地方法堆疊中的物件參考,
JVM 類加載順序
(1):加載 獲取類的二進制位元組流,將其靜態存盤結構轉化為方法區的運行時資料結構
(2):校驗 檔案格式驗證,元資料驗證,位元組碼驗證,符號參考驗證
(3):準備 在方法區中對類的static變數分配記憶體并設定類變數資料型別默認的初始值,不包括實體變數,實體變數將會在物件實體化的時候隨著物件一起分配在Java堆中
(4):決議 將常量池內的符號參考替換為直接參考的程序
(5):初始化 為類的靜態變數賦予正確的初始值(Java代碼中被顯式地賦予的值)
JVM三種類加載器
(1):啟動類加載器(home) 加載jvm核心類別庫,如java.lang.*等
(2):擴展類加載器(ext), 父加載器為啟動類加載器,從jre/lib/ext下加載類別庫
(3):應用程式類加載器(用戶classpath路徑) 父加載器為擴展類加載器,從環境變數中加載類
雙親委派機制
(1):類加載器收到類加載的請求
(2):把這個請求委托給父加載器去完成,一直向上委托,直到啟動類加載器
(3):啟動器加載器檢查能不能加載,能就加載(結束);否則,拋出例外,通知子加載器進行加載
(4):保障類的唯一性和安全性以及保證JDK核心類的優先加載
雙親委派模型有啥作用:
保證java基礎類在不同的環境還是同一個Class物件,避免出現了自定義類覆寫基礎類的情況,導致出現安全問題,還可以避免類的重復加載,
如何打破雙親委派模型?
(1):自定義類加載器,繼承ClassLoader類重寫loadClass方法;
(2):SPI
tomcat是如何打破雙親委派模型:
tomcat有著特殊性,它需要容納多個應用,需要做到應用級別的隔離,而且需要減少重復性加載,所以劃分為:/common 容器和應用共享的類資訊,/server容器本身的類資訊,/share應用通用的類資訊,/WEB-INF/lib應用級別的類資訊,整體可以分為:boostrapClassLoader->ExtensionClassLoader->ApplicationClassLoader->CommonClassLoader->CatalinaClassLoader(容器本身的加載器)/ShareClassLoader(共享的)->WebAppClassLoader,雖然第一眼是滿足雙親委派模型的,但是不是的,因為雙親委派模型是要先提交給父類裝載,而tomcat是優先判斷是否是自己負責的檔案位置,進行加載的,
SPI: (Service Provider interface)
(1):服務提供介面(服務發現機制):
(2):通過加載ClassPath下META_INF/services,自動加載檔案里所定義的類
(3):通過ServiceLoader.load/Service.providers方法通過反射拿到實作類的實體
SPI應用?
(1):應用于JDBC獲取資料庫驅動連接程序就是應用這一機制
(2):apache最早提供的common-logging只有介面.沒有實作..發現日志的提供商通過SPI來具體找到日志提供商實作類
雙親委派機制缺陷?
(1):雙親委派核心是越基礎的類由越上層的加載器進行加載, 基礎的類總是作為被呼叫代碼呼叫的API,無法實作基礎類呼叫用戶的代碼….
(2):JNDI服務它的代碼由啟動類加載器去加載,但是他需要調獨立廠商實作的應用程式,如何解決? 執行緒背景關系件類加載器(Thread Context ClassLoader), JNDI服務使用這個執行緒背景關系類加載器去加載所需要的SPI代碼,也就是父類加載器請求子類加載器去完成類加載動作Java中所有涉及SPI的加載動作基本上都采用這種方式,例如JNDI,JDBC
導致fullGC的原因
(1):老年代空間不足
(2):永久代(方法區)空間不足
(3):顯式呼叫system.gc()
堆外記憶體的優缺點
Ehcache中的一些版本,各種 NIO 框架,Dubbo,Memcache 等中會用到,NIO包下ByteBuffer來創建堆外記憶體 堆外記憶體,其實就是不受JVM控制的記憶體,
相比于堆內記憶體有幾個優勢:
減少了垃圾回收的作業,因為垃圾回識訓暫停其他的作業, 加快了復制的速度,因為堆內在 flush 到遠程時,會先復制到直接記憶體(非堆記憶體),然后在發送;而堆外記憶體相當于省略掉了復制這項作業, 可以擴展至更大的記憶體空間,比如超過 1TB 甚至比主存還大的空間,
缺點總結如下:
堆外記憶體難以控制,如果記憶體泄漏,那么很難排查,通過-XX:MaxDirectMemerySize來指定,當達到閾值的時候,呼叫system.gc來進行一次full gc 堆外記憶體相對來說,不適合存盤很復雜的物件,一般簡單的物件或者扁平化的比較適合 jstat查看記憶體回收概況,實時查看各個磁區的分配回收情況, jmap查看記憶體堆疊,查看記憶體中物件占用大小, jstack查看執行緒堆疊,死鎖,性能瓶頸
JVM七種垃圾收集器
(1): Serial 收集器 復制演算法,單執行緒,新生代)
(2): ParNew 收集器(復制演算法,多執行緒,新生代)
(3): Parallel Scavenge 收集器(多執行緒,復制演算法,新生代,高吞吐量)
(4):Serial Old 收集器(標記-整理演算法,老年代)
(5):Parallel Old 收集器(標記-整理演算法,老年代,注重吞吐量的場景下,jdk8默認采用 Parallel Scavenge + Parallel Old 的組合)
(6):CMS 收集器(標記-清除演算法,老年代,垃圾回收執行緒幾乎能做到與用戶執行緒同時作業,吞吐量低,記憶體碎片)以犧牲吞吐量為代價來獲得最短回收停頓時間-XX:+UseConcMarkSweepGC jdk1.8 默認垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代) jdk1.9 默認垃圾收集器G1
使用場景:
(1):應用程式對停頓比較敏感
(2):在JVM中,有相對較多存活時間較長的物件(老年代比較大)會更適合使用CMS
cms垃圾回收程序:
(1):初始標識<找到gcroot(stw)>
GC Roots有以下幾種:
1:系統類加載器加載的物件
2:處于激活狀態的執行緒
3:JNI堆疊中的物件
4:正在被用于同步的各種鎖物件
5:JVM自身持有的物件,比如系統類加載器等,
(2):并發標記(三色標記演算法) 三色標記演算法處理并發標記出現物件參考變化情況: 黑:自己+子物件標記完成 灰:自己完成,子物件未完成 白:未標記; 并發標記 黑->灰->白 重新標記 灰->白參考消失,黑參考指向->白,導致白漏標 cms處理辦法是incremental update方案 (增量更新)把黑色變成灰色 多執行緒下并發標記依舊會產生漏標問題,所以cms必須remark一遍(jdk1.9以后不用cms了)
G1 處理方案:
SATB(snapshot at the begining)把白放入堆疊中,標記程序是和應用程式并發運行的(不需要Stop-The-World) 這種方式會造成某些是垃圾的物件也被當做是存活的,所以G1會使得占用的記憶體被實際需要的記憶體大,不過下一次就回收了 ZGC 處理方案: 顏色指標(color pointers) 2*42方=4T
(3):重新標記(stw)
(4)并發清理
備注:重新標記是防止標記成垃圾之后,物件被參考
(5):G1 收集器(新生代 + 老年代,在多 CPU 和大記憶體的場景下有很好的性能) G1在java9 便是默認的垃圾收集器,是cms 的替代者 邏輯分代,用磁區(region)的思想(默認分2048份) 還是有stw 為解決CMS演算法產生空間碎片HotSpot提供垃圾收集器,通過-XX:+UseG1GC來啟用
G1中提供了三種模式垃圾回收模式
(1):young gc(eden region被耗盡無法申請記憶體時,就會觸發)
(2):mixed gc(當老年代大小占整個堆大小百分比達到該閾值時,會觸發)
(3):full gc(物件記憶體分配速度過快,mixed gc來不及回收,導致老年代被填滿,就會觸發)
(8):ZGC和shenandoah (oracle產收費) no stw
arthas 監控工具
(1):dashboard命令查看總體jvm運行情況
(2):jvm顯示jvm詳細資訊
(3):thread 顯示jvm里面所有執行緒資訊(類似于jstack) 查看死鎖執行緒命令thread -b
(4):sc * 顯示所有類(search class)
(5):trace 跟蹤方法
定位頻繁full GC,堆記憶體滿 oom
第一步:jps獲取行程號 第二步:jmap -histo pid | head -20 得知有個物件在不斷創建 備注:jmap如果線上服務器堆記憶體特別大,,會卡死需堆轉存(一般會說在測驗環境壓測,匯出轉存) -XX:+HeapDumpOnOutOfMemoryError或jmap -dumpLformat=b,file=xxx pid 轉出檔案進行分析 (arthas沒有實作jmap命令)heapdump --live /xxx/xx.hprof匯出檔案
G1垃圾回收器(重點)
回收程序 (1):young gc(年輕代回收)--當年輕代的Eden區用盡時--stw 第一階段,掃描根, 根是指static變數指向的物件,正在執行的方法呼叫鏈條上的區域變數等 第二階段,更新RS(Remembered Sets), 處理dirty card queue中的card,更新RS,此階段完成后,RS可以準確的反映老年代對所在的記憶體分段中物件的參考 第三階段,處理RS, 識別被老年代物件指向的Eden中的物件,這些被指向的Eden中的物件被認為是存活的物件, 第四階段,復制物件, 此階段,物件樹被遍歷,Eden區記憶體段中存活的物件會被復制到Survivor區中空的記憶體分段 第五階段,處理參考, 處理Soft,Weak,Phantom,Final,JNI Weak 等參考,
(2):concrruent marking(老年代并發標記) 當堆記憶體使用達到一定值(默認45%)時,不需要Stop-The-World,在并發標記前先進行一次young gc
(3):混合回收(mixed gc) 并發標記程序結束以后,緊跟著就會開始混合回收程序,混合回收的意思是年輕代和老年代會同時被回收
(4):Full GC? Full GC是指上述方式不能正常作業,G1會停止應用程式的執行,使用單執行緒的記憶體回收演算法進行垃圾回收,性能會非常差,應用程式停頓時間會很長,要避免Full GC的發生,一旦發生需要進行調整,
什么時候發生Full GC呢?
比如堆記憶體太小,當G1在復制存活物件的時候沒有空的記憶體分段可用,則會回退到full gc,這種情況可以通過增大記憶體解決
盡管G1堆記憶體仍然是分代的,但是同一個代的記憶體不再采用連續的記憶體結構
年輕代分為Eden和Survivor兩個區,老年代分為Old和Humongous兩個區
新分配的物件會被分配到Eden區的記憶體分段上
Humongous區用于保存大物件,如果一個物件占用的空間超過記憶體分段Region的一半;
如果物件的大小超過一個甚至幾個分段的大小,則物件會分配在物理連續的多個Humongous分段上,
Humongous物件因為占用記憶體較大并且連續會被優先回收
為了在回收單個記憶體分段的時候不必對整個堆記憶體的物件進行掃描(單個記憶體分段中的物件可能被其他記憶體分段中的物件參考)引入了RS資料結構,RS使得G1可以在年輕代回收的時候不必去掃描老年代的物件,從而提高了性能,每一個記憶體分段都對應一個RS,RS保存了來自其他分段內的物件對于此分段的參考
JVM會對應用程式的每一個參考賦值陳述句object.field=object進行記錄和處理,把參考關系更新到RS中,但是這個RS的更新并不是實時的,G1維護了一個Dirty Card Queue
那為什么不在參考賦值陳述句處直接更新RS呢?
這是為了性能的需要,使用佇列性能會好很多,
執行緒本地分配緩沖區(TLAB: Thread Local Allocation Buffer)?
堆疊上分配->tlab->堆上分配 由于堆記憶體是應用程式共享的,應用程式的多個執行緒在分配記憶體的時候需要加鎖以進行同步,為了避免加鎖,提高性能每一個應用程式的線程會被分配一個TLAB,TLAB中的記憶體來自于G1年輕代中的記憶體分段,當物件不是Humongous物件,TLAB也能裝的下的時候,物件會被優先分配于創建此物件的執行緒的TLAB中,這樣分配會很快,因為TLAB隸屬于執行緒,所以不需要加鎖
PLAB: Promotion Thread Local Allocation Buffer
G1會在年輕代回收程序中把Eden區中的物件復制(“提升”)到Survivor區中,Survivor區中的物件復制到Old區中,G1的回收程序是多執行緒執行的,為了避免多個執行緒往同一個記憶體分段進行復制,那么復制的程序也需要加鎖,為了避免加鎖,G1的每個執行緒都關聯了一個PLAB,這樣就不需要進行加鎖了
OOM問題定位方法
(1):jmap -heap 10765如上圖,可以查看新生代,老生代堆記憶體的分配大小以及使用情況;
(2):jstat 查看GC收集情況
(3):jmap -dump:live,format=b,file=到本地
(4):通過MAT工具打開分析
DUBBO
dubbo流程
(1):生產者(Provider)啟動,向注冊中心(Register)注冊
(2):消費者(Consumer)訂閱,而后注冊中心通知消費者
(3):消費者從生產者進行消費
(4):監控中心(Monitor)統計生產者和消費者
Dubbo推薦使用什么序列化框架,還有哪些?
推薦使用Hessian序列化,還有Duddo、FastJson、Java自帶序列化
Dubbo默認使用的是什么通信框架,還有哪些?
默認使用 Netty 框架,也是推薦的選擇,另外內容還集成有Mina、Grizzly,
Dubbo有哪幾種負載均衡策略,默認是哪種?
(1):隨機呼叫<默認>
(2):權重輪詢
(3):最少活躍數
(4):一致性Hash
RPC流程
(1)消費者呼叫需要消費的服務,
(2):客戶端存根將方法、入參等資訊序列化發送給服務端存根
(3):服務端存根反序列化操作根據解碼結果呼叫本地的服務進行相關處理
(4):本地服務執行具體業務邏輯并將處理結果回傳給服務端存根
(5):服務端存根序列化
(6):客戶端存根反序列化
(7):服務消費方得到最終結果
RPC框架的實作目標PC框架的實作目標是把呼叫、編碼/解碼的程序給封裝起來,讓用戶感覺上像呼叫本地服務一樣的呼叫遠程服務
服務暴露、服務參考、服務呼叫(TODO)
Redis
redis單執行緒為什么執行速度這么快?
(1):純記憶體操作,避免大量訪問資料庫,減少直接讀取磁盤資料,redis將資料儲存在記憶體里面,讀寫資料的時候都不會受到硬碟 I/O 速度的限制,所以速度快
(2):單執行緒操作,避免了不必要的背景關系切換和競爭條件,也不存在多行程或者多執行緒導致的切換而消耗CPU,不用去考慮各種鎖的問題,不存在加鎖釋放鎖操作,沒有因為可能出現死鎖而導致的性能消耗
(3):采用了非阻塞I/O多路復用機制
Redis資料結構底層實作
String:
(1)Simple dynamic string(SDS)的資料結構
struct sdshdr{
//記錄buf陣列中已使用位元組的數量
//等于 SDS 保存字串的長度
int len;
//記錄 buf 陣列中未使用位元組的數量
int free;
//位元組陣列,用于保存字串
char buf[];
}
它的優點: (1)不會出現字串變更造成的記憶體溢位問題
(2)獲取字串長度時間復雜度為1
(3)空間預分配, 惰性空間釋放free欄位,會默認留夠一定的空間防止多次重分配記憶體
應用場景: String 快取結構體用戶資訊,計數
Hash:
陣列+鏈表的基礎上,進行了一些rehash優化; 1.Reids的Hash采用鏈地址法來處理沖突,然后它沒有使用紅黑樹優化,
2.哈希表節點采用單鏈表結構,
3.rehash優化 (采用分而治之的思想,將龐大的遷移作業量劃分到每一次CURD中,避免了服務繁忙)
應用場景: 保存結構體資訊可部分獲取不用序列化所有欄位
List:
應用場景: (1):比如twitter的關注串列,粉絲串列等都可以用Redis的list結構來實作
(2):list的實作為一個雙向鏈表,即可以支持反向查找和遍歷
Set:
內部實作是一個 value為null的HashMap,實際就是通過計算hash的方式來快速排重的,這也是set能提供判斷一個成員 是否在集合內的原因, 應用場景: 去重的場景,交集(sinter)、并集(sunion)、差集(sdiff),實作如共同關注、共同喜好、二度好友等功能
Zset:
內部使用HashMap和跳躍表(SkipList)來保證資料的存盤和有序,HashMap里放的是成員到score的映射,而跳躍表里存放的是所有的成員,排序依據是HashMap里存的score,使用跳躍表的結構可以獲得比較高的查找效率,并且在實作上比較簡單, 跳表:每個節點中維持多個指向其他節點的指標,從而達到快速訪問節點的目的 應用場景: 實作延時佇列
redis事務
(1):Multi開啟事務
(2):Exec執行事務塊內命令
(3):Discard 取消事務
(4):Watch 監視一個或多個key,如果事務執行前key被改動,事務將打斷
redis事務的實作特征
(1):所有命令都將會被串行化的順序執行,事務執行期間,Redis不會再為其它客戶端的請求提供任何服務,從而保證了事物中的所有命令被原子的執行
(2):Redis事務中如果有某一條命令執行失敗,其后的命令仍然會被繼續執行
(3):在事務開啟之前,如果客戶端與服務器之間出現通訊故障并導致網路斷開,其后所有待執行的陳述句都將不會被服務器執行,然而如果網路中斷事件是發生在客戶端執行EXEC命令之后,那么該事務中的所有命令都會被服務器執行
(4):當使用Append-Only模式時,Redis會通過呼叫系統函式write將該事務內的所有寫操作在本次呼叫中全部寫入磁盤,
然而如果在寫入的程序中出現系統崩潰,如電源故障導致的宕機,那么此時也許只有部分資料被寫入到磁盤,而另外一部分資料卻已經丟失,
Redis服務器會在重新啟動時執行一系列必要的一致性檢測,一旦發現類似問題,就會立即退出并給出相應的錯誤提示,此時,我們就要充分利用Redis工具包中提供的redis-check-aof工具,該工具可以幫助我們定位到資料不一致的錯誤,并將已經寫入的部分資料進行回滾,修復之后我們就可以再次重新啟動Redis服務器了
Redis的同步機制?
(1):全量拷貝, 1.slave第一次啟動時,連接Master,發送PSYNC命令,
2.master會執行bgsave命令來生成rdb檔案,期間的所有寫命令將被寫入緩沖區,
master bgsave執行完畢,向slave發送rdb檔案
slave收到rdb檔案,丟棄所有舊資料,開始載入rdb檔案
rdb檔案同步結束之后,slave執行從master緩沖區發送過來的所以寫命令,
此后 master 每執行一個寫命令,就向slave發送相同的寫命令,
(2):增量拷貝 如果出現網路閃斷或者命令丟失等例外情況,從節點之前保存了自身已復制的偏移量和主節點的運行ID
主節點根據偏移量把復制積壓緩沖區里的資料發送給從節點,保證主從復制進入正常狀態,
redis集群模式性能優化
(1) Master最好不要做任何持久化作業,如RDB記憶體快照和AOF日志檔案
(2) 如果資料比較重要,某個Slave開啟AOF備份資料,策略設定為每秒同步一次
(3) 為了主從復制的速度和連接的穩定性,Master和Slave最好在同一個局域網內
(4) 盡量避免在壓力很大的主庫上增加從庫
(5) 主從復制不要用圖狀結構,用單向鏈表結構更為穩定,即:Master <- Slave1 <- Slave2 <- Slave3…這樣的結構方便解決單點故障問題,實作Slave對Master的替換,如果Master掛了,可以立刻啟用Slave1做Master,其他不變,
Redis集群方案
(1):官方cluster方案
(2):twemproxy
代理方案twemproxy是一個單點,很容易對其造成很大的壓力,所以通常會結合keepalived來實twemproy的高可用
(3):codis 基于客戶端來進行分片
集群不可用場景
(1):master掛掉,且當前master沒有slave
(2):集群超過半數以上master掛掉,無論是否有slave集群進入fail狀態
redis 最適合的場景
(1):會話快取session cache
(2):排行榜/計數器ZRANGE
(3):發布/訂閱
快取淘汰策略
(1):先進先出演算法(FIFO)
(2):最近使用最少Least Frequently Used(LFU)
(3):最長時間未被使用的Least Recently Used(LRU)
當存在熱點資料時,LRU的效率很好,但偶發性的、周期性的批量操作會導致LRU命中率急劇下降,快取污染情況比較嚴重
redis過期key洗掉策略
(1):惰性洗掉,cpu友好,但是浪費cpu資源
(2):定時洗掉(不常用)
(3):定期洗掉,cpu友好,節省空間
快取雪崩以及處理辦法
同一時刻大量快取失效;
處理方法:
(1):快取資料增加過期標記
(2):設定不同的快取失效時間
(3):雙層快取策略C1為短期,C2為長期
(4):定時更新策略
快取擊穿原因以及處理辦法
頻繁請求查詢系統中不存在的資料導致;
處理方法:
(1):cache null策略,查詢反饋結果為null仍然快取這個null結果,設定不超過5分鐘過期時間
(2):布隆過濾器,所有可能存在的資料映射到足夠大的bitmap中 google布隆過濾器:基于內存,重啟失效不支持大資料量,無法在分布式場景 redis布隆過濾器:可擴展性,不存在重啟失效問題,需要網路io,性能低于google
redis阻塞原因
(1):資料結構使用不合理bigkey
(2):CPU飽和
(3):持久化阻塞,rdb fork子執行緒,aof每秒刷盤等
hot key出現造成集群訪問量傾斜解決辦法
(1):使用本地快取
(2): 利用分片演算法的特性,對key進行打散處理(給hot key加上前綴或者后綴,把一個hotkey 的數量變成 redis 實體個數N的倍數M,從而由訪問一個 redis key 變成訪問 N * M 個redis key)
Redis分布式鎖
2.6版本以后lua腳本保證setnx跟setex進行原子性(setnx之后,未setex,服務掛了,鎖不釋放) a獲取鎖,超過過期時間,自動釋放鎖,b獲取到鎖執行,a代碼執行完remove鎖,a和b是一樣的key,導致a釋放了b的鎖, 解決辦法:remove之前判斷value(高并發下value可能被修改,應該用lua來保證原子性)
Redis如何做持久化
bgsave做鏡像全量持久化,aof做增量持久化,因為bgsave會耗費較長時間,不夠實時,在停機的時候會導致大量丟失資料 ,所以需要aof來配合使用,在redis實體重啟時,會使用bgsave持久化檔案重新構建記憶體,再使用aof重放近期的操作指令來 實 現完整恢復重啟之前的狀態,
對方追問那如果突然機器掉電會怎樣?
取決于aof日志sync屬性的配置,如果不要求性能,在每條寫指令時都sync一下磁盤,就不會丟失資料,但是在高性能的要求下每次都sync是不現實的,一般都使用定時sync,比如1s1次,這個時候最多就會丟失1s的資料.
redis鎖續租問題?
(1):基于redis的redission分布式可重入鎖RLock,以及配合java集合中lock;
(2):Redission 內部提供了一個監控鎖的看門狗,不斷延長鎖的有效期,默認檢查鎖的超時時間是30秒
(3):此方案的問題:如果你對某個redis master實體,寫入了myLock這種鎖key的value,此時會異步復制給對應的master ,slave實體,但是這個程序中一旦發生redis master宕機,主備切換,redis slave變為了redis master,
接著就會導致,客戶端2來嘗試加鎖的時候,在新的redis master上完成了加鎖,而客戶端1也以為自己成功加了鎖, 此時就會導致多個客戶端對一個分布式鎖完成了加鎖 解決辦法:只需要將新的redis實體,在一個TTL時間內,對客戶端不可用即可,在這個時間內,所有客戶端鎖將被失效或者自動釋放.
bgsave的原理是什么?
fork和cow,fork是指redis通過創建子行程來進行bgsave操作,cow指的是copy on write,子行程創建后,父子行程共享資料段,父行程繼續提供讀寫服務,寫進的頁面資料會逐漸和子行程分離開來,
RDB與AOF區別
(1):R檔案格式緊湊,方便資料恢復,保存rdb檔案時父行程會fork出子行程由其完成具體持久化作業,最大化redis性能,恢復大資料集速度更快,只有手動提交save命令或關閉命令時才觸發備份操作;
(2):A記錄對服務器的每次寫操作(默認1s寫入一次),保存資料更完整,在redis重啟是會重放這些命令來恢復資料,操作效率高,故障丟失資料更少,但是檔案體積更大;
1億個key,其中有10w個key是以某個固定的已知的前綴開頭的,如果將它們全部找出來?
使用keys指令可以掃出指定模式的key串列, 如果這個redis正在給線上的業務提供服務,那使用keys指令會有什么問題? redis的單執行緒的,keys指令會導致執行緒阻塞一段時間,線上服務會停頓,直到指令執行完畢,服務才能恢復,這個時候可以使用scan指令,scan指令可以無阻塞的提取出指定模式的key串列,但是會有一定的重復概率,在客戶端做一次去重就可以了 ,但是整體所花費的時間會比直接用keys指令長,
如何使用Redis做異步佇列?
一般使用list結構作為佇列,rpush生產訊息,lpop消費訊息,當lpop沒有訊息的時候,要適當sleep一會再重試,
可不可以不用sleep呢?
list還有個指令叫blpop,在沒有訊息的時候,它會阻塞住直到訊息到來,
能不能生產一次消費多次呢?
使用pub/sub主題訂閱者模式,可以實作1:N的訊息佇列,
pub/sub有什么缺點?
在消費者下線的情況下,生產的訊息會丟失,得使用專業的訊息佇列如rabbitmq等,
redis如何實作延時佇列?
使用sortedset,想要執行時間的時間戳作為score,訊息內容作為key呼叫zadd來生產訊息,消費者用zrangebyscore指令獲取N秒之前的資料輪詢進行處理,
為啥redis zset使用跳躍鏈表而不用紅黑樹實作?
(1):skiplist的復雜度和紅黑樹一樣,而且實作起來更簡單,
(2):在并發環境下紅黑樹在插入和洗掉時需要rebalance,性能不如跳表,
MYSQL
資料庫三范式
一: 確保每列的原子性
二:非主鍵列不存在對主鍵的部分依賴 (要求每個表只描述一件事情)
三: 滿足第二范式,并且表中的列不存在對非主鍵列的傳遞依賴
資料庫主從復制原理
(1):主庫db的更新事件(update、insert、delete)被寫到binlog
(2):主庫創建一個binlog dump thread執行緒,把binlog的內容發送到從庫
(3):從庫創建一個I/O執行緒,讀取主庫傳過來的binlog內容并寫入到relay log.
(4):從庫還會創建一個SQL執行緒,從relay log里面讀取內容寫入到slave的db.
復制方式分類
(1):異步復制(默認) 主庫寫入binlog日志后即可成功回傳客戶端,無須等待binlog日志傳遞給從庫的程序,但是一旦主庫宕機,就有可能出現丟失資料的情況,
(2)半同步復制:( 5.5版本之后) (安裝半同步復制插件)確保從庫接收完成主庫傳遞過來的binlog內容已經寫入到自己的relay log(傳送log)后才會通知主庫上面的等待執行緒,如果等待超時,則關閉半同步復制,并自動轉換為異步復制模式,直到至少有一臺從庫通知主庫已經接收到binlog資訊為止
存盤引擎
(1):Myiasm是mysql默認的存盤引擎,不支持資料庫事務,行級鎖,外鍵;插入更新需鎖表,效率低,查詢速度快,Myisam使用的是非聚集索引
(2):innodb 支持事務,底層為B+樹實作,適合處理多重并發更新操作,普通select都是快照讀,快照讀不加鎖,InnoDb使用的是聚集索引
聚集索引
(1):聚集索引就是以主鍵創建的索引
(2):每個表只能有一個聚簇索引,因為一個表中的記錄只能以一種物理順序存放,實際的資料頁只能按照一顆 B+ 樹進行排序
(3):表記錄的排列順序和與索引的排列順序一致
(4):聚集索引存盤記錄是物理上連續存在
(5):聚簇索引主鍵的插入速度要比非聚簇索引主鍵的插入速度慢很多
(6):聚簇索引適合排序,非聚簇索引不適合用在排序的場合,因為聚簇索引葉節點本身就是索引和資料按相同順序放置在一起,索引序即是資料序,資料序即是索引序,所以很快,非聚簇索引葉節點是保留了一個指向資料的指標,索引本身當然是排序的,但是資料并未排序,資料查詢的時候需要消耗額外更多的I/O,所以較慢
(7):更新聚集索引列的代價很高,因為會強制innodb將每個被更新的行移動到新的位置
非聚集索引
(1):除了主鍵以外的索引
(2):聚集索引的葉節點就是資料節點,而非聚簇索引的葉節點仍然是索引節點,并保留一個鏈接指向對應資料塊
(3):聚簇索引適合排序,非聚簇索引不適合用在排序的場合
(4):聚集索引存盤記錄是物理上連續存在,非聚集索引是邏輯上的連續,
使用聚集索引為什么查詢速度會變快?
使用聚簇索引找到包含第一個值的行后,便可以確保包含后續索引值的行在物理相鄰
建立聚集索引有什么需要注意的地方嗎?
在聚簇索引中不要包含經常修改的列,因為碼值修改后,資料行必須移動到新的位置,索引此時會重排,會造成很大的資源浪費
InnoDB 表對主鍵生成策略是什么樣的?
優先使用用戶自定義主鍵作為主鍵,如果用戶沒有定義主鍵,則選取一個Unique鍵作為主鍵,如果表中連Unique鍵都沒有定義的話,則InnoDB會為表默認添加一個名為row_id隱藏列作為主鍵,
非聚集索引最多可以有多少個?
每個表你最多可以建立249個非聚簇索引,非聚簇索引需要大量的硬碟空間和記憶體
BTree 與 Hash 索引有什么區別?
(1):BTree索引可能需要多次運用折半查找來找到對應的資料塊 (2):HASH索引是通過HASH函式,計算出HASH值,在表中找出對應的資料 (3):大量不同資料等值精確查詢,HASH索引效率通常比B+TREE高 (4):HASH索引不支持模糊查詢、范圍查詢和聯合索引中的最左匹配規則,而這些Btree索引都支持
資料庫索引優缺點
(1):需要查詢,排序,分組和聯合操作的欄位適合建立索引
(2):索引多,資料更新表越慢,盡量使用欄位值不重復比例大的欄位作為索引,聯合索引比多個獨立索引效率高
(3):對資料進行頻繁查詢進建立索引,如果要頻繁更改資料不建議使用索引
(4):當對表中的資料進行增加、洗掉和修改的時候,索引也要動態的維護,降低了資料的維護速度,
索引的底層實作是B+樹,為何不采用紅黑樹,B樹?
(1):B+Tree非葉子節點只存盤鍵值資訊,降低B+Tree的高度,所有葉子節點之間都有一個鏈指標,資料記錄都存放在葉子節點中
(2): 紅黑樹這種結構,h明顯要深的多,效率明顯比B-Tree差很多
(3):B+樹也存在劣勢,由于鍵會重復出現,因此會占用更多的空間,但是與帶來的性能優勢相比,空間劣勢往往可以接受,因此B+樹的在資料庫中的使用比B樹更加廣泛
索引失效條件
(1):條件是or,如果還想讓or條件生效,給or每個欄位加個索引
(2):like開頭%
(3):如果列型別是字串,那一定要在條件中將資料使用引號參考起來,否則不會使用索引
(4):where中索引列使用了函式或有運算
資料庫事務特點
ACID 原子性,一致性,隔離性,永久性
資料庫事務說是如何實作的?
(1):通過預寫日志方式實作的,redo和undo機制是資料庫實作事務的基礎
(2):redo日志用來在斷電/資料庫崩潰等狀況發生時重演一次刷資料的程序,把redo日志里的資料刷到資料庫里,保證了事務 的持久性(Durability)
(3):undo日志是在事務執行失敗的時候撤銷對資料庫的操作,保證了事務的原子性
資料庫事務隔離級別
(1):讀未提交read-uncommitted-- 臟,不可重復讀--幻讀 A讀取了B未提交的事務,B回滾,A 出現臟讀;
(2):不可重復讀read-committed-- 不可重復讀--幻讀 A只能讀B已提交的事務,但是A還沒結束,B又更新資料隱式提交,然后A又讀了一次出現不可重復讀;
(3):可重復讀repeatable-read<默認>-- 幻讀 事務開啟,不允許其他事務的UPDATE修改操作 A讀取B已提交的事務,然而B在該表插入新的行,之后A在讀取的時候多出一行,出現幻讀;
(4):串行化serializable--
七種事務傳播行為
(1)Propagation.REQUIRED<默認> 如果當前存在事務,則加入該事務,如果當前不存在事務,則創建一個新的事務,
(2)Propagation.SUPPORTS 如果當前存在事務,則加入該事務;如果當前不存在事務,則以非事務的方式繼續運行,
(3)Propagation.MANDATORY 如果當前存在事務,則加入該事務;如果當前不存在事務,則拋出例外,
(4)Propagation.REQUIRES_NEW 重新創建一個新的事務,如果當前存在事務,延緩當前的事務,
(5)Propagation.NOT_SUPPORTED 以非事務的方式運行,如果當前存在事務,暫停當前的事務,
(6)Propagation.NEVER 以非事務的方式運行,如果當前存在事務,則拋出例外,
(7)Propagation.NESTED 如果沒有,就新建一個事務;如果有,就在當前事務中嵌套其他事務,
產生死鎖的四個必要條件
(1):互斥: 資源x的任意一個時刻只能被一個執行緒持有 (2):占有且等待:執行緒1占有資源x的同時等待資源y,并不釋放x (3):不可搶占:資源x一旦被執行緒1占有,其他執行緒不能搶占x (4):回圈等待:執行緒1持有x,等待y,執行緒2持有y,等待x 當全部滿足時才會死鎖
@Transaction
底層實作是AOP,動態代理 (1):實作是通過Spring代理來實作的,生成當前類的代理類,呼叫代理類的invoke()方法,在invoke()方法中呼叫 TransactionInterceptor攔截器的invoke()方法;
(2):非public方式其事務是失效的;
(3):自呼叫也會失效,因為動態代理機制導致
(4)多個方法外層加入try...catch,解決辦法是可以在catch里 throw new RuntimeException()來處理
分布式事務
XA方案
有一個事務管理器的概念,負責協調多個資料庫(資源管理器)的事務 不適合高并發場景,嚴重依賴資料庫層面,同步阻塞問題;協調者故障則所有參與者會阻塞
TCC方案
嚴重依賴代碼補償和回滾,一般銀行用,和錢相關的支付、交易等相關的場景,我們會用TCC Try,對各個服務的資源做檢測,對資源進行鎖定或者預留 Confirm,在各個服務中執行實際的操作 Cancel,如果任何一個服務的業務方法執行出錯,那么這里就需要進行補償,即執行已操作成功的業務邏輯的回滾操作
可靠訊息最終一致性方案
1):本地訊息服務 本地訊息表其實是國外的 ebay 搞出來的這么一套思想, 主動方是認證服務,有個訊息例外處理系統,mq,還有訊息消費端應用系統,還有采集服務;
在我認證回傳資料中如果有發票是已經認證的,在處理認證資料的操作與發送訊息在同一個本地事務中,業務執行完,訊息資料也同時存在一條待確認的資料; 發送訊息給mq,,mq發送訊息給訊息消費端服務,同時存一份訊息資料,然后發送給采集服務,進行抵賬表更新操作; 采集服務邏輯處理完以后反饋給訊息消費端服務,其服務洗掉訊息資料,同時通知認證服務,把訊息記錄改為已確認成功費狀態; 對于例外流程,訊息例外處理系統會查詢認證服務中過期未確認的訊息發送給mq,相當于重試
2):獨立訊息最終一致性方案: A 主動方應用系統,B訊息服務子系統,C訊息狀態確認子系統,C2訊息管理子系統 D 訊息恢復子系統,mq ,訊息消費端E ,被動系統F
流程:
A預發送訊息給B,然后執行A業務邏輯,B存盤預發送訊息,A執行完業務邏輯發送業務操作結果給B,B更新預發送訊息為確認并發送訊息狀態同時發送訊息給mq,然后被E監聽然后發送給F消費掉
C:對預發送訊息例外的處理,去查詢待確認狀態超時的訊息,去A中查詢進行資料處理,如果A中業務處理成功了,那么C需改訊息狀態為確認并發送狀態,然后發送訊息給mq;如果A中業務處理失敗了..那么C直接把訊息洗掉即可.
C2 : 查詢訊息的頁面,對訊息的可視化,以及批量處理死亡訊息;
D: B給mq放入資料如果失敗,,通過D去重試,多次重試失敗,訊息設定為死亡
E:確保F執行完成,發送訊息給B洗掉訊息
優化建議:
(1)資料庫:如果用redis,持久化要配置成appendfsync always,確保每次新添加訊息都能持久化進磁盤
(2)在被動方應用業務冪等性判斷比較麻煩或者比較耗性能情況下,增加訊息日志記錄表.用于判斷之前有無發送過;
最大努力通知性(定期校對)
(1)業務主動方完成業務處理之后,設定時間階梯型通知規則向業務活動的被動方發送訊息,允許訊息丟失.
(2)被動方根據定時策略,向主動方查詢,恢復丟失的業務訊息
(3)被動方的處理結果不影響主動方的處理結果
(4)需增加業務查詢,通知服務,校對系統服務的建設成本
(5)適用于對業務最終一致性的時間敏感度低,跨企業的業務通知活動
(6)比如銀行通知,商戶通知,交易業務平臺間商戶通知,多次通知,查詢校對等
Seata(阿里)
應用層基于SQL決議實作了自動補償,從而最大程度的降低業務侵入性; 將分布式事務中TC(事務協調者)獨立部署,負責事務的注冊、回滾; 通過全域鎖實作了寫隔離與讀隔離,
網路
TCP和UDP的比較
TCP向上層提供面向連接的可靠服務 ,UDP向上層提供無連接不可靠服務, 雖然 UDP 并沒有 TCP 傳輸來的準確,但是也能在很多實時性要求高的地方有所作為 對資料準確性要求高,速度可以相對較慢的,可以選用TCP
TCP三次握手

TCP四次揮手
(1):客戶端發送終止命令FIN
(2):服務端收到后回復ACK,處于close_wait狀態
(3):服務器將關閉前需要發送資訊發送給客戶端后處于last_ack狀態
(4):客戶端收到FIN后發送ack后處于tim-wait而后進入close狀態

為什么要進行第三次握手
為了防止服務器端開啟一些無用的連接增加服務器開銷以及防止已失效的連接請求報文段突然又傳送到了服務端
JDK1.8新特性
Lambda運算式
java也開始承認了函式式編程, 就是說函式既可以作為引數,也可以作為回傳值, 大大的簡化了代碼的開發
default關鍵字
打破介面里面是只能有抽象方法,不能有任何方法的實作,介面里面也可以有方法的實作了
新時間日期APILocalDate | LocalTime | LocalDateTime
之前使用的java.util.Date月份從0開始,我們一般會+1使用,很不方便,java.time.LocalDate月份和星期都改成了enum java.util.Date和SimpleDateFormat都不是執行緒安全的,而LocalDate和LocalTime和最基本的String一樣,是不變型別,不但執行緒安全,而且不能修改, 新介面更好用的原因是考慮到了日期時間的操作,經常發生往前推或往后推幾天的情況,用java.util.Date配合Calendar要寫好多代碼,而且一般的開發人員還不一定能寫對,
JDK1.7與JDK1.8 ConcurrentHashMap對比
(1):JDK1.7版本的ReentrantLock+Segment+HashEntry(陣列)
(2):JDK1.7采用segment的分段鎖機制實作執行緒安全
(3):JDK1.8版本中synchronized+CAS+HashEntry(陣列)+紅黑樹
(4):JDK1.8采用CAS+Synchronized保證執行緒安全
(5):查詢時間復雜度從原來的遍歷鏈表O(n),變成遍歷紅黑樹O(logN)
1.8 HashMap陣列+鏈表+紅黑樹來實作hashmap,當碰撞的元素個數大于8時 & 總容量大于64,會有紅黑樹的引入 除了添加之后,效率都比鏈表高,1.8之后鏈表新進元素加到末尾
JDK1.8使用synchronized來代替重入鎖ReentrantLock?
(1):因為粒度降低了,在相對而言的低粒度加鎖方式,synchronized并不比ReentrantLock差
(2):基于JVM的synchronized優化空間更大
(3):在大資料量下,基于API的ReentrantLock會比基于JVM的記憶體壓力開銷更多的記憶體
JDK1.9新特性
模塊系統:
模塊是一個包的容器,Java 9 最大的變化之一是引入了模塊系統(Jigsaw 專案),
集合工廠方法
通常,您希望在代碼中創建一個集合(例如,List 或 Set ),并直接用一些元素填充它, 實體化集合,幾個 “add” 呼叫,使得代碼重復, Java 9,添加了幾種集合工廠方法:
Set<Integer> ints = Set.of(1, 2, 3);
List<String> strings = List.of("first", "second");
改進的 Stream API
Stream 介面中添加了 4 個新的方法:dropWhile, takeWhile, ofNullable,還有個 iterate 方法的新多載方法
改進的 Javadoc:
Javadoc 現在支持在 API 檔案中的進行搜索,另外,Javadoc 的輸出現在符合兼容 HTML5 標準,
redis代理集群模式,spring有哪些注解,b+b 紅黑樹區別,三次握手,valitile重排序底層代碼, cas 事務的4個特性,java8 java11 特性, filter和interceptor的區別 @autowired原理, dispatcherservlet,分布式事務解決方案spring都有哪些模塊,fork join佇列,排序演算法,
集合
java的集合框架有哪幾種:
兩種:collection和map,其中collection分為set和List,
List你使用過哪些
ArrayList和linkedList使用的最多,也最具代表性,
你知道vector和ArrayList和linkedList的區別嘛
ArrayList實作是一個陣列,可變陣列,默認初始化長度為10,也可以我們設定容量,但是沒有設定的時候是默認的空陣列,只有在第一步add的時候會進行擴容至10(重新創建了陣列),后續擴容按照3/2的大小進行擴容,是執行緒不安全的,適用多讀取,少插入的情況
linkedList是基于雙向鏈表的實作,使用了尾插法的方式,內部維護了鏈表的長度,以及頭節點和尾節點,所以獲取長度不需要遍歷,適合一些插入/洗掉頻繁的情況,
Vector是執行緒安全的,實作方式和ArrayList相似,也是基于陣列,但是方法上面都有synchronized關鍵詞修飾,其擴容方式是原來的兩倍,
hashMap和hashTable和ConcurrentHashMap的區別
hashMap是map型別的一種最常用的資料結構,其底部實作是陣列+鏈表(在1.8版本后變為了陣列+鏈表/紅黑樹的方式),其key是可以為null的,默認hash值為0,擴容以2的冪等次(為什么,,,因為只有是2的冪等次的時候(n-1)&x==x%n,當然不一定只有一個原因),是執行緒不安全的
hashTable的實作形式和hashMap差不多,它是執行緒安全的,是繼承了Dictionary,也是key-value的模式,但是其key不能為null,
ConcurrentHashMap是JUC并發包的一種,在hashMap的基礎上做了修改,因為hashmap其實是執行緒不安全的,那在并發情況下使用hashTable嘛,但是hashTable是全程加鎖的,性能不好,所以采用分段的思想,把原本的一個陣列分成默認16段,就可以最多容納16個執行緒并發操作,16個段叫做Segment,是基于ReetrantLock來實作的
說說你了解的hashmap吧
hashMap是Map的結構,內部用了陣列+鏈表的方式,在1.8后,當鏈表長度達到8的時候,會變成紅黑樹,這樣子就可以把查詢的復雜度變成O(nlogn)了,默認負載因子是0.75,為什么是0.75呢?
我們知道當負載因子太小,就很容易觸發擴容,如果負載因子太大就容易出現碰撞,所以這個是空間和時間的一個均衡點,在1.8的hashmap介紹中,就有描述了,貌似是0.75的負載因子中,能讓隨機hash更加滿足0.5的泊松分布,
除此之外,1.7的時候是頭插法,1.8后就變成了尾插法,主要是為了解決rehash出現的死回圈問題,而且1.7的時候是先擴容后插入,1.8則是先插入后擴容(為什么?正常來說,如果先插入,就有可能節點變為樹化,那么是不是多做一次樹轉化,比1.7要多損耗,個人猜測,因為讀寫問題,因為hashmap并不是執行緒安全的,如果說是先擴容,后寫入,那么在擴容期間,是訪問不到新放入的值的,是不是不太合適,所以會先放入值,這樣子在擴容期間,那個值是在的),
1.7版本的時候用了9次擾動,5次異或,4次位移,減少hash沖突,但是1.8就只用了兩次,覺得就足夠了一次異或,一次位移,
concurrentHashMap呢
concurrentHashMap是執行緒安全的map結構,它的核心思想是分段鎖,在1.7版本的時候,內部維護了segment陣列,默認是16個,segment中有一個table陣列(相當于一個segmeng存放著一個hashmap,,,),segment繼承了reentrantlock,使用了互斥鎖,map的size其實就是segment陣列的count和,而在1.8的時候做了一個大改版,廢除了segment,采用了cas加synchronize方式來進行分段鎖(還有自旋鎖的保證),而且節點物件改用了Node不是之前的HashEntity,
Node可以支持鏈表和紅黑樹的轉化,比如TreeBin就是繼承了Node,這樣子可以直接用instanceof來區分,1.8的put就很復雜來,會先計算出hash值,然后根據hash值選出Node陣列的下標(默認陣列是空的,所以一開始put的時候會初始化,指定負載因子是0.75,不可變),判斷是否為空,如果為空,則用cas的操作來賦值首節點,如果失敗,則因為自旋,會進入非空節點的邏輯,這個時候會用synchronize加鎖頭節點(保證整條鏈路鎖定)這個時候還會進行二次判斷,是否是同一個首節點,在分首節點到底是鏈表還是樹結構,進行遍歷判斷,
concurrentHashMap的擴容方式
1.7版本的concurrentHashMap是基于了segment的,segment內部維護了HashEntity陣列,所以擴容是在這個基礎上的,類比hashmap的擴容,
1.8版本的concurrentHashMap擴容方式比較復雜,利用了ForwardingNode,先會根據機器內核數來分配每個執行緒能分到的busket數,(最小是16),這樣子可以做到多執行緒協助遷移,提升速度,然后根據自己分配的busket數來進行節點轉移,如果為空,就放置ForwardingNode,代表已經遷移完成,如果是非空節點(判斷是不是ForwardingNode,是就結束了),加鎖,鏈路回圈,進行遷移,
hashMap的put方法的程序
判斷key是否是null,如果是null對應的hash值就是0,獲得hash值過后則進行擾動,(1.7是9次,5次異或,4次位移,1.8是2次),獲取到的新hash值找出所在的index,(n-1)&hash,根據下標找到對應的Node/entity,然后遍歷鏈表/紅黑樹,如果遇到hash值相同且equals相同,則覆寫值,如果不是則新增,如果節點數大于8了,則進行樹化(1.8),完成后,判斷當前的長度是否大于閥值,是就擴容(1.7是先擴容在put),
為什么修改hashcode方法要修改equals
都是map惹的禍,我們知道在map中判斷是否是同一個物件的時候,會先判斷hash值,在判斷equals的,如果我們只是重寫了hashcode,沒有順便修改equals,比如Intger,hashcode就是value值,如果我們不改寫equals,而是用了Object的equals,那么就是判斷兩者指標是否一致了,那就會出現valueOf和new出來的物件會對于map而言是兩個物件,那就是個問題了
TreeMap了解嘛
TreeMap是Map中的一種很特殊的map,我們知道Map基本是無序的,但是TreeMap是會自動進行排序的,也就是一個有序Map(使用了紅黑樹來實作),如果設定了Comparator比較器,則會根據比較器來對比兩者的大小,如果沒有則key需要是Comparable的子類(代碼中沒有事先check,會直接拋出轉化例外,有點坑啊),
LinkedHashMap了解嘛
LinkedHashMap是HashMap的一種特殊分支,是某種有序的hashMap,和TreeMap是不一樣的概念,是用了HashMap+鏈表的方式來構造的,有兩者有序模式:訪問有序,插入順序,插入順序是一直存在的,因為是呼叫了hashMap的put方法,并沒有多載,但是多載了newNode方法,在這個方法中,會把節點插入鏈表中,訪問有序默認是關閉的,如果打開,則在每次get的時候都會把鏈表的節點移除掉,放到鏈表的最后面,這樣子就是一個LRU的一種實作方式,
資料結構+演算法
TODO(未完待續)
總結
內容過于硬核了,導致很多排版細節,我沒辦法做得像其他期一樣精致了,大家見諒,
涉及的內容和東西太多了,可能很多都是點到為止,也有很多不全的,也有很多錯誤的點,已經快3W字了,我校驗實在困難,我會放在GitHub上面,大家可以跟我一起更新這個文章,造福后人吧,
搞不好下次我需要看的時候,我都得看著這個復習了,
我是敖丙,一個在互聯網茍且偷生的工具人,
你知道的越多,你不知道的越多,人才們的 【三連】 就是丙丙創作的最大動力,我們下期見!
注:如果本篇博客有任何錯誤和建議,歡迎人才們留言,你快說句話啊!
文章持續更新,可以微信搜索「 三太子敖丙 」第一時間閱讀,回復【資料】【面試】【簡歷】有我準備的一線大廠面試資料和簡歷模板,本文 GitHub https://github.com/JavaFamily 已經收錄,有大廠面試完整考點,歡迎Star,

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/155283.html
標籤:Java
