java常見面試題整理
ThreadLocal
Synchronized實現記憶體共享,ThreadLocal為每個執行緒維護一個本地變數。
採用空間換時間,它用於執行緒間的資料隔離,為每一個使用該變數的執行緒提供一個副本,每個執行緒都可以獨立地改變自己的副本,而不會和其他執行緒的副本衝突。
ThreadLocal類中維護一個Map,用於儲存每一個執行緒的變數副本,Map中元素的鍵為執行緒物件,而值為對應執行緒的變數副本。
Java記憶體模型:
Java虛擬機器規範中將Java執行時資料分為六種。
- 程式計數器:是一個數據結構,用於儲存當前正常執行的程式的記憶體地址。Java虛擬機器的多執行緒就是通過執行緒輪流切換並分配處理器時間來實現的,為了執行緒切換後能恢復到正確的位置,每條執行緒都需要一個獨立的程式計數器,互不影響,該區域為“執行緒私有”。
- Java虛擬機器棧:執行緒私有的,與執行緒生命週期相同,用於儲存區域性變量表,操作棧,方法返回值。區域性變量表放著基本資料型別,還有物件的引用。
- 本地方法棧:跟虛擬機器棧很像,不過它是為虛擬機器使用到的Native方法服務。
- Java堆:所有執行緒共享的一塊記憶體區域,物件例項幾乎都在這分配記憶體。
- 方法區:各個執行緒共享的區域,儲存虛擬機器載入的類資訊,常量,靜態變數,編譯後的程式碼。
執行時常量池:代表執行時每個class檔案中的常量表。包括幾種常量:編譯時的數字常量、方法或者域的引用。
java虛擬機器常用命令工具
- jps:顯示系統中所有Hotspot虛擬機器程序
- .jstat:檢視jvm的GC情況
- jstack:顯示虛擬機器的執行緒棧資訊
- jinfo:顯示虛擬機器的配置資訊
- jmap:用於生成虛擬機器的記憶體快照資訊(打印出某個java程序(使用pid)記憶體內的,所有‘物件’的情況(如:產生那些物件,及其數量))
使用jmap dump 分析JVM記憶體狀態
java虛擬機器常用命令工具
java GC
java GC是在什麼時候,對什麼東西,做了什麼事情?
在什麼時候:
- 新生代有一個Eden區和兩個survivor區,首先將物件放入Eden區,如果空間不足就向其中的一個survivor區上放,如果仍然放不下就會引發一次發生在新生代的minor GC,將存活的物件放入另一個survivor區中,然後清空Eden和之前的那個survivor區的記憶體。在某次GC過程中,如果發現仍然又放不下的物件,就將這些物件放入老年代記憶體裡去。
- 大物件以及長期存活的物件直接進入老年區。
- 當每次執行minor GC的時候應該對要晉升到老年代的物件進行分析,如果這些馬上要到老年區的老年物件的大小超過了老年區的剩餘大小,那麼執行一次Full
GC以儘可能地獲得老年區的空間。
對什麼東西:從GC Roots搜尋不到,而且經過一次標記清理之後仍沒有復活的物件。
做什麼:
新生代:複製清理;
老年代:標記-清除和標記-整理演算法;
永久代:存放Java中的類和載入類的類載入器本身。
Synchronized 與Lock
ReentrantLock 擁有Synchronized相同的併發性和記憶體語義,此外還多了 鎖投票,定時鎖等候和中斷鎖等候
執行緒A和B都要獲取物件O的鎖定,假設A獲取了物件O鎖,B將等待A釋放對O的鎖定,
如果使用 synchronized ,如果A不釋放,B將一直等下去,不能被中斷
如果 使用ReentrantLock,如果A不釋放,可以使B在等待了足夠長的時間以後,中斷等待,而幹別的事情ReentrantLock獲取鎖定與三種方式:
a) lock(), 如果獲取了鎖立即返回,如果別的執行緒持有鎖,當前執行緒則一直處於休眠狀態,直到獲取鎖
b) tryLock(), 如果獲取了鎖立即返回true,如果別的執行緒正持有鎖,立即返回false;
c)tryLock(long timeout,TimeUnit unit), 如果獲取了鎖定立即返回true,如果別的執行緒正持有鎖,會等待引數給定的時間,在等待的過程中,如果獲取了鎖定,就返回true,如果等待超時,返回false;
d) lockInterruptibly:如果獲取了鎖定立即返回,如果沒有獲取鎖定,當前執行緒處於休眠狀態,直到或者鎖定,或者當前執行緒被別的執行緒中斷總體的結論先擺出來:
- synchronized:
在資源競爭不是很激烈的情況下,偶爾會有同步的情形下,synchronized是很合適的。原因在於,編譯程式通常會盡可能的進行優化synchronize,另外可讀性非常好,不管用沒用過5.0多執行緒包的程式設計師都能理解。 - ReentrantLock:
ReentrantLock提供了多樣化的同步,比如有時間限制的同步,可以被Interrupt的同步(synchronized的同步是不能Interrupt的)等。在資源競爭不激烈的情形下,效能稍微比synchronized差點點。但是當同步非常激烈的時候,synchronized的效能一下子能下降好幾十倍。而ReentrantLock確還能維持常態。
- synchronized:
String、StringBuffer與StringBuilder之間區別
每次操作字串,String會生成一個新的物件,而StringBuffer不會;StringBuilder是非執行緒安全的,StringBuffer是執行緒安全的
對於三者使用的總結:
- 如果要操作少量的資料用 = String
- 單執行緒操作字串緩衝區 下操作大量資料 = StringBuilder
fail-fast
機制是java集合(Collection)中的一種錯誤機制。當多個執行緒對同一個集合的內容進行操作時,就可能會產生fail-fast事件。
例如:當某一個執行緒A通過iterator去遍歷某集合的過程中,若該集合的內容被其他執行緒所改變了;那麼執行緒A訪問集合時,就會丟擲ConcurrentModificationException異常,產生fail-fast事件
Volatile和Synchronized:
Volatile和Synchronized四個不同點:
- 粒度不同,前者針對變數 ,後者鎖物件和類
- syn阻塞,volatile執行緒不阻塞
- syn保證三大特性,volatile不保證原子性
- syn編譯器優化,volatile不優化
要使 volatile 變數提供理想的執行緒安全,必須同時滿足下面兩個條件:
- 對變數的寫操作不依賴於當前值。
- 該變數沒有包含在具有其他變數的不變式中。
CAS(Compare And Swap) 無鎖演算法:
CAS是樂觀鎖技術,當多個執行緒嘗試使用CAS同時更新同一個變數時,只有其中一個執行緒能更新變數的值,而其它執行緒都失敗,失敗的執行緒並不會被掛起,而是被告知這次競爭中失敗,並可以再次嘗試。CAS有3個運算元,記憶體值V,舊的預期值A,要修改的新值B。當且僅當預期值A和記憶體值V相同時,將記憶體值V修改為B,否則什麼都不做。
執行緒池
執行緒池的作用:
在程式啟動的時候就建立若干執行緒來響應處理,它們被稱為執行緒池,裡面的執行緒叫工作執行緒
第一:降低資源消耗。通過重複利用已建立的執行緒降低執行緒建立和銷燬造成的消耗。
第二:提高響應速度。當任務到達時,任務可以不需要等到執行緒建立就能立即執行。
第三:提高執行緒的可管理性。
常用執行緒池:ExecutorService 是主要的實現類,其中常用的有
Executors.newSingleThreadPool(),newFixedThreadPool(),newcachedTheadPool(),newScheduledThreadPool()。
hashcode(),equal()方法深入解析
Java對於eqauls方法和hashCode方法是這樣規定的:
1、如果兩個物件相同,那麼它們的hashCode值一定要相同;
2、如果兩個物件的hashCode相同,它們並不一定相同(上面說的物件相同指的是用eqauls方法比較。)
Java語言對equals()的要求如下,這些要求是必須遵循的。
- 對稱性:如果x.equals(y)返回是“true”,那麼y.equals(x)也應該返回是“true”。
- 反射性:x.equals(x)必須返回是“true”。
- 類推性:如果x.equals(y)返回是“true”,而且y.equals(z)返回是“true”,那麼z.equals(x)也應該返回是“true”。
- 還有一致性:如果x.equals(y)返回是“true”,只要x和y內容一直不變,不管你重複x.equals(y)多少次,返回都是“true”。
•任何情況下,x.equals(null),永遠返回是“false”;x.equals(和x不同型別的物件)永遠返回是“false”。
自動裝箱拆箱
//自動裝箱
Integer total = 99;
//自定拆箱
int totalprim = total;
簡單一點說,裝箱就是自動將基本資料型別轉換為包裝器型別;拆箱就是自動將包裝器型別轉換為基本資料型別。
反射機制
如何寫一個不可變類?
- 將類宣告為final,所以它不能被繼承
- 將所有的成員宣告為私有的,這樣就不允許直接訪問這些成員
- 對變數不要提供setter方法
- 將所有可變的成員宣告為final,這樣只能對它們賦值一次
- 通過構造器初始化所有成員,進行深拷貝(deep copy)
- 在getter方法中,不要直接返回物件本身,而是克隆物件,並返回物件的拷貝
java nio
- Channel,Buffer 和 Selector(多路複用器) 構成了核心的API。其它元件,如Pipe和FileLock
- Channel:FileChannel,DatagramChannel,SocketChannel,ServerSocketChanne
- Buffer:ByteBuffer,CharBuffer,DoubleBuffer,FloatBuffer,IntBuffer,LongBuffer,ShortBuffer
- 資料可以從Channel讀到Buffer中,也可以從Buffer 寫到Channel中
- Selector允許單執行緒處理多個 Channel。如果你的應用打開了多個連線(通道),但每個連線的流量都很低,
傳統的IO和NIO的區別其本質就是阻塞和非阻塞的區別
阻塞概念:應用程式在獲取網路資料的時候,如果網路傳輸很慢,那麼程式就會一直處於等待狀態,直到資料傳輸結束為止。
非阻塞概念:應用程式直接可以獲取已經準備好的網路資料,無需等待。
io為同步阻塞的形式nio為同步非阻塞的形式。NIO1.0並沒有實現非同步的概念,在jdk1.7之後的NIO2.0才真正的實現了非同步非阻塞的概念。
同步時:應用程式會直接參與IO讀寫操作,並且我們的程式會直接阻塞到某一個方法上,直到資料準備就緒。或者採用輪詢的策略實時檢查資料的就緒狀態,如果就緒則獲取資料。
非同步時:就是所用IO操作交給作業系統處理,我們編寫的應用程式不參與IO的操作,我們的程式不需要關心IO的讀寫,當作業系統完成了IO的讀寫操作時,會給我們的應用程式傳送通知,我們的應用程式直接拿走資料即可。
AIO程式設計
AIO的特點:
1. 讀完了再通知我
2. 不會加快IO,只是在讀完後進行通知
3. 使用回撥函式,進行業務處理
在理解了NIO的基礎上,看AIO,區別在於AIO是等讀寫過程完成後再去呼叫回撥函式。
NIO是同步非阻塞的
AIO是非同步非阻塞的
由於NIO的讀寫過程依然在應用執行緒裡完成,所以對於那些讀寫過程時間長的,NIO就不太適合。
而AIO的讀寫過程完成後才被通知,所以AIO能夠勝任那些重量級,讀寫過程長的任務。
索引
什麼時候使用索引:
1. 經常出現在group by,order by和distinc關鍵字後面的欄位
2. 經常與其他表進行連線的表,在連線欄位上應該建立索引
3. 經常出現在Where子句中的欄位
4. 經常出現用作查詢選擇的欄位
MyISAM和InnoDB索引實現對比
兩種型別最主要的差別就是Innodb 支援事務處理與外來鍵和行級鎖。
聚集索引和非聚集索引
spring IOC
Spring支援三種依賴注入方式,分別是屬性(Setter方法)注入,構造注入和介面注入。
在Spring中,那些組成應用的主體及由Spring IOC容器所管理的物件被稱之為Bean。
Spring的IOC容器通過反射的機制例項化Bean並建立Bean之間的依賴關係。
簡單地講,Bean就是由Spring IOC容器初始化、裝配及被管理的物件。
獲取Bean物件的過程,首先通過Resource載入配置檔案並啟動IOC容器,然後通過getBean方法獲取bean物件,就可以呼叫他的方法。
Spring 3中為Bean定義了5中作用域,分別為singleton(單例)、prototype(原型)、request、session和global session
Spring框架IOC容器和AOP解析
AOP
Spring AOP兩種實現機制是什麼?
- 如果是有介面宣告的類進行AOP 時,spring呼叫的是java.lang.reflection.Proxy 類來做處理
- 如果是沒有介面宣告的類時, spring通過cglib包和內部類來實現
在AOP,許可權控制,事務管理等方面都有動態代理的實現。JDK本身有實現動態代理技術,但是略有限制,即被代理的類必須實現某個介面,否則無法使用
JDK自帶的動態代理,因此,如果不滿足條件,就只能使用另一種更加靈活,功能更加強大的動態代理技術—— CGLIB。Spring裡會自動在JDK的代理和CGLIB之間切換,同時我們也可以強制Spring使用CGLIB。
Spring AOP應用場景:效能檢測,訪問控制,日誌管理,事務等。
預設的策略是如果目標類實現介面,則使用JDK動態代理技術,如果目標物件沒有實現介面,則預設會採用CGLIB代理
<aop:aspectj-autoproxy proxy-target-class="true" />
配置了這句話的話就會強制使用cglib代理。 預設就是false。
spring mvc 原理
SpringMVC執行原理
1. 客戶端請求提交到DispatcherServlet
2. 由DispatcherServlet控制器查詢HandlerMapping,找到並分發到指定的Controller中。
4. Controller呼叫業務邏輯處理後,返回ModelAndView
5. DispatcherServlet查詢一個或多個ViewResoler檢視解析器,找到ModelAndView指定的檢視
6. 檢視負責將結果顯示到客戶端
Spring:基於註解的Spring MVC(上)
一個Http請求
DNS域名解析 –> 發起TCP的三次握手 –> 建立TCP連線後發起http請求 –> 伺服器響應http請求,瀏覽器得到html程式碼 –> 瀏覽器解析html程式碼,並請求html程式碼中的資源(如javascript、css、圖片等) –> 瀏覽器對頁面進行渲染呈現給使用者
HTTP 請求方式: GET和POST的比較
GET - 從指定的伺服器中獲取資料
POST - 提交資料給指定的伺服器處理
GET方法:
使用GET方法時,查詢字串(鍵值對)被附加在URL地址後面一起傳送到伺服器:
/test/demo_form.jsp?name1=value1&name2=value2
特點:
GET請求能夠被快取
GET請求會儲存在瀏覽器的瀏覽記錄中
以GET請求的URL能夠儲存為瀏覽器書籤
GET請求有長度限制
GET請求主要用以獲取資料
POST方法:
使用POST方法時,查詢字串在POST資訊中單獨存在,和HTTP請求一起傳送到伺服器:
POST /test/demo_form.jsp HTTP/1.1
Host: w3schools.com
name1=value1&name2=value2
特點:
POST請求不能被快取下來
POST請求不會儲存在瀏覽器瀏覽記錄中
以POST請求的URL無法儲存為瀏覽器書籤
POST請求沒有長度限制
https
Session與Cookie
- session儲存在伺服器,客戶端不知道其中的資訊;cookie儲存在客戶端,伺服器能夠知道其中的資訊。
- session中儲存的是物件,cookie中儲存的是字串。
- session不能區分路徑,同一個使用者在訪問一個網站期間,所有的session在任何一個地方都可以訪問到。而cookie中如果設定了路徑引數,那麼同一個網站中不同路徑下的cookie互相是訪問不到的。
- Cookie的不可跨域名性
事務
事務的特性:⑴ 原子性(Atomicity),⑵ 一致性(Consistency),⑶ 隔離性(Isolation),⑷ 永續性(Durability)
如果不考慮事務的隔離性,會發生的幾種問題:
- 髒讀:髒讀是指在一個事務處理過程裡讀取了另一個未提交的事務中的資料。
- 不可重複讀是指在對於資料庫中的某個資料,一個事務範圍內多次查詢卻返回了不同的資料值,這是由於在查詢間隔,被另一個事務修改並提交了。
- 幻讀是事務非獨立執行時發生的一種現象。
四種隔離級別:
1. Serializable (序列化):可避免髒讀、不可重複讀、幻讀的發生
2. Repeatable read (可重複讀):可避免髒讀、不可重複讀的發生。
3. Read committed (讀已提交):可避免髒讀的發生。
4. Read uncommitted (讀未提交):最低級別,任何情況都無法保證。
spring事務
- 切點資訊,用於定位實施事物切面的業務類方法
- 控制事務行為的事務屬性,這些屬性包括事物隔離級別,事務傳播行為,超時時間,回滾規則。
Spring通過aop/tx Schema 名稱空間和@Transaction註解技術來進行宣告式事物配置。
Mybatis面試題
每一個Mybatis的應用程式都以一個SqlSessionFactory物件的例項為核心。首先用位元組流通過Resource將配置檔案讀入,然後通過SqlSessionFactoryBuilder().build方法建立SqlSessionFactory,然後再通過SqlSessionFactory.openSession()方法建立一個SqlSession為每一個數據庫事務服務。
經歷了Mybatis初始化 –>建立SqlSession –>執行SQL語句,返回結果三個過程
mybatis#{}和${}的區別是什麼?
1,mybatis${}
是Properties檔案中的變數佔位符,它可以用於標籤屬性值和sql內部,屬於靜態文字替換,比如${driver}會被靜態替換為com.mysql.jdbc.Driver。
2,#{}
是sql的引數佔位符,Mybatis會將sql中的#{}替換為?號,在sql執行前會使用PreparedStatement的引數設定方法,按序給sql的?號佔位符設定引數值,比如ps.setInt(0,parameterValue),#{item.name}的取值方式為使用反射從引數物件中獲取item物件的name屬性值,相當於param.getItem().getName()。
- #將傳入的資料都當成一個字串,會對自動傳入的資料加一個雙引號。如:order by #user_id#,如果傳入的值是111,那麼解析成sql時的值為order by “111”, 如果傳入的值是id,則解析成的sql為order by “id”.
- user_id$,如果傳入的值是111,那麼解析成sql時的值為order by user_id, 如果傳入的值是id,則解析成的sql為order by id.
- #方式能夠很大程度防止sql注入。
- $方式無法防止Sql注入。
- $方式一般用於傳入資料庫物件,例如傳入表名
- 一般能用#的就別用$.
servlet/filter/listener/interceptor區別與聯絡
- servlet:servlet是一種執行伺服器端的java應用程式,具有獨立於平臺和協議的特性,並且可以動態的生成web頁面,它工作在客戶端請求與伺服器響應的中間層
filter:filter是一個可以複用的程式碼片段,可以用來轉換HTTP請求、響應和頭資訊.Filter對使用者請求進行預處理,接著將請求交給Servlet進行處理並生成響應,最後Filter再對伺服器響應進行後處理。
Filter有如下幾個用處。- 在HttpServletRequest到達Servlet之前,攔截客戶的HttpServletRequest。
- 根據需要檢查HttpServletRequest,也可以修改HttpServletRequest頭和資料。
- 在HttpServletResponse到達客戶端之前,攔截HttpServletResponse。
- 根據需要檢查HttpServletResponse,也可以修改HttpServletResponse頭和資料。
Filter有如下幾個種類。
使用者授權的Filter:Filter負責檢查使用者請求,根據請求過濾使用者非法請求。
日誌Filter:詳細記錄某些特殊的使用者請求。
負責解碼的Filter:包括對非標準編碼的請求解碼。
能改變XML內容的XSLT Filter等。
Filter可負責攔截多個請求或響應;一個請求或響應也可被多個請求攔截。
建立一個Filter只需兩個步驟:
- 建Filter處理類;
- web.xml檔案中配置Filter。
3. listener:監聽器,從字面上可以看出listener主要用來監聽只用。通過listener可以監聽web伺服器中某一個執行動作,並根據其要求作出相應的響應。通俗的語言說就是在application,session,request三個物件建立消亡或者往其中新增修改刪除屬性時自動執行程式碼的功能元件。比如spring 的總監聽器 會在伺服器啟動的時候例項化我們配置的bean物件 、 hibernate 的 session 的監聽器會監聽session的活動和生命週期,負責建立,關閉session等活動。
Servlet的監聽器Listener,它是實現了javax.servlet.ServletContextListener 介面的伺服器端程式,它也是隨web應用的啟動而啟動,只初始化一次,隨web應用的停止而銷燬。主要作用是: 做一些初始化的內容新增工作、設定一些基本的內容、比如一些引數或者是一些固定的物件等等。
4. interceptor:是在面向切面程式設計的,就是在你的service或者一個方法,前呼叫一個方法,或者在方法後呼叫一個方法,是基於JAVA的反射機制
5. 載入次序
servlet、filter、listener是配置到web.xml中(web.xml 的載入順序是:context-param -> listener -> filter -> servlet ),interceptor不配置到web.xml中,struts的攔截器配置到struts.xml中。spring的攔截器配置到spring.xml中
6. 幾個區別:
1,servlet 流程是短的,url傳來之後,就對其進行處理,之後返回或轉向到某一自己指定的頁面。它主要用來在 業務處理之前進行控制.
2,filter 流程是線性的, url傳來之後,檢查之後,可保持原來的流程繼續向下執行,被下一個filter, servlet接收等,而servlet 處理之後,不會繼續向下傳遞。filter功能可用來保持流程繼續按照原來的方式進行下去,或者主導流程,而servlet的功能主要用來主導流程。filter可用來進行字元編碼的過濾,檢測使用者是否登陸的過濾,禁止頁面快取等
3, servlet,filter都是針對url之類的,而listener是針對物件的操作的,如session的建立,session.setAttribute的發生,在這樣的事件發生時做一些事情。可用來進行:Spring整合Struts,為Struts的action注入屬性,web應用定時任務的實現,線上人數的統計等
4,interceptor 攔截器,類似於filter,不過在struts.xml中配置,不是在web.xml,並且不是針對URL的,而是針對action,當頁面提交action時,進行過濾操作,相當於struts1.x提供的plug-in機制,可以看作,前者是struts1.x自帶的filter,而interceptor 是struts2 提供的filter.
與filter不同點:(1)不在web.xml中配置,而是在struts.xml中完成配置,與action在一起
( 2 ) 可由action自己指定用哪個interceptor 來在接收之前做事
HashMap與HashTable的區別。
- HashMap是非執行緒安全的,HashTable是執行緒安全的。
- HashMap的鍵和值都允許有null值存在,而HashTable則不行。
- 因為執行緒安全的問題,HashMap效率比HashTable的要高。
HashMap與TreeMap的應用與區別
TreeMap原理
- TreeMap是紅黑樹結構,紅黑樹是有序的,所以TreeMap也是有序的
- TreeMap的鍵不能為null
TreeMap效率不如HashMap,Map需要有序的場合才使用TreeMap
2種辦法讓HashMap執行緒安全
- 通過Collections.synchronizedMap()返回一個新的Map,這個新的map就是執行緒安全的. 這個要求大家習慣基於介面程式設計,因為返回的並不是HashMap,而是一個Map的實現.
重改寫了HashMap,具體的可以檢視java.util.concurrent.ConcurrentHashMap. 這個方法比方法一有了很大的改進.
類載入器工作機制:
- 裝載:將Java二進位制程式碼匯入jvm中,生成Class檔案。
- 連線:a)校驗:檢查載入Class檔案資料的正確性 b)準備:給類的靜態變數分配儲存空間 c)解析:將符號引用轉成直接引用
雙親委派模型
使用者自定義載入器(Application ClassLoader)->應用程式載入器->擴充套件類載入器(Extension ClassLoader)->啟動類載入器(Bootstrap ClassLoader)。
雙親委派模型的式作過程是:如果一個類載入器收到了類載入的請求,它首先不會自己去嘗試載入這個類,而是把這個請求委派給父類載入器去完成,每一個層次的類載入器都是如此,因此所有的載入請求最終都應該傳送到頂層的啟動類載入器中,只有當父載入器反饋自己無法完全這個載入請求時,子載入器才會嘗試自己去載入。
JVM(1):Java 類的載入機制
ConcurrentHashMap
- ConcurrentHashMap的應用場景是高併發,但是並不能保證執行緒安全,而同步的HashMap和HashMap的是鎖住整個容器,而加鎖之後ConcurrentHashMap不需要鎖住整個容器,只需要鎖住對應的Segment就好了,所以可以保證高併發同步訪問,提升了效率。
- 可以多執行緒寫。
ConcurrentHashMap把HashMap分成若干個Segmenet
1.get時,不加鎖,先定位到segment然後在找到頭結點進行讀取操作。而value是volatile變數,所以可以保證在競爭條件時保證讀取最新的值,如果讀到的value是null,則可能正在修改,那麼就呼叫ReadValueUnderLock函式,加鎖保證讀到的資料是正確的。
2.Put時會加鎖,一律新增到hash鏈的頭部。
3.Remove時也會加鎖,由於next是final型別不可改變,所以必須把刪除的節點之前的節點都複製一遍。
4.ConcurrentHashMap允許多個修改操作併發進行,其關鍵在於使用了鎖分離技術。它使用了多個鎖來控制對Hash表的不同Segment進行的修改。
ConcurrentHashMap的應用場景是高併發,但是並不能保證執行緒安全,而同步的HashMap和HashTable的是鎖住整個容器,而加鎖之後ConcurrentHashMap不需要鎖住整個容器,只需要鎖住對應的segment就好了,所以可以保證高併發同步訪問,提升了效率。
ConcurrentHashMap能夠保證每一次呼叫都是原子操作,但是並不保證多次呼叫之間也是原子操作。
- 在jdk1.8中主要做了2方面的改進
改進一:取消segments欄位,直接採用transient volatile HashEntry<K,V>[] table
儲存資料,採用table陣列元素作為鎖,從而實現了對每一行資料進行加鎖,進一步減少併發衝突的概率。
改進二:將原先table陣列+單向連結串列的資料結構,變更為table陣列+單向連結串列+紅黑樹的結構。對於hash表來說,最核心的能力在於將key hash之後能均勻的分佈在陣列中。如果hash之後雜湊的很均勻,那麼table陣列中的每個佇列長度主要為0或者1。但實際情況並非總是如此理想,雖然ConcurrentHashMap類預設的載入因子為0.75,但是在資料量過大或者運氣不佳的情況下,還是會存在一些佇列長度過長的情況,如果還是採用單向列表方式,那麼查詢某個節點的時間複雜度為O(n);因此,對於個數超過8(預設值)的列表,jdk1.8中採用了紅黑樹的結構,那麼查詢的時間複雜度可以降低到O(logN),可以改進效能。
HashMap,ConcurrentHashMap與LinkedHashMap的區別
- ConcurrentHashMap是使用了鎖分段技術技術來保證執行緒安全的,鎖分段技術:首先將資料分成一段一段的儲存,然後給每一段資料配一把鎖,當一個執行緒佔用鎖訪問其中一個段資料的時候,其他段的資料也能被其他執行緒訪問
- ConcurrentHashMap 是在每個段(segment)中執行緒安全的
- LinkedHashMap維護一個雙鏈表,可以將裡面的資料按寫入的順序讀出
Vector和ArrayList的區別
- ArrayList在記憶體不夠時預設是擴充套件50% + 1個,Vector是預設擴充套件1倍。
- Vector提供indexOf(obj, start)介面,ArrayList沒有。
- Vector屬於執行緒安全級別的,但是大多數情況下不使用Vector,因為執行緒安全需要更大的系統開銷。
Java中Vector和ArrayList的區別
LinkedHashMap
1)Map 介面的雜湊表和連結列表實現,具有可預知的迭代順序。此實現與 HashMap 的不同之處在於,後者維護著一個運行於所有條目的雙重連結列表。此連結列表定義了迭代順序,該迭代順序通常就是將鍵插入到對映中的順序(插入順序)。
(2)注意,如果在對映中重新插入 鍵,則插入順序不受影響。(如果在呼叫 m.put(k, v) 前 m.containsKey(k) 返回了 true,則呼叫時會將鍵 k 重新插入到對映 m 中。)
(3)此實現可以讓客戶避免未指定的、由 HashMap(及 Hashtable)所提供的通常為雜亂無章的排序工作,同時無需增加與 TreeMap 相關的成本。使用它可以生成一個與原來順序相同的對映副本,而與原對映的實現無關:
(4) 提供特殊的構造方法來建立連結雜湊對映,該雜湊對映的迭代順序就是最後訪問其條目的順序,從近期訪問最少到近期訪問最多的順序(訪問順序)。這種對映很適合構建 LRU 快取。呼叫 put 或 get 方法將會訪問相應的條目(假定呼叫完成後它還存在)。putAll 方法以指定對映的條目集迭代器提供的鍵-值對映關係的順序,為指定對映的每個對映關係生成一個條目訪問。任何其他方法均不生成條目訪問。特別是,collection 檢視上的操作不 影響底層對映的迭代順序。 removeEldestEntry(Map.Entry)
LinkedHashMap超詳細分析
ThreadPoolExecutor
構造方法引數說明
corePoolSize:核心執行緒數,預設情況下核心執行緒會一直存活,即使處於閒置狀態也不會受存keepAliveTime限制。除非將allowCoreThreadTimeOut設定為true。
maximumPoolSize:執行緒池所能容納的最大執行緒數。超過這個數的執行緒將被阻塞。當任務佇列為沒有設定大小的LinkedBlockingDeque時,這個值無效。
keepAliveTime:非核心執行緒的閒置超時時間,超過這個時間就會被回收。
unit:指定keepAliveTime的單位,如TimeUnit.SECONDS。當將allowCoreThreadTimeOut設定為true時對corePoolSize生效。
workQueue:執行緒池中的任務佇列.
常用的有三種佇列,SynchronousQueue,LinkedBlockingDeque,ArrayBlockingQueue。
threadFactory:執行緒工廠,提供建立新執行緒的功能。ThreadFactory是一個介面,只有一個方法
原理
- 如果當前池大小 poolSize 小於 corePoolSize ,則建立新執行緒執行任務。
- 如果當前池大小 poolSize 大於 corePoolSize ,且等待佇列未滿,則進入等待佇列
- 如果當前池大小 poolSize 大於 corePoolSize 且小於 maximumPoolSize ,且等待佇列已滿,則建立新執行緒執行任務。
- 如果當前池大小 poolSize 大於 corePoolSize 且大於 maximumPoolSize ,且等待佇列已滿,則呼叫拒絕策略來處理該任務。
- 執行緒池裡的每個執行緒執行完任務後不會立刻退出,而是會去檢查下等待佇列裡是否還有執行緒任務需要執行,如果在 keepAliveTime 裡等不到新的任務了,那麼執行緒就會退出。
Executor拒絕策略
- AbortPolicy:為java執行緒池預設的阻塞策略,不執行此任務,而且直接丟擲一個執行時異常,切記ThreadPoolExecutor.execute需要try
catch,否則程式會直接退出。 - DiscardPolicy:直接拋棄,任務不執行,空方法
- DiscardOldestPolicy:從佇列裡面拋棄head的一個任務,並再次execute 此task。
- CallerRunsPolicy:在呼叫execute的執行緒裡面執行此command,會阻塞入口
- 使用者自定義拒絕策略:實現RejectedExecutionHandler,並自己定義策略模式
執行緒池之拒絕策略
CopyOnWriteArrayList
CopyOnWriteArrayList : 寫時加鎖,當新增一個元素的時候,將原來的容器進行copy,複製出一個新的容器,然後在新的容器裡面寫,寫完之後再將原容器的引用指向新的容器,而讀的時候是讀舊容器的資料,所以可以進行併發的讀,但這是一種弱一致性的策略。
使用場景:CopyOnWriteArrayList適合使用在讀操作遠遠大於寫操作的場景裡,比如快取。
CachedThreadPool vs FixedThreadPool
- CachedThreadPool:是通過 java.util.concurrent.Executors 建立的 ThreadPoolExecutor 例項。這個例項會根據需要,線上程可用時,重用之前構造好的池中執行緒。這個執行緒池在執行 大量短生命週期的非同步任務時(many short-lived asynchronous task),可以顯著提高程式效能。呼叫 execute 時,可以重用之前已構造的可用執行緒,如果不存在可用執行緒,那麼會重新建立一個新的執行緒並將其加入到執行緒池中。如果執行緒超過 60 秒還未被使用,就會被中止並從快取中移除。因此,執行緒池在長時間空閒後不會消耗任何資源。
- FixedThreadPool 是通過 java.util.concurrent.Executors 建立的 ThreadPoolExecutor 例項。這個例項會複用 固定數量的執行緒 處理一個 共享的無邊界佇列 。任何時間點,最多有 nThreads 個執行緒會處於活動狀態執行任務。如果當所有執行緒都是活動時,有多的任務被提交過來,那麼它會一致在佇列中等待直到有執行緒可用。如果任何執行緒在執行過程中因為錯誤而中止,新的執行緒會替代它的位置來執行後續的任務。所有執行緒都會一致存於執行緒池中,直到顯式的執行 ExecutorService.shutdown() 關閉。
- SingleThreadPool 是通過 java.util.concurrent.Executors 建立的 ThreadPoolExecutor 例項。這個例項只會使用單個工作執行緒來執行一個無邊界的佇列。(注意,如果單個執行緒在執行過程中因為某些錯誤中止,新的執行緒會替代它執行後續執行緒)。它可以保證認為是按順序執行的,任何時候都不會有多於一個的任務處於活動狀態。和 newFixedThreadPool(1) 的區別在於,如果執行緒遇到錯誤中止,它是無法使用替代執行緒的。
執行緒池 FixedThreadPool vs CachedThreadPool
死鎖
死鎖產生的四個必要條件
- 互斥條件:資源是獨佔的且排他使用,程序互斥使用資源,即任意時刻一個資源只能給一個程序使用,其他程序若申請一個資源,而該資源被另一程序佔有時,則申請者等待直到資源被佔有者釋放。
- 不可剝奪條件:程序所獲得的資源在未使用完畢之前,不被其他程序強行剝奪,而只能由獲得該資源的程序資源釋放。
- 請求和保持條件:程序每次申請它所需要的一部分資源,在申請新的資源的同時,繼續佔用已分配到的資源。
- 迴圈等待條件:在發生死鎖時必然存在一個程序等待佇列{P1,P2,…,Pn},其中P1等待P2佔有的資源,P2等待P3佔有的資源,…,Pn等待P1佔有的資源,形成一個程序等待環路,環路中每一個程序所佔有的資源同時被另一個申請,也就是前一個程序佔有後一個程序所深情地資源。
解決死鎖
一是死鎖預防,就是不讓上面的四個條件同時成立。
二是,合理分配資源。
三是使用銀行家演算法,如果該程序請求的資源作業系統剩餘量可以滿足,那麼就分配。
程序間的通訊方式
- 管道( pipe):管道是一種半雙工的通訊方式,資料只能單向流動,而且只能在具有親緣關係的程序間使用。程序的親緣關係通常是指父子程序關係。
- 有名管道 (named pipe) : 有名管道也是半雙工的通訊方式,但是它允許無親緣關係程序間的通訊。
- 訊號量( semophore ) :
訊號量是一個計數器,可以用來控制多個程序對共享資源的訪問。它常作為一種鎖機制,防止某程序正在訪問共享資源時,其他程序也訪問該資源。因此,主要作為程序間以及同一程序內不同執行緒之間的同步手段。 - 訊息佇列( message queue ) :
訊息佇列是由訊息的連結串列,存放在核心中並由訊息佇列識別符號標識。訊息佇列克服了訊號傳遞資訊少、管道只能承載無格式位元組流以及緩衝區大小受限等缺點。 - 訊號 ( sinal ) : 訊號是一種比較複雜的通訊方式,用於通知接收程序某個事件已經發生。
- 共享記憶體( shared memory )
:共享記憶體就是對映一段能被其他程序所訪問的記憶體,這段共享記憶體由一個程序建立,但多個程序都可以訪問。共享記憶體是最快的 IPC
方式,它是針對其他程序間通訊方式執行效率低而專門設計的。它往往與其他通訊機制,如訊號量,配合使用,來實現程序間的同步和通訊。 - 套接字( socket ) : 套解口也是一種程序間通訊機制,與其他通訊機制不同的是,它可用於不同機器間的程序通訊。
程序和執行緒的關係
- 程序是表示資源分配的基本單位,又是排程執行的基本單位。執行緒是程序中執行運算的最小單位,亦即執行處理機排程的基本單位
- 一個執行緒只能屬於一個程序,而一個程序可以有多個執行緒,但至少有一個執行緒。執行緒是作業系統可識別的最小執行和排程單位。
- 源分配給程序,同一程序的所有執行緒共享該程序的所有資源。 同一程序中的多個執行緒共享程式碼段(程式碼和常量),資料段(全域性變數和靜態變數),擴充套件段(堆儲存)。但是每個執行緒擁有自己的棧段,棧段又叫執行時段,用來存放所有區域性變數和臨時變數。
- 處理機分給執行緒,即真正在處理機上執行的是執行緒。
- 執行緒在執行過程中,需要協作同步。不同程序的執行緒間要利用訊息通訊的辦法實現同步。
程序與執行緒的區別和聯絡
程序排程演算法
先來先服務,短作業優先,最短剩餘時間優先,高響應比優先,優先順序,時間片輪轉
作業系統的程序排程演算法
JAVA 中堆和棧的區別,說下java 的記憶體機制
a.基本資料型別比變數和物件的引用都是在棧分配的
b.堆記憶體用來存放由new建立的物件和陣列
c.類變數(static修飾的變數),程式在一載入的時候就在堆中為類變數分配記憶體,堆中的記憶體地址存放在棧中
d.例項變數:當你使用java關鍵字new的時候,系統在堆中開闢並不一定是連續的空間分配給變數,是根據零散的堆記憶體地址,通過雜湊演算法換算為一長串數字以表徵這個變數在堆中的”物理位置”,例項變數的生命週期–當例項變數的引用丟失後,將被GC(垃圾回收器)列入可回收“名單”中,但並不是馬上就釋放堆中記憶體
e.區域性變數: 由宣告在某方法,或某程式碼段裡(比如for迴圈),執行到它的時候在棧中開闢記憶體,當局部變數一但脫離作用域,記憶體立即釋放
JVM在新版本的改進更新以及相關知識
一.JVM在新版本的改進更新以及相關知識
Java 8: 從永久代(PermGen)到元空間(Metaspace)
Minor GC、Major GC和Full GC之間的區別
多型
多型就是指程式中定義的引用變數所指向的具體型別和通過該引用變數發出的方法呼叫在程式設計時並不確定,而是在程式執行期間才確定,即一個引用變數倒底會指向哪個類的例項物件,該引用變數發出的方法呼叫到底是哪個類中實現的方法,必須在由程式執行期間才能決定。
多型的概念:同一操作作用於不同物件,可以有不同的解釋,有不同的執行結果,這就是多型,簡單來說就是:父類的引用指向子類物件
三個必要條件:繼承、重寫、向上轉型。
繼承:在多型中必須存在有繼承關係的子類和父類。
重寫:子類對父類中某些方法進行重新定義,在呼叫這些方法時就會呼叫子類的方法。
向上轉型:在多型中需要將子類的引用賦給父類物件,只有這樣該引用才能夠具備技能呼叫父類的方法和子類的方法。
wait()和sleep()的區別
sleep來自Thread類,和wait來自Object類
呼叫sleep()方法的過程中,執行緒不會釋放物件鎖。而 呼叫 wait 方法執行緒會釋放物件鎖
sleep睡眠後不出讓系統資源,wait讓出系統資源其他執行緒可以佔用CPU
sleep(milliseconds)需要指定一個睡眠時間,時間一到會自動喚醒
Object有哪些公用方法?
a.方法equals測試的是兩個物件是否相等
b.方法clone進行物件拷貝
c.方法getClass返回和當前物件相關的Class物件
d.方法notify,notifyall,wait都是用來對給定物件進行執行緒同步的
Override和Overload的含義以及區別
a.Overload顧名思義是重新載入,它可以表現類的多型性,可以是函式裡面可以有相同的函式名但是引數名、返回值、型別不能相同;或者說可以改變引數、型別、返回值但是函式名字依然不變。
b.就是ride(重寫)的意思,在子類繼承父類的時候子類中可以定義某方法與其父類有相同的名稱和引數,當子類在呼叫這一函式時自動呼叫子類的方法,而父類相當於被覆蓋(重寫)了。
Java的四種引用,強弱軟虛,以及用到的場景
- 強引用:強引用有引用變數指向時永遠不會被垃圾回收,JVM寧願丟擲OutOfMemory錯誤也不會回收這種物件。
- .軟引用(SoftReference):如果一個物件具有軟引用,記憶體空間足夠,垃圾回收器就不會回收它;如果記憶體空間不足了,就會回收這些物件的記憶體。只要垃圾回收器沒有回收它,該物件就可以被程式使用。
- 弱引用(WeakReference):弱引用也是用來描述非必需物件的,當JVM進行垃圾回收時,無論記憶體是否充足,都會回收被弱引用關聯的物件。在java中,用java.lang.ref.WeakReference類來表示。
- 虛引用和前面的軟引用、弱引用不同,它並不影響物件的生命週期。在java中用java.lang.ref.PhantomReference類表示。如果一個物件與虛引用關聯,則跟沒有引用與之關聯一樣,在任何時候都可能被垃圾回收器回收。
軟引用及物件重獲方法實現Java物件的快取記憶體
Java的四種引用,強弱軟虛,用到的場景
AQS
- AQS使用一個int成員變數來表示同步狀態,通過內建的FIFO佇列來完成獲取資源執行緒的排隊工作。
private volatile int state;//共享變數,使用volatile修飾保證執行緒可見性
- 2種同步方式:獨佔式,共享式。獨佔式如ReentrantLock,共享式如Semaphore,CountDownLatch,組合式的如ReentrantReadWriteLock
- 節點的狀態
CANCELLED,值為1,表示當前的執行緒被取消;
SIGNAL,值為-1,表示當前節點的後繼節點包含的執行緒需要執行,也就是unpark;
CONDITION,值為-2,表示當前節點在等待condition,也就是在condition佇列中;
PROPAGATE,值為-3,表示當前場景下後續的acquireShared能夠得以執行;
值為0,表示當前節點在sync佇列中,等待著獲取鎖。 - 模板方法模式
protected boolean tryAcquire(int arg) : 獨佔式獲取同步狀態,試著獲取,成功返回true,反之為false
protected boolean tryRelease(int arg) :獨佔式釋放同步狀態,等待中的其他執行緒此時將有機會獲取到同步狀態;
protected int tryAcquireShared(int arg) :共享式獲取同步狀態,返回值大於等於0,代表獲取成功;反之獲取失敗;
protected boolean tryReleaseShared(int arg) :共享式釋放同步狀態,成功為true,失敗為false
AQS維護一個共享資源state,通過內建的FIFO來完成獲取資源執行緒的排隊工作。該佇列由一個一個的Node結點組成,每個Node結點維護一個prev引用和next引用,分別指向自己的前驅和後繼結點。雙端雙向連結串列。
- 獨佔式:樂觀的併發策略
acquire
a.首先tryAcquire獲取同步狀態,成功則直接返回;否則,進入下一環節;
b.執行緒獲取同步狀態失敗,就構造一個結點,加入同步佇列中,這個過程要保證執行緒安全;
c.加入佇列中的結點執行緒進入自旋狀態,若是老二結點(即前驅結點為頭結點),才有機會嘗試去獲取同步狀態;否則,當其前驅結點的狀態為SIGNAL,執行緒便可安心休息,進入阻塞狀態,直到被中斷或者被前驅結點喚醒。
release
release的同步狀態相對簡單,需要找到頭結點的後繼結點進行喚醒,若後繼結點為空或處於CANCEL狀態,從後向前遍歷找尋一個正常的結點,喚醒其對應執行緒。
- 獨佔式:樂觀的併發策略
- 共享式:
共享式地獲取同步狀態.同步狀態的方法tryAcquireShared返回值為int。
a.當返回值大於0時,表示獲取同步狀態成功,同時還有剩餘同步狀態可供其他執行緒獲取;
b.當返回值等於0時,表示獲取同步狀態成功,但沒有可用同步狀態了;
c.當返回值小於0時,表示獲取同步狀態失敗。 - AQS實現公平鎖和非公平鎖
非公平鎖中,那些嘗試獲取鎖且尚未進入等待佇列的執行緒會和等待佇列head結點的執行緒發生競爭。公平鎖中,在獲取鎖時,增加了isFirst(current)判斷,當且僅當,等待佇列為空或當前執行緒是等待佇列的頭結點時,才可嘗試獲取鎖。
Java併發包基石-AQS詳解
Java裡的阻塞佇列
7個阻塞佇列。分別是
ArrayBlockingQueue :一個由陣列結構組成的有界阻塞佇列。
LinkedBlockingQueue :一個由連結串列結構組成的有界阻塞佇列。
PriorityBlockingQueue :一個支援優先順序排序的無界阻塞佇列。
DelayQueue:一個使用優先順序佇列實現的無界阻塞佇列。
SynchronousQueue:一個不儲存元素的阻塞佇列。
LinkedTransferQueue:一個由連結串列結構組成的無界阻塞佇列。
LinkedBlockingDeque:一個由連結串列結構組成的雙向阻塞佇列。
新增元素
Java中的阻塞佇列介面BlockingQueue繼承自Queue介面。BlockingQueue介面提供了3個新增元素方法。
add:新增元素到佇列裡,新增成功返回true,由於容量滿了新增失敗會丟擲IllegalStateException異常
offer:新增元素到佇列裡,新增成功返回true,新增失敗返回false
put:新增元素到佇列裡,如果容量滿了會阻塞直到容量不滿
刪除方法
3個刪除方法
poll:刪除佇列頭部元素,如果佇列為空,返回null。否則返回元素。
remove:基於物件找到對應的元素,並刪除。刪除成功返回true,否則返回false
take:刪除佇列頭部元素,如果佇列為空,一直阻塞到佇列有元素並刪除
字元流和位元組流
- Java中的位元組流處理的最基本單位為單個位元組,它通常用來處理二進位制資料。兩個位元組流類是InputStream和OutputStream,它們分別代表了組基本的輸入位元組流和輸出位元組流。
- 字元流:Java中的字元流處理的最基本的單元是Unicode碼元(大小2位元組),它通常用來處理文字資料。
- 字元流與位元組流的區別
a,位元組流操作的基本單元為位元組;字元流操作的基本單元為Unicode碼元。
b,位元組流預設不使用緩衝區;字元流使用緩衝區。
c,位元組流通常用於處理二進位制資料,實際上它可以處理任意型別的資料,但它不支援直接寫入或讀取Unicode碼元;字元流通常處理文字資料,它支援寫入及讀取Unicode碼元。
java nio
RabbitMQ實現遲佇列
- Time To Live(TTL)
A: 通過佇列屬性設定,佇列中所有訊息都有相同的過期時間。
B: 對訊息進行單獨設定,每條訊息TTL可以不同。 - Dead Letter Exchanges(DLX)
RabbitMQ的Queue可以配置x-dead-letter-exchange 和x-dead-letter-routing-key(可選)兩個引數,如果佇列內出現了dead letter,則按照這兩個引數重新路由轉發到指定的佇列。
x-dead-letter-exchange:出現dead letter之後將dead letter重新發送到指定exchange