Java高級面試題解析(二):百度Java面試題前200頁(精選)

基本概念css

操做系統中 heap 和 stack 的區別html

heap是堆,stack是棧,是兩種不一樣的數據結構。堆是隊列優先,先進先出;棧是先進後出。
在java多線程中,每一個線程都有本身的棧;不一樣的線程共享一個堆。
在java內存中,棧中存放的大多數是方法的參數、局部變量,調用完後當即釋放空間;堆中存放的是由new建立的對象和數組,生命週期由JVM的垃圾回收算法決定。

 

什麼是基於註解的切面實現前端

首先說切面編程:爲了方便,將一些公共的相似的地方抽取出來,開發時只須要關注具體業務,這個公共相似的東西就是通知(Advice,包括安全、事物、日誌等),能夠使用通知的地方稱爲鏈接點(JoinPoint,如每一個方法的先後、拋出異常的地方),
使用了通知的鏈接點爲切入點(Pointcut),通知和切入點結合就是切面(Aspect),引入(introduction)和使用切面的類爲目標(target),使用的是代理(proxy)來
實現整套AOP機制
一開始配置切入點是在spring的xml文件中進行的,以後能夠在目標類中使用註解實現,spring在啓動時會加載和掃描包含註解之處,而後再實現動態代理。使用到的註解包括:@Aspect、@Pointcut、@Before、@After、@Component等。
demo能夠參考這裏:https://github.com/aJavaBird/demo.spring.aspect

 

什麼是 對象/關係 映射集成模塊java

對象關係映射(Object Relational Mapping,簡稱ORM)是經過描述對象和數據庫之間映射的元數據,將面嚮對象語言程序中的對象自動持久化到數據庫中。甚至開發人員都不用管數據庫的存儲,只需知道如何存對象便可。已有的ORM框架有Hibernate、mybatis等。

 

什麼是 Java 的反射機制mysql

JAVA反射機制是在運行狀態中,對於任意一個類,都可以知道這個類的全部屬性和方法;對於任意一個對象,都可以調用它的任意方法和屬性;這種動態獲取信息以及動態調用對象方法的功能稱爲java語言的反射機制。

 

什麼是 ACIDreact

ACID是數據庫事務正確執行的四個基本要素的縮寫:
原子性(Atomicity),多個操做要麼所有成功,要麼所有失敗
一致性(Consistency),如轉帳,轉出的和收到的金額一致
隔離性(Isolation),多個事務之間互不影響
持久性(Durability),事務執行OK後存入數據庫實現持久化

 

BS與CS的聯繫與區別linux

C/S(Client/Server):是指須要安裝的客戶端應用程序。  
B/S(Brower/Server):是指能夠用瀏覽器直接訪問的應用程序。

 

Cookie 和 Session的區別nginx

cookie是服務器存儲在本地計算機上的小塊文本,瀏覽器解析cookie並保存爲本地文本。
cookie的內容主要包括,名稱值、到期時間、路徑和域。
生命週期爲瀏覽器會話的cookie爲會話cookie,會話cookie一般保存在內存中,瀏覽器關閉則會話cookie消失,用戶其餘cookie會保存到硬盤中,下次打開瀏覽器時讀取(但受到到期時間限制),js能夠寫入和讀取cookie。
當程序爲客戶請求建立會話時,服務器端會先檢查客戶端是否包含會話ID,如已包含說明客戶端以前已經建立了一個會話,如無,則爲客戶端建立會話並生成會話ID(sessionID),會話ID會返回客戶端並保存。
cookie存在客戶端,能被客戶端程序所窺探和修改; session(會話)存儲在服務器端,不存在信息泄露風險。
關閉瀏覽器後,session失效(由於session依賴於cookie中的JSESSIONID,到期時間爲-1,關閉瀏覽器則失效),另外服務器也會設置session超時時長,不然太多的session會致使內存溢出。

 

如何讓前端 js 沒法使用 cookie,保證網址安全?git

有如下兩種辦法:
1、修改nginx,在 server模塊 下面加入如下內容 : 
add_header Set-Cookie "HttpOnly"; # 在Cookie中設置了"HttpOnly"屬性,經過程序(JS、Applet等)將沒法讀取到Cookie
add_header Set-Cookie "Secure"; # 指示瀏覽器僅經過 HTTPS 鏈接傳回 cookie
add_header X-Frame-Options "SAMEORIGIN"; # 不容許一個頁面在 <frame>, </iframe> 或者 <object> 中展示的標記
2、java後臺 : 
response.addHeader("Set-Cookie", "mycookie=112; Path=/; HttpOnly"); //設置cookie 
response.addHeader("Set-Cookie", "mycookie=112; Path=/; Secure; HttpOnly"); //設置https的cookie
response.setHeader("x-frame-options", "SAMEORIGIN"); // 設置x-frame-options

 

fail-fast 與 fail-safe 機制有什麼區別程序員

fail-fast(快速失敗)和fail-safe(安全失敗):之因此會有這兩個概念是由於同步修改(當一個或多個線程正在遍歷一個集合Collection時,另外一個線程修改了這個集合內容<增、刪、改>)的存在。
快速失敗是在併發修改時拋出ConcurrentModificationException(如多個線程同時操做ArrayList時會有),解決思路是使用java.util.concurrent 包下的集合類(如使用CopyOnWriteArrayList代替ArrayList)。
安全失敗是結合在遍歷時不直接在集合內容上訪問,而是先複製原有集合內容,在拷貝的集合上進行遍歷,故不會拋出ConcurrentModificationException。
兩者區別:fail-fast(快速失敗)是在集合內容上直接遍歷,多線程時會報異常;fail-safe(安全失敗)是在複本上遍歷,浪費內存,同時會形成某個線程讀取數據不許確的狀況,但不會報異常。

 

get 和 post請求的區別

w3c「標準答案」:
get在瀏覽器回退時是無害的,而post會再次提交請求;
get產生的url能夠被瀏覽器收藏爲書籤,而post不行;
get請求僅支持url編碼(application/x-www-form-urlencoded),而post支持多種編碼類型(application/x-www-form-urlencoded 或 ultipart/form-data);
get請求的參數會被完整地保存在瀏覽器歷史記錄中,post參數不會保存在瀏覽器歷史中;
get的數據長度的受限(由於URL 的長度是受限制的<URL 的最大長度是 2048 個字符>),post數據長度不受限制;
get對數據類型的限制只容許 ASCII 字符,post沒有限制,也容許二進制數據;
post 比 get 更安全,由於參數不會被保存在瀏覽器歷史或 web 服務器日誌中;
get 數據在 URL 中對全部人都是可見的,post 數據不會顯示在 URL 中。
高手答案:
get和post是HTTP協議中的兩種發送請求的方法,HTTP底層都是TCP/IP,故get和post都是TCP連接,兩者能作的事情其實都是同樣的,給get加上request body,給post加上url參數,技術上都是能夠實現的。
而致使get和post在「標準答案」中的區別,是因爲HTTP的規定和瀏覽器/服務器的限制,致使他們在應用過程當中的不一樣。
另外,get產生一個TCP數據包,post產生2個TCP數據包:
get請求瀏覽器會把http header 和 data 一併發送出去,服務器返回200;對於post,瀏覽器先發送header,服務器響應100 continue,瀏覽器再發送data,服務器響應200 ok。
由於post發送請求須要兩步,故用時會比get更久一點,但並不建議用get替換post。並不是全部瀏覽器都會在post中發送2次包,firefox僅發送一次。

 

Interface 與 abstract 類的區別

一、接口(Interface)須要被實現,抽象類(abstract類)須要被繼承。  
二、一個類能夠實現多個接口,但一個類只能繼承一個抽象類。  
三、接口裏面的方法所有是抽象的,抽象類裏面能夠有非抽象的方法。
補充:
接口中的方法和變量必須是public的(其中變量必須是public static final的),抽象類中則能夠使private的;
接口中不能包含初始化塊(static 塊和非靜態塊),抽象類中能夠。

 

IOC的優勢是什麼

靈活地提供不一樣子類的實現(解耦)、提升程序靈活性、可擴展性和可維護性。

 

IO 和 NIO的區別,NIO優勢

IO面向流,阻塞式,NIO面向緩存(通道、緩存),非阻塞式;
在鏈接很少時,IO編寫容易,方便使用,但隨着鏈接數增長,IO會出現瓶頸,由於每一個IO鏈接都須要消耗一個線程,NIO解決了IO的瓶頸問題,能夠1個線程處理多個鏈接,由於非阻塞IO處理鏈接是異步的,當某個鏈接發送請求到服務器時,服務器把它看成一個事件,分配給相應的函數處理。

 

Java 8 / Java 7 爲咱們提供了什麼新功能

Java7 新特性:  
一、switch裏面的case條件能夠使用字符串了  
二、運用 List<String> tempList = new ArrayList<>(); 即泛型實例化類型自動推斷   
Java8 新特性:   
一、Java8 容許咱們給接口添加一個非抽象的方法實現,只須要使用 default 關鍵字便可  
二、lambda 表達式  

 

什麼是競態條件? 舉個例子說明。

程序運行順序會影響最終結果,併發編程中,因爲不恰當的執行時序出現不正確的結果,就是競態條件。
例如:2個線程同時給一個int值加1,由於同時讀取的值,故最終結果可能僅加了1而不是2,程序讀取了最後返回的那個線程的值。
解決:在易出現競態條件的地方使用 synchronized 同步

 

JRE、JDK、JVM 及 JIT 之間有什麼不一樣

JVM(java 虛擬機):JVM 處理字節碼文件,讓 java 語言實現跨平臺。  
JRE(java運行時環境):JRE 是 JVM 的一個超集(JVM對於一個平臺或者操做系統是明確的,而JRE倒是一個通常的概念,他表明了完整的運行時環境)。  
JDK(java開發工具箱):JDK 包含了 JRE 和 Java的開發環境。   
JIT(即時編譯器):即時編譯器是種特殊的編譯器,它經過把字節碼變成機器碼來提升JVM的效率。

 

MVC的各個部分都有那些技術來實現?如何實現?

Model層:能夠用普通的 JavaBean 來實現。  
View層:能夠用 JSP 或者 JS 來實現。  
Controller層:能夠用 Struts2 或者 Spring MVC 來實現。

 

RPC 通訊和 RMI 區別

RPC(Remote Procedure Call Protocol)遠程過程調用協議,經過網絡從遠程計算機上請求調用某種服務;
RMI(Remote Method Invocation)遠程方法調用,可以讓在客戶端Java虛擬機上的對象像調用本地對象同樣調用服務端Java 虛擬機中的對象上的方法。
區別:
RMI: 直接獲取遠端方法的簽名,進行調用。優勢是強類型、編譯期可檢查錯誤;缺點是隻限於java語言
RPC: 採用客戶端/服務器方式(請求/響應),發送請求到服務器端,服務端執行方法後返回結果。優勢是跨語言跨平臺,缺點是編譯期沒法排錯,只能在運行時檢查。

 

什麼是 Web Service(Web服務)

Web Service 就是經過網絡調用其餘網站的資源

 

JSWDL開發包的介紹。JAXP、JAXM的解釋。SOAP、UDDI,WSDL解釋。

JAXP:(Java API for XML Parsing) 定義了在Java中使用DOM, SAX, XSLT的通用的接口。這樣在你的程序中你只要使用這些通用的接口,當你須要改變具體的實現時候也不須要修改代碼。  
JAXM:(Java API for XML Messaging) 是爲SOAP通訊提供訪問方法和傳輸機制的API。  
SOAP:即簡單對象訪問協議(Simple Object Access Protocol),它是用於交換XML編碼信息的輕量級協議。    
UDDI:UDDI的目的是爲電子商務創建標準;UDDI是一套基於Web的、分佈式的、爲Web Service提供的、信息註冊中心的實現標準規範,同時也包含一組使企業能將自身提供的Web Service註冊,以使別的企業可以發現的訪問協議的實現標準。    
WSDL:是一種 XML 格式,用於將網絡服務描述爲一組端點,這些端點對包含面向文檔信息或面向過程信息的消息進行操做。這種格式首先對操做和消息進行抽象描述,而後將其綁定到具體的網絡協議和消息格式上以定義端點。相關的具體端點即組合成爲抽象端點(服務)。  

 

WEB容器主要有哪些功能? 並請列出一些常見的WEB容器名字。

WEB容器的功能:通訊支持、管理servlet的生命週期、多線程支持、jsp支持(將jsp翻譯成java)  
常見的WEB容器:Tomcat、WebLogic、WebSphere  

 

一個」.java」源文件中是否能夠包含多個類(不是內部類)?有什麼限制

能夠,一個「.java」源文件裏面能夠包含多個類,可是隻容許有一個public類,而且類名必須和文件名一致。

 

簡單說說你瞭解的類加載器。是否實現過類加載器

類加載器負責加載Java類的字節碼到Java虛擬機中。   
本身實現類加載器通常須要繼承 java.lang.ClassLoader ,覆寫 findClass(String name)方法。

 

解釋一下什麼叫AOP(面向切面編程)

AOP(Aspect Oriented Programming),即面向切面編程,它利用一種稱爲"橫切"的技術,剖解開封裝的對象內部,並將那些影響了多個類的公共行爲封裝到一個可重用模塊,並將其命名爲"Aspect",即切面。
所謂"切面",簡單說就是將那些與業務無關,卻爲業務模塊所共同調用的邏輯封裝起來,便於減小系統的重複代碼,下降模塊之間的耦合度,並有利於將來的可操做性和可維護性。

 

請簡述 Servlet 的生命週期及其相關的方法

一、實例化階段:Servlet容器負責加載和實例化Servlet,調用Servlet的構造方法
二、初始化階段:服務器調用Servlet的init方法進行初始化(只在第一次請求時調用) 
三、請求處理階段:服務器調用Servlet的service方法,而後根據請求方式調用相應的doXXX方法
四、服務終止階段:服務器調用Servlet的destroy方法銷燬Servlet實例  

 

請簡述一下 Ajax 的原理及實現步驟

Ajax 即「Asynchronous Javascript And XML」(異步 JavaScript 和 XML),經過在後臺與服務器進行少許數據交換,能夠使網頁實現異步更新。這意味着能夠在不從新加載整個網頁的狀況下,對網頁的某部分進行更新。  
原理:HTTP協議的異步通訊  
實現步驟:  
一、建立一個XMLHttpRequest對象  
二、調用該對象的open方法  
三、設置回調函數

 

簡單描述Struts的主要功能

一、獲取表單內容,並組織生成參數對象
二、根據請求的參數轉發請求給適當的控制器
三、在控制器中調用業務接口
四、將業務接口返回的結果包裝起來發送給指定的視圖,並由視圖完成處理結果的展示
五、作一些簡單的校驗或是國際化工做

 

什麼是 N 層架構

N層架構是一種軟件抽象的層次結構,是對複雜軟件的一種縱向切分,每一層次中完成同一類型的操做,以便將各類代碼根據其完成的使命來進行分割,以下降軟件的複雜度,提升其可維護性。
通常來講,層次之間是向下依賴的,下層代碼未肯定其接口前,上層代碼是沒法開發的,下層代碼接口的變化將使上層的代碼一塊兒變化。

 

什麼是CORBA?用途是什麼

CORBA(Common Object Request Broker Architecture 公共對象請求代理體系結構)是由OMG組織制訂的一種標準的面向對象應用程序體系規範。
用途:
一、存取來自現行桌面應用程序的分佈信息和資源
二、使現有業務數據和系統成爲可供利用的網絡資源
三、爲某一特定業務用的定製的功能和能力來加強現行桌面工具和應用程序
四、改變和發展基於網絡的系統以反映新的拓撲結構或新資源

 

什麼是Java虛擬機?爲何Java被稱做是「平臺無關的編程語言」

Java虛擬機是執行字節碼文件(.class)的虛擬機進程。  
由於不一樣的平臺裝有不一樣的Java虛擬機,它們可以將相同的.class文件,解釋成不一樣平臺所須要的機器碼。因此Java被稱爲平臺無關的編程語言。

 

什麼是正則表達式?用途是什麼?哪一個包使用正則表達式來實現模式匹配

正則表達式:是對字符串操做的一種邏輯公式,就是用事先定義好的一些特定字符、及這些特定字符的組合,組成一個「規則字符串」,用這個「規則字符串」來表達對字符串的過濾邏輯。  
用途包括:   
一、字符串匹配  
二、指定字符串替換  
三、指定字符串查找  
四、字符串分割  
正則表達式的包:java.util.regex包

 

什麼是懶加載(Lazy Loading)

懶加載:即爲延遲加載,顧名思義就是在須要的時候才加載,這樣作效率會比較低,可是佔用內存低。

 

什麼是尾遞歸,爲何須要尾遞歸

若是一個函數中全部遞歸形式的調用都出如今函數的末尾,咱們稱這個遞歸函數是尾遞歸的。
爲何須要尾遞歸:尾遞歸和普通遞歸的不一樣點在對內存的佔用,普通遞歸建立stack後內存減小,而尾遞歸只會佔用恆量的內存。
(每個函數在調用下一個函數以前,都能作到先把當前本身佔用的棧給先釋放了,尾遞歸的調用鏈上能夠作到只有一個函數在使用棧,所以能夠無限地調用!尾遞歸的實現依賴於編譯器的幫助<或者說語言的規定>)

 

什麼是控制反轉(Inversion of Control)與依賴注入(Dependency Injection)

控制反轉:是指將建立對象的功能交給Spring容器,在咱們須要使用對象的時候不須要本身建立,能夠直接從容器中獲取。     
依賴注入:動態的向某個對象提供它所依賴的其餘對象。

 

關鍵字

finalize

 

什麼是finalize()方法

Java 能夠使用 finalize() 方法在垃圾收集器將對象從內存中清除出去以前作一些必要的清理工做。

 

finalize()方法何時被調用

這個方法是由垃圾收集器在肯定這個對象沒有被引用時 對這個對象進行調用的。

 

析構函數(finalization)的目的是什麼

析構函數的目的是:在清除對象前,完成一些清理工做,好比:釋放內存等。

 

final 和 finalize 的區別

final關鍵字能夠用於類、方法、變量前,用來表示該類、方法、變量具備不可變的特性。
finalize方法用於回收資源,能夠爲任何一個類添加finalize方法。該方法將在垃圾回收器清除對象以前調用。

 

final
final關鍵字有哪些用法

用來修飾變量,表示該變量的值沒法改變;
用來修飾方法,表示該方法沒法被重寫;
用來修飾類,表示該類沒法被繼承。

 

final 與 static 關鍵字能夠用於哪裏?它們的做用是什麼

final:
用來修飾變量,表示該變量的值沒法改變;
用來修飾方法,表示該方法沒法被重寫;
用來修飾類,表示該類沒法被繼承。

static:
靜態變量,能夠用類名直接訪問
靜態方法,能夠經過類名直接調用

 

volatile 修飾符的有過什麼實踐

1,當寫一個volatile變量時,JMM(java內存模型)會把該線程本地內存中的全部共享變量刷新到主內存中去
2,當讀取一個volatile變量時,該線程會將本地內存置爲無效,線程將從主內存中讀取共享變量。
總結,volatile變量能夠實現線程之間的通訊。
當對一個volatile變量寫操做時,實際上就是指明我對主內存中的共享變量產生更改,接下來讀取這個volatile變量的線程就會從主線程中取回最新的共享變量值。
一種實踐是用 volatile 修飾 longdouble 變量,使其能按原子類型來讀寫。doublelong 都是64位寬,所以對這兩種類型的讀是分爲兩部分的,第一次讀取第一個 32 位,而後再讀剩下的 32 位,這個過程不是原子的,但 Java 中 volatile 型的 longdouble 變量的讀寫是原子的。volatile 修復符的另外一個做用是提供內存屏障(memory barrier),例如在分佈式框架中的應用。簡單的說,就是當你寫一個 volatile 變量以前,Java 內存模型會插入一個寫屏障(write barrier),讀一個 volatile 變量以前,會插入一個讀屏障(read barrier)。意思就是說,在你寫一個 volatile 域時,能保證任何線程都能看到你寫的值,同時,在寫以前,也能保證任何數值的更新對全部線程是可見的,由於內存屏障會將其餘全部寫的值更新到緩存。

 

volatile 類型變量提供什麼保證?能使得一個非原子操做變成原子操做嗎

volatile 能夠保證 對象的可見性 和 程序的順序性,沒法保證操做的原子性。

 

能建立 volatile 數組嗎?

volatile修飾的變量若是是對象或數組之類的,其含義是對象獲數組的地址具備可見性,可是數組或對象內部的成員改變不具有可見性

 

transient變量有什麼特色

java 的transient關鍵字的做用是須要實現Serilizable接口,將不須要序列化的屬性前添加關鍵字transient,序列化對象的時候,這個屬性就不會序列化到指定的目的地中。

 

public static void 寫成 static public void會怎樣

public static void 能夠寫成 static public void   
但不能寫成  public void static 

 

 

a = a + b 與 a += b 的區別?

+= 隱式的將加操做的結果類型強制轉換爲持有結果的類型。若是兩這個整型相加,如 byteshort 或者 int,首先會將它們提高到 int 類型,而後在執行加法操做。
byte a = 127;byte b = 127;b = a + b; // error : cannot convert from int to byteb += a; // ok
(由於 a+b 操做會將 a、b 提高爲 int 類型,因此將 int 類型賦值給 byte 就會編譯出錯)

 

邏輯操做符 (&,|,^)與條件操做符(&&,||)的區別

a.條件操做只能操做布爾型的,而邏輯操做不只能夠操做布爾型,並且能夠操做數值型
b.邏輯操做不會產生短路.如: 
int a = 0; 
int b = 0; 
if( (a = 3) > 0 || (b = 3) > 0 ) //操後a =3,b=0. 
if( (a = 3) > 0 | (b = 3) > 0 ) //操後a =3,b=3。

 

3*0.1 == 0.3 將會返回什麼?true 仍是 false?

false,由於有些浮點數不能徹底精確的表示出來
(float)3*(float)0.1 == (float)0.3  ---->  true
4*0.1 == 0.4  ---->  true
因此對於小數,須要比較時,最穩的作法是使用BigDecimal,可是BigDecimal也存在問題
new BigDecimal(3).multiply(new BigDecimal(0.1)).compareTo(new BigDecimal(0.3)) == 0  ---->  false
// 使用String入參
new BigDecimal("3").multiply(new BigDecimal("0.1")).compareTo(new BigDecimal("0.3")) == 0  ---->  true

 

float f=3.4; 是否正確?

不正確。
3.4是雙精度數,將雙精度型(double)賦值給浮點型(float)屬於下轉型,會形成精度損失,所以須要強制類型轉換float f =(float)3.4; 或者寫成float f =3.4f;

 

short s1 = 1; s1 = s1 + 1;有什麼錯?

前者有錯,後者正確。
s1 short型 經過 + 運算後s1 自動轉爲int 型 因此錯;
+= 是複合賦值操做符,複合賦值等價於簡單賦值,因此s1+=1等效於s1=(short)(s1+1)。

 

如何去小數四捨五入保留小數點後兩位

(1)BigDecimal b = new BigDecimal(f);    double f1 = b.setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
(2)new java.text.DecimalFormat("#.00").format(3.1415926);
(3)double d = 3.1415926;    String result = String.format("%.2f", d);
(4)Math.round(5.2644555 * 100) * 0.01d;

 

char 型變量中能不能存貯一箇中文漢字,爲何

在Java中,char類型佔2個字節,並且Java默認採用Unicode編碼,一個Unicode碼是16位,因此一個Unicode碼佔兩個字節,Java中不管漢子仍是英文字母都是用Unicode編碼來表示的。
因此,在Java中,char類型變量能夠存儲一箇中文漢字。

 

咱們能將 int 強制轉換爲 byte 類型的變量嗎?若是該值大於 byte 類型的範圍,將會出現什麼現象

是的,咱們能夠作強制轉換,可是 Java 中 int32 位的,而 byte8 位的,因此,若是強制轉化,int 類型的高 24 位將會被丟棄,byte 類型的範圍是從 -128127

 

如何權衡是使用無序的數組仍是有序的數組

有序數組查詢容易,插入難。無序數組插入容易,查詢難。

 

簡述 ConcurrentLinkedQueue LinkedBlockingQueue 的用處和不一樣之處。

Java提供的線程安全的Queue能夠分爲阻塞隊列和非阻塞隊列,其中阻塞隊列的典型例子是BlockingQueue,非阻塞隊列的典型例子是ConcurrentLinkedQueue,在實際應用中要根據實際須要選用阻塞隊列或者非阻塞隊列。
ConcurrentLinkedQueue 是非阻塞隊列的表明,沒有鎖,使用CAS實現,有 add(e)、offer(e)、remove()、poll()和peek(),size() 是要遍歷一遍集合的,速很慢,因此判空時,儘可能要避免用size(),而改用isEmpty()。
LinkedBlockingQueue 是阻塞隊列,內部則是基於鎖,增長了 put(e)、take() 兩個阻塞方法。
LinkedBlockingQueue 多用於任務隊列,ConcurrentLinkedQueue 多用於消息隊列
單生產者,單消費者  用 LinkedBlockingqueue
多生產者,單消費者   用 LinkedBlockingqueue
單生產者 ,多消費者   用 ConcurrentLinkedQueue
多生產者 ,多消費者   用 ConcurrentLinkedQueue

 

HashMap的工做原理是什麼

HashMap使用的是哈希表(也稱散列表,是根據關鍵碼值(Key value)而直接進行訪問的數據結構),哈希表有多種不一樣的實現方法,經常使用的實現方式是「數組+鏈表」實現,即「鏈表的數組」。
首先,HashMap中存儲的對象爲數組Entry[](每一個Entry就是一個<key,value>),存和取時根據 key 的 hashCode 相關的算法獲得數組的下標,put 和 get 都根據算法的下標取得元素在數組中的位置。
萬一兩個key分別有相同的下標,那麼怎麼處理呢?
使用鏈表,把下標相同的 Entry 放到一個鏈表中,存儲時,存到第一個位置,並把next指向以前的第一個元素(若是是已存在的key,則須要遍歷鏈表),取值時,遍歷鏈表,經過equals方法獲取Entry。
// 存儲時:
int hash = key.hashCode(); // 這個hashCode方法這裏不詳述,只要理解每一個key的hash是一個固定的int值
int index = hash & (Entry[].length-1); // 二進制的按位與運算 
Entry[index] = value;
// 取值時:
int hash = key.hashCode();
int index = hash & (Entry[].length-1); // 二進制的按位與運算
return Entry[index]; // 此處須要遍歷 Entry[index] 的鏈表取值,此處爲簡寫
關於原理細節,可參考文章 https://www.cnblogs.com/holyshengjie/p/6500463.html 前半部分
關於源碼實現,可參考文章 https://www.cnblogs.com/chengxiao/p/6059914.html 【關於爲何覆蓋 equals方法 後須要同時覆蓋 hashCode方法】 在此文末尾
經過以上理解,若是 hashCode方法 寫得很差,好比全部 key 對象 都返回 同一個 int 值,那麼效率也不會高,由於就至關於一個鏈表了。

 

HashMap 的 table的容量如何肯定?loadFactor 是什麼? 該容量如何變化?這種變化會帶來什麼問題?

①、table 數組大小是由 capacity 這個參數肯定的,默認是16,也能夠構造時傳入,最大限制是1<<30;
②、loadFactor 是裝載因子,主要目的是用來確認table 數組是否須要動態擴展,默認值是0.75,好比table 數組大小爲 16,裝載因子爲 0.75 時,threshold 就是12,當 table 的實際大小超過 12 時,table就須要動態擴容;
③、擴容時,調用 resize() 方法,將 table 長度變爲原來的兩倍(注意是 table 長度,而不是 threshold)
④、若是數據很大的狀況下,擴展時將會帶來性能的損失,在性能要求很高的地方,這種損失極可能很致命。
參考自:https://www.jianshu.com/p/75adf47958a7

 

HashMap 和 HashTable、ConcurrentHashMap 的區別

這個問題面試的時候很常見。
參考: https://blog.csdn.net/ZytheMoon/article/details/88376749
參考: https://www.jianshu.com/p/c00308c32de4

 

HashSet和TreeSet有什麼區別

相同點:
單列集合,元素不可重複
不一樣點
1. 底層存儲的數據結構不一樣
   HashSet底層用的是HashMap哈希表結構存儲,而TreeSet底層用的是TreeMap樹結構存儲
2.存儲時保證數據惟一性依據不一樣
   HashSet是經過複寫hashCode()方法和equals()方法來保證的,而TreeSet經過Compareable接口的compareTo()方法來保證的
3.有序性不同
   HashSet無序,TreeSet有序
存儲原理:
HashSet:底層數據結構是哈希表,本質就是對哈希值的存儲,經過判斷元素的hashCode方法和equals方法來保證元素的惟一性,當hashCode值不相同,就直接存儲了,不用在判斷equals了,當hashCode值相同時,會在判斷一次euqals方法的返回值是否爲true,若是爲true則視爲用一個元素,不用存儲,若是爲false,這些相同哈希值不一樣內容的元素都存放一個桶裏(當哈希表中有一個桶結構,每個桶都有一個哈希值)
TreeSet:底層的數據結構是二叉樹,能夠對Set集合中的元素進行排序,這種結構,能夠提升排序性能, 根據比較方法的返回值肯定的,只要返回的是0.就表明元素重複

 

HashSet 內部是如何工做的

HashSet 的內部採用 HashMap來實現。因爲 Map 須要 key 和 value,因此全部 key 的都有一個默認 value。相似於 HashMap,HashSet 不容許重複的 key,只容許有一個null key,意思就是 HashSet 中只容許存儲一個 null 對象。

 

WeakHashMap 是怎麼工做的?

WeakHashMap實現了Map接口,是HashMap的一種實現,他使用弱引用做爲內部數據的存儲方案,WeakHashMap能夠做爲簡單緩存表的解決方案,當系統內存不夠的時候,垃圾收集器會自動的清除沒有在其餘任何地方被引用的鍵值對。

 

Set

Set 裏的元素是不能重複的,那麼用什麼方法來區分重複與否呢?是用 == 仍是 equals()? 它們有何區別?

HashSet 根據 hashCode()方法和equals() 方法肯定元素是否重複;TreeSet 根據 compareTo()方法

 

TreeMap和TreeSet在排序時如何比較元素?Collections工具類中的sort()方法如何比較元素?

TreeSet要求存放的對象所屬的類必須實現Comparable接口,該接口提供了比較元素的compareTo()方法,當插入元素時會回調該方法比較元素的大小。
TreeMap要求存放的鍵值對映射的鍵必須實現Comparable接口從而根據鍵對元素進行排序。
Collections工具類的sort方法有兩種重載的形式,第一種要求傳入的待排序容器中存放的對象必須實現Comparable接口以實現元素的比較;第二種不強制性的要求容器中的元素必須可比較,可是要求傳入第二個參數,參數是Comparator接口的子類型(須要重寫compare方法實現元素的比較),至關於一個臨時定義的排序規則,其實就是經過接口注入比較元素大小的算法,也是對回調模式的應用(Java中對函數式編程的支持)。

 

一個已經構建好的 TreeSet,怎麼完成倒排序。

使用Collections工具類中的sort()方法,實現Comparator接口,內部排序和treeSet的相反便可。

 

簡述一致性 Hash 算法

經過環形Hash空間把數據、機器等經過必定的hash算法處理後映射到環上,經過虛擬節點保證平衡性 
一致性hash算法提出了在動態變化的Cache環境中,斷定哈希算法好壞的四個定義:
1、平衡性(Balance)
2、單調性(Monotonicity)
3、分散性(Spread)
4、負載(Load)
普通的哈希算法(也稱硬哈希)採用簡單取模的方式,將機器進行散列,這在cache環境不變的狀況下能取得讓人滿意的結果,可是當cache環境動態變化時,這種靜態取模的方式顯然就不知足單調性的要求(當增長或減小一臺機子時,幾乎全部的存儲內容都要被從新散列到別的緩衝區中)。
一致性哈希算法的基本實現原理是將機器節點和key值都按照同樣的hash算法映射到一個0~2^32的圓環上。當有一個寫入緩存的請求到來時,計算Key值k對應的哈希值Hash(k),若是該值正好對應以前某個機器節點的Hash值,則直接寫入該機器節點,若是沒有對應的機器節點,則順時針查找下一個節點,進行寫入,若是超過2^32還沒找到對應節點,則從0開始查找(由於是環狀結構)。

 

Object有哪些公用方法

clone、equals、hashCode、getClass、wait、notify、notifyAll、toString

 

LinkedHashMap 和 PriorityQueue 的區別是什麼

PriorityQueue保證最高或者最低優先級的的元素老是在隊列頭部,可是LinkedHashMap維持的順序是元素插入的順序。當遍歷一個PriorityQueue 時,沒有任何順序保證,可是LinkedHashMap 課保證遍歷順序是元素插入的順序。

 

 

LinkedList 是單向鏈表仍是雙向鏈表

雙向

 

ArrayList 和 HashMap 的默認大小是多數

在Java7中,ArrayList的默認大小是10個元素,HashMap的默認大小是16個元素(必須是2的冪)
ArrayList:內部實現是一個Object的數組。初始默認大小爲0,固然也能夠在其構造方法中設置。當添加一個Object時,默認擴充數組容量爲10。而後每次擴充的新的數組大小等於,(原始容量*3/2)和(數組的長度+1)之間的較大值。根據每次增長一個Object,可得該狀況每次擴充的固定大小爲3/2。當初始大小爲手動設置的時候,每次擴充的新的數組大小等於,(原始容量*3/2)和(數組的長度+1)之間的較大值。
HashMap:內部實現是一個Entry的數組,默認大小是空的數組。初始化的容量是16,負載因子是0.75(當數組元素數量大於總容量的負載因子的時候,擴充數組)。當默認不是空的數組時,當達到負載因子的比例的時候,每次擴充初始容量的2倍。

 

Comparator 與 Comparable 接口是幹什麼的?列出它們的區別

Comparable & Comparator 都是用來實現集合中元素的比較、排序的,只是 Comparable 是在集合內部定義的方法實現的排序,Comparator 是在集合外部實現的排序。
comparable接口:
優勢:對於單元素集合能夠實現直接排序
缺點:對於多元素排序,排序依據是固定不可更改的。(元素內部只能實現一次compareTo方法)
comparator接口:
元素的排序依據時刻變的,因此能夠經過定義多個外部類的方式來實現不一樣的排序。使用時按照需求選取。
建立外部類的代價太大。

 

如何實現對象克隆

1). 實現Cloneable接口並重寫Object類中的clone()方法;
2). 實現Serializable接口,經過對象的序列化和反序列化實現克隆,能夠實現真正的深度克隆

 

深拷貝和淺拷貝區別

簡單的來講就是,在有指針的狀況下,淺拷貝只是增長了一個指針指向已經存在的內存,而深拷貝就是增長一個指針而且申請一個新的內存,使這個增長的指針指向這個新的內存,採用深拷貝的狀況下,釋放內存的時候就不會出如今淺拷貝時重複釋放同一內存的錯誤,修改對象也是

 

寫clone()方法時,一般都有一行代碼,是什麼

super.clone();

 

如何構建不可變的類結構?關鍵點在哪裏

1)將類聲明爲final,因此它不能被繼承
(2)將全部的成員聲明爲私有的,這樣就不容許直接訪問這些成員
(3)對變量不要提供setter方法
(4)將全部可變的成員聲明爲final,這樣只能對它們賦值一次
(5)經過構造器初始化全部成員,進行深拷貝(deep copy)
(6)在getter方法中,不要直接返回對象自己,而是克隆對象,並返回對象的拷貝

 

能建立一個包含可變對象的不可變對象嗎

能夠的,咱們是能夠建立一個包含可變對象的不可變對象的,你只須要謹慎一點,不要共享可變對象的引用就能夠了,若是須要變化時,就返回原對象的一個拷貝。
set屬性時,不要這樣:this.person = person; 而要這樣:this.person = new Person(person.getName(),person.getAge()); ---> 由於傳入的 person 對象隨時會被外界修改

 

 

方法能夠同時便是 static 又是 synchronized 的嗎

能夠。若是這樣作的話,JVM會獲取和這個對象關聯的java.lang.Class實例上的鎖。這樣作等於:
synchronized(XYZ.class) {
}

 

若是main方法被聲明爲private會怎樣

能夠編譯,運行時報錯

 

 

垃圾回收的最佳作法是什麼

1、標記-清除算法:
    首先標記出全部須要回收的對象,在標記完成後統一回收掉全部被標記的對象。標記過程當中 實際上即時上面說的finaLize()的過程。主要缺點一個是效率問題。另一個是空間問題,標記清除後會產生大量不連續的內存碎片。
2、複製算法:
   這種算法將可用內存按容量劃分爲大小相等的兩塊,每次只使用其中的一塊,當這一塊的內存用完了。就將還存活着的對象複製到另一塊上面,而後再把已經使用過的內存空間一次清理掉。
3、標記-整理算法:
    複製收集算法在對象存活率較高時就要執行較多的複製操做,效率將會遍低。更關鍵的是,若是不想浪費50%的空間,就須要有額外的空間進行分配擔保,以對應被使用的內存中全部對象都100%存活的極端狀況,因此在老年代通常不能直接選用這種算法。
標記過程仍然與標記-清除算法同樣,可是後續步驟不是直接將對可回收對象進行清理,而是讓全部存活的對象都向領一端移動,而後直接清理掉端邊界之外的內存。
4、分代收集算法:
  當代商業虛擬機的垃圾收集都採用的是「分代收集算法」 ,根據對象的存活週期的不一樣,將內存化爲幾塊,通常是把java堆分爲新生代和老年代。這樣就能夠根據各個年代的特色採用最合適的收集算法。
新生代選用複製算法,老年代使用標記-清理算法 或者 標記-整理算法。

 

GC收集器有哪些

1、serial收集器
serial是一個單線程的垃圾收集器,它只會使用一個cpu、一個收集線程工做;它在進行gc的時候,必須暫停其餘全部線程到它工做結束(這種暫停每每讓人難以接受)。
對於單個cpu的狀況下,serial是沒有線程交互的開銷,效率要好於其餘。例如在一個簡單的桌面應用,只有幾十M的內存,他的停頓時間可能只有幾十毫秒,因此通常默認的client模式下都是用的serial作默認垃圾收集器。
2、parnew收集器
parnew其實就是serial的多線程版本,parnew在單線程的狀況下甚至不如serial,parnew是除了serial以外惟一能和CMS配合的。
parnew默認開啓收集線程數和cpu的數量相同,咱們能夠利用-XX:ParallelGCThreads參數來控制它開啓的收集線程數。
3、parallel scavenge收集器
parallel scavenge主要就是關注吞吐量。
所謂吞吐量:運行用戶代碼的世界/(運行用戶代碼時間+GC花費的時間)。
parallel scavenge收集器中,提供了2個參數來控制吞吐量:
-XX:GCTimeRatio:gc時間佔用的總比例,也就是吞吐量的倒數。
-XX:MaxGCPauseMillis:最大的暫停毫秒數(這個數值並不是越小越好,若是把他設置小了,系統會根據這個值調整空間的大小,也就會加快GC的頻率)
parallel scavenge能夠設置開啓-XX:UseAdaptiveSizePolicy,開啓這個參數以後就無需關注新生代大小eden和survivor等比例,晉升老年代對象年齡的這些細節了。
4、serial old收集器
serial收集器的老年代版本,使用標記整理算法,主要有兩個做用:
jdk5以前和parallel scavenge配合使用
做爲cms失敗的後備收集方案
5、parallel old收集器
是parallel收集器的老年代版本,用於和parallel收集器搭配組合的,由於parallel收集器不能和cms組合,可是和serial old收集器效率又過低。
對吞吐量和CPU敏感度特別關注的應用能夠使用parallel+parallel old的組合。
6、CMS收集器、特色、過程、缺點
CMS的適用特色:
但願JAVA垃圾回收器回收垃圾的時間儘量短;
應用運行在多CPU的機器上,有足夠的CPU資源;
有比較多生命週期長的對象;
但願應用的響應時間短。
CMS的過程:
初始標記:標記一下GC ROOT能直接關聯的對象,速度很快,這個階段是會STW。
併發標記:GC ROOT的引用鏈的查找過程,標記能關聯到GC ROOT的對象,這一個階段是不須要STW的。
從新標記:在併發標記階段,應用的線程可能產生新的垃圾,因此須要從新標記,這個階段也是會STW。
併發清除:這個階段就是真正的回收垃圾的對象的階段,無需STW。
CMS的缺點:
對cpu比較敏感。
可能出現浮動垃圾:在併發清除階段,用戶仍是繼續使用的,這時候就會有新的垃圾出現,CMS只能等下一次GC才能清除掉他們。
CMS運行期間預留內存不夠的話,就會出現concurrent Mode Failure,這時候就會啓動serial收集器進行收集。
CMS基於標記清除算法實現,會產生內存碎片空間。碎片空間過多就會對大對象的分配空間形成麻煩。爲了解決碎片問題,CMS提供一個參數來控制是否在GC的時候整合內存中的碎片,這個碎片整合的操做是沒法併發的,會延長STW的時間。
7、G1收集器
G1的特色:
利用多CPU來縮短STW的時間
能夠獨立管理整個堆(使用分代算法)
總體是基於標記-整理,局部使用複製算法,不會產生碎片空間
能夠預測停頓:G1吧整個堆分紅多個Region,而後計算每一個Region裏面的垃圾大小(根據回收所得到的空間大小和回收所須要的時間的經驗值),在後臺維護一個優先列表,每次根據容許的收集時間,優先回收價值最大的Region。
G1的運行過程:
初始標記:標記一下GC ROOT能直接關聯的對象,速度很快,這個階段是會STW。
併發標記:在GC ROOT中運用可達性分析算法,找出存活的對象,耗時較長,可是無需STW。
最終標記:修正併發標記期間用戶線程對垃圾對象的修改,須要停頓線程,可是能夠並行執行。
篩選回收:先計算回收各個Region的價值,而後根據用戶需求來進行回收。

 

串行(serial)收集器和吞吐量(throughput)收集器的區別是什麼

串行GC:整個掃描和複製過程均採用單線程的方式,相對於吞吐量GC來講簡單;適合於單CPU、客戶端級別。
吞吐量GC:採用多線程的方式來完成垃圾收集;適合於吞吐量要求較高的場合,比較適合中等和大規模的應用程序。
吞吐量收集器使用並行版本的新生代垃圾收集器,它用於中等規模和大規模數據的應用程序。而串行收集器對大多數的小應用(在現代處理器上須要大概100M左右的內存)就足夠了。

 

 

JVM的永久代中會發生垃圾回收嗎

垃圾回收不會發生在永久代,若是永久代滿了或者是超過了臨界值,會觸發徹底垃圾回收(Full GC)。若是你仔細查看垃圾收集器的輸出信息,就會發現永久代也是被回收的。這就是爲何正確的永久代大小對避免Full GC是很是重要的緣由。

 

標記清除、標記整理、複製算法的原理與特色?分別用在什麼地方

1:標記清除:直接將要回收的對象標記,發送gc的時候直接回收:特色回收特別快,可是回收之後會形成不少不連續的內存空間,所以適合在老年代進行回收,CMS(current mark-sweep),就是採用這種方法來會後老年代的。
2:標記整理:就是將要回收的對象移動到一端,而後再進行回收,特色:回收之後的空間連續,缺點:整理要花必定的時間,適合老年代進行會後,parallel Old(針對parallel scanvange gc的) gc和Serial old就是採用該算法進行回收的。
3:複製算法:將內存劃分紅原始的是相等的兩部分,每次只使用一部分,這部分用完了,就將還存活的對象複製到另外一塊內存,將要回收的內存所有清除。這樣只要進行少許的賦值就可以完成收集。比較適合不少對象的回收,同時還有老年代對其進行擔保。(serial new和parallel new和parallel scanvage)

 

說說你知道的幾種主要的jvm 參數

-Xms3550m:設置JVM促使內存爲3550m。此值能夠設置與-Xmx相同,以免每次垃圾回收完成後JVM從新分配內存。
-Xmx3550m:設置JVM最大可用內存爲3550M
-Xss128k: 設置每一個線程的堆棧大小。JDK5.0之後每一個線程堆棧大小爲1M,之前每一個線程堆棧大小爲256K。
-XX:MaxPermSize=16m :設置持久代大小爲16m
-XX:NewRatio=4 :設置年輕代(包括Eden和兩個Survivor區)與年老代的比值(除去持久代)。設置爲4,則年輕代與年老代所佔比值爲1:4,年輕代佔整個堆棧的1/5
-XX:SurvivorRatio=4 :設置年輕代中Eden區與Survivor區的大小比值。設置爲4,則兩個Survivor區與一個Eden區的比值爲2:4,一個Survivor區佔整個年輕代的1/6
-XX:+UseParallelOldGC :配置年老代垃圾收集方式爲並行收集。JDK6.0支持對年老代並行收集。

 

Java 類加載器都有哪些

1)Bootstrap ClassLoader
負責加載$JAVA_HOME中jre/lib/rt.jar裏全部的class,由C++實現,不是ClassLoader子類
2)Extension ClassLoader
負責加載java平臺中擴展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目錄下的jar包
3)App ClassLoader
負責記載classpath中指定的jar包及目錄中class
4)Custom ClassLoader
屬於應用程序根據自身須要自定義的ClassLoader,如tomcat、jboss都會根據j2ee規範自行實現ClassLoader
加載過程當中會先檢查類是否被已加載,檢查順序是自底向上,從Custom ClassLoader到BootStrap ClassLoader逐層檢查,只要某個classloader已加載就視爲已加載此類,保證此類只全部ClassLoader加載一次。而加載的順序是自頂向下,也就是由上層來逐層嘗試加載此類。

 

JVM如何加載字節碼文件

JVM主要完成三件事:
1、經過一個類的全限定名(包名與類名)來獲取定義此類的二進制字節流(Class文件)。而獲取的方式,能夠經過jar包、war包、網絡中獲取、JSP文件生成等方式。
2、將這個字節流所表明的靜態存儲結構轉化爲方法區的運行時數據結構。這裏只是轉化了數據結構,並未合併數據。(方法區就是用來存放已被加載的類信息,常量,靜態變量,編譯後的代碼的運行時內存區域)
3、在內存中生成一個表明這個類的java.lang.Class對象,做爲方法區這個類的各類數據的訪問入口。這個Class對象並無規定是在Java堆內存中,它比較特殊,雖爲對象,但存放在方法區中。

 

JVM內存分哪幾個區,每一個區的做用是什麼

1、程序計數器(Program Counter Register)
       程序計數器是一個比較小的內存區域,用於指示當前線程所執行的字節碼執行到了第幾行,能夠理解爲是當前線程的行號指示器。字節碼解釋器在工做時,會經過改變這個計數器的值來取下一條語句指令。
  每一個程序計數器只用來記錄一個線程的行號,因此它是線程私有(一個線程就有一個程序計數器)的。
  若是程序執行的是一個Java方法,則計數器記錄的是正在執行的虛擬機字節碼指令地址;若是正在執行的是一個本地(native,由C語言編寫完成)方法,則計數器的值爲Undefined,因爲程序計數器只是記錄當前指令地址,因此不存在內存溢出的狀況,所以,程序計數器也是全部JVM內存區域中惟一一個沒有定義OutOfMemoryError的區域。
2、虛擬機棧(JVM Stack)
       一個線程的每一個方法在執行的同時,都會建立一個棧幀(Statck Frame),棧幀中存儲的有局部變量表、操做站、動態連接、方法出口等,當方法被調用時,棧幀在JVM棧中入棧,當方法執行完成時,棧幀出棧。
  局部變量表中存儲着方法的相關局部變量,包括各類基本數據類型,對象的引用,返回地址等。在局部變量表中,只有long和double類型會佔用2個局部變量空間(Slot,對於32位機器,一個Slot就是32個bit),其它都是1個Slot。須要注意的是,局部變量表是在編譯時就已經肯定好的,方法運行所須要分配的空間在棧幀中是徹底肯定的,在方法的生命週期內都不會改變。
  虛擬機棧中定義了兩種異常,若是線程調用的棧深度大於虛擬機容許的最大深度,則拋出StatckOverFlowError(棧溢出);不過多數Java虛擬機都容許動態擴展虛擬機棧的大小(有少部分是固定長度的),因此線程能夠一直申請棧,直到內存不足,此時,會拋出OutOfMemoryError(內存溢出)。
  每一個線程對應着一個虛擬機棧,所以虛擬機棧也是線程私有的。
3、本地方法棧(Native Method Statck)
       本地方法棧在做用,運行機制,異常類型等方面都與虛擬機棧相同,惟一的區別是:虛擬機棧是執行Java方法的,而本地方法棧是用來執行native方法的,在不少虛擬機中(如Sun的JDK默認的HotSpot虛擬機),會將本地方法棧與虛擬機棧放在一塊兒使用。
  本地方法棧也是線程私有的。
4、堆區(Heap)
       堆區是理解Java GC機制最重要的區域,沒有之一。在JVM所管理的內存中,堆區是最大的一塊,堆區也是Java GC機制所管理的主要內存區域,堆區由全部線程共享,在虛擬機啓動時建立。堆區的存在是爲了存儲對象實例,原則上講,全部的對象都在堆區上分配內存(不過現代技術裏,也不是這麼絕對的,也有棧上直接分配的)。
  通常的,根據Java虛擬機規範規定,堆內存須要在邏輯上是連續的(在物理上不須要),在實現時,能夠是固定大小的,也能夠是可擴展的,目前主流的虛擬機都是可擴展的。若是在執行垃圾回收以後,仍沒有足夠的內存分配,也不能再擴展,將會拋出OutOfMemoryError:Java heap space異常。
5、方法區(Method Area)
       在Java虛擬機規範中,將方法區做爲堆的一個邏輯部分來對待,但事實上,方法區並非堆(Non-Heap);另外,很多人的博客中,將Java GC的分代收集機制分爲3個代:青年代,老年代,永久代,這些做者將方法區定義爲「永久代」,這是由於,對於以前的HotSpot Java虛擬機的實現方式中,將分代收集的思想擴展到了方法區,並將方法區設計成了永久代。不過,除HotSpot以外的多數虛擬機,並不將方法區當作永久代,HotSpot自己,也計劃取消永久代。本文中,因爲筆者主要使用Oracle JDK6.0,所以仍將使用永久代一詞。
  方法區是各個線程共享的區域,用於存儲已經被虛擬機加載的類信息(即加載類時須要加載的信息,包括版本、field、方法、接口等信息)、final常量、靜態變量、編譯器即時編譯的代碼等。
  方法區在物理上也不須要是連續的,能夠選擇固定大小或可擴展大小,而且方法區比堆還多了一個限制:能夠選擇是否執行垃圾收集。通常的,方法區上執行的垃圾收集是不多的,這也是方法區被稱爲永久代的緣由之一(HotSpot),但這也不表明着在方法區上徹底沒有垃圾收集,其上的垃圾收集主要是針對常量池的內存回收和對已加載類的卸載。
  在方法區上進行垃圾收集,條件苛刻並且至關困難,效果也不使人滿意,因此通常不作太多考慮,能夠留做之後進一步深刻研究時使用。
  在方法區上定義了OutOfMemoryError:PermGen space異常,在內存不足時拋出。
  運行時常量池(Runtime Constant Pool)是方法區的一部分,用於存儲編譯期就生成的字面常量、符號引用、翻譯出來的直接引用(符號引用就是編碼是用字符串表示某個變量、接口的位置,直接引用就是根據符號引用翻譯出來的地址,將在類連接階段完成翻譯);運行時常量池除了存儲編譯期常量外,也能夠存儲在運行時間產生的常量(好比String類的intern()方法,做用是String維護了一個常量池,若是調用的字符「abc」已經在常量池中,則返回池中的字符串地址,不然,新建一個常量加入池中,並返回地址)。

 

解釋內存中的棧(stack)、堆(heap)和方法區(method area)的用法

堆區: 
   1.存儲的所有是對象,每一個對象都包含一個與之對應的class的信息。(class的目的是獲得操做指令) 
   2.jvm只有一個堆區(heap)被全部線程共享,堆中不存放基本類型和對象引用,只存放對象自己 
棧區: 
   1.每一個線程包含一個棧區,棧中只保存基礎數據類型的對象和自定義對象的引用(不是對象),對象都存放在堆區中 
   2.每一個棧中的數據(原始類型和對象引用)都是私有的,其餘棧不能訪問。 
   3.棧分爲3個部分:基本類型變量區、執行環境上下文、操做指令區(存放操做指令)。 
方法區: 
    1.又叫靜態區,跟堆同樣,被全部的線程共享。方法區包含全部的class和static變量。 
    2.方法區中包含的都是在整個程序中永遠惟一的元素,如class,static變量。

 

簡述內存分配與回收策略

1)對象優先分配到新生代的Eden區
(2)大對象直接進入老年代【JVM中提供了一個-XX:PretenureSizeThreshold參數(這個參數只對Serial和ParNew這兩個新生代垃圾收集器有效),令大於這個參數的對象直接在老年代中分配】
(3)長期存活的對象將進入老年代【每熬過一次GC,年齡+1,當這個值到達一個閥值(默認15,可經過-XX:MaxTenuringThreshold來設置)時,這個對象就會被移到老年代中】
(4)動態對象年齡判斷【JVM也不是要去一個對象必須達到MaxTenuringThreshold設置的年齡閥值才能進入老年代,若是Survivor中的對象知足同年齡(好比N)對象所佔空間達到了Survivor總空間的一半的時候,那麼年齡大於或者等於N的對象均可以進入老年代,無需等待閥值】
(5)空間分配擔保【新生代採用複製算法,可是會形成空間的浪費,故而提出了一種「空間擔保機制」來提升複製算法的空間利用率,使複製算法的浪費從50%降到了10%。而老年代的內存就充當了這個擔保者,而且因爲沒有其餘內存來擔保老年代,因此老年代若是不想產生空間內存碎片那麼只能使用「標記-整理」算法了】

 

簡述重排序,內存屏障,happen-before,主內存,工做內存

重排序:大多數現代微處理器都會採用將指令亂序執行(out-of-order execution,簡稱OoOE或OOE)的方法,在條件容許的狀況下,直接運行當前有能力當即執行的後續指令,避開獲取下一條指令所需數據時形成的等待。經過亂序執行的技術,處理器能夠大大提升執行效率。
內存屏障:內存屏障(Memory Barrier,或有時叫作內存柵欄,Memory Fence)是一種CPU指令,用於控制特定條件下的重排序和內存可見性問題。Java編譯器也會根據內存屏障的規則禁止重排序。
happen—before(是一個原則):
Java內存模型中定義的兩項操做之間的偏序關係,若是操做A先行發生於操做B,其意思就是說,在發生操做B以前,操做A產生的影響都能被操做B觀察到,「影響」包括修改了內存中共享變量的值、發送了消息、調用了方法等,它與時間上的前後發生基本沒有太大關係。這個原則特別重要,它是判斷數據是否存在競爭、線程是否安全的主要依據。

 

簡述 主內存 和 工做內存

JVM將內存組織爲主內存和工做內存兩個部分。
主內存主要包括本地方法區和堆。
每一個線程都有一個工做內存,工做內存中主要包括兩個部分,一個是屬於該線程私有的棧和對主存部分變量拷貝的寄存器(包括程序計數器PC和cup工做的高速緩存區)。  
1.全部的變量都存儲在主內存中(虛擬機內存的一部分),對於全部線程都是共享的。
2.每條線程都有本身的工做內存,工做內存中保存的是主存中某些變量的拷貝,線程對變量的全部操做都必須在工做內存中進行,而不能直接讀寫主內存中的變量。
3.線程之間沒法直接訪問對方的工做內存中的變量,線程間變量的傳遞均須要經過主內存來完成。

 

Java中存在內存泄漏問題嗎?請舉例說明

通常來說,內存泄漏主要有兩種狀況:
一是在堆中申請了空間沒有被釋放;
二是對象已再也不被使用,但還仍然在內存中保留着。
垃圾回收機制的引入能夠有效地解決第一種狀況;而對於第二種狀況,垃圾回收機制則沒法保證再也不使用的對象會被釋放。所以Java語言中的內存泄漏主要指的第二種狀況。
Java語言中,容易引發內存泄漏的緣由有不少,主要有如下幾個方面的內容:
(1)靜態集合類,例如HashMap和Vector。若是這些容器爲靜態的,因爲它們的聲明週期與程序一致,那麼容器中的對象在程序結束以前將不能被釋放,從而形成內存泄漏。
(2)各類鏈接,例如數據庫的鏈接、網絡鏈接以及IO鏈接等。
(3)監聽器。在Java語言中,每每會使用到監聽器。一般一個應用中會用到多個監聽器,但在釋放對象的同時每每沒有相應的刪除監聽器,這也可能致使內存泄漏。
(4)變量不合理的做用域。通常而言,若是一個變量定義的做用域大於其使用範圍,頗有可能會形成內存泄漏,另外一方面若是沒有及時地把對象設置爲Null,頗有可能會致使內存泄漏的放生
(5)單例模式可能會形成內存泄漏

 

簡述 Java 中軟引用(SoftReferenc)、弱引用(WeakReference)和虛引用

強引用(StrongReference):強引用是使用最廣泛的引用。若是一個對象具備強引用,那垃圾回收器毫不會回收它
軟引用(SoftReference):若是一個對象只具備軟引用,則內存空間足夠,垃圾回收器就不會回收它;若是內存空間不足了,就會回收這些對象的內存。只要垃圾回收器沒有回收它,該對象就能夠被程序使用。軟引用可用來實現內存敏感的高速緩存。 
弱引用(WeakReference):只具備弱引用的對象比軟引用的擁有更短暫的生命週期。在垃圾回收器線程掃描它所管轄的內存區域的過程當中,一旦發現了只具備弱引用的對象,無論當前內存空間足夠與否,都會回收它的內存。不過,因爲垃圾回收器是一個優先級很低的線程,所以不必定會很快發現那些只具備弱引用的對象。
虛引用(PhantomReference):形同虛設的引用,與其餘幾種引用都不一樣,虛引用並不會決定對象的生命週期。若是一個對象僅持有虛引用,那麼它就和沒有任何引用同樣,在任什麼時候候均可能被垃圾回收器回收。
虛引用必須和引用隊列 (ReferenceQueue)聯合使用。當垃圾回收器準備回收一個對象時,若是發現它還有虛引用,就會在回收對象的內存以前,把這個虛引用加入到與之 關聯的引用隊列中。

 

jstack,jstat,jmap,jconsole怎麼用

jstat 用於監控基於HotSpot的JVM,對其堆的使用狀況進行實時的命令行的統計【類的加載及卸載狀況,新生代、老生代及持久代的容量及使用狀況,垃圾收集狀況 等】
    jstat -gcutil pid 用於查看新生代、老生代及持代垃圾收集的狀況
    jstat -gc pid 查看指定進程的gc狀況
    jstat -class pid  顯示加載class的數量,及所佔空間等信息
jstack 主要用來查看某個Java進程內的線程堆棧信息,在發生死鎖時能夠用jstack -l pid來觀察鎖持有狀況
jmap 用來查看堆內存使用情況,通常結合jhat使用
    使用jmap -heap pid查看進程堆內存使用狀況,包括使用的GC算法、堆配置參數和各代中堆內存使用狀況
    jmap -histo pid 打印每一個class的實例數目,內存佔用,類全名信息
    jmap -dump:format=b,file=myObj.log pid 使用hprof二進制形式,輸出jvm的heap(堆)內容到文件
jconsole 一個java GUI監視工具,能夠以圖表化的形式顯示各類數據。

 

32 位 JVM 和 64 位 JVM 的最大堆內存分別是多數?32 位和 64 位的 JVM,int 類型變量的長度是多數?

理論上說上 32 位的 JVM 堆內存能夠到達 2^32,即 4GB,但實際上會比這個小不少,不一樣操做系統之間不一樣。
64 位 JVM容許指定最大的堆內存,理論上能夠達到 2^64,這是一個很是大的數字,實際上你能夠指定堆內存大小到 100GB。
32 位和 64 位的 JVM 中,int 類型變量的長度是相同的,都是 32 位或者 4 個字節。

 

怎樣經過 Java 程序來判斷 JVM 是 32 位 仍是 64 位

System.out.println("jdk的版本爲:" + System.getProperty("sun.arch.data.model") ); 

 

什麼狀況下會發生棧內存溢出

與線程棧相關的內存異常有兩個:
a)StackOverflowError(棧溢出,方法調用層次太深,內存不夠新建棧幀)
b)OutOfMemoryError(線程太多,內存不夠新建線程)

 

雙親委派模型是什麼

雙親委派模型工做過程是:若是一個類加載器收到類加載的請求,它首先不會本身去嘗試加載這個類,而是把這個請求委派給父類加載器完成。每一個類加載器都是如此,只有當父加載器在本身的搜索範圍內找不到指定的類時(即ClassNotFoundException),子加載器纔會嘗試本身去加載。
其中,類加載器包括:啓動類加載器(Bootstrap ClassLoader)、擴展類加載器(Extension ClassLoader)、應用程序類加載器(Application ClassLoader)

 

爲何須要雙親委派模型呢?

試想一個場景:黑客自定義一個java.lang.String類,該String類具備系統的String類同樣的功能,只是在某個函數稍做修改。好比equals函數,這個函數常用,若是在這這個函數中,黑客加入一些「病毒代碼」。而且經過自定義類加載器加入到JVM中。此時,若是沒有雙親委派模型,那麼JVM就可能誤覺得黑客自定義的java.lang.String類是系統的String類,致使「病毒代碼」被執行。
而有了雙親委派模型,黑客自定義的java.lang.String類永遠都不會被加載進內存。由於首先是最頂端的類加載器加載系統的java.lang.String類,最終自定義的類加載器沒法加載java.lang.String類。

 

如何實現雙親委派模型?

每次經過先委託父類加載器加載,當父類加載器沒法加載時,再本身加載。其實ClassLoader類默認的loadClass方法已經幫咱們寫好了,咱們無需去寫。
咱們能夠自定義咱們的類加載器,只須要 extends ClassLoader,覆蓋 findClass 方法便可
咱們能夠在代碼裏面查看使用的是哪一個類加載器:System.out.println("恩,是的,我是由 " + getClass().getClassLoader().getClass() + " 加載進來的");
其餘代碼:
MyClassLoader classLoader = new MyClassLoader("D:/test"); // 入參是 classPath 
Class clazz = classLoader.loadClass("com.huachao.cl.Test");
Object obj = clazz.newInstance();
Method helloMethod = clazz.getDeclaredMethod("hello", null);

 

如何打破雙親委派模型?

若是不想打破雙親委派模型,就重寫 ClassLoader 類中的 findClass() 方法便可,沒法被父類加載器加載的類最終會經過這個方法被加載。
而若是想打破雙親委派模型則須要重寫 ClassLoader 類中的 loadClass() 方法(固然其中的坑也不會少)。
典型打破雙親委派的例子:
tomcat 爲了實現隔離性(一個tomcat下面可能部署了多個項目,多個項目之間若是有同包同名的class,須要各個項目單獨隔離加載),故 WebappClassLoader.loadClass() 加載本身的目錄下的class文件,不會傳遞給父類加載器
補充:tomcat 的 WebappClassLoader 是 各個Webapp私有的類加載器,加載每一個項目下的 WEB-INFO/lib 和  WEB-INFO/class 路徑下的 class

 

數據庫事務隔離級別

讀未提交:能夠讀到其餘事務未提交的內容,因此會有髒讀、不可重複讀、幻讀 存在。
讀已提交:只能讀到已經提交了的內容,避免了髒讀,可是不可重複讀、幻讀 依然存在,原理是使用的快照讀。
可重複讀:專門針對「不可重複讀」這種狀況而制定的隔離級別(仍是有幻讀 <由insert、delete產生> 存在),MySql的默認隔離級別,原理是當事務啓動時不容許進行update(但能夠 delete、insert)。
串行化:事務「串行化順序執行」,也就是一個一個排隊執行。髒讀、不可重複讀、幻讀 均可避免,可是效率奇差。

 

多線程的幾種實現方式

繼承Thread類建立線程
實現Runnable接口建立線程
實現Callable接口經過FutureTask包裝器來建立Thread線程 【FutureTask是 Future接口和 Runnable 接口 的 實現類,是Future接口的一個惟一實現類】
使用ExecutorService、Callable、Future實現有返回結果的線程
【Callable接口可看做Runnable接口的升級版,可是裏面的方法不是run(),而是call()】
【經過Future對象可瞭解任務執行狀況,可取消任務的執行,還可獲取任務執行的結果。】

 

什麼是線程安全

線程安全就是多線程訪問時,採用了加鎖機制,當一個線程訪問該類的某個數據時,進行保護,其餘線程不能進行訪問直到該線程讀取完,其餘線程纔可以使用。不會出現數據不一致或者數據污染。 線程不安全就是不提供數據訪問保護,有可能出現多個線程前後更改數據形成所獲得的數據是髒數據。

 

哪些集合類是線程安全的

Vector、Statck、HashTable、Enumeration
StringBuffer是線程安全,而StringBuilder是線程不安全的。

 

多線程中的忙循環是什麼

忙循環就是程序員用循環讓一個線程等待,不像傳統方法wait(), sleep() 或 yield() 它們都放棄了CPU控制,而忙循環不會放棄CPU,它就是在運行一個空循環。這麼作的目的是爲了保留CPU緩存,在多核系統中,一個等待線程醒來的時候可能會在另外一個內核運行,這樣會重建緩存。爲了不重建緩存和減小等待重建的時間就能夠使用它了。(好比自旋鎖)

 

什麼是線程局部變量

ThreadLocal線程局部變量 高效地爲每一個使用它的線程提供單獨的線程局部變量值的副本
ThreadLocalMap是ThreadLocal的一個內部類,不對外使用的。當使用ThreadLocal存值時,首先是獲取到當前線程對象,而後獲取到當前線程本地變量Map,最後將當前使用的ThreadLocal和傳入的值放到Map中,也就是說ThreadLocalMap中存的值是[ThreadLocal對象, 存放的值],這樣作的好處是,每一個線程都對應一個本地變量的Map,因此一個線程能夠存在多個線程本地變量。
ThreadLocal 經常使用方法是:void remove()、T get()、set(T) 方法
ThreadLocal 使用舉例:Hiberante的Session 工具類HibernateUtil、經過不一樣的線程對象設置Bean屬性,保證各個線程Bean對象的獨立性

 

ThreadLocal 原理?

ThreadLocal的實現是:每一個Thread 維護一個 ThreadLocalMap 映射表,這個映射表的 key 是 ThreadLocal實例自己,value 是真正須要存儲的 Object。
補充:ThreadLocalMap 是 ThreadLocal 的static 內部類。

 

談一談弱引用,有什麼實踐經驗?

弱引用(WeakReference):弱引用的對象比軟引用的擁有更短暫的生命週期,在垃圾回收器線程掃描它所管轄的內存區域的過程當中,一旦發現了只具備弱引用的對象,無論當前內存空間足夠與否,都會回收它的內存。不過,因爲垃圾回收器是一個優先級很低的線程,所以不必定會很快發現那些只具備弱引用的對象。
實踐經驗:
在現實狀況寫代碼的時候, 咱們每每經過把全部指向某個對象的 referece 置空來保證這個對象在下次GC運行的時候被回收【如:myObj = null; 這樣的語句】。
這樣作可讓GC自動回收這塊內存,可是一兩個變量沒問題,若是變量太多,豈不是不少麻煩的代碼?
另外,若是使用緩存對象的話,雖然把對象的 referece 置爲null,可是緩存中的引用是不會被GC的。
爲了解決上述兩個問題,咱們能夠直接使用弱引用數據類型(WeakReference、WeakHashMap),GC會回收以後沒有使用到的 WeakReference 對象,而存在 WeakHashMap 中的key,只要後面沒有使用到就會被GC清除回收內存。

 

ThreadLocal 中有使用弱引用嗎,ThreadLocal 會有內存泄露狀況嗎?

ThreadLocal 中有使用弱引用,ThreadLocal 使用 set 方法設置值的時候,最終存入Entry對象中,而Entry對象 繼承自 WeakReference。
ThreadLocal 會有內存泄露(參考 http://www.importnew.com/22039.html):
1、使用static的ThreadLocal,延長了ThreadLocal的生命週期,可能致使的內存泄漏;
2、分配使用了ThreadLocal又再也不調用get、set、remove()方法,那麼就會致使內存泄漏【get(),set(),remove()方法發現key爲空時,會進行空間回收】

 

ThreadLocalMap 爲何 要 使用 弱引用做爲key?

ThreadLocalMap 是使用ThreadLocal的弱引用做爲key的。
因爲ThreadLocalMap的生命週期跟Thread同樣長,若是都沒有手動刪除對應key,都會致使內存泄漏,可是使用弱引用能夠多一層保障:弱引用ThreadLocal不會內存泄漏,對應的value在下一次ThreadLocalMap調用set,get,remove的時候會被清除。【補充,若是使用 ThreadLocal 存大對象,而且尚未及時remove,會形成內存溢出】

 

ThreadLocal 最佳實踐

ThreadLocal內存泄漏的根源是:因爲ThreadLocalMap的生命週期跟Thread同樣長,若是沒有手動刪除對應key就會致使內存泄漏,而不是由於弱引用。
每次使用完ThreadLocal,都調用它的remove()方法,清除數據。
在使用線程池的狀況下,沒有及時清理ThreadLocal,不只是內存泄漏的問題,更嚴重的是可能致使業務邏輯出現問題。因此,使用ThreadLocal就跟加鎖完要解鎖同樣,用完就清理。

 

線程和進程有什麼區別?進程間如何通信

進程是資源分配的最小單位,線程是程序執行的最小單位。
進程有本身的獨立地址空間,每啓動一個進程,系統就會爲它分配地址空間,創建數據表來維護代碼段、堆棧段和數據段,這種操做很是昂貴。而線程是共享進程中的數據的,使用相同的地址空間,所以CPU切換一個線程的花費遠比進程要小不少,同時建立一個線程的開銷也比進程要小不少。
線程之間的通訊更方便,同一進程下的線程共享全局變量、靜態變量等數據,而進程之間的通訊須要以通訊的方式(IPC)進行。
傳統的進程間通訊的方式有大體以下幾種: (
1) 管道(PIPE) (2) 命名管道(FIFO) (3) 信號量(Semphore) (4) 消息隊列(MessageQueue) (5) 共享內存(SharedMemory) (6) Socket 咱們把Java進程理解爲JVM進程,傳統的這些大部分技術是沒法被咱們的應用程序利用了(這些進程間通訊都是靠系統調用來實現的)。可是Java也有不少方法能夠進行進程間通訊的, 除了上面提到的Socket以外,固然首選的IPC能夠使用RMI,固然還有RPC。

 

同步和異步有何異同,在什麼狀況下分別使用他們?舉例說明

若是數據將在線程間共享。例如正在寫的數據之後可能被另外一個線程讀到,或者正在讀的數據可能已經被另外一個線程寫過了,那麼這些數據就是共享數據,必須進行同步存取。
當應用程序在對象上調用了一個須要花費很長時間來執行的方法,而且不但願讓程序等待方法的返回時,就應該使用異步編程,在不少狀況下采用異步途徑每每更有效率。
Java中交互方式分爲同步和異步兩種:
同步交互:指發送一個請求,須要等待返回,而後纔可以發送下一個請求,有個等待過程;
異步交互:指發送一個請求,不須要等待返回,隨時能夠再發送下一個請求,即不須要等待。 
區別:一個須要等待,一個不須要等待,在部分狀況下,咱們的項目開發中都會優先選擇不須要等待的異步交互方式。
哪些狀況建議使用同步交互呢?好比銀行的轉帳系統,對數據庫的保存操做等等,都會使用同步交互操做,其他狀況都優先使用異步交互

 

 

ArrayBlockingQueue, CountDownLatch的用法

二者都是 java.util.concurrent 包下的類
ArrayBlockingQueue:一個由數組支持的有界阻塞隊列。它的本質是一個基於數組的BlockingQueue的實現。
它的容納大小是固定的。此隊列按 FIFO(先進先出)原則對元素進行排序。
ArrayBlockingQueue 是線程安全的,緣由是 內部是經過 ReentrantLock 實現加鎖的
不接受 null 元素
插入數據:
(1)add(e)//隊列未滿時,返回true;隊列滿則拋出IllegalStateException2)offer(e)//隊列未滿時,返回true;隊列滿時返回false;還有另一個api:offer(e, time, unit)3)put(e)//隊列未滿時,直接插入沒有返回值;隊列滿時會阻塞等待,一直等到隊列未滿時再插入
隊列元素的刪除:
(1)remove()//隊列不爲空時,返回隊首值並移除;隊列爲空時拋出NoSuchElementException()2)poll()//隊列不爲空時返回隊首值並移除;隊列爲空時返回null。非阻塞當即返回,另外的api:poll(time, unit)3)take(e)//隊列不爲空返回隊首值並移除;當隊列爲空時會阻塞等待,一直等到隊列不爲空時再返回隊首值。
size() 返回隊列的長度
【
併發包中的其餘阻塞隊列:
ArrayBlockingQueue:一個由數組結構組成的有界阻塞隊列;
LinkedBlockingQueue:一個由鏈表結構組成的有界阻塞隊列,api 和 ArrayBlockingQueue 基本一致;
PriorityBlockingQueue:一個支持優先級排序的無界阻塞隊列。 
非阻塞隊列:
ConcurrentLinkedQueue:
入隊(插入)使用 add(e),或者 offer(e)< 插入到隊列尾部,add 方法內部調用的實際上是offer >,入隊的操做都是由CAS算法完成
出隊(刪除)使用 poll(),移除隊首並返回元素,若是此隊列爲空,則返回 null。
那麼對於棧呢:java.util.Stack 繼承自 java.util.Vector,而 Vector 是List的子類
Stack 方法(除了List中的方法外的):push(e) 往棧中存入;peek() 獲取棧頂元素(不出棧);pop() 獲取棧頂元素且出棧(在peek 和 pop 時,若是沒有對象,則異常 EmptyStackException)
】
CountDownLatch:用給定的計數初始化CountDownLath。調用countDown()方法計數減 1,在計數被減到 0以前,調用await方法會一直阻塞。減爲 0以後,則會迅速釋放全部阻塞等待的線程,而且調用await操做會當即返回。

 

ConcurrentHashMap的併發度是什麼

ConcurrentHashMap的併發度就是segment的大小,默認爲16,這意味着最多同時能夠有16條線程操做ConcurrentHashMap

 

CyclicBarrier 和 CountDownLatch有什麼不一樣?各自的內部原理和用法是什麼

CountDownLatch
一個線程活多個線程,等待另一個線程或者多個線程完成某個事情以後才繼續執行。
減計數方式
計算爲0時釋放全部等待的線程
計數爲0時,沒法重置
調用countDown()方法計數減一,調用await()方法只進行阻塞,對計數沒任何影響    
不可重複利用
關鍵方法:await()、countDown()

CyclicBarrier
多個線程之間互相等待,任何一個線程完成以前,全部線程都必須等待。
加計數方式
計數達到指定值時釋放全部等待線程
計數達到指定值時,計數置爲0從新開始
調用await()方法計數加1,若加1後的值不等於構造方法的值,則線程阻塞
可重複利用
關鍵方法:await()、reset()

 

Semaphore的用法

Semaphore 是 synchronized 的增強版,做用是控制線程的併發數量。就這一點而言,單純的synchronized 關鍵字是實現不了的。
Semaphore 須要多個線程使用同一個 Semaphore實例。
關鍵方法:
semaphore.acquire(); // 申請進入
semaphore.release(); // 釋放
在 semaphore.acquire() 和 semaphore.release()之間的代碼,同一時刻只容許制定個數的線程進入【 制定個數 由初始化時做爲參數傳入 】
若是在 acquire 和 release 之間的代碼是一個比較慢和複製的運算,如內存佔用過多,或者棧深度很深等,jvm會中斷這塊代碼,並拋出異常 InterruptedException 
acquire() 方法的擴展:tryAcquire() 、 tryAcquire(int permits)、 tryAcquire(int permits , long timeout , TimeUint unit) 
參考:https://www.cnblogs.com/klbc/p/9500947.html

 

啓動一個線程是調用 run() 仍是 start() 方法?start() 和 run() 方法有什麼區別

啓動一個線程,應該調用的是start(),調用run()是在當前線程運行。
start()是真正的多線程,調用start()會新開一個線程,和main方法線程併發執行;調用run()則是在main方法中運行線程方法,並非多線程。

 

sleep() 方法和對象的 wait() 方法均可以讓線程暫停執行,它們有什麼區別

sleep()方法(休眠)是線程類(Thread)的靜態方法,調用此方法會讓當前線程暫停執行指定的時間,將執行機會(CPU)讓給其餘線程,可是對象的鎖依然保持,所以休眠時間結束後會自動恢復
wait()是Object類的方法,調用對象的wait()方法致使當前線程放棄對象的鎖(線程暫停執行),進入對象的等待池(wait pool),只有調用對象的notify()方法(或notifyAll()方法)時才能喚醒等待池中的線程進入等鎖池(lockpool),若是線程從新得到對象的鎖就能夠進入就緒狀態
關於wait和notify,能夠參考: https://www.cnblogs.com/YuyuanNo1/p/8004471.html
wait 和 notifyAll 能夠實現消費者生產者,使用sleep 則不行(參考上一行連接)。

 

notify() 和 notifyAll() 的區別是什麼?

以用 notify 和 notifyAll 來通知那些等待中的線程從新開始運行。不一樣之處在於,notify 僅僅通知一個線程,而且咱們不知道哪一個線程會收到通知,然而 notifyAll 會通知全部等待中的線程。
換言之,若是隻有一個線程在等待一個信號燈,notify和notifyAll都會通知到這個線程。但若是多個線程在等待這個信號燈,那麼notify只會通知到其中一個,而其它線程並不會收到任何通知,而notifyAll會喚醒全部等待中的線程。 參考: https:
//www.cnblogs.com/YuyuanNo1/p/8004471.html

 

 

yield方法有什麼做用?sleep() 方法和 yield() 方法有什麼區別

1sleep()方法給其餘線程運行機會時不考慮線程的優先級,所以會給低優先級的線程以運行的機會;yield()方法只會給相同優先級或更高優先級的線程以運行的機會;
2)線程執行sleep()方法後轉入阻塞(blocked)狀態,而執行yield()方法後轉入就緒(ready)狀態;
3sleep()方法聲明拋出InterruptedException,而yield()方法沒有聲明任何異常; 
4sleep()方法比yield()方法(跟操做系統CPU調度相關)具備更好的可移植性.

 

Java 中如何中止一個線程

使用退出標誌,使線程正常退出,也就是當run方法完成後線程終止。
使用stop方法強行終止,可是不推薦這個方法,由於stop和suspend及resume同樣都是過時做廢的方法。
使用interrupt方法中斷線程。
【補充:
棄用stop方法,緣由是stop方法是直接kill線程,破壞了線程的原子性;
interrupt()並不會終止處於「運行狀態」的線程!它只會將線程的中斷標記設爲true。因此,必須配合 isInterrupted() 方法使用,在run()內部中止線程。
如:
        // main 方法中調用:t.interrupt();
        public void run() {
            int i = 0;
            while (!Thread.currentThread().isInterrupted()) {
                System.out.println("thread is runing -- " + (++i));
            }
            System.out.println("thread is Interrupted");
        }
】

 

stop() 和 suspend() 方法爲什麼不推薦使用

stop():不安全,它會解除由線程獲取的全部鎖定,當在一個線程對象上調用stop()方法時,這個線程對象所運行的線程就會當即中止,會破壞線程的原子性;
suspend():容易發生死鎖。調用suspend()的時候,目標線程會停下來,但卻仍然持有在這以前得到的鎖定。此時,其餘任何線程都不能訪問鎖定的資源,除非被"掛起"的線程恢復運行。

 

join() 方法總結

t.join() 必須在 t.start() 運行以後,纔有意義(兩者調換順序執行也是沒有意義的)。
在A線程中調用了B線程的join()方法時,表示A線程讓出cpu給B,直到 B運行完了以後,A線程才能繼續執行。
join() 能夠用來實現線程之間的同步。
其實,join方法是經過調用線程的wait方法來達到同步的目的的。

 

什麼是線程組,爲何在Java中不推薦使用

ThreadGroup線程組表示一個線程的集合。此外,線程組也能夠包含其餘線程組。線程組構成一棵樹,在樹中,除了初始線程組外,每一個線程組都有一個父線程組。
容許線程訪問有關本身的線程組的信息,可是不容許它訪問有關其線程組的父線程組或其餘任何線程組的信息。線程組的目的就是對線程進行管理。
爲何不推薦使用
1.線程組ThreadGroup對象中比較有用的方法是stop、resume、suspend等方法,因爲這幾個方法會致使線程的安全問題(主要是死鎖問題),已經被官方廢棄掉了,因此線程組自己的應用價值就大打折扣了。
2.線程組ThreadGroup不是線程安全的,這在使用過程當中獲取的信息並不全是及時有效的,這就下降了它的統計使用價值。

 

你是如何調用 wait(方法的)?使用 if 塊仍是循環?爲何

wait() 方法應該在循環調用,由於當線程獲取到 CPU 開始執行的時候,其餘條件可能尚未知足,因此在處理前,循環檢測條件是否知足會更好
能夠參考 Thread類 的 join 方法 的源碼

public final synchronized void join(long millis)
throws InterruptedException {
  long base = System.currentTimeMillis();
  long now = 0;

  if (millis < 0) {
    throw new IllegalArgumentException("timeout value is negative");
  }
  
  if (millis == 0) {
    while (isAlive()) {
      wait(0);
    }
  } else {
    while (isAlive()) {
      long delay = millis - now;
      if (delay <= 0) {
        break;
      }
      wait(delay);
      now = System.currentTimeMillis() - base;
    }
  }
}

 

線程池是什麼?爲何要使用它

下降資源消耗、提高響應速度、提升線程的可管理性

 

如何建立一個Java線程池

能夠經過 java.util.concurrent.ThreadPoolExecutor 來建立一個線程池:
new  ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, milliseconds, runnableTaskQueue, threadFactory, handler);
參數說明:
1、corePoolSize(線程池的基本大小):當提交一個任務到線程池時,線程池會建立一個線程來執行任務,即便其餘空閒的基本線程可以執行新任務也會建立線程,等到須要執行的任務數大於線程池基本大小時就再也不建立。若是調用了線程池的prestartAllCoreThreads方法,線程池會提早建立並啓動全部基本線程。
2、maximumPoolSize(線程池最大數量):線程池容許建立的最大線程數。若是隊列滿了,而且已建立的線程數小於最大線程數,則線程池會再建立新的線程執行任務。值得注意的是,若是使用了無界的任務隊列這個參數就沒用了。
3、keepAliveTime(線程活動時間):線程池的工做線程空閒後,保持存活的時間。因此若是任務不少,而且每一個任務執行的時間比較短,能夠調大時間,提升線程利用率。
4、TimeUnit(線程活動時間的單位):可選的單位有天(Days)、小時(HOURS)、分鐘(MINUTES)、毫秒(MILLISECONDS)、微秒(MICROSECONDS,千分之一毫秒)和納秒(NANOSECONDS,千分之一微秒)
5、runnableTaskQueue(任務隊列):用於保存等待執行的任務的阻塞隊列。 能夠選擇如下幾個阻塞隊列:
(1)ArrayBlockingQueue:是一個基於數組結構的有界阻塞隊列,FIFO(先進先出)。
(2)LinkedBlockingQueue:一個基於鏈表結構的阻塞隊列,此隊列按FIFO (先進先出) 排序元素,吞吐量一般要高於ArrayBlockingQueue,靜態工廠方法Executors.newFixedThreadPool()使用了這個隊列。
(3)SynchronousQueue:一個不存儲元素的阻塞隊列。每一個插入操做必須等到另外一個線程調用移除操做,不然插入操做一直處於阻塞狀態,吞吐量一般要高於LinkedBlockingQueue,靜態工廠方法Executors.newCachedThreadPool()使用了這個隊列。
(4)PriorityBlockingQueue:一個具備優先級的無限阻塞隊列。
6、threadFactory:用於設置建立線程的工廠,能夠經過線程工廠給每一個建立出來的線程設置更有意義的名字。
7、RejectedExecutionHandler (飽和策略):當隊列和線程池都滿了,說明線程池處於飽和狀態,那麼必須採起一種策略還處理新提交的任務。它能夠有以下四個選項:
AbortPolicy:直接拋出異常,默認狀況下采用這種策略
CallerRunsPolicy:只用調用者所在線程來運行任務
DiscardOldestPolicy:丟棄隊列裏最近的一個任務,並執行當前任務
DiscardPolicy:不處理,丟棄掉

 

corePoolSize  和 maximumPoolSize 的區別是啥?

關於 ThreadPoolExecutor 相關的任務線程,它包含兩部分:正在線程池中運行的任務線程、在taskQueue 中排隊等待運行的任務線程。
1、當線程池初始化完成以後,executorService.submit(new Thread(...)); 加入須要運行的任務線程,由於線程池初始化是沒有線程運行的,因此當提交一個任務到線程池時,線程池會建立一個線程來執行任務;
2、當線程池中正在運行的線程達到 corePoolSize 個時,線程會放到 taskQueue 中排隊等候;
3、當 taskQueue(阻塞隊列)的容量達到上限(即隊列中不能再加入任務線程了),而當前的poolSize(就是正在線程池中運行的任務線程個數)小於 maximumPoolSize 時,則新增線程來處理任務;
4、當 taskQueue 的容量達到上限,且 poolSize = maximumPoolSize,那麼線程池已經達到極限,會根據飽和策略RejectedExecutionHandler拒絕新的任務。
像極了小時候作客的這個場景:
小朋友作客,桌上放了一桌的土豆條,小朋友有一個碗(taskQueue),碗裏能夠放10根土豆條(taskQueue容量 = 10),正常狀況下,小朋友每次能夠吃2根土豆條(嘴巴就是線程池,corePoolSize = 2),小朋友狼吞虎嚥的吃每次能夠吃4根土豆條(maximumPoolSize = 4)。
坐在餐桌邊後,小朋友開始吃土豆,先往嘴裏放了2根土豆條,但怕其餘小朋友和本身搶,而後趕忙往本身的碗里加土豆條(加入隊列),直到碗滿了(如今小朋友嘴裏2個土豆條,碗裏10個土豆條)。小朋友依然懼怕由於本身動做慢比其餘人少吃,因此小朋友開始狼吞虎嚥地吃(嘴裏4個土豆條,碗裏10個土豆條)。

 

newCache 和 newFixed 有什麼區別?簡述原理。構造函數的各個參數的含義是什麼,好比 coreSize, maxsize 等

newSingleThreadExecutor 返回以個包含單線程的Executor,將多個任務交給此Exector時,這個線程處理完一個任務後接着處理下一個任務,若該線程出現異常,將會有一個新的線程來替代。
newFixedThreadPool 返回一個包含指定數目線程的線程池,若是任務數量多於線程數目,那麼沒有沒有執行的任務必須等待,直到有任務完成爲止。
newCachedThreadPool 根據用戶的任務數建立相應的線程來處理,該線程池不會對線程數目加以限制,徹底依賴於JVM能建立線程的數量,可能引發內存不足。 
底層是基於ThreadPoolExecutor實現,藉助reentrantlock保證併發。 
coreSize核心線程數,maxsize最大線程數。

 

調用線程池的 shutdown 或 shutdownNow 方法來關閉線程池,兩者區別

void shutdown() 
    中止接收外部submit的任務
    內部正在跑的任務和隊列裏等待的任務,會執行完
List<Runnable> shutdownNow()
    先中止接收外部提交的任務
    忽略隊列裏等待的任務
    嘗試將正在跑的任務interrupt中斷
    返回未執行的任務列表
關閉功能 【從強到弱】 依次是:shuntdownNow() > shutdown() > awaitTermination()

 

爲何吞吐量 LinkedBlockingQueue 大於 ArrayBlockingQueue?

ArrayBlockingQueue 實現的隊列中的鎖是沒有分離的,即添加操做和移除操做採用的同一個ReenterLock鎖,而LinkedBlockingQueue實現的隊列中的鎖是分離的,其添加採用的是putLock,移除採用的則是takeLock,這樣能大大提升隊列的吞吐量

 

線程池的關閉方式有幾種,各自的區別是什麼

見上上個問題

 

ArrayBlockingQueue 和 LinkedBlockingQueue的區別?

1.隊列中鎖的實現不一樣
     ArrayBlockingQueue實現的隊列中的鎖是沒有分離的,即生產和消費用的是同一個鎖;
     LinkedBlockingQueue實現的隊列中的鎖是分離的,即生產用的是putLock,消費是takeLock;
2.在生產或消費時操做不一樣
     ArrayBlockingQueue基於數組,在生產和消費的時候,是直接將枚舉對象插入或移除的,不會產生或銷燬任何額外的對象實例;
     LinkedBlockingQueue基於鏈表,在生產和消費的時候,須要把枚舉對象轉換爲Node<E>進行插入或移除,會生成一個額外的Node對象,這在長時間內須要高效併發地處理大批量數據的系統中,其對於GC的影響仍是存在必定的區別;
3.隊列大小初始化方式不一樣
     ArrayBlockingQueue是有界的,必須指定隊列的大小;
     LinkedBlockingQueue是無界的,能夠不指定隊列的大小,可是默認是Integer.MAX_VALUE。固然也能夠指定隊列大小,從而成爲有界的;
注意:
1.在使用LinkedBlockingQueue時,若用默認大小且當生產速度大於消費速度時候,有可能會內存溢出;
2.在使用ArrayBlockingQueue和LinkedBlockingQueue分別對1000000個簡單字符作入隊操做時,
       LinkedBlockingQueue的消耗是ArrayBlockingQueue消耗的10倍左右,
       即LinkedBlockingQueue消耗在1500ms左右,而ArrayBlockingQueue只需150ms左右。
性能測試:不限容量的LinkedBlockingQueue的吞吐量 > ArrayBlockingQueue > 限定容量的LinkedBlockingQueue

 

ConcurrentLinkedQueue 和 LinkedBlockingQueue的區別

Java提供的線程安全的Queue能夠分爲阻塞隊列和非阻塞隊列,其中阻塞隊列的典型例子是BlockingQueue,非阻塞隊列的典型例子是ConcurrentLinkedQueue,在實際應用中要根據實際須要選用阻塞隊列或者非阻塞隊列。
ConcurrentLinkedQueue 是非阻塞隊列的表明,沒有鎖,使用CAS實現,有 add(e)、offer(e)、remove()、poll()和peek(),size() 是要遍歷一遍集合的,速很慢,因此判空時,儘可能要避免用size(),而改用isEmpty()。
LinkedBlockingQueue 是阻塞隊列,內部則是基於鎖,增長了 put(e)、take() 兩個阻塞方法。
LinkedBlockingQueue 多用於任務隊列,ConcurrentLinkedQueue 多用於消息隊列
單生產者,單消費者  用 LinkedBlockingqueue
多生產者,單消費者   用 LinkedBlockingqueue
單生產者 ,多消費者   用 ConcurrentLinkedQueue
多生產者 ,多消費者   用 ConcurrentLinkedQueue
【
補充:
java.util.concurrent包提供的容器(Queue、List、Set),Map,從名字上能夠大概區分爲Concurrent,CopyOnWrite,Blocking*等三類,一樣是線程安全容器,能夠簡單認爲:
1、Concurrent類型沒有CopyOnWrite之類容器相對較重的修改開銷;
2、可是,Concurrent提供了較低的遍歷一致性,與弱一致性相對應的,就是同步容器常見的行爲「fast-fail」,也就是檢測到容器在遍歷過程當中發生了修改,則拋出「ConcurrentModificationException」,再也不繼續遍歷;
3、弱一致性另一個體現就是size等操做準確性是有限的,未必是100%正確;

 

CAS 和 AQS 簡述

CAS(Compare And Swap),即比較並交換。
CAS操做包含三個操做數——內存位置(V)、預期原值(A)和新值(B)
若是內存位置的值與預期原值相匹配,那麼處理器會自動將該位置值更新爲新值。不然,處理器不作任何操做。
CAS 的實現:AtomicInteger、AtomicBoolean、AtomicLong,裏面的方法:getAndSet(newVal)【設置新值】、getAndIncrement()【自增】
AQS是JDK下提供的一套用於實現基於FIFO等待隊列的阻塞鎖和相關的同步器的一個同步框架。
AQS 使用了一個原子的int status來做爲同步器的狀態(如:獨佔鎖,1表明已佔有,0表明未佔有),經過該類提供的原子修改status方法(getState setState and compareAnsSetState),咱們能夠把它做爲同步器的基礎框架類來實現各類同步器。
AQS 的實現:CountDownLatch、CyclicBarrier、Semaphore(synchronized 的增強版,做用是控制線程的併發數量)、ReentrantLock(重入鎖)、ReentrantReadWriteLock(讀寫鎖)

 

線程池中submit() 和 execute()方法有什麼區別?

execute(Runnable x) 沒有返回值。能夠執行任務,但沒法判斷任務是否成功完成。
submit(Runnable x) 返回一個future。能夠用這個future來判斷任務是否成功完成。

 

 

什麼是多線程中的上下文切換

即便是單核CPU也支持多線程執行代碼,CPU經過給每一個線程分配CPU時間片來實現這個機制。時間片是CPU分配給各個線程的時間,由於時間片很是短,因此CPU經過不停地切換線程執行,讓咱們感受多個線程時同時執行的,時間片通常是幾十毫秒(ms)。
CPU經過時間片分配算法來循環執行任務,當前任務執行一個時間片後會切換到下一個任務。可是,在切換前會保存上一個任務的狀態,以便下次切換回這個任務時,能夠再次加載這個任務的狀態,從任務保存到再加載的過程就是一次上下文切換。
這就像咱們同時讀兩本書,當咱們在讀一本英文的技術書籍時,發現某個單詞不認識,因而便打開中英文詞典,可是在放下英文書籍以前,大腦必須先記住這本書讀到了多少頁的第多少行,等查完單詞以後,可以繼續讀這本書。這樣的切換是會影響讀書效率的,一樣上下文切換也會影響多線程的執行速度。

 

你對線程優先級的理解是什麼

現代操做系統基本採用時分的形式調度運行的線程,線程分配獲得的時間片的多少決定了線程使用處理器資源的多少,也對應了線程優先級這個概念。在JAVA線程中,經過一個int priority來控制優先級,範圍爲1-10,其中10最高,默認值爲5
線程優先級有繼承性,若是主線程啓動threadA線程且threadA線程沒有另外賦予優先級,則threadA線程優先級和main線程同樣。優先級與執行順序無關
CPU儘可能將執行資源讓給線程優先級高的,即線程優先級高的老是會大部分先執行,可是不表明高優先級的線程所有都先執行完再執行低優先級的線程
優先級具備隨機性,優先級高的不必定每次都先執行

 

什麼是線程調度器 (Thread Scheduler) 和時間分片 (Time Slicing)

線程調度器是一個操做系統服務,它負責爲Runnable狀態的線程分配CPU時間。一旦咱們建立一個線程並啓動它,它的執行便依賴於線程調度器的實現。時間分片是指將可用的CPU時間分配給可用的Runnable線程的過程。
分配CPU時間能夠基於線程優先級或者線程等待的時間。線程調度並不受到Java虛擬機控制,因此由應用程序來控制它是更好的選擇(也就是說不要讓你的程序依賴於線程的優先級)。

 

mysql 聯合索引詳解

聯合索引又叫複合索引。對於複合索引:Mysql從左到右的使用索引中的字段,一個查詢能夠只使用索引中的一部份,但只能是最左側部分。例如索引是key index (a,b,c)。 能夠支持a | a,b| a,b,c 3種組合進行查找,但不支持 b,c進行查找 .當最左側字段是常量引用時,索引就十分有效。
如 test 表中的複合索引(a,b,c) 查詢優劣以下:
優: select * from test where a=10 and b>50
差: select * from test order by b
優: select * from test where a=10 order by b
優: select * from test where a=10 and b=10 order by c
優: select * from test where a=10 and b>10 order by b
差: select * from test where a=10 and b>10 order by c

 

請說出你所知的線程同步的方法

1、同步方法,synchronized關鍵字修飾的方法
2、同步代碼塊,有synchronized關鍵字修飾的語句塊
3、wait與notify
4、使用特殊域變量(volatile)實現線程同步
5、使用重入鎖實現線程同步(ReentrantLock)
6、使用局部變量實現線程同步(使用ThreadLocal管理變量,則每個使用該變量的線程都得到該變量的副本,副本之間相互獨立,這樣每個線程均可以隨意修改本身的變量副本,而不會對其餘線程產生影響)
7、使用阻塞隊列(如 LinkedBlockingQueue)實現線程同步

 

synchronized 的原理是什麼

synchronized 的語義底層是經過一個monitor的對象來完成,其實wait/notify等方法也依賴於monitor對象,這就是爲何只有在同步的塊或者方法中才能調用wait/notify等方法,不然會拋出java.lang.IllegalMonitorStateException的異常的緣由
每一個對象有一個監視器鎖(monitor)。當monitor被佔用時就會處於鎖定狀態,線程執行monitorenter指令時嘗試獲取monitor的全部權,過程以下:
1、若是monitor的進入數爲0,則該線程進入monitor,而後將進入數設置爲1,該線程即爲monitor的全部者。
2、若是線程已經佔有該monitor,只是從新進入,則進入monitor的進入數加1.
3、若是其餘線程已經佔用了monitor,則該線程進入阻塞狀態,直到monitor的進入數爲0,再從新嘗試獲取monitor的全部權。


synchronized 和 ReentrantLock 有什麼不一樣

1)ReentrantLock 擁有Synchronized相同的併發性和內存語義,此外還多了鎖投票,定時鎖等候和中斷鎖等候
(2)synchronized是在JVM層面上實現的,不但能夠經過一些監控工具監控synchronized的鎖定,並且在代碼執行時出現異常,JVM會自動釋放鎖定,可是使用Lock則不行,lock是經過代碼實現的,要保證鎖定必定會被釋放,就必須將unLock()放到finally{}中
(3)在資源競爭不是很激烈的狀況下,Synchronized的性能要優於ReetrantLock,可是在資源競爭很激烈的狀況下,Synchronized的性能會降低幾十倍,可是ReetrantLock的性能能維持常態

 

什麼場景下能夠使用 volatile 替換 synchronized

volatile 能夠保證 對象的可見性 和 程序的順序性,沒法保證操做的原子性。
volatile適用於新值不依賴於舊值的情形,好比:1寫N讀、單線程修改變量或不依賴當前值,且不與其餘變量構成不變性條件時候使用

 

有T1,T2,T3三個線程,怎麼確保它們按順序執行?怎樣保證T2在T1執行完後執行,T3在T2執行完後執行

使用join方法。三個線程同時start,在T3的run中,調用T2.join,讓T2執行完成後再執行T3,在T2的run中,調用T1.join,讓T1執行完成後再讓T2執行

 

當一個線程進入一個對象的 synchronized 方法A 以後,其它線程是否可進入此對象的 synchronized 方法B

若是方法A內部調用了wait,則能夠進入其餘synchronized方法;
若是方法A內部沒有調用wait,則不能進入其餘synchronized方法;
若是其餘方法是static,它用的同步鎖是當前類的字節碼,與非靜態的方法不能同步,由於非靜態的方法用的是this

 

使用 synchronized 修飾靜態方法和非靜態方法有什麼區別

非靜態方法是獲取對象鎖(如this)
靜態方法是獲取類鎖 (如:Demo.class)

 

如何從給定集合那裏建立一個 synchronized 的集合

咱們能夠使用 Collections.synchronizedCollection(Collection c)根據指定集合來獲取一個synchronized(線程安全的)集合。
或者:
List list = Collections.synchronizedList(new ArrayList());
Set set = Collections.synchronizedSet(new HashSet());
Map map = Collections.synchronizedMap(new HashMap());

 

ReadWriteLock是什麼?

實現類ReentrantReadWriteLock,可重入的讀寫鎖,容許多個讀線程得到ReadLock,但只容許一個寫線程得到WriteLock
讀寫鎖的機制:
   "讀-讀" 不互斥
   "讀-寫" 互斥
   "寫-寫" 互斥

 

解釋如下名詞:重排序,自旋鎖,偏向鎖,輕量級鎖,可重入鎖,公平鎖,非公平鎖,樂觀鎖,悲觀鎖

指令重排序
是JVM爲了優化指令,提升程序運行效率,在不影響單線程程序執行結果的前提下,儘量地提升並行度。編譯器、處理器也遵循這樣一個目標。注意是單線程。多線程的狀況下指令重排序就會給程序員帶來問題。
volatile關鍵字經過提供「內存屏障」的方式來防止指令被重排序,爲了實現volatile的內存語義,編譯器在生成字節碼時,會在指令序列中插入內存屏障來禁止特定類型的處理器重排序。
自旋鎖
自旋鎖能夠使線程在沒有取得鎖的時候,不被掛起,而轉去執行一個空循環,(即所謂的自旋,就是本身執行空循環),若在若干個空循環後,線程若是能夠得到鎖,則繼續執行。若線程依然不能得到鎖,纔會被掛起。
使用自旋鎖後,線程被掛起的概率相對減小,線程執行的連貫性相對增強。所以,對於那些鎖競爭不是很激烈,鎖佔用時間很短的併發線程,具備必定的積極意義,但對於鎖競爭激烈,單線程鎖佔用很長時間的併發程序,自旋鎖在自旋等待後,每每毅然沒法得到對應的鎖,不只僅白白浪費了CPU時間,最終仍是免不了被掛起的操做 ,反而浪費了系統的資源。
在JDK1.6中,Java虛擬機提供-XX:+UseSpinning參數來開啓自旋鎖,使用-XX:PreBlockSpin參數來設置自旋鎖等待的次數。
在JDK1.7開始,自旋鎖的參數被取消,虛擬機再也不支持由用戶配置自旋鎖,自旋鎖老是會執行,自旋鎖次數也由虛擬機自動調整。
偏向鎖 
Java6引入的一項多線程優化,偏向鎖是爲了在無多線程競爭的狀況下儘可能減小沒必要要的輕量級鎖執行路徑,由於輕量級鎖的獲取及釋放依賴屢次CAS原子指令,而偏向鎖只須要在置換ThreadID的時候依賴一次CAS原子指令(因爲一旦出現多線程競爭的狀況就必須撤銷偏向鎖,因此偏向鎖的撤銷操做的性能損耗必須小於節省下來的CAS原子指令的性能消耗)。「偏向」的意思是,偏向鎖假定未來只有第一個申請鎖的線程會使用鎖(不會有任何線程再來申請鎖)。
輕量級鎖
「輕量級」是相對於使用操做系統互斥量來實現的傳統鎖而言的。可是,首先須要強調一點的是,輕量級鎖並非用來代替重量級鎖的,它的本意是在沒有多線程競爭的前提下,減小傳統的重量級鎖使用產生的性能消耗。在解釋輕量級鎖的執行過程以前,先明白一點,輕量級鎖所適應的場景是線程交替執行同步塊的狀況,若是存在同一時間訪問同一鎖的狀況,就會致使輕量級鎖膨脹爲重量級鎖。
可重入鎖
可重入鎖,也叫作遞歸鎖,指的是同一線程 外層函數得到鎖以後 ,內層遞歸函數仍然有獲取該鎖的代碼,但不受影響。
在JAVA環境下 ReentrantLock 和synchronized 都是 可重入鎖
公平鎖
就是很公平,在併發環境中,每一個線程在獲取鎖時會先查看此鎖維護的等待隊列,若是爲空,或者當前線程線程是等待隊列的第一個,就佔有鎖,不然就會加入到等待隊列中,之後會按照FIFO的規則從隊列中取到本身
非公平鎖
比較粗魯,上來就直接嘗試佔有鎖,若是嘗試失敗,就再採用相似公平鎖那種方式。
樂觀鎖
樂觀鎖是一種樂觀思想,即認爲讀多寫少,遇到併發寫的可能性低,每次去拿數據的時候都認爲別人不會修改,因此不會上鎖,可是在更新的時候會判斷一下在此期間別人有沒有去更新這個數據,採起在寫時先讀出當前版本號,而後加鎖操做(比較跟上一次的版本號,若是同樣則更新),若是失敗則要重複讀-比較-寫的操做。
java中的樂觀鎖基本都是經過CAS操做實現的,CAS是一種更新的原子操做,比較當前值跟傳入值是否同樣,同樣則更新,不然失敗。
悲觀鎖
悲觀鎖是就是悲觀思想,即認爲寫多,遇到併發寫的可能性高,每次去拿數據的時候都認爲別人會修改,因此每次在讀寫數據的時候都會上鎖,這樣別人想讀寫這個數據就會block直到拿到鎖。java中的悲觀鎖就是Synchronized,AQS框架下的鎖則是先嚐試cas樂觀鎖去獲取鎖,獲取不到,纔會轉換爲悲觀鎖,如RetreenLock。

 

爲何要使用消息隊列?

緣由有3個:
解耦:傳統的方式是多個系統之間調用,多個系統之間耦合關係太強,每當某個系統有修改後(或者新系統接入),其餘系統也須要對應進行修改,而使用消息隊列則能夠下降耦合性
異步:傳統系統中,一些非必須的業務邏輯也使用同步進行操做,致使運行時間過長;使用消息隊列,把這些邏輯放入隊列處理,能夠較快地得到返回
削峯:成千上萬個用戶訪問系統時,對數據庫的壓力會比較大,使用中間件,能夠根據數據庫的處理能力,從消息隊列中取數據,從而避免了數據庫掛掉
參考:http://www.cnblogs.com/williamjie/p/9481780.html

 

使用了消息隊列會有什麼缺點?

1、系統可用性下降:以前是兩個系統之間直接調用,如今加了一箇中間件,至關於多了一個步驟,並且萬一中間件掛了,那麼系統運行就存在問題了。
2、系統複雜性增長:加入中間件後,系統須要考慮的東西增長,如何保證消息的一致性、如何保證消息不被重複消費、如何保證消息的可靠傳輸。

 

消息隊列如何選型?

基本比較:
ActiveMQ:開發語言java,單機吞吐量萬級,時效性ms級,可用性高(主從架構)
RabbitMQ:開發語言erlang,單機吞吐量萬級,時效性us級,可用性高(主從架構)
RocketMQ:開發語言java,單機吞吐量10萬級,時效性ms級,可用性很是高(分佈式架構)
kafka:開發語言scala,單機吞吐量10萬級,時效性ms級之內,可用性很是高(分佈式架構)
功能特性比較:
ActiveMQ:成熟的產品,在不少公司獲得應用;有較多的文檔;各類協議支持較好
RabbitMQ:基於erlang開發,因此併發能力很強,性能極其好,延時很低;管理界面較豐富
RocketMQ:MQ功能比較完備,擴展性佳
kafka:只支持主要的MQ功能,像一些消息查詢,消息回溯等功能沒有提供,畢竟是爲大數據準備的,在大數據領域應用廣。
結論:
(1)中小型軟件公司,建議選RabbitMQ.一方面,erlang語言天生具有高併發的特性,並且他的管理界面用起來十分方便。正所謂,成也蕭何,敗也蕭何!他的弊端也在這裏,雖然RabbitMQ是開源的,然而國內有幾個能定製化開發erlang的程序員呢?所幸,RabbitMQ的社區十分活躍,能夠解決開發過程當中遇到的bug,這點對於中小型公司來講十分重要。不考慮rocketmq和kafka的緣由是,一方面中小型軟件公司不如互聯網公司,數據量沒那麼大,選消息中間件,應首選功能比較完備的,因此kafka排除。不考慮rocketmq的緣由是,rocketmq是阿里出品,若是阿里放棄維護rocketmq,中小型公司通常抽不出人來進行rocketmq的定製化開發,所以不推薦。
(2)大型軟件公司,根據具體使用在rocketMq和kafka之間二選一。一方面,大型軟件公司,具有足夠的資金搭建分佈式環境,也具有足夠大的數據量。針對rocketMQ,大型軟件公司也能夠抽出人手對rocketMQ進行定製化開發,畢竟國內有能力改JAVA源碼的人,仍是至關多的。至於kafka,根據業務場景選擇,若是有日誌採集功能,確定是首選kafka了。具體該選哪一個,看使用場景。
我的以爲中小型企業,也能夠選擇ActiveMQ的,由於ActiveMQ是一個比較成熟的產品(最老的MQ),功能也很齊全,惟一不足的就是版本更新太慢(官方維護少)。

 

如何保證消息消費的順序性?

若是存在多個消費者,那麼就讓每一個消費者對應一個queue,而後把要發送 的數據全都放到一個queue,這樣就能保證全部的數據只到達一個消費者從而保證每一個數據到達數據庫都是順序的。
拆分多個queue,每一個queue一個consumer,就是多一些queue而已,確實是麻煩點;或者就一個queue可是對應一個consumer,而後這個consumer內部用內存隊列作排隊,而後分發給底層不一樣的worker來處理
參考: https://juejin.im/post/5c9b1c155188251d806727b2

 

MQ積壓幾百萬條數據怎麼辦?

通常這個時候,只能操做臨時緊急擴容了,具體操做步驟和思路以下:
1
、先修復consumer的問題,確保其恢復消費速度,而後將現有consumer都停掉 2、新建一個topic,partition是原來的10倍,臨時創建好原先10倍或者20倍的queue數量 3、而後寫一個臨時的分發數據的consumer程序,這個程序部署上去消費積壓的數據,消費以後不作耗時的處理,直接均勻輪詢寫入臨時創建好的10倍數量的queue 4、接着臨時徵用10倍的機器來部署consumer,每一批consumer消費一個臨時queue的數據 5、這種作法至關因而臨時將queue資源和consumer資源擴大10倍,以正常的10倍速度來消費數據 6、等快速消費完積壓數據以後,得恢復原先部署架構,從新用原先的consumer機器來消費消息

 

如何保證消息不被重複消費?

形成重複消費的緣由?,就是由於網絡傳輸等等故障,確認信息沒有傳送到消息隊列,致使消息隊列不知道本身已經消費過該消息了,再次將該消息分發給其餘的消費者。
如何解決?這個問題針對業務場景來答分如下幾點
  (1)好比,你拿到這個消息作數據庫的insert操做。那就容易了,給這個消息作一個惟一主鍵,那麼就算出現重複消費的狀況,就會致使主鍵衝突,避免數據庫出現髒數據。
  (2)再好比,你拿到這個消息作redis的set的操做,那就容易了,不用解決,由於你不管set幾回結果都是同樣的,set操做原本就算冪等操做。
  (3)若是上面兩種狀況還不行,上大招。準備一個第三方介質,來作消費記錄。以redis爲例,給消息分配一個全局id,只要消費過該消息,將<id,message>以K-V形式寫入redis。那消費者開始消費前,先去redis中查詢有沒消費記錄便可。

 

如何保證消費的可靠性傳輸?

可靠性傳輸,每種MQ都要從三個角度來分析:生產者弄丟數據、消息隊列弄丟數據、消費者弄丟數據
對於RabbitMQ:
(1)生產者丟數據:RabbitMQ提供 transaction 和 confirm模式 來確保生產者不丟消息
     transaction 機制就是說,發送消息前,開啓事物(channel.txSelect()),而後發送消息,若是發送過程當中出現什麼異常,事物就會回滾(channel.txRollback()),若是發送成功則提交事物(channel.txCommit())。缺點就是吞吐量降低了。
     生產上用 confirm模式的居多。一旦channel進入confirm模式,全部在該信道上面發佈的消息都將會被指派一個惟一的ID(從1開始),一旦消息被投遞到全部匹配的隊列以後,rabbitMQ就會發送一個Ack給生產者(包含消息的惟一ID),這就使得生產者知道消息已經正確到達目的隊列了.若是rabiitMQ沒能處理該消息,則會發送一個Nack消息給你,你能夠進行重試操做。
(2)處理消息隊列丟數據的狀況,通常是開啓持久化磁盤的配置。這個持久化配置能夠和confirm機制配合使用,你能夠在消息持久化磁盤後,再給生產者發送一個Ack信號。這樣,若是消息持久化磁盤以前,rabbitMQ陣亡了,那麼生產者收不到Ack信號,生產者會自動重發。
     那麼如何持久化呢,就下面兩步:1、將queue的持久化標識durable設置爲true,則表明是一個持久的隊列;2、發送消息的時候將deliveryMode=23)消費者丟數據:消費者丟數據通常是由於採用了自動確認消息模式。這種模式下,消費者會自動確認收到信息。這時rahbitMQ會當即將消息刪除,這種狀況下若是消費者出現異常而沒能處理該消息,就會丟失該消息。至於解決方案,採用手動確認消息便可。
對於kafka:
Producer在發佈消息到某個Partition時,先經過ZooKeeper找到該Partition的Leader,而後不管該Topic的Replication Factor爲多少(也即該Partition有多少個Replication),Producer只將該消息發送到該Partition的Leader。Leader會將該消息寫入其本地Log。每一個Follower都從Leader中pull數據。
(1)生產者丟數據:在kafka生產中,基本都有一個leader和多個follwer。follwer會去同步leader的信息。所以,爲了不生產者丟數據,作以下兩點配置:
                   1)在producer端設置acks=all。這個配置保證了,follwer同步完成後,才認爲消息發送成功。 2)在producer端設置retries=MAX,一旦寫入失敗,這無限重試
(2)消息隊列弄丟數據:針對消息隊列丟數據的狀況,無外乎就是,數據還沒同步,leader就掛了,這時zookpeer會將其餘的follwer切換爲leader,那數據就丟失了。針對這種狀況,應該作兩個配置。
                   1)replication.factor參數,這個值必須大於1,即要求每一個partition必須有至少2個副本
                   2)min.insync.replicas參數,這個值必須大於1,這個是要求一個leader至少感知到有至少一個follower還跟本身保持聯繫
                   這兩個配置加上上面生產者的配置聯合起來用,基本可確保kafka不丟數據
(3)消費者丟數據:這種狀況通常是自動提交了offset,而後你處理程序過程當中掛了。kafka覺得你處理好了。解決方案也很簡單,改爲手動提交便可。
                   (補充:offset:指的是kafka的topic中的每一個消費組消費的下標。簡單的來講就是一條消息對應一個offset下標,每次消費數據的時候若是提交offset,那麼下次消費就會從提交的offset加一那裏開始消費。好比一個topic中有100條數據,我消費了50條而且提交了,那麼此時的kafka服務端記錄提交的offset就是49(offset從0開始),那麼下次消費的時候offset就從50開始消費。)
對於ActiveMQ:
(1)生產者丟數據:能夠使用同步發送消息:org.apache.activemq.ActiveMQConnectionFactory中引入: <property name="useAsyncSend" value="true" />  <!-- false:同步 true:異步 -->2)消息隊列弄丟數據:對於Queue而言,支持 storeCursor(消息存到storeEngine中)、vmQueueCursor(消息存於內存)、fileQueueCursor(消息存到臨時文件中)。其中storeCursor是一個「綜合」策略,持久化消息使用fileQueueCurosr支持,非持久化消息使用vmQueueCursor支持。
                       對應的 activemq.xml 下的配置是:pendingQueuePolicy(PendingQueueMessageStoragePolicy : 待消息轉存策略)
(3)消費者丟數據:使用消息事務(吞吐量不高,去掉),或者消息應答模式(acknowledge model)改成客戶端手動確認
                   確認機制(ack_mod):
                   Session.AUTO_ACKNOWLEDGE 自動確認;Session.CLIENT_ACKNOWLEDGE 客戶端手動確認;Session.DUPS_OK_ACKNOWLEDGE 自動批量確認;Session.SESSION_TRANSACTED 事務提交併確認

 

簡述鎖的等級方法鎖、對象鎖、類鎖

方法鎖(synchronized修飾方法時):每一個類實例對應一把鎖,每一個 synchronized 方法都必須得到調用該方法的類實例的鎖方能執行,不然所屬線程阻塞
對象鎖(synchronized修飾方法或代碼塊):當一個對象中有synchronized 方法 或 synchronized block 的時候調用此對象的同步方法或進入其同步區域時,就必須先得到對象鎖
類鎖(synchronized修飾靜態的方法或代碼塊):因爲一個class不論被實例化多少次,其中的靜態方法和靜態變量在內存中都只有一份。故一旦一個靜態的方法被申明爲synchronized,則此類全部的實例化對象在調用此方法,共用同一把鎖,咱們稱之爲類鎖
我的理解:對象鎖 是 方法鎖的父集

 

Java中活鎖和死鎖有什麼區別?

死鎖:是指兩個或兩個以上的進程(或線程)在執行過程當中,因爭奪資源而形成的一種互相等待的現象,若無外力做用,它們都將沒法推動下去。
死鎖發生的四個條件:
1、互斥條件:線程對資源的訪問是排他性的,若是一個線程對佔用了某資源,那麼其餘線程必須處於等待狀態,直到資源被釋放。
2、請求和保持條件:線程T1至少已經保持了一個資源R1佔用,但又提出對另外一個資源R2請求,而此時,資源R2被其餘線程T2佔用,因而該線程T1也必須等待,但又對本身保持的資源R1不釋放。
3、不剝奪條件:線程已得到的資源,在未使用完以前,不能被其餘線程剝奪,只能在使用完之後由本身釋放。
4、環路等待條件:在死鎖發生時,必然存在一個「進程-資源環形鏈」,進程p0(或線程)等待p1佔用的資源,p1等待p2佔用的資源,pn等待p0佔用的資源。
活鎖:是指線程1能夠使用資源,但它很禮貌,讓其餘線程先使用資源,線程2也能夠使用資源,但它很紳士,也讓其餘線程先使用資源。這樣你讓我,我讓你,最後兩個線程都沒法使用資源。
補充:
飢餓:(低優先級)線程一直得不到運行
死鎖進程等待永遠不會被釋放的資源,餓死進程等待會被釋放但卻不會分配給本身的資源
死鎖必定涉及多個進程,而飢餓或被餓死的進程可能只有一個。在飢餓的情形下,系統中有至少一個進程能正常運行,只是飢餓進程得不到執行機會

 

怎麼檢測一個線程是否擁有鎖

java.lang.Thread中有一個方法叫holdsLock(),它返回true若是當且僅當當前線程擁有某個具體對象的鎖

 

Executors類是什麼? Executor和Executors的區別

Executor 是接口對象,執行咱們的線程任務,實現類是ThreadPoolExecutor ;
Executors 是工具類,不一樣方法按照咱們的需求建立了不一樣的線程池,來知足業務的需求。

 

有哪些無鎖數據結構,他們實現的原理是什麼

無鎖數據結構的實現主要基於兩個方面:原子性操做和內存訪問控制方法
java 1.5提供了一種無鎖隊列(wait-free/lock-free)ConcurrentLinkedQueue,可支持多個生產者多個消費者線程的環境
ConcurrentLinkedQueue 是一個基於連接節點的無界線程安全隊列,它採用先進先出的規則對節點進行排序,它採用了 CAS 算法來實現

 

如何在Java中獲取線程堆棧

Java虛擬機提供了線程轉儲(thread dump)的後門,經過這個後門能夠把線程堆棧打印出來。
jdk自帶的打印線程堆棧的工具:jstack
示例:jstack –l 23561 >> xxx.dump

 

說出 3 條在 Java 中使用線程的最佳實踐

1)給你的線程起個有意義的名字,這樣能夠方便找bug或追蹤。
    如:OrderProcessor, QuoteProcessor or TradeProcessor這種名字比Thread-1. Thread-2 and Thread-3好多了。
    主要框架甚至JDK都遵循這個最佳實踐。
(2)最低限度的使用同步和鎖,縮小臨界區。
    所以相對於同步方法我更喜歡同步塊,它給我擁有對鎖的絕對控制權。
(3)多用同步類少用wait和notify。CountDownLatch, Semaphore, CyclicBarrier和Exchanger這些同步類簡化了編碼操做,而用wait和notify很難實現對複雜控制流的控制。
(4)多用併發集合少用同步集合。
    併發集合比同步集合的可擴展性更好,因此在併發編程時使用併發集合效果更好。若是下一次你須要用到map,你應該首先想到用ConcurrentHashMap。

 

在線程中你怎麼處理不可捕捉異常

爲了保證主線程不被阻塞,線程之間基本相互隔離,因此線程之間不管是異常仍是通訊都不共享。固然,由於你抓異常是主線程,而異常是在子線程出現,能夠用thread.setUncaughtExceptionHandler()去處理線程的異常。

 

請說出與線程同步以及線程調度相關的方法

wait():使一個線程處於等待(阻塞)狀態,而且釋放所持有的對象的鎖;
sleep():使一個正在運行的線程處於睡眠狀態,是一個靜態方法,調用此方法要處理InterruptedException異常; 
notify():喚醒一個處於等待狀態的線程,固然在調用此方法的時候,並不能確切的喚醒某一個等待狀態的線程,而是由JVM肯定喚醒哪一個線程,並且與優先級無關;
notityAll():喚醒全部處於等待狀態的線程,該方法並非將對象的鎖給全部線程,而是讓它們競爭,只有得到鎖的線程才能進入就緒狀態;
補充:Java 5經過Lock接口提供了顯式的鎖機制(explicit lock),加強了靈活性以及對線程的協調。Lock接口中定義了加鎖(lock())和解鎖(unlock())的方法,同時還提供了newCondition()方法來產生用於線程之間通訊的Condition對象;
此外,Java 5還提供了信號量機制(semaphore),信號量能夠用來限制對某個共享資源進行訪問的線程的數量。在對資源進行訪問以前,線程必須獲得信號量的許可(調用Semaphore對象的acquire()方法);
在完成對資源的訪問後,線程必須向信號量歸還許可(調用Semaphore對象的release()方法)。

 

如何確保 main() 方法所在的線程是 Java 程序最後結束的線程

咱們能夠使用Thread類的join()方法來確保全部程序建立的線程在main()方法退出前結束。

 

Error 和 Exception有什麼區別

Error類通常是指與虛擬機相關的問題,如系統崩潰,虛擬機錯誤,內存空間不足,方法調用棧溢出等。如java.lang.StackOverFlowError和Java.lang.OutOfMemoryError。
對於這類錯誤,Java編譯器不去檢查他們。對於這類錯誤的致使的應用程序中斷,僅靠程序自己沒法恢復和預防,遇到這樣的錯誤,建議讓程序終止。 Exception類表示程序能夠處理的異常,能夠捕獲且可能恢復。遇到這類異常,應該儘量處理異常,使程序恢復運行,而不該該隨意終止異常。

 

UnsupportedOperationException是什麼

java.lang.UnsupportedOperationException是不支持功能異常。
經常出如今使用Arrays.asList()後調用add,remove這些方法時。
緣由是:
Arrays.asList() 返回java.util.Arrays$ArrayList, 而不是ArrayList。Arrays$ArrayList和ArrayList都是繼承AbstractList,remove,add等 method在AbstractList中是默認throw UnsupportedOperationException並且不做任何操做。
ArrayList override這些方法來對list進行操做,可是Arrays$ArrayList沒有override remove(
int),add(int)等,因此throw UnsupportedOperationException。 解決上面的問題只須要把list再放進java.util.ArrayList中就好了,List list2=new ArrayList(list),而後就能夠用lists來作各類操做了。 參考: https://blog.csdn.net/dwmul/article/details/84258273

 

什麼是受檢查的異常,什麼是運行時異常

Java提供了兩類主要的異常 :RuntimeException 和 checked exception。
RuntimeException 就是運行時異常,咱們能夠不處理,當出現這樣的異常時,老是由虛擬機接管。好比:咱們歷來沒有人去處理過 NullPointerException 異常,它就是運行時異常。
checked異常也就是咱們常常遇到的IO異常,以及SQL異常都是這種異常。對於這種異常,JAVA編譯器強制要求咱們必需對出現的這些異常進行catch。

 

運行時異常與通常異常有何異同

運行時異常表示虛擬機的一般操做中可能遇到的異常,是一種常見運行錯誤。java 編譯器要求方法必須聲明拋出可能發生的非運行時異常,可是並不要求必須聲明拋出未被捕獲的運行時異常。

 

簡述一個你最多見到的runtime exception(運行時異常)

當試圖將對象強制轉換爲不是實例的子類時,拋出ClassCastException
一個整數「除以零」時,拋出ArithmeticException異常
當應用程序試圖在須要對象的地方使用 null 時,拋出NullPointerException異常
指示索引或者爲負,或者超出字符串的大小,拋出StringIndexOutOfBoundsException異常
若是應用程序試圖建立大小爲負的數組,則拋出NegativeArraySizeException異常。

 

若是執行finally代碼塊以前方法返回告終果,或者JVM退出了,finally塊中的代碼還會執行嗎

若是在 try塊 以前就方法返回,則finally塊中代碼不會執行;若是是在 try塊 中方法返回,finally裏的代碼會繼續執行;JVM退出(如 System.exit(0);),則finally塊中的代碼不會執行

 

throw 和 throws 有什麼區別?

throws 老是出如今一個函數頭中,用來標明該成員函數可能拋出的各類異常,但這不是編譯器強制的。若是方法拋出了異常那麼調用這個方法的時候就須要將這個異常處理。
throw 是用來拋出任意異常的,按照語法你能夠拋出任意 Throwable,throw能夠中斷程序運行,所以能夠用來代替return。

 

既然咱們能夠用RuntimeException來處理錯誤,那麼你認爲爲何Java中還存在檢查型異常

這是一個有爭議的問題,在回答該問題時你應當當心。
其中一個理由是,存在檢查型異常是一個設計上的決定,受到了諸如C++等比Java更早的編程語言設計經驗的影響。Java 確保了你可以優雅的對異常進行處理。

 

經過 JDBC 鏈接數據庫有哪幾種方式

DriverManager、DataSource子類、DBCP、c3p0

 

JDBC 中如何進行事務處理

Connection提供了事務處理的方法,經過調用setAutoCommit(false)能夠設置手動提交事務;當事務完成後用commit()顯式提交事務;若是在事務處理過程當中發生異常則經過rollback()進行事務回滾。
除此以外,從JDBC 3.0中還引入了Savepoint(保存點)的概念,容許經過代碼設置保存點並讓事務回滾到指定的保存點。

 

什麼是 JdbcTemplate

Spring使用模板方式封裝jdbc數據庫操做固定流程,並提供豐富callback回調接口功能,方便用戶自定義加工細節,更好模塊化jdbc操做,簡化傳統的JDBC操做的複雜和繁瑣過程。 
(1) execute方法:能夠用於執行任何SQL語句,通常用於執行DDL語句;
(2) update方法及batchUpdate方法:update方法用於執行新增、修改、刪除等語句;batchUpdate方法用於執行批處理相關語句;
(3) query方法及queryForXXX方法:用於執行查詢相關語句;
(4) call方法:用於執行存儲過程、函數相關語句。

 

什麼是 DAO 模塊

DAO(DataAccess Object)顧名思義是一個爲數據庫或其餘持久化機制提供了抽象接口的對象,在不暴露數據庫實現細節的前提下提供了各類數據操做。

 

列出 5 個應該遵循的 JDBC 最佳實踐

1. 使用PrearedStatement 來避免 SQL 異常(能夠防止SQL注入)
2. 使用ConnectionPool(鏈接池)
3. 禁用自動提交(事務)
4. 使用Batch Update,能夠減小數據庫數據傳輸的往返次數,從而提升性能
5. 使用變量綁定而不是字符串拼接
6. 儘可能使用標準的SQL語句,從而在某種程度上避免數據庫對SQL支持的差別

 

 

File類型中定義了什麼方法來建立一級目錄

java.io.File.mkdir():只能建立一級目錄,且父目錄必須存在,不然沒法成功建立一個目錄。
java.io.File.mkdirs():能夠建立多級目錄,父目錄不必定存在。

 

爲了提升讀寫性能,能夠採用什麼流

緩衝流

 

Java中有幾種類型的流

字節流,字符流。字節流繼承於InputStream OutputStream,字符流繼承於InputStreamReader OutputStreamWriter。在java.io包中還有許多其餘的流,主要是爲了提升性能和使用方便。

 

JDK 爲每種類型的流提供了一些抽象類以供繼承,分別是哪些類

字節流繼承於InputStream OutputStream,
字符流繼承於InputStreamReader OutputStreamWriter。

 

對文本文件操做用什麼I/O流

FileReader、FileWriter

 

對各類基本數據類型和String類型的讀寫,採用什麼流

DataInputStream、DataOutputStream

 

能指定字符編碼的 I/O 流類型是什麼

InputStreamReader、OutputStreamWriter

 

什麼是序列化?如何實現 Java 序列化及注意事項

序列化:將一個對象編碼成一個字節流,經過保存或傳輸這些字節流數據來達到數據持久化的目的; 
反序列化:將字節流轉換成一個對象;
實現方式:
1.對象實現了序列化接口Serializable
2.實現接口Externalizable
Externlizable接口繼承了java的序列化接口,並增長了兩個方法:
     void writeExternal(ObjectOutput out) throws IOException;
     void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
3.其它方式:
是把對象包裝成JSON字符串傳輸。好比使用Google的gson-2.2.2.jar 進行轉義    
採用谷歌的ProtoBuf
注意事項:
1、若是子類實現Serializable接口而父類未實現時,父類不會被序列化,但此時父類必須有個無參構造方法,不然會拋InvalidClassException異常。
2、靜態變量不會被序列化,那是類的「菜」,不是對象的。
3、transient關鍵字修飾變量能夠限制序列化。
4、虛擬機是否容許反序列化,不只取決於類路徑和功能代碼是否一致,一個很是重要的一點是兩個類的序列化 ID 是否一致,就是 private static final long serialVersionUID = 1L5、Java 序列化機制爲了節省磁盤空間,具備特定的存儲規則,當寫入文件的爲同一對象時,並不會再將對象的內容進行存儲,而只是再次存儲一份引用。反序列化時,恢復引用關係。
6、序列化到同一個文件時,如第二次修改了相同對象屬性值再次保存時候,虛擬機根據引用關係知道已經有一個相同對象已經寫入文件,所以只保存第二次寫的引用,因此讀取時,都是第一次保存的對象。讀者在使用一個文件屢次 writeObject 須要特別注意這個問題(基於第5點)。

 

Serializable 與 Externalizable 的區別

Serializable:一個對象想要被序列化,那麼它的類就要實現 此接口,這個對象的全部屬性(包括private屬性、包括其引用的對象)均可以被序列化和反序列化來保存、傳遞;

Externalizable:是Serializable接口的子接口,有時咱們不但願序列化那麼多,能夠使用這個接口,這個接口的writeExternal()和readExternal()方法能夠指定序列化哪些屬性。

 

socket 選項 TCP NO DELAY 是指什麼

public boolean getTcpNoDelay() throws SocketException
public void setTcpNoDelay(boolean on) throws SocketException
在默認狀況下,客戶端向服務器發送數據時,會根據數據包的大小決定是否當即發送。當數據包中的數據不多時,如只有1個字節,而數據包的頭卻有幾十個字節(IP頭+TCP頭)時,系統會在發送以前先將較小的包合併到軟大的包後,一塊兒將數據發送出去。在發送下一個數據包時,系統會等待服務器對前一個數據包的響應,當收到服務器的響應後,再發送下一個數據包,這就是所謂的Nagle算法;在默認狀況下,Nagle算法是開啓的。這種算法雖然能夠有效地改善網絡傳輸的效率,但對於網絡速度比較慢,並且對實現性的要求比較高的狀況下(如遊戲、Telnet等),使用這種方式傳輸數據 會使得客戶端有明顯的停頓現象。所以,最好的解決方案就是須要Nagle算法時就使用它,不須要時就關閉它。而使用setTcpToDelay正好能夠滿 足這個需求。當使用setTcpNoDelay(true)將Nagle算法關閉後,客戶端每發送一次數據,不管數據包的大小都會將這些數據發送出去。

 

Socket 工做在 TCP/IP 協議棧是哪一層

Socket是一組編程接口(API)。介於傳輸層和應用層,嚮應用層提供統一的編程接口。應用層沒必要了解TCP/IP協議細節。直接經過對Socket接口函數的調用完成數據在IP網絡的傳輸。

 

TCP、UDP 區別及 Java 實現方式

TCP---傳輸控制協議,提供的是面向鏈接、可靠的字節流服務。當客戶和服務器彼此交換數據前,必須先在雙方之間創建一個TCP鏈接,以後才能傳輸數據。TCP提供超時重發,丟棄重複數據,檢驗數據,流量控制等功能,保證數據能從一端傳到另外一端。
UDP---用戶數據報協議,是一個簡單的面向數據報的運輸層協議。UDP不提供可靠性,它只是把應用程序傳給IP層的數據報發送出去,可是並不能保證它們能到達目的地。因爲UDP在傳輸數據報前不用在客戶和服務器之間創建一個鏈接,且沒有超時重發等機制,故而傳輸速度很快
Java 經過Socket實現TCP 和 UDP

 

直接緩衝區與非直接緩衝器有什麼區別?

非直接緩衝區:經過allocate()分配緩衝區,將緩衝區創建在JVM的內存中
直接緩衝區:經過allocateDirect()分配直接緩衝區,將緩衝區創建在物理內存中,能夠提升效率。

 

怎麼讀寫 ByteBuffer?ByteBuffer 中的字節序是什麼

ByteBuffer是NIO裏用得最多的Buffer,它包含兩個實現方式:HeapByteBuffer是基於Java堆的實現,而DirectByteBuffer則使用了unsafe的API進行了堆外的實現。
put(byte)和get()。分別是往ByteBuffer裏寫一個字節,和讀一個字節。

 

當用System.in.read(buffer)從鍵盤輸入一行n個字符後,存儲在緩衝區buffer中的字節數是多少

存儲在緩衝區buffer中的字節數有n+2個,即除輸入的n個字符後,還存儲了回車和換行字符。

 

解釋下多態性(polymorphism),封裝性(encapsulation),內聚(cohesion)以及耦合(coupling)

多態:方法的重載(Overload)和覆蓋(Override)
內聚:設計某個模塊或者關注點時,模塊或關注點內部的一系列相關功能的相關程度的高低。高內聚提供了更好的可維護性和可複用性。而低內聚的模塊則代表模塊直接的依賴程度高,那麼一旦修改了該模塊依賴的對象則沒法使用該模塊,必須也進行相應的修改才能夠繼續使用。
耦合:軟件工程中對象之間的耦合度就是對象之間的依賴性。

 

對象封裝的原則是什麼?

簡化用戶接口,隱藏實現細節,這個是封裝的根本目的。
1、必須保證接口是功能的全集,即接口可以覆蓋全部需求。 不能完成必要功能的封裝是沒有意義的;
2、儘可能使接口是最小冗餘的(單一職責原則);
3、要保證接口是穩定的,將接口和實現分離,並將實現隱藏(依賴倒置原則,要面向接口編程);
4、一旦接口被公佈,永遠也不要改變它(開閉原則,對擴展開發,對修改關閉)

 

反射中 Class.forName 和 ClassLoader 區別

java中class.forName()和classLoader均可用來對類進行加載。
class.forName()前者除了將類的.class文件加載到jvm中以外,還會對類進行解釋,執行類中的static塊。
而classLoader只幹一件事情,就是將.class文件加載到jvm中,不會執行static中的內容,只有在newInstance纔會去執行static塊。
Class.forName(name, initialize, loader)帶參函數也可控制是否加載static塊。

 

反射建立類實例的三種方式是什麼

1)方式一:Object類中的getClass()方法的。想要用這種方式,必需要明確具體的類,並建立對象。麻煩。
(2)方式二:任何數據類型(基本數據類型和引用數據類型)都具有一個靜態的屬性.class來獲取其對應的Class對象。相對簡單,可是仍是要明確用到類中的靜態成員。仍是不夠擴展。
(3)方式三:只要經過給定的類的 字符串名稱就能夠獲取該類,更爲擴展。但是用Class.forName的方法完成。這種方式只要有名稱便可,更爲方便,擴展性更強。 

 

如何讓前端 js 沒法使用 cookie,保證網址安全?

有如下兩種辦法:
1、修改nginx,在 server模塊 下面加入如下內容 : 
    add_header                  Set-Cookie "HttpOnly";          # 在Cookie中設置了"HttpOnly"屬性,經過程序(JS、Applet等)將沒法讀取到Cookie
    add_header                  Set-Cookie "Secure";            # 指示瀏覽器僅經過 HTTPS 鏈接傳回 cookie
    add_header                  X-Frame-Options "SAMEORIGIN";   # 不容許一個頁面在 <frame>, </iframe> 或者 <object> 中展示的標記
2、java後臺 : 
    response.addHeader("Set-Cookie", "mycookie=112; Path=/; HttpOnly"); //設置cookie 
    response.addHeader("Set-Cookie", "mycookie=112; Path=/; Secure; HttpOnly"); //設置https的cookie
    response.setHeader("x-frame-options", "SAMEORIGIN"); //設置x-frame-options

 

mybatis的緩存機制

mybaits提供一級緩存,和二級緩存。
1、一級緩存:
一級緩存的做用域是同一個SqlSession(不一樣的sqlSession之間的緩存數據是互相不影響的),在同一個sqlSession中兩次執行相同的sql語句,第一次執行完畢會將數據庫中查詢的數據寫到緩存(內存),
第二次會從緩存中獲取數據將再也不從數據庫查詢,從而提升查詢效率。當一個sqlSession結束後該sqlSession中的一級緩存也就不存在了。 Mybatis默認開啓一級緩存。
2、二級緩存; 二級緩存是mapper級別的緩存,多個SqlSession去操做同一個Mapper的sql語句,多個SqlSession去操做數據庫獲得數據會存在二級緩存區域,多個SqlSession能夠共用二級緩存,二級緩存是跨SqlSession的。 Mybatis默認沒有開啓二級緩存須要在setting全局參數中配置開啓二級緩存。 mybaits的二級緩存是mapper範圍級別,除了在SqlMapConfig.xml設置二級緩存的總開關,還要在具體的mapper.xml中開啓二級緩存。 3、刷新緩存: 若是sqlSession去執行commit操做(執行插入、更新、刪除),則清空SqlSession中的一級緩存,這樣作的目的爲了讓緩存中存儲的是最新的信息,避免髒讀。

 

如何經過反射獲取和設置對象私有字段的值

能夠經過類對象的getDeclaredField()方法獲取字段(Field)對象,而後再經過字段對象的setAccessible(true)將其設置爲能夠訪問,接下來就能夠經過get/set方法來獲取/設置字段的值了。

 

何時使用享元模式

統有大量類似對象。 須要緩衝池的場景。
如:JAVA中的 String,若是有則返回,若是沒有則建立一個字符串保存在字符串緩存池裏面。數據庫的數據池。

 

緩存穿透,緩存擊穿,緩存雪崩解決方案分析

1、緩存穿透:前端向後端查詢一個不存在的數據,因爲緩存是不命中時被動寫的,而且出於容錯考慮,若是從存儲層查不到數據則不寫入緩存,致使每次查詢都須要從數據庫中查詢,緩存失去意義。流量大是容易形成DB掛掉,存在被人利用和進行攻擊的可能。
方案:若是查詢的數據不存在,則將數據進行空緩存,可將緩存的時間調短一點,好比5分鐘。
2、緩存擊穿:對於一些即將過時的但比較熱門的key,若是過時的一瞬間,不少請求進來,就會形成DB的很大的負擔。
方案:使用互斥鎖,在獲取數據的時候,新建一個短期的key做爲鎖,獲取到鎖的請求,去DB查詢數據,並寫入緩存,同時刪除這個鎖,而其餘請求的線程則自旋(線程循環等待)等待。
3、緩存雪崩:設置緩存時採用了相同的過時時間,致使緩存在某一時刻同時失效,請求所有轉發到DB,DB瞬時壓力太重雪崩。
方案:在設置緩存時,將緩存的過時時間分散,能夠在原定時間基礎上加上一個隨機時間,好比過時時間加上 1~5分鐘 中的一個隨機時間。

 

哪一種依賴注入方式你建議使用,構造器注入,仍是 Setter方法注入

Setter
setter方法設定依賴關係顯得更加直觀,更加天然。
若是依賴關係(或繼承關係)較爲複雜,那麼構造函數也會至關龐大(咱們須要在構造函數中設定全部依賴關係)
對於某些第三方類庫而言,可能要求咱們的組件必須提供一個默認的構造函數(如Struts中的Action),此時構造的依賴注入機制就體現出其侷限性,難以完成咱們指望的功能。

 

Redis分佈式鎖的正確實現方式

代碼以下:
    private static final String LOCK_SUCCESS = "OK";
    private static final String SET_IF_NOT_EXIST = "NX";
    private static final String SET_WITH_EXPIRE_TIME = "PX";
    // 嘗試獲取 redis 分佈式鎖
    public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {
        String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
        if (LOCK_SUCCESS.equals(result)) {
            return true;
        }
        return false;
    }
說明:
加鎖的 關鍵代碼是 :jedis.set(String key, String value, String nxxx, String expx, int time)
1、這個語句是原子的,就算系統掛了,鎖只會要麼加上要麼沒加上,不會有兩個線程同時得到鎖;
2、value 爲 requestId(有key做爲鎖不就夠了嗎,爲何還要用到value?---> 由於設置requestId後,就知道這個鎖是哪一個線程加的,解開鎖也必須這個線程才行),能夠使用 java 的UUID;
3、只有key不存在時才加,存在了則不作任何操做;
4、設置了過時時間,不會形成線程死鎖。

 

Zookeeper的分佈式鎖實現方式

1、客戶端鏈接zookeeper,並在/lock 下建立臨時的且有序的子節點,第一個客戶端對應的子節點爲/lock/lock-0000000000,第二個爲/lock/lock-0000000001,以此類推。
2、客戶端獲取/lock下的子節點列表,判斷本身建立的子節點是否爲當前子節點列表中序號最小的子節點,若是是則認爲得到鎖,不然監聽/lock的子節點變動消息,得到子節點變動通知後重復此步驟直至得到鎖。
3、執行業務代碼。
4、完成業務邏輯,刪除對應的子節點釋放鎖。
(若是某個線程在建立臨時節點後,服務掛了,由於zookeeper有定時的心跳機制,檢測到線程鏈接斷開,就會刪除此線程建立的臨時節點)
這個實現不足:
當lock下面的子節點變化後,全部線程都會獲取/lock 下全部子節點,並循環,判斷當前節點是不是最小的節點,這個會形成不少沒必要要的消耗。
改進:
每一個節點的建立者只須要關注比本身序號小的那個節點,每一個節點所關心的節點只有一個。
改進中存在的問題:
若是比本身序號小的哪一個節點是由於網絡掛了斷開的鏈接,那麼豈不是當前線程直接獲取到鎖了嗎?
錯,獲取鎖的時候,仍是步驟2 的算法,獲取 /lock 下的子節點列表,判斷本身的節點是不是最小的。這不過這個時候,只有這一個線程被喚醒了,其餘線程仍是繼續等待。

 

Zookeeper的分佈式鎖實現方式(二)

其實上面的算法已經有實現封裝了,不用本身寫代碼了。直接拿來用就能夠了。
能夠使用 menagerie 來實現以上分佈式鎖,menagerie基於Zookeeper實現了java.util.concurrent 包的一個分佈式版本。
github地址:https://github.com/sfines/menagerie
這個封裝是更大粒度上對各類分佈式一致性使用場景的抽象。其中最基礎和經常使用的是一個分佈式鎖的實現: org.menagerie.locks.ReentrantZkLock

 

請思考一個方案,實現分佈式環境下的 CountDownLatch

使用zookeeper實現(可參考 org.menagerie.latches.ZkCountDownLatch )、或者使用緩存實現

 

如何判斷鏈表中是否有環?

能夠使用哈希緩存,也能夠使用快慢指針。

 

說出數據鏈接池的工做機制是什麼

數據庫鏈接池在初始化時將建立必定數量的數據庫鏈接放到鏈接池中,這些數據庫鏈接的數量是由最小數據庫鏈接數來設定的。不管這些數據庫鏈接是否被使用,鏈接池都將一直保證至少擁有這麼多的鏈接數量。
鏈接池的最大數據庫鏈接數量限定了這個鏈接池能佔有的最大鏈接數,當應用程序向鏈接池請求的鏈接數超過最大鏈接數量時,這些請求將被加入到等待隊列中。 數據庫鏈接池的最小鏈接數和最大鏈接數的設置要考慮到下列幾個因素:  
1) 最小鏈接數是鏈接池一直保持的數據庫鏈接,因此若是應用程序對數據庫鏈接的使用量不大,將會有大量的數據庫鏈接資源被浪費;   2) 最大鏈接數是鏈接池能申請的最大鏈接數,若是數據庫鏈接請求超過此數,後面的數據庫鏈接請求將被加入到等待隊列中,這會影響以後的數據庫操做。   3) 若是最小鏈接數與最大鏈接數相差太大,那麼最早的鏈接請求將會獲利,以後超過最小鏈接數量的鏈接請求等價於創建一個新的數據庫鏈接。不過,這些大於最小鏈接數的數據庫鏈接在使用完不會立刻被釋放,它將被放到鏈接池中等待重複使用或是空閒超時後被釋放。

 

如何搭建一個高可用系統

1,主備/集羣模式,防止單點
2,限流,削峯,防止後端壓力過大
3,熔斷機制,相似與限流
4,容災機制,多機房/異地部署
減小單點 – 多點的地方不少,如機房(同城異地雙機房),應用服務器,DNS服務器,SFTP服務器,LBS,緩存服務器,數據庫,消息服務器,代理服務器和專線等,如系統經過專線調用對方服務,須要考慮同時拉聯通和電信的專線。優先使用軟負載,使用硬負載兜底。
減小依賴 – 減小DNS依賴,減小遠程服務依賴,DNS依賴能夠嘗試設置本地host,用工具給全部服務器推送最新的域名映射關係,經過本地緩存或近端服務減小RPC調用。
限制循環 – 避免無限死循環,致使CPU利用率百分百,能夠設置for循環的最大循環次數,如最大循環1000次。
控制流量 – 避免異常流量對應用服務器產生影響,能夠對指定服務設置流量限制,如QPS,TPS 
           TPS:Transactions Per Second(每秒傳輸的事物處理個數);QPS:每秒查詢率。對於一個頁面的一次訪問,造成一個TPS;但一次頁面請求,可能產生屢次對服務器的請求,一次對服務器的請求是一次QPS
精準監控 – 對CPU利用率,load,內存,帶寬,系統調用量,應用錯誤量,PV,UV和業務量進行監控,避免內存泄露和異常代碼對系統產生影響,配置監控必定要精準。
無狀態 – 服務器不能保存用戶狀態數據,如在集羣環境下不能用static變量保存用戶數據,不能長時間把用戶文件存放在服務器本地。
容量規劃 – 按期對容量進行評估。如大促前進行壓測和容量預估,根據須要進行擴容。
功能開關 – 打開和關閉某些功能,好比消息量過大,系統處理不了,把開關打開後直接丟棄消息不處理。上線新功能增長開關,若是有問題關閉新功能。
設置超時 – 設置鏈接超時和讀超時設置,不該該太大,若是是內部調用鏈接超時能夠設置成1秒,讀超時3秒,外部系統調用鏈接超時能夠設置成3秒,讀超時設置成20秒。
重試策略 – 當調用外部服務異常時能夠設置重試策略,每次重試時間遞增,可是須要設置最大重試次數和重試開關,避免對下游系統產生影響。
隔離 – 應用隔離,模塊隔離,機房隔離【每一個機房的服務都有本身的服務分組,本機房的服務應該只調用本機房服務,不進行跨機房調用;其中一個機房服務發生問題時能夠經過DNS/負載均衡將請求所有切到另外一個機房】,線程池隔離【把請求分類,而後交給不一樣的線程池處理,當一種業務的請求處理髮生問題時,不會將故障擴散到其餘線程池】,動靜隔離【動態內容和靜態資源分離,通常應該將靜態資源放在CDN上】,熱點隔離【秒殺、搶購等熱點,須要獨立系統或者服務器進行隔離,從而保證秒殺/搶購不會影響主流程】
異步調用 – 同步調用改爲異步調用,解決遠程調用故障或調用超時對系統的影響。
熱點緩存 – 對熱點數據進行緩存,下降RPC調用。
分級緩存 – 優先讀本地緩存,其次讀分佈式緩存。經過推模式更新本地緩存。
服務降級 – 若是系統出現響應緩慢等情況,能夠關閉部分功能,從而釋放系統資源,保證核心服務的正常運行。
流量蓄洪 – 當流量陡增時,能夠將請求進行蓄洪,如把請求保存在數據庫中,再按照指定的QPS進行泄洪,有效的保護下游系統,也保證了服務的可用性。當調用對方系統,對方系統響應緩慢或無響應時,可採起自動蓄洪。
服務權重 – 在集羣環境中,可自動識別高性能服務,拒絕調用性能低的服務。如在集羣環境中,對調用超時的服務器進行權重下降,優先調用權重高的服務器。
依賴簡化– 減小系統之間的依賴,好比使用消息驅動,A和B系統經過消息服務器傳遞數據,當A不可用時,短期內不影響B系統提供服務。
灰度和回滾 – 發佈新功能只讓部分服務器生效,且觀察幾天逐漸切流,若是出現問題隻影響部分客戶。出現問題快速回滾,或者直接下線灰度的機器。
減小遠程調用
熔斷機制 – 增長熔斷機制,當監控出線上數據出現大幅跌漲時,及時中斷,避免對業務產生更大影響(可以使用開源框架:hystrix(中文名「豪豬」))。
運行時加載模塊 – 咱們會把常常變的業務代碼變成一個個業務模塊,使用Java的ClassLoader在運行時動態加載和卸載模塊,當某個模塊有問題時候,能夠快速修復。
自動備份 – 程序,系統配置和數據按期進行備份。可以使用linux命令和shell腳本定時執行備份策略,自動進行本地或異地。出現問題時能快速從新部署。
線上壓測 – 系統的對外服務須要進行壓測,知道該服務能承受的QPS 和 TPS,從而作出相對準確的限流。

 

如何在基於Java的Web項目中實現文件上傳和下載 

在Sevlet3之前,Servlet API中沒有支持上傳功能的API,推薦使用Apache的commons-fileupload。
Sevlet3以後增長了Multipart支持能夠直接實現文件的上傳和下載

 

如何實現一個秒殺系統,保證只有幾位用戶能買到某件商品

1,頁面開啓倒計時,要保證不能把下單接口暴露過早暴露出來,防止機器刷下單接口
2,前端限流,好比nginx對下單接口限流,命中限流則返回302到秒殺頁(nginx配置語句:limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;)
3,後端單獨部署,獨立域名和nginx,與線上正常運行的系統隔離開來,避免影響到線上環境
4,因爲生成訂單操做比較耗時,採用隊列的方式來解耦下單成功和生成訂單,針對進入後端的請求,採用redis自減,針對自減結果>0的請求則認爲下單成功,觸發一個生成訂單的消息,而後當即返回給用戶結果
5,用戶方面,針對秒殺成功有兩種處理方式
  a,用戶端收到秒殺成功的結果,則開啓提示頁面,並進入倒計時,倒計時時間爲訂單生成的預估時間
  b,秒殺成功後,給當前用戶在redis中生成一個訂單生成狀態的標識,用戶端開啓提示頁面,loading,並輪詢後端訂單生成狀態,生成成功以後讓前端跳轉到訂單頁面
6,訂單服務訂閱下單系統發送的消息,並開始生成訂單,生成訂單成功以後更新redis中用戶秒殺訂單的狀態爲已生成訂單
系統應該有頁面和接口,頁面用於展現用戶界面,接口用於獲取數據
界面:秒殺頁面,秒殺成功頁面,秒殺失敗頁面,命中限流頁面(查看訂單頁面不算秒殺系統的功能)
接口:秒殺下單接口,秒殺成功獲取訂單生成狀態接口

 

如何實現負載均衡,有哪些算法能夠實現

1、輪詢法
  將請求按順序輪流地分配到後端服務器上,它均衡地對待後端的每一臺服務器,而不關心服務器實際的鏈接數和當前的系統負載。
2、隨機法
    經過系統的隨機算法,根據後端服務器的列表大小值來隨機選取其中的一臺服務器進行訪問。由機率統計理論能夠得知,隨着客戶端調用服務端的次數增多,
其實際效果愈來愈接近於平均分配調用量到後端的每一臺服務器,也就是輪詢的結果。
3、源地址哈希法
    源地址哈希的思想是根據獲取客戶端的IP地址,經過哈希函數計算獲得的一個數值,用該數值對服務器列表的大小進行取模運算,獲得的結果即是客服端要訪問服務器的序號。採用源地址哈希法進行負載均衡,同一IP地址的客戶端,當後端服務器列表不變時,它每次都會映射到同一臺後端服務器進行訪問。
4、加權輪詢法
  不一樣的後端服務器可能機器的配置和當前系統的負載並不相同,所以它們的抗壓能力也不相同。給配置高、負載低的機器配置更高的權重,讓其處理更多的請;而配置低、負載高的機器,給其分配較低的權重,下降其系統負載,加權輪詢能很好地處理這一問題,並將請求順序且按照權重分配到後端。
5、加權隨機法
    與加權輪詢法同樣,加權隨機法也根據後端機器的配置,系統的負載分配不一樣的權重。不一樣的是,它是按照權重隨機請求後端服務器,而非順序。
6、最小鏈接數法
    最小鏈接數算法比較靈活和智能,因爲後端服務器的配置不盡相同,對於請求的處理有快有慢,它是根據後端服務器當前的鏈接狀況,動態地選取其中當前積壓鏈接數最少的一臺服務器來處理當前的請求,儘量地提升後端服務的利用效率,將負責合理地分流到每一臺服務器。

 

如何設計創建和保持 100w 的長鏈接

能夠使用Netty NIO框架,但須要擴大JVM內存,估計20G以上,JVM內存高了full GC時間會長可能會達到幾秒,須要優化GC算法,
能夠使用代理服務器來分散鏈接
負載均衡 + 反向代理。

 

如何避免瀏覽器緩存

1、HTML Meta標籤控制緩存
能夠在HTML頁面的<head>節點中加入<meta>標籤:
<META HTTP-EQUIV="Pragma" CONTENT="no-cache">
<META HTTP-EQUIV="Expires" CONTENT="0">
使用上很簡單,但只有部分瀏覽器能夠支持
2、HTTP頭信息控制緩存
1) 在java代碼中進行控制:
response.setHeader( "Pragma", "no-cache" ); 
response.setDateHeader("Expires", 0); 
response.addHeader( "Cache-Control", "no-cache" );//瀏覽器和緩存服務器都不該該緩存頁面信息
【
下面的代碼設置強緩存:
java.util.Date date = new java.util.Date(); 
response.setDateHeader("Expires",date.getTime()+20000); //Expires:過期期限值 
response.setHeader("Cache-Control", "public");          //Cache-Control來控制頁面的緩存與否,public:瀏覽器和緩存服務器均可以緩存頁面信息;
response.setHeader("Pragma", "Pragma");                 //Pragma:設置頁面是否緩存,爲Pragma則緩存,no-cache則不緩存
2) 經過配置web服務器的方式,讓web服務器在響應資源的時候統一添加 Expires 和Cache-Control Header
   tomcat提供了一個ExpiresFilter專門用來配置強緩存。
   nginx 配置:
   location ~ .*\.(js|css)$ {
      #若是頻繁更新,則能夠設置得小一點。
      expires 1d;  
      add_header Cache-Control max-age=86400;
      etag on;
   } 

 

大型網站在架構上應當考慮哪些問題

1、海量數據的處理
2、數據併發的處理
3、文件存貯的問題
4、數據關係的處理
5、數據索引的問題
6、分佈式處理
7、Ajax的利弊分析
8、數據安全性的分析
9、數據同步和集羣的處理的問題
10、數據共享的渠道以及OPENAPI趨勢

 

描述下經常使用的重構技巧

No.1:重複代碼的提煉
No.2:冗長方法的分割
No.3:嵌套條件分支的優化(1)
No.4:嵌套條件分支的優化(2)
No.5:去掉一次性的臨時變量
No.6:消除過長參數列表
No.7:提取類或繼承體系中的常量
No.8:讓類提供應該提供的方法
No.9:拆分冗長的類
No.10:提取繼承體系中重複的屬性與方法到父類

 

每一個程序員要注意的 9 種反模式

1 過早優化
2 單車車庫(避免花費太多時間在瑣碎的事情上,好比花了好幾個小時或者幾天來討論app的顏色問題。)
3 分析癱瘓(對問題的過分分析,阻礙了行動和進展。寧願迭代,也不要過分分析和猜想。)
4 上帝類(上帝類是控制不少其它類,以及有不少依賴類,也就有更大的責任。)
5 新增類恐懼症
6 內部平臺效應
7 魔法數和字符串(避免在代碼中出現未註釋、未命名的數字和字符串字面量。)
8 數字管理
9 無用的(幽靈)類(無用類自己沒有真正的責任,常常用來指示調用另外一個類的方法或增長一層沒必要要的抽象層。)

 

大家線上應用的 JVM 參數有哪些

-Xmx4g        
設置JVM最大可用內存爲4g。
-Xms4g        
設置JVM初始內存爲4g。此值能夠設置與-Xmx相同,以免每次垃圾回收完成後JVM從新分配內存。
-XX:NewRatio=2 
設置年輕代(包括Eden和兩個Survivor區)與年老代的比值(除去持久代)。設置爲2,則年輕代與年老代所佔比值爲1:2,年輕代佔整個堆棧的1/3
-XX:SurvivorRatio=4        
年輕代中Eden區與兩個Survivor區的比值。注意Survivor區有兩個。如:4,表示Eden:Survivor=42,一個Survivor區佔整個年輕代的1/6
-XX:PermSize=256m         
設置持久代大小爲256m
-XX:MaxPermSize=512m         
設置持久代最大爲512m
-Xss256k         
設置每一個線程的堆棧大小。JDK5.0之後每一個線程堆棧大小爲1M,之前每一個線程堆棧大小爲256K。更具應用的線程所需內存大小進行調整。在相同物理內存下,減少這個值能生成更多的線程。可是操做系統對一個進程內的線程數仍是有限制的,不能無限生成,經驗值在3000~5000左右。
-XX:+DisableExplicitGC
關閉System.gc() 
-XX:+UseParNewGC
設置年輕代爲並行收集,可與CMS收集同時使用,JDK5.0以上,JVM會根據系統配置自行設置,因此無需再設置此值
-XX:ParallelGCThreads=4
並行收集器的線程數,此值最好配置與處理器數目相等 一樣適用於CMS 
-XX:+UseConcMarkSweepGC
使用CMS內存收集 
-XX:+UseCMSCompactAtFullCollection
在FULL GC的時候,對年老代的壓縮,CMS是不會移動內存的,所以很是容易產生碎片,致使內存不夠用,所以內存的壓縮這個時候就會被啓用。增長這個參數是個好習慣。可能會影響性能,可是能夠消除碎片
-XX:+CMSParallelRemarkEnabled
下降標記停頓 
-XX:MaxTenuringThreshold=3
垃圾最大年齡,即對象在Survivor區存在的年齡爲3(複製一次年齡+1),若是設置爲0的話,則年輕代對象不通過Survivor區,直接進入年老代. 對於年老代比較多的應用,能夠提升效率.若是將此值設置爲一個較大值,則年輕代對象會在Survivor區進行屢次複製,這樣能夠增長對象再年輕代的存活 時間,增長在年輕代即被回收的機率該參數只有在串行GC時纔有效.
-XX:+CMSParallelRemarkEnabled
下降標記停頓(線上配置重複了) 
-XX:CMSInitiatingOccupancyFraction=70
使用cms做爲垃圾回收,使用70%後開始CMS收集,爲了保證不出現promotion failed(見下面介紹)錯誤,該值的設置須要知足如下公式CMSInitiatingOccupancyFraction計算公式
CMSInitiatingOccupancyFraction值與Xmn的關係公式:
    上面介紹了promontion faild產生的緣由是EDEN空間不足的狀況下將EDEN與From survivor中的存活對象存入To survivor區時,To survivor區的空間不足,再次晉升到old gen區,而old gen區內存也不夠的狀況下產生了promontion faild從而致使full gc.那能夠推斷出:eden+from survivor < old gen區剩餘內存時,不會出現promontion faild的狀況,即:
(Xmx-Xmn)*(1-CMSInitiatingOccupancyFraction/100)>=(Xmn-Xmn/(SurvivorRatior+2))  進而推斷出:
           CMSInitiatingOccupancyFraction <=((Xmx-Xmn)-(Xmn-Xmn/(SurvivorRatior+2)))/(Xmx-Xmn)*100
例如:
    當xmx=128 xmn=36 SurvivorRatior=1時 CMSInitiatingOccupancyFraction<=((128.0-36)-(36-36/(1+2)))/(128-36)*100 =73.913
    當xmx=128 xmn=24 SurvivorRatior=1時 CMSInitiatingOccupancyFraction<=((128.0-24)-(24-24/(1+2)))/(128-24)*100=84.615…
         當xmx=3000 xmn=600 SurvivorRatior=1時  CMSInitiatingOccupancyFraction<=((3000.0-600)-(600-600/(1+2)))/(3000-600)*100=83.33
CMSInitiatingOccupancyFraction低於70% 須要調整xmn或SurvivorRatior值。
-XX:CMSFullGCsBeforeCompaction=0
多少次後進行內存壓縮,因爲併發收集器不對內存空間進行壓縮,整理,因此運行一段時間之後會產生"碎片",使得運行效率下降.此值設置運行多少次GC之後對內存空間進行壓縮,整理.
-XX:+UseFastAccessorMethods
原始類型的快速優化 
-XX:+UseBiasedLocking
鎖機制的性能改善     
-Dcom.sun.management.jmxremote
使用jvisualvm經過JMX的方式遠程監控JVM的運行狀況,還要求再配置ssl、port、authenticate等參數,單獨配置這個可能沒有,或者能夠使用默認配置,須要確認???
-Djava.awt.headless=true
有時咱們會在咱們的J2EE工程中使用一些圖表工具如:jfreechart,用於在web網頁輸出GIF/JPG等流,在winodws環境下,通常咱們的app server在輸出圖形時不會碰到什麼問題,
可是在linux/unix環境下常常會碰到一個exception致使你在winodws開發環境下圖片顯 示的好好但是在linux/unix下卻顯示不出來,所以加上這個參數以避免避這樣的狀況出現. 調試參數: -verbose:gc 表示輸出虛擬機中GC的詳細狀況 -XX:+PrintGC 加上參數能夠在輸出日誌中能夠查看垃圾回收先後堆的大小, 即打印gc日誌 -XX:+PrintGCDetails 打印gc日誌的更加詳細的信息 -XX:+PrintGCDateStamps (-XX:+PrintGCDateStamps或者-XX:+PrintGCTimeStamps),輸出GC發生時,gc發生的時間 -XX:+PrintAdaptiveSizePolicy 打印自適應收集的大小。默認關閉。 -XX:+PrintHeapAtGC 打印GC先後的詳細堆棧信息 -XX:+PrintTenuringDistribution 查看每次minor GC後新的存活週期的閾值 -Xloggc:/***/gc.log 把相關日誌信息記錄到文件以便分析(gc.log) -XX:ErrorFile=/**/hs_err_pid.log 生成error 文件的路徑(hs_err_pid.log) -XX:HeapDumpPath=/**/java.hprof 指定HeapDump的文件路徑或目錄(java.hprof) 配置地址: -Dconfig.home=/**/config config配置文件路徑 -Dlogback.configurationFile=/**/logback.xml logback配置文件logback.xml文件位置

 

GC日誌包括些什麼內容?

主要內容包括:
1、時間戳、相對時間戳;
2、GC回收類型(yong/old)、垃圾回收區域(不一樣的垃圾回收器,對應的區域名詞可能不一樣,包括:DefNew<串行-年輕代>、Tenured<老年代>、Perm<方法區>、PSYoungGen<ParNew-年輕代>、ParOldGen<ParNew-老年代>等);
3、年輕代(老年代)回收前大小、年輕代(老年代)回收後大小、年輕代(老年代)總大小、堆回收前大小、堆回收後大小、堆總大小;
4、GC的CPU耗時、系統喚醒GC耗時、總耗時

 

怎樣看新生代GC日誌?

新生代回收的一行日誌:
2014-07-18T16:02:17.606+0800: 611.633: [GC 611.633: [DefNew: 843458K->2K(948864K), 0.0059180 secs] 2186589K->1343132K(3057292K), 0.0059490 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
含義大概以下:
2014-07-18T16:02:17.606+0800【當前時間戳】: 611.633【相對<JVM啓動的>時間戳】: [GC【表示Young GC】 611.633: [DefNew【單線程Serial年輕代GC】: 843458K【年輕代垃圾回收前的大小】->2K【年輕代回收後的大小】(948864K【年輕代總大小】), 0.0059180 secs【本次回收用時】] 2186589K【整個堆回收前的大小】->1343132K【整個堆回收後的大小】(3057292K【堆總大小】), 0.0059490 secs【回收時間】] [Times: user=0.00【cpu耗時】 sys=0.00【系統喚醒GC耗時】, real=0.00 secs【實際耗時,等於前兩個和】]

 

怎樣看老年代GC日誌?

和新生代相似,老年代回收的一行日誌:
2014-07-18T16:19:16.794+0800: 1630.821: [GC 1630.821: [DefNew: 1005567K->111679K(1005568K), 0.9152360 secs]1631.736: [Tenured:
2573912K->1340650K(2574068K), 1.8511050 secs] 3122548K->1340650K(3579636K), [Perm : 17882K->17882K(21248K)], 2.7854350 secs] [Times: user=2.57 sys=0.22, real=2.79 secs]
含義以下:
2014-07-18T16:19:16.794+0800【時間戳】: 1630.821【相對<JVM啓動的>時間戳】: [GC【年輕代GC】 1630.821: [DefNew【使用的垃圾回收器】: 1005567K【年輕代回收前大小】->111679K【年輕代回收後大小】(1005568K【年輕代總大小】), 0.9152360 secs【回收用時】]1631.736【此時相對<JVM啓動的>時間戳】: [Tenured【老年代】:
2573912K【老年代回收前大小】->1340650K【老年代回收後大小】(2574068K【【老年代總大小】】), 1.8511050 secs] 3122548K【整個堆回收前大小】->1340650K【整個堆回收後大小】(3579636K【堆總大小】), [Perm : 17882K【方法區回收前大小】->17882K【方法區回收後大小】(21248K【方法區總大小】)], 2.7854350 secs【回收用時】] [Times: user=2.57【cpu耗時】 sys=0.22【系統喚醒GC耗時】, real=2.79 secs【總耗時】]

 

完整GC日誌舉例

[GC [PSYoungGen: 4423K->320K(9216K)] 4423K->320K(58880K), 0.0011900 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] 
[Full GC (System) [PSYoungGen: 320K->0K(9216K)] [ParOldGen: 0K->222K(49664K)] 320K->222K(58880K) [PSPermGen: 2458K->2456K(21248K)], 0.0073610 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
Heap
 PSYoungGen      total 9216K, used 491K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
  eden space 8192K, 6% used [0x00000000ff600000,0x00000000ff67af50,0x00000000ffe00000)
  from space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
  to   space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
 ParOldGen       total 49664K, used 222K [0x00000000c5800000, 0x00000000c8880000, 0x00000000ff600000)
  object space 49664K, 0% used [0x00000000c5800000,0x00000000c58378a0,0x00000000c8880000)
 PSPermGen       total 21248K, used 2466K [0x00000000c0600000, 0x00000000c1ac0000, 0x00000000c5800000)
  object space 21248K, 11% used [0x00000000c0600000,0x00000000c0868b48,0x00000000c1ac0000)
gc日誌中的最後(Heap日後的部分)貌似是系統運行完成前的快照

 

解釋什麼是 MESI 協議(緩存一致性) 

當CPU寫數據時,若是發現操做的變量是共享變量,即在其餘CPU中也存在該變量的副本,會發出信號通知其餘CPU將該變量的緩存行置爲無效狀態,所以當其餘CPU須要讀取這個變量時,發現本身緩存中緩存該變量的緩存行是無效的,那麼它就會從內存從新讀取。
MESI協議中的狀態
CPU中每一個緩存行(caceh line)使用4種狀態進行標記(使用額外的兩位(bit)表示):
M: 被修改(Modified)
該緩存行只被緩存在該CPU的緩存中,而且是被修改過的(dirty),即與主存中的數據不一致,該緩存行中的內存須要在將來的某個時間點(容許其它CPU讀取請主存中相應內存以前)寫回(write back)主存。當被寫回主存以後,該緩存行的狀態會變成獨享(exclusive)狀態。
E: 獨享的(Exclusive)
該緩存行只被緩存在該CPU的緩存中,它是未被修改過的(clean),與主存中數據一致。該狀態能夠在任什麼時候刻當有其它CPU讀取該內存時變成共享狀態(shared)。一樣地,當CPU修改該緩存行中內容時,該狀態能夠變成Modified狀態。
S: 共享的(Shared)
該狀態意味着該緩存行可能被多個CPU緩存,而且各個緩存中的數據與主存數據一致(clean),當有一個CPU修改該緩存行中,其它CPU中該緩存行能夠被做廢(變成無效狀態(Invalid))。
I: 無效的(Invalid)
該緩存是無效的(可能有其它CPU修改了該緩存行)。

 

談談 reactor 模型

Reactor模式,這個模式是出如今NIO中。
反應器設計模式(Reactor pattern)是一種爲處理併發服務請求,並將請求提交到一個或者多個服務處理程序的事件設計模式。當客戶端請求抵達後,服務處理程序使用多路分配策略,由一個非阻塞的線程來接收全部的請求,而後派發這些請求至相關的工做線程進行處理。 
Reactor模式主要包含下面幾部份內容。 初始事件分發器(Initialization Dispatcher):用於管理Event Handler,定義註冊、移除EventHandler等。 同步(多路)事件分離器(Synchronous Event Demultiplexer):無限循環等待新事件的到來,一旦發現有新的事件到來,就會通知初始事件分發器去調取特定的事件處理器。 系統處理程序(Handles):操做系統中的句柄,是對資源在操做系統層面上的一種抽象,它能夠是打開的文件、一個鏈接(Socket)、Timer等。 事件處理器(Event Handler): 定義事件處理方法,以供Initialization Dispatcher回調使用。 對於Reactor模式,能夠將其看作由兩部分組成,一部分是由Boss組成,另外一部分是由worker組成。Boss就像老闆同樣,主要是拉活兒、談項目,一旦Boss接到活兒了,就下發給下面的work去處理。也能夠看作是項目經理和程序員之間的關係。 reactor設計模式,是一種基於事件驅動的設計模式。Reactor框架是ACE各個框架中最基礎的一個框架,其餘框架都或多或少地用到了Reactor框架。

 

虛擬內存是什麼

虛擬內存是計算機系統內存管理的一種技術。它使得應用程序認爲它擁有連續可用的內存(一個連續完整的地址空間),而實際上,它一般是被分隔成多個物理內存碎片,還有部分暫時存儲在外部磁盤存儲器上,在須要時進行數據交換。
與沒有使用虛擬內存技術的系統相比,使用這種技術的系統使得大型程序的編寫變得更容易,對真正的物理內存(例如RAM)的使用也更有效率。 注意:虛擬內存不僅是「用磁盤空間來擴展物理內存」的意思——這只是擴充內存級別以使其包含硬盤驅動器而已。把內存擴展到磁盤只是使用虛擬內存技術的一個結果,它的做用也能夠經過覆蓋或者把處於不活動狀態的程序以及它們的數據所有交換到磁盤上等方式來實現。

 

闡述下 SOLID 原則

SOLID(單一功能、開閉原則、里氏替換、接口隔離以及依賴反轉)是由羅伯特·C·馬丁在21世紀早期[1] 引入的記憶術首字母縮略字

 

請簡要講一下你對測試驅動開發(TDD)的認識

測試驅動開發(Test-driven development,縮寫爲TDD)是一種軟件開發過程當中的應用方法,由極限編程中倡導,以其倡導先寫測試程序,而後編碼實現其功能得名。測試驅動開發始於20世紀90年代。
測試驅動開發的目的是取得快速反饋並使用「illustrate the main line」方法來構建程序。 測試驅動開發是戴兩頂帽子思考的開發方式:先戴上實現功能的帽子,在測試的輔助下,快速實現其功能;再戴上重構的帽子,在測試的保護下,經過去除冗餘的代碼,提升代碼質量。測試驅動着整個開發過程:首先,驅動代碼的設計和功能的實現;其後,驅動代碼的再設計和重構。 正面評價 能夠有效的避免過分設計帶來的浪費。可是也有人強調在開發前須要有完整的設計再實施能夠有效的避免重構帶來的浪費。 可讓開發者在開發中擁有更全面的視角。 負面評價 開發者可能只完成知足了測試的代碼,而忽略了對實際需求的實現。有實踐者認爲用結對編程的方式能夠有效的避免這個問題。 會放慢開發實際代碼的速度,特別對於要求開發速度的原型開發形成不利。這裏須要考慮開發速度須要包含功能和品質兩個方面,單純的代碼速度可能不能徹底表明開發速度。

 

什麼是 zab 協議

ZAB ( ZooKeeper Atomic Broadcast , ZooKeeper 原子消息廣播協議)是zookeeper數據一致性的核心算法。
ZAB 協議並不像 Paxos 算法(一種通用的分佈式一致性算法),它是一種特別爲 ZooKeeper 設計的崩潰可恢復的原子消息廣播算法。
ZAB協議主要實現了:
  1.使用一個單一的主進程來接收並處理客戶端的全部事務請求,並採用 ZAB 的原子廣播協議,將服務器數據的狀態變動以事務 Proposal 的形式廣播到全部的副本進程上去。
  2.保證一個全局的變動序列被順序應用。好比P1的事務t1多是建立節點「/a」,t2多是建立節點「/a/aa」,只有先建立了父節點「/a」,才能建立子節點「/a/aa」。
  3.當前主進程出現異常狀況的時候,依舊可以正常工做。
ZAB 協議的核心:定義了事務請求的處理方式。
  全部事務請求必須由一個全局惟一的服務器來協調處理,這樣的服務器被稱爲 Leader服務器,而餘下的其餘服務器則成爲 Follower 服務器。 Leader 服務器負責將一個客戶端事務請求轉換成一個事務proposal(提議),並將該 Proposal分發給集羣中全部的Follower服務器。
以後 Leader 服務器須要等待全部Follower 服務器的反饋,一旦超過半數的Follower服務器進行了正確的反饋後,那麼 Leader 就會再次向全部的 Follower服務器分發Commit消息,要求其將前一個proposal進行提交。

 

ZAB 協議的兩種基本的模式

分別是崩潰恢復和消息廣播。
  當整個服務框架在啓動過程當中,或是當 Leader 服務器出現網絡中斷、崩潰退出與重啓等異常狀況時, ZAB 協議就會進入恢復模式並選舉產生新的 Leader 服務器。當選舉產生了新的Leader 服務器同時集羣中已經有過半的機器與該 Leader 服務器完成了狀態同步以後,
ZAB 協議就會退出恢復模式。   當集羣中已經有過半的 Follower 服務器完成了和 Leader 服務器的狀態同步,那麼整個服務框架就能夠進入消息廣播模式了。當一臺一樣遵照 ZAB 協議的服務器啓動後加入到集羣中時,若是此時集羣中已經存在一個 Leader 服務器在負責進行消息廣播 ,
那麼新加人的服務器就會自覺地進人數據恢復模式:找到 Leader 所在的服務器,並與其進行數據同步,而後一塊兒參與到消息廣播流程中去。

 

zookeeper 如何選舉的?

在集羣初始化階段,當有一臺服務器ZK1啓動時,其單獨沒法進行和完成Leader選舉,當第二臺服務器ZK2啓動時,此時兩臺機器能夠相互通訊,每臺機器都試圖找到Leader,因而進入Leader選舉過程。選舉過程開始,過程以下: 
  (1) 每一個Server(服務器狀態爲LOOKING)發出一個投票。ZK1和ZK2都會將本身做爲Leader服務器來進行投票,每次投票會包含所推舉的服務器的myid和ZXID,使用(myid, ZXID)來表示,此時ZK1的投票爲(1, 0),ZK2的投票爲(2, 0),而後各自將這個投票發給集羣中其餘機器。 
    (2) 接受來自各個服務器的投票。集羣的每一個服務器收到投票後,首先判斷該投票的有效性,如檢查是不是本輪投票、是否來自LOOKING狀態的服務器。
    (3) 處理投票。針對每個投票,服務器都須要將別人的投票和本身的投票進行比較,規則以下 
        · 優先檢查ZXID。ZXID比較大的服務器優先做爲Leader。 
    · 若是ZXID相同,那麼就比較myid。myid較大的服務器做爲Leader服務器。
            對於ZK1而言,它的投票是(1, 0),接收ZK2的投票爲(2, 0),首先會比較二者的ZXID,均爲0,再比較myid,此時ZK2的myid最大,因而ZK2勝。ZK1更新本身的投票爲(2, 0),並將投票從新發送給ZK2。
    (4) 統計投票。每次投票後,服務器都會統計投票信息,判斷是否已經有過半機器接受到相同的投票信息,若是有,則就是Leader。
    (5) 改變服務器狀態。一旦肯定了Leader,每一個服務器就會更新本身的狀態,若是是Follower,那麼就變動爲FOLLOWING,若是是Leader,就變動爲LEADING。
        當新的Zookeeper節點ZK3啓動時,發現已經有Leader了,再也不選舉,直接將直接的狀態從LOOKING改成FOLLOWING。

 

爲何說 zookeeper 集羣 最好是奇數個節點?

Zookeeper 有三種運行模式:單機模式、僞集羣模式和集羣模式。集羣模式有「 過半存活便可用 」的特性:
    2節點zookeeper,一個主節點掛了,另一個備節點由於沒有過半,沒法對外提供集羣服務,容錯數爲0
    5節點zookeeper,兩個主節點掛了,另外三個備節點過半,對外提供集羣服務,容錯數爲2
    6節點zookeeper,兩個主節點掛了,另外四個備節點過半,對外提供集羣服務,容錯數爲2;若是掛了三個節點,則沒法對外提供服務
總結: 
    1.成功選舉Leader必需要備節點過半,2n和2n-1(n>1)的容錯數是同樣的都是 n-12.集羣服務偶數節點也是能夠的,偶數容錯數和奇數同樣,因此不必浪費一個節點資源。 
    因此,最好是奇數個,並非不能是偶數個,由於偶數個(2n個節點)的容錯率和奇數個(2n-1)的容錯率同樣,不必浪費一個節點。

 

什麼是領域模型(domain model)?貧血模型(anaemic domain model) 和充血模型(rich domain model)有什麼區別 

業務對象模型(也叫領域模型 domain model)是描述業務用例實現的對象模型。
貧血模型是指使用的領域對象中只有setter和getter方法(POJO),全部的業務邏輯都不包含在領域對象中而是放在業務邏輯層。
充血模型將大多數業務邏輯和持久化放在領域對象中,業務邏輯(業務門面)只是完成對業務邏輯的封裝、事務和權限等的處理。

 

什麼是領域驅動開發(Domain Driven Development)

將問題抽象爲一個領域解決方案。並針對此領域(即抽象)進行開發的方式。
領域模型的核心是抽象和分治(less is more)。
問題分層、問題分塊、關注要素,忽略細節 ;關注抽象,忽略具體……

 

HTTPS和HTTP的區別

在應用層(http)和傳輸層(tcp)間加了一層會話層(SSL)
在URL前加https://前綴代表是用SSL加密的。 你的電腦與服務器之間收發的信息傳輸將更加安全。
Web服務器啓用SSL須要得到一個服務器證書並將該證書與要使用SSL的服務器綁定。
http和https使用的是徹底不一樣的鏈接方式,用的端口也不同,前者是80,後者是443。http的鏈接很簡單,是無狀態的,... 
HTTPS協議是由SSL+HTTP協議構建的可進行加密傳輸、身份認證的網絡協議
要比http協議安全

 

傳輸層常見編程協議有哪些?並說出各自的特色 

TCP協議:面向鏈接的可靠傳輸協議。利用TCP進行通訊時,首先要經過三步握手,以創建通訊雙方的鏈接。TCP提供了數據的確認和數據重傳的機制,保證發送的數據必定能到達通訊的對方。
UDP協議:是無鏈接的,不可靠的傳輸協議。採用UDP進行通訊時不用創建鏈接,能夠直接向一個IP地址發送數據,可是不能保證對方是否能收到
相關文章
相關標籤/搜索