Java社招面試題
文章目錄
StringBuffer 和 StringBuilder 的區別
一般的有死鎖怎么形成的,怎么解決死鎖
HashMap,ConcurrentHashMap,LinkedHashMap的區別
synchronized 和 ReentrantLock 的異同
SpringMVC的運行原理
分布式鎖怎么實現
BIO 和 NIO區別
new 一個對象,JVM 里面都干了啥
volatile 關鍵字
Synchronized 關鍵字在 1.6 做了哪些優化
AQS和CAS
StringBuffer 和 StringBuilder 的區別
可變性。String 不可變,StringBuilder 與 StringBuffer 是可變的。
String 類中使用只讀字符數組保存字符串,private?final?char?value [],所以是不可變的(Java 9 中底層把 char 數組換成了 byte 數組,占用更少的空間)。
StringBuilder 與 StringBuffer 都繼承自 AbstractStringBuilder 類,在 AbstractStringBuilder 中也是使用字符數組保存字符串,char [] value,這兩種對象都是可變的。
線程安全性。String 和 StringBuffer 是線程安全的,StringBuilder 是非線程安全的。
String 線程安全是因為其對象是不可變的,StringBuffer 線程安全是因為對方法加了同步鎖或者對調用的方法加了同步鎖。
StringBuilder并沒有對方法進行加同步鎖,所以是非線程安全的。
性能。
String 的性能較差,因為每次對 String 類型進行改變的時候,都會生成一個新的 String 對象,然后將指針指向新的 String 對象。
而 StringBuffer/StringBuilder 性能更高,是因為每次都是對對象本身進行操作,而不是生成新的對象并改變對象引用。一般情況下 StringBuilder 相比 StringBuffer 可獲得 10%~15% 左右的性能提升。
點評:
如果要操作少量的數據用 String; 單線程操作字符串緩沖區下操作大量數據 StringBuilder; 多線程操作字符串緩沖區下操作大量數據 StringBuffer;
一般的有死鎖怎么形成的,怎么解決死鎖
什么是線程死鎖?
死鎖是指兩個或兩個以上的進程(線程)在執行過程中,由于競爭資源或者由于彼此通信而造成的一種阻塞的現象,若無外力作用,它們都將無法推進下去。此時稱系統處于死鎖狀態或系統產生了死鎖。
死鎖產生的條件是什么?
(1) 互斥條件:該資源任意一個時刻只由一個線程占用;
(2) 請求與保持條件:一個線程 / 進程因請求資源而阻塞時,對已獲得的資源保持不放;
(3) 不剝奪條件:線程 / 進程已獲得的資源在末使用完之前不能被其他線程 / 進程強行剝奪,只有自己使用完畢后才釋放資源;
(4) 循環等待條件:若干線程 / 進程之間形成一種頭尾相接的循環等待資源關系。
如何避免線程死鎖?
針對死鎖產生的條件進行一一拆解:
(1) 破壞互斥條件:無法破壞,因為使用鎖的本意就是想讓它們互斥的(臨界資源需要互斥訪問);
(2) 破壞請求與保持條件:一次性申請所有的資源;
(3) 破壞不剝奪條件:占用部分資源的線程進一步申請其他資源時,如果申請不到,可以主動釋放它占有的資源;
(4) 破壞循環等待條件:按某一順序申請資源,釋放資源則反序釋放。破壞循環等待條件(最常用)。
HashMap,ConcurrentHashMap,LinkedHashMap的區別
1.HashMap 是線程不安全的,HashTable 是線程安全的。 2.HashMap 的鍵需要重新計算對象的 hash 值,而 HashTable 直接使用對象的 hashCode。 3.HashMap 的值和鍵都可以為 null,HashTable 的值和鍵都不能為 null。 4.HashMap 的數組的默認初始化大小為 16,HashTable 為 11;HashMap 擴容時會擴大兩倍,HashTable 擴大兩倍 + 1;
1
2
3
4
LinkedHashMap維護一個雙鏈表,可以將里面的數據按寫入的順序讀出
基礎特性不同:
HashMap 的 key 和 value 可以為 null,ConcurrentHashMap 的 key 和 value 不能為 null。
內部數據結構不同:
HashMap 在 JDK1.7 中采用的數據結構是數組 + 鏈表,在 JDK1.8 中采用的數據結構是數組 + 鏈表 / 紅黑二叉樹;
ConcurrentHashMap 在 JDK1.7 中采用的數據結構是分段的數組 + 鏈表,JDK1.8 的內部數據結構采用的數據結構是數組 + 鏈表 / 紅黑二叉樹(同 HashMap 一致)。
線程安全不同:
HashMap 是非線程安全的;
ConcurrentHashMap 是線程安全的;
ConcurrentHashMap
JDK1.7 中,ConcurrentHashMap 采用 HashEntry+Segment的結構,ConcurrentHashMap 里一共 16個 Segment,Segment 是可重入鎖ReentrantLock的子類,每個 Segment 對應一個 HashEntry 鍵值對數組。當對 HashEntry 數組的數據進行修改時,必須首先獲得對應的 Segment 鎖,因此,多線程訪問容器里不同 Segment 的數據,就不會存在鎖競爭,從而提升并發性能。
JDK1.8 中則摒棄了 Segment 的概念,并發控制使用 synchronized 和 CAS 來操作,雖然在 JDK1.8 中還能看到 Segment 的數據結構,但是已經簡化了屬性,只是為了兼容舊版本。來看看核心的 put 方法。
補充
CAS原子語義來處理加減等操作,CAS 全稱Compare And Swap(比較與交換),通過判斷內存某個位置的值是否與預期值相等,如果相等則進行值更新。CAS 是內部是通過 Unsafe類實現,而 Unsafe 類的方法都是native的,在 JNI里是借助于一個 CPU 指令完成的,屬于原子操作。
synchronized 和 ReentrantLock 的異同
1. 相同點:Lock 能完成 synchronized 所實現的所有功能;
2. 不同點:Lock 有比 synchronized 更精確的線程語義和更好的性能,而且不強制性的要求一定要獲得鎖。synchronized 會自動釋放鎖,而 Lock 則要求手工釋放。更具體地來說,有以下差異:
(1) 含義不同
Synchronized 是關鍵字,屬于 JVM 層面,底層是通過 monitorenter 和 monitorexit 完成,依賴于 monitor 對象來完成;
Lock 是 java.util.concurrent.locks.lock 包下的,是 JDK1.5 以后引入的新API 層面的鎖;
(2) 使用方法不同
Synchronized 不需要用戶手動釋放鎖,代碼完成之后系統自動讓線程釋放鎖;ReentrantLock 需要用戶手動釋放鎖,沒有手動釋放可能導致死鎖;
(3) 等待是否可以中斷
Synchronized 不可中斷,除非拋出異常或者正常運行完成;
ReentrantLock 可以中斷。
一種是通過 tryLock(long timeout, TimeUnit unit),
另一種是lockInterruptibly ()放代碼塊中,調用interrupt ()方法進行中斷;
(4) 加鎖是否公平
Synchronized 是非公平鎖;
ReentrantLock 默認非公平鎖,
可以在構造方法傳入 boolean 值,true 代表公平鎖,false 代表非公平鎖;
SpringMVC的運行原理
分布式鎖怎么實現
CAP理論
即滿足一致性(Consistency)、可用性(Availability)和分區容錯性(Partition tolerance)
1.基于數據庫。
基于數據庫的實現方式的核心思想是:在數據庫中創建一個表,表中包含方法名等字段,并在方法名字段上創建唯一索引,想要執行某個方法,就使用這個方法名向表中插入數據,成功插入則獲取鎖,執行完成后刪除對應的行數據釋放鎖。
2.基于緩存環境,redis,memcache等。
(1)獲取鎖的時候,使用setnx加鎖,并使用expire命令為鎖添加一個超時時間,超過該時間則自動釋放鎖,鎖的value值為一個隨機生成的UUID,通過此在釋放鎖的時候進行判斷。
(2)獲取鎖的時候還設置一個獲取的超時時間,若超過這個時間則放棄獲取鎖。
(3)釋放鎖的時候,通過UUID判斷是不是該鎖,若是該鎖,則執行delete進行鎖釋放。
3.基于zookeeper。
(1)創建一個目錄mylock;
(2)線程A想獲取鎖就在mylock目錄下創建臨時順序節點;
(3)獲取mylock目錄下所有的子節點,然后獲取比自己小的兄弟節點,如果不存在,則說明當前線程順序號最小,獲得鎖;
(4)線程B獲取所有節點,判斷自己不是最小節點,設置監聽比自己次小的節點;
(5)線程A處理完,刪除自己的節點,線程B監聽到變更事件,判斷自己是不是最小的節點,如果是則獲得鎖。
BIO 和 NIO區別
BIO(Blocking IO)阻塞IO
NIO(Non-Blocking IO)非阻塞IO
共同點:兩者都是同步操作。即必須先進行IO操作后才能進行下一步操作。
不同點:
BIO多線程對某資源進行IO操作時會出現阻塞,即一個線程進行IO操作完才會通知另外的IO操作線程,必須等待。
NIO多線程對某資源進行IO操作時會把資源先操作至內存緩沖區。然后詢問是否IO操作就緒,是則進行IO操作,否則進行下一步操作,然后不斷的輪詢是否IO操作就緒,直到iIO操作就緒后進行相關操作。
new 一個對象,JVM 里面都干了啥
先是加載,驗證,準備,解析,初始化
volatile 關鍵字
從原子性,可見性,指令重排三個方面說了
1.保證可見性:線程之間可見性(及時通知)
2.不保證原子性
3.禁止指令重排
Synchronized 關鍵字在 1.6 做了哪些優化
從鎖消除,鎖粗化,偏向鎖,輕量級鎖,重量級鎖解鎖了一遍。
1.適應自旋鎖:為了減少線程狀態改變帶來的消耗 不停地執行當前線程
2.鎖消除:不可能存在共享數據競爭的鎖進行消除
3.鎖粗化: 將連續的加鎖 精簡到只加一次鎖
4.輕量級鎖: 無競爭條件下 通過CAS消除同步互斥
5.偏向鎖:無競爭條件下 消除整個同步互斥,連CAS都不操作。
AQS和CAS
CAS
CAS(Compare And Swap),即比較并交換。是解決多線程并行情況下使用鎖造成性能損耗的一種機制, CAS操作包含三個操作數—— 內存位置(V)、預期原值(A)和新值(B)。 如果 內存位置的值與預期原值相匹配,那么處理器會自動將該位置值更新為新值。否則,處理器不做任何操作。 無論哪種情況,它都會在CAS指令之前返回該位置的值。CAS有效地說明了“
我認為位置V應該包含值A;如果包含該值,則將B放到這個位置;否則,不要更改該位置,只告訴我這個位置現在的值即可。
AQS
AQS 的原理
抽象隊列同步器
AQS(AbstractQueuedSynchronizer)核心思想是,如果被請求的資源空閑,則將當前請求資源的線程設置為有效的工作線程,并且將共享資源設置為鎖定狀態;如果被請求的資源被占用,則需要一套線程阻塞等待以及喚醒分配的機制,該機制基于一個 FIFO(先進先出)的等待隊列實現。
AQS 的應用
作為一個用來構建鎖和同步器的框架,AQS 能簡單且高效地構造出大量同步器,事實上 java.util.concurrent.concurrent 包內許多并發類都是基于 AQS 構建。這些同步器從資源共享方式的方式來看,可以分為兩類:
(1)Exclusive(獨占):只有一個線程能執行,如 ReentrantLock。又可分為公平鎖和非公平鎖:
A、公平鎖:按照線程在隊列中的排隊順序,先到者先拿到鎖;
B、非公平鎖:當線程要獲取鎖時,無視隊列順序直接去搶鎖,誰搶到就是誰的。
(2) Share(共享):多個線程可同時執行,如 Semaphore/CountDownLatch/CyclicBarrier 等。
此外,也可以通過 AQS 來自定義同步器,自定義同步器在實現時只需要實現共享資源 state 的獲取與釋放方式即可,至于具體線程等待隊列的維護(如獲取資源失敗入隊 / 喚醒出隊等),AQS 已經在頂層實現好了。
Java 任務調度 容器
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。