日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > java >内容正文

java

Java面试手册——高频问题总结(二)

發(fā)布時(shí)間:2024/3/13 java 23 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java面试手册——高频问题总结(二) 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

這里將Java集合和垃圾回收的知識(shí)總結(jié),放到(二)中。對(duì)Java平臺(tái)的理解、Java基礎(chǔ)知識(shí)、面向?qū)ο笳?qǐng)參考Java面試手冊(cè)——高頻問題總結(jié)(一)

Java高頻問題面試:

序號(hào)文章
1Java面試手冊(cè)——高頻問題總結(jié)(一)
2Java面試手冊(cè)——高頻問題總結(jié)(二)
3Java基礎(chǔ)面試突擊
4Java虛擬機(jī)——JVM總結(jié)
5JVM面試突擊

文章目錄

      • 四、Java集合
        • 1. Java集合框架的基礎(chǔ)接口有哪些?
        • 2. Collection 和 Collections 有什么區(qū)別?
        • 3. List、Set、Map是否繼承自Collection接口?
        • 4. HashMap 和 HashTable 的區(qū)別?
        • 5. ArrayList、Vector 和 LinkedList 的區(qū)別是什么?ArrayList 是否會(huì)越界?
        • 6. Array 和 ArrayList 區(qū)別?
        • 7. HashMap 和 ConcurrentHashMap 的區(qū)別?
        • 8. HashMap的內(nèi)部具體如何實(shí)現(xiàn)(高頻問題)?
        • 9. HashMap 底層,負(fù)載因子,為啥是2^n?
        • 10. ConcurrentHashMap的原理是什么?ConcurrentHashMap 鎖加在了什么地方?
        • 11. TreeMap 底層,紅黑樹原理?
        • 12. 什么是迭代器?Iterator 和 ListIterator 的區(qū)別是什么?
        • 13. HashSet 的實(shí)現(xiàn)原理?
        • 14. 快速失敗(fail-fast)和安全失敗(fail-safe)的區(qū)別是什么?
        • 15. HashMap操作注意事項(xiàng)以及優(yōu)化?
      • 五、 JVM 和 垃圾回收 GC
        • 1. JVM、JDK和JRE的關(guān)系
        • 2. JVM回收算法和回收器,CMS采用哪種回收算法,怎么解決內(nèi)存碎片問題?
        • 3. 類加載過程
        • 4. JVM分區(qū),JVM內(nèi)存結(jié)構(gòu)
        • 5. Java虛擬機(jī)的作用?
        • 6. GC如何判斷對(duì)象需要被回收?
        • 7. JVM內(nèi)存模型是什么?
        • 8. JVM是如何實(shí)現(xiàn)線程的?
        • 9. JVM最大內(nèi)存限制是多少?
        • 10. 描述一下JVM加載class文件的原理機(jī)制?
        • 11. 除了哪個(gè)區(qū)域外,虛擬機(jī)內(nèi)存其它運(yùn)行區(qū)域都會(huì)發(fā)生OutOfMemoryError? 什么情況下會(huì)出現(xiàn)堆內(nèi)存溢出?
        • 12. Java中內(nèi)存泄漏是什么?什么時(shí)候會(huì)出現(xiàn)內(nèi)存泄漏?
        • 13. 垃圾回收器的原理是什么?
      • 六、Java 多線程
        • 1. 什么是進(jìn)程?什么是線程?
        • 2. 線程的實(shí)現(xiàn)方式?創(chuàng)建線程的方法?
        • 3. Thread類中的 start() 和 run() 方法有什么區(qū)別?啟動(dòng)一個(gè)線程是用哪個(gè)方法?
        • 4. 解釋下線程的幾種可用狀態(tài)?
        • 5. 多線程同步的方法?
        • 6. 如何在兩個(gè)線程間共享數(shù)據(jù)?
        • 7. 如何線程安全的實(shí)現(xiàn)一個(gè)計(jì)數(shù)器?
        • 8. 線程池的種類?運(yùn)行流程,參數(shù),策略?線程池有什么好處?
        • 9. 講一下AQS?
        • 10. 如何理解Java多線程回調(diào)的方法?
        • 11. 同步方法和同步代碼塊的區(qū)別?
        • 12. sleep() 和 wait() 有什么區(qū)別?
        • 13. 在監(jiān)視器(Monitor)內(nèi)部,如何做到線程同步的?程序應(yīng)該做哪種級(jí)別的同步?
        • 14. 同步和異步有何異同,在什么情況下分別使用他們?舉例說明。
        • 15. 說出所知道的線程同步的方法?
        • 16. 線程的sleep() 和yield() 方法有什么區(qū)別?
        • 17. 如何保證線程安全?
        • 18. CyclicBarrier 和 CountDownLatch的區(qū)別?
        • 19. 講一下公平鎖和非公平鎖在reetranklock里的實(shí)現(xiàn)。
        • 20. 講一下Synchronized,可重入是怎么實(shí)現(xiàn)的?
        • 21. 鎖和同步的區(qū)別。
        • 22. 什么是死鎖?如何確保N個(gè)線程可以訪問N個(gè)資源同時(shí)又不導(dǎo)致死鎖?
        • 23. 簡(jiǎn)述Synchronized 和 java.util.cncurrent.locks.Lock的異同?
      • 七、IO 和 NIO
        • 1. 什么是IO流?
        • 2. Java中有幾種類型的流?
        • 3. 字節(jié)流和字符流哪個(gè)好?怎么選擇?
        • 4. IO模型有幾種?分別介紹一下。
        • 5. NIO和IO的區(qū)別?以及使用場(chǎng)景?
        • 6. NIO的核心組件是什么,分別介紹一下?

四、Java集合

1. Java集合框架的基礎(chǔ)接口有哪些?

Collection為集合層級(jí)的根接口。一個(gè)集合代表一組對(duì)象,這些對(duì)象即為它的元素。Java平臺(tái)不提供這個(gè)接口任何直接實(shí)現(xiàn)。

Set是一個(gè)不能包含重復(fù)元素的集合。

List是一個(gè)有序集合,可以包含重復(fù)元素。可以通過它的索引來訪問任何元素。List更像長度動(dòng)態(tài)變換的數(shù)組。

Map是一個(gè)將key映射到value的對(duì)象。一個(gè)Map不能包含重復(fù)的key:每個(gè)key最多只能映射一個(gè)value。

一些其它的接口有Queue、Dequeue、SortedSet、SortedMap 和 ListIteator。

2. Collection 和 Collections 有什么區(qū)別?

Collection 是一個(gè)集合接口,它提供了對(duì)集合對(duì)象進(jìn)行基本操作的通用接口方法,所有集合都是它的子類,比如List、Set 等。

Collections 是一個(gè)包裝類,包含了很多靜態(tài)方法,不能被實(shí)例化,就像一個(gè)工具類,比如提供排序方法:Collections.sort(list)。

3. List、Set、Map是否繼承自Collection接口?

List,Set是,Map不是。Map是鍵值對(duì)映射容器,與List和Set有明顯的區(qū)別,而Set存儲(chǔ)的零散的元素且不允許有重復(fù)的元素,List是線性結(jié)構(gòu)的容器,適用于按數(shù)值索引訪問元素的情況。

4. HashMap 和 HashTable 的區(qū)別?

HashMap 和 HashTable 都實(shí)現(xiàn)了 Map 接口,很多特性相似。不同點(diǎn)如下:

  • HashMap 允許 key 和 value 為 null,而 HashTable 不允許
  • HashTable 是同步的,而 HashMap 不是,所以HashMap適合單線程環(huán)境,HashTable適合多線程環(huán)境。
  • 在Java1.4 中引入了LinkedHashMap,HashMap 的一個(gè)子類,想要遍歷順序,很容易從HashMap 轉(zhuǎn)向 LinkedHashMap,但是HashTable的順序是不可預(yù)知的。
  • HashMap提供對(duì)key的Set進(jìn)行遍歷,因?yàn)樗莊ail-fast 的,但HashTable 提供對(duì)key的Enumeration 進(jìn)行遍歷,它不支持fail-fast。
  • HashTable 被認(rèn)為是個(gè)遺留的類,如果尋求在迭代的時(shí)候修改Map, 應(yīng)該使用ConcurrentHashMap。
  • HashTable同步,HashMap異步:同步指多線程安全,異步指多線程不安全。在Hashtable中,所有的public方法都加上了synchronized,所以說Hashtable是同步的,而HashMap沒有,所以HashMap是異步的。

    詳細(xì)請(qǐng)參考:對(duì)比HashTable、HashMap、TreeMap有什么不同?

    5. ArrayList、Vector 和 LinkedList 的區(qū)別是什么?ArrayList 是否會(huì)越界?

    ArrayList 和 LinkedList的區(qū)別

  • 數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn): ArrayList是動(dòng)態(tài)數(shù)組的數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn),而LinkedList 是雙向鏈表的數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)。
  • 隨機(jī)訪問效率: ArrayList 比 LinkedList在隨機(jī)訪問的時(shí)候效率要高,因?yàn)長inkedList是線性的數(shù)據(jù)存儲(chǔ)方式,所以需要移動(dòng)指針從前往后依次查找。
  • 增加和刪除效率: 在非首尾的增加和刪除操作,LinkedList要比ArrayList效率要高,因?yàn)锳rrayList增刪操作要影響數(shù)組內(nèi)的其他數(shù)據(jù)的下標(biāo)。
  • 綜合來說,在需要頻繁讀取集合中的元素時(shí),更推薦使用ArrayList,而在插入和刪除操作較多時(shí),更推薦使用LinkedList。

    ArrayList 和 Vector 的異同

    ArrayList和Vector在很多時(shí)候都很類似。

  • 兩者都是基于索引的,內(nèi)部由一個(gè)數(shù)組支持。
  • 兩者維護(hù)插入的順序,我們可以根據(jù)插入順序來獲取元素。
  • ArrayList和Vector的迭代器實(shí)現(xiàn)都是fail-fast的。
  • ArrayList和Vector兩者允許null值,也可以使用索引值對(duì)元素進(jìn)行隨機(jī)訪問。
  • 以下是ArrayList和Vector的不同點(diǎn)。

  • Vector是同步的,而ArrayList不是。然而,如果你尋求在迭代的時(shí)候?qū)α斜磉M(jìn)行改變,你應(yīng)該使用CopyOnWriteArrayList。
  • ArrayList比Vector快,它因?yàn)橛型?#xff0c;不會(huì)過載。
  • ArrayList更加通用,因?yàn)槲覀兛梢允褂肅ollections工具類輕易地獲取同步列表和只讀列表。
  • 詳細(xì)請(qǐng)參考:對(duì)比Vector、ArrayList、LinkedList有何區(qū)別?

    ArrayList 并發(fā) add() 可能出現(xiàn)數(shù)組下標(biāo)越界異常。

    6. Array 和 ArrayList 區(qū)別?

  • Array 可以存儲(chǔ)基本數(shù)據(jù)類型和對(duì)象,ArrayList只能存儲(chǔ)對(duì)象。
  • Array 是指定固定大小,而ArrayList 大小是自動(dòng)擴(kuò)展的。
  • Array 內(nèi)置方法沒有ArrayList 多,比如 addAll、removeAll、iteration等方法只有ArrayList有。
  • 7. HashMap 和 ConcurrentHashMap 的區(qū)別?

    HashMap是線程不安全的,put時(shí)在多線程情況下,會(huì)形成環(huán)從而導(dǎo)致死循環(huán)。CoucurrentHashMap 是線程安全的,采用分段鎖機(jī)制,減少鎖的粒度。

    8. HashMap的內(nèi)部具體如何實(shí)現(xiàn)(高頻問題)?

    從結(jié)構(gòu)上來講,HashMap 是數(shù)組+鏈表+紅黑樹 (JDK1.8增加了紅黑樹部分)實(shí)現(xiàn)的,如下圖所示:
    兩個(gè)問題:數(shù)據(jù)底層具體存儲(chǔ)的是什么?這樣的存儲(chǔ)方式有什么優(yōu)點(diǎn)?

    (1)HashMap類中有一個(gè)非常重要的字段就是Node[] table, 即哈希桶數(shù)組,明顯它是一個(gè)Node數(shù)組。看一下Node[JDK1.8]:
    Node是HashMap的一個(gè)內(nèi)部類,實(shí)現(xiàn)了Map.Entry接口,本質(zhì)上就是一個(gè)映射(鍵值對(duì))。上圖中每個(gè)黑色圓點(diǎn)就是一個(gè)Node對(duì)象。

    (2)HashMap就是使用哈希表來存儲(chǔ)的。哈希表為解決哈希沖突,Java中HashMap采用鏈地址法。

    如果哈希桶數(shù)組很大,即使較差的Hash算法也會(huì)比較分散,如果哈希桶數(shù)組數(shù)組很小,即使好的Hash算法也會(huì)出現(xiàn)較多碰撞,所以就需要在空間成本和時(shí)間成本之間權(quán)衡,其實(shí)就是在根據(jù)實(shí)際情況確定哈希桶數(shù)組的大小,并在此基礎(chǔ)上設(shè)計(jì)好的hash算法減少Hash碰撞。那么通過什么方式來控制map使得Hash碰撞的概率又小,哈希桶數(shù)組(Node] table)占用空間又少呢?答案就是好的Hash算法和擴(kuò)容機(jī)制

    9. HashMap 底層,負(fù)載因子,為啥是2^n?

    負(fù)載因子默認(rèn)是0.75,2^n 是為了讓散列更加均勻,例如出現(xiàn)極端情況都散列在數(shù)組中的一個(gè)下標(biāo),那么HashMap會(huì)由 O(1) 復(fù)雜度退化為 O(n) 的。

    10. ConcurrentHashMap的原理是什么?ConcurrentHashMap 鎖加在了什么地方?

    (1)ConcurrentHashMap的原理是什么?
    ConcurrentHashMap類中包含兩個(gè)靜態(tài)內(nèi)部類HashEntrySegment

    HashEntry 用來封裝映射表的鍵/值對(duì)Segment用來充當(dāng)鎖的角色,每個(gè)Segment 對(duì)象守護(hù)整個(gè)散列映射表的若干個(gè)桶。每個(gè)桶是由若干個(gè)HashEntry對(duì)象鏈接起來的鏈表。一個(gè)ConcurrentHashMap 實(shí)例中包含由若干個(gè)Segment對(duì)象組成的數(shù)組。HashEntry 用來封裝散列映射表中的鍵值對(duì)。在HashEntry類中, key,hash 和 next域都被聲明為final 型,value域被聲明為 volatile型

    在ConcurrentHashMap中,在散列時(shí)如果產(chǎn)生“碰撞”,將采用“分離鏈接法”來處理“碰撞”:把“碰撞”的 HashEntry對(duì)象鏈接成一個(gè)鏈表。由于HashEntry的next域?yàn)閒inal 型,所以新節(jié)點(diǎn)只能在鏈表的表頭處插入。下圖是在一個(gè)空桶中依次插入A,B,C三個(gè)HashEntry對(duì)象后的結(jié)構(gòu)圖:

    插入三個(gè)節(jié)點(diǎn)后桶的結(jié)構(gòu)示意圖:

    注意:由于只能在表頭插入,所以鏈表中節(jié)點(diǎn)的順序和插入的順序相反。

    Segment類繼承于ReentrantLock 類,從而使得Segment對(duì)象能充當(dāng)鎖的角色。每個(gè)Segment 對(duì)象用來守護(hù)其(成員對(duì)象 table 中)包含的若干個(gè)桶。

    (2)ConcurrentHashMap 鎖加在了什么地方?

    加在每個(gè) Segment 上面。

    (3)ConcurrentHashMap有什么優(yōu)勢(shì),1.7,1.8區(qū)別?

    Concurrenthashmap線程安全的,1.7是在 jdk1.7中采用Segment + HashEntry的方式進(jìn)行實(shí)現(xiàn)的,lock 加在Segment上面。1.7size計(jì)算是先采用不加鎖的方式,連續(xù)計(jì)算元素的個(gè)數(shù),最多計(jì)算3次:

  • 如果前后兩次計(jì)算結(jié)果相同,則說明計(jì)算出來的元素個(gè)數(shù)是準(zhǔn)確的;
  • 如果前后兩次計(jì)算結(jié)果都不同,則給每個(gè)Segment進(jìn)行加鎖,再計(jì)算一次元素的個(gè)數(shù);
  • 1.8 中放棄了Segment臃腫的設(shè)計(jì),取而代之的是采用Node + CAS+ Synchronized來保證并發(fā)安全進(jìn)行實(shí)現(xiàn),1.8中使用一個(gè)volatile類型的變量baseCount記錄元素的個(gè)數(shù),當(dāng)插入新數(shù)據(jù)或則刪除數(shù)據(jù)時(shí),會(huì)通過addCount()方法更新baseCount,通過累加baseCount和CounterCell數(shù)組中的數(shù)量,即可得到元素的總個(gè)數(shù)。

    11. TreeMap 底層,紅黑樹原理?

    TreeMap 的實(shí)現(xiàn)就是紅黑樹數(shù)據(jù)結(jié)構(gòu),也就說是一棵自平衡的排序二叉樹,這樣就可以保證當(dāng)需要快速檢索指定節(jié)點(diǎn)。

    紅黑樹的插入、刪除、遍歷時(shí)間復(fù)雜度都為0(log N),所以性能上低于哈希表。但是哈希表無法提供鍵值對(duì)的有序輸出,紅黑樹因?yàn)槭桥判虿迦氲?#xff0c;可以按照鍵的值的大小有序輸出。

    紅黑樹性質(zhì):

  • 性質(zhì)1:每個(gè)節(jié)點(diǎn)要么是紅色,要么是黑色。
  • 性質(zhì)2:根節(jié)點(diǎn)永遠(yuǎn)是黑色的。
  • 性質(zhì)3:所有的葉節(jié)點(diǎn)都是空節(jié)點(diǎn)(即null),并且是黑色的。
  • 性質(zhì)4:每個(gè)紅色節(jié)點(diǎn)的兩個(gè)子節(jié)點(diǎn)都是黑色。(從每個(gè)葉子到根的路在上個(gè)會(huì)有網(wǎng)個(gè)連續(xù)的紅色節(jié)點(diǎn))。
  • 性質(zhì)5:從任一節(jié)點(diǎn)到其子樹中每個(gè)葉子節(jié)點(diǎn)的路徑都包含相同數(shù)量的黑色節(jié)點(diǎn)。
  • 12. 什么是迭代器?Iterator 和 ListIterator 的區(qū)別是什么?

    什么是迭代器?

    Iterator 提供了統(tǒng)一遍歷操作集合元素的統(tǒng)一接口, Collection接口實(shí)現(xiàn)Iterable接口。

    每個(gè)集合都通過實(shí)現(xiàn)Iterable接口中iterator()方法返回Iterator接口的實(shí)例,然后對(duì)集合的元素進(jìn)行迭代操作。

    有一點(diǎn)需要注意的是:在迭代元素的時(shí)候不能通過集合的方法刪除元素,否則會(huì)拋出ConcurrentModificationException異常.但是可以通過Iterator接口中的remove()方法進(jìn)行刪除。

    lterator和 ListIterator的區(qū)別是:

  • Iterator可用來遍歷Set和List集合,但是ListIterator只能用來遍歷List。
  • lterator對(duì)集合只能是前向遍歷,ListIterator既可以前向也可以后向。
  • ListIterator實(shí)現(xiàn)了Iterator接口,并包含其他的功能,比如:增加元素,替換元素,獲取前一個(gè)和后一個(gè)元素的索引,等等。
  • 13. HashSet 的實(shí)現(xiàn)原理?

    HashSet 是基于HashMap實(shí)現(xiàn)的,HashSet的底層使用HashMap來保存所有元素,因?yàn)镠ashSet的實(shí)現(xiàn)比較簡(jiǎn)單,相關(guān)HashSet的操作,基本上都是直接調(diào)用底層HashMap的相關(guān)方法來完成的,HashSet不允許有重復(fù)的值。

    14. 快速失敗(fail-fast)和安全失敗(fail-safe)的區(qū)別是什么?

    Iterator的安全失敗是基于對(duì)底層集合做拷貝,因此,它不受源集合上修改的影響。

    java.util包下面的所有的集合類都是快速失敗的,而java.util.concurrent包下面的所有的類都是安全失敗的

    快速失敗的迭代器會(huì)拋出ConcurrentModificationException異常,而安全失敗的迭代器永遠(yuǎn)不會(huì)拋出這樣的異常。

    15. HashMap操作注意事項(xiàng)以及優(yōu)化?

  • 擴(kuò)容是一個(gè)特別耗性能的操作、所以當(dāng)程序員在使用HashMap的時(shí)候,估算map的大小,初始化的時(shí)候給一個(gè)大致的數(shù)值,避免map進(jìn)行頻繁的擴(kuò)容。
  • 負(fù)載因子是可以修改的,也可以大于1,但是建議不要輕易修改,除非情況非常特殊。
  • HashMap是線程不安全的,不要在并發(fā)的環(huán)境中同時(shí)操作HashMap,建議使用ConcurrentHashMap
  • JDK1.8引入紅黑樹大程度優(yōu)化了HashMap的性能。
  • 五、 JVM 和 垃圾回收 GC

    1. JVM、JDK和JRE的關(guān)系

    見Java面試手冊(cè)——高頻問題總結(jié)(一)中JVM、JDK和JRE的關(guān)系。

    2. JVM回收算法和回收器,CMS采用哪種回收算法,怎么解決內(nèi)存碎片問題?

    見Java虛擬機(jī)——JVM總結(jié)中垃圾回收算法。

    3. 類加載過程

    見Java虛擬機(jī)——JVM總結(jié)中類加載過程。

    4. JVM分區(qū),JVM內(nèi)存結(jié)構(gòu)

    見Java虛擬機(jī)——JVM總結(jié)中JVM分區(qū),JVM內(nèi)存結(jié)構(gòu)。

    5. Java虛擬機(jī)的作用?

    解釋運(yùn)行字節(jié)碼程序,消除平臺(tái)相關(guān)性。

    JVM將Java字節(jié)碼解釋為具體平臺(tái)的具體指令。一般的高級(jí)語言如要在不同的平臺(tái)上運(yùn)行,至少需要編譯成不同的目標(biāo)代碼。而引入JVM后,Java語言在不同平臺(tái)上運(yùn)行時(shí)不需要重新編譯。Java語言使用模式Java虛擬機(jī)屏蔽了與具體平臺(tái)相關(guān)的信息,使得Java語言編譯程序只需生成在Java虛擬機(jī)上運(yùn)行的目標(biāo)代碼(字節(jié)碼),就可以在多種平臺(tái)上不加修改地運(yùn)行。Java虛擬機(jī)在執(zhí)行字節(jié)碼時(shí),把字節(jié)碼解釋成具體平臺(tái)上的機(jī)器指令執(zhí)行。

    假設(shè)一個(gè)場(chǎng)景,要求stop the world時(shí)間非常短,你會(huì)怎么設(shè)計(jì)垃圾回收機(jī)制?

    絕大多數(shù)新創(chuàng)建的對(duì)象分配在Eden 區(qū)。

    在Eden區(qū)發(fā)生一次GC后,存活的對(duì)象移到其中一個(gè)Survivor 區(qū)。

    在Eden區(qū)發(fā)生一次GC后,對(duì)象是存放到Survivor區(qū),這個(gè)Survivor區(qū)已經(jīng)存在其他存活的對(duì)象。

    一旦一個(gè)Survivor區(qū)已滿,存活的對(duì)象移動(dòng)到另外一個(gè)Survivor區(qū)。然后之前那個(gè)空間已滿Survivor區(qū)將置為空,沒有任何數(shù)據(jù)。

    經(jīng)過重復(fù)多次這樣的步驟后依舊存活的對(duì)象將被移到老年代。

    6. GC如何判斷對(duì)象需要被回收?

    即使在可達(dá)性分析算法中不可達(dá)的對(duì)象,也并非是“非回收不可”的, 這時(shí)候它們暫時(shí)處于“等待”階段,要真正宣告一個(gè)對(duì)象回收,至少要經(jīng)歷兩次標(biāo)記過程:如果對(duì)象在進(jìn)行可達(dá)性分析后發(fā)現(xiàn)沒有與GC Roots相連接的引用鏈,那它將會(huì)被第一次標(biāo)記并且進(jìn)行一次篩選,篩選的條件是此對(duì)象是否有必要執(zhí)行finalize()方法。當(dāng)對(duì)象沒有覆蓋finalize()方法,或者finalize()方法已經(jīng)被虛擬機(jī)調(diào)用過,虛擬機(jī)將這兩種情況都視為“沒有必要執(zhí)行”。(即意味著直接回收)

    如果這個(gè)對(duì)象被判定為有必要執(zhí)行finalize()方法,那么這個(gè)對(duì)象將會(huì)放置在一個(gè)叫做F-Queue 的隊(duì)列之中,并在稍后由一個(gè)由虛擬機(jī)自動(dòng)建立的、低優(yōu)先級(jí)的Finalizer線程去執(zhí)行它。這里所謂的“執(zhí)行”是指虛擬機(jī)會(huì)觸發(fā)這個(gè)方法,但并不承諾會(huì)等待它運(yùn)行結(jié)束,這樣做的原因是,如果一個(gè)對(duì)象在finalize()方法中執(zhí)行緩慢,或者發(fā)生了死循環(huán)(更極端的情況),將很可能會(huì)導(dǎo)致F-Queue 隊(duì)列中其他對(duì)象永久處于等待,甚至導(dǎo)致整個(gè)內(nèi)存回收系統(tǒng)崩潰。

    finalize()方法是對(duì)象逃脫回收的最后一次機(jī)會(huì),稍后GC將對(duì)F-Queue中的對(duì)象進(jìn)行第二次小規(guī)模的標(biāo)記,如果對(duì)象要在finalize()中跳出回收一一只要重新與引用鏈上的任何一個(gè)對(duì)象建立關(guān)聯(lián)即可,譬如把自己(this關(guān)鍵字)賦值給某個(gè)類變量或者對(duì)象的成員變量,那在第二次標(biāo)記時(shí)它將被移除出“即將回收”的集合;如果對(duì)象這時(shí)候還沒有逃脫,那基本上它就真的被回收了。

    7. JVM內(nèi)存模型是什么?

    Java內(nèi)存模型(簡(jiǎn)稱JMM),JMM決定一個(gè)線程對(duì)共享變量的寫入何時(shí)對(duì)另一個(gè)線程可見。從抽象的角度來看,JMM定義了線程和主內(nèi)存之間的抽象關(guān)系:線程之間的共享變量存儲(chǔ)在主內(nèi)存(main memory)中,每個(gè)線程都有一個(gè)私有的本地內(nèi)存(local memory),本地內(nèi)存中存儲(chǔ)了該線程以讀/寫共享變量的副本。

    本地內(nèi)存是JMM的一個(gè)抽象概念,并不真實(shí)存在。它涵蓋了緩存,寫緩沖區(qū),寄存器以及其他的硬件和編譯器優(yōu)化。其關(guān)系模型圖如下圖所示:

    8. JVM是如何實(shí)現(xiàn)線程的?

    線程是比進(jìn)程更輕量級(jí)的調(diào)度執(zhí)行單位。線程可以把一個(gè)進(jìn)程的資源分配和執(zhí)行調(diào)度分開。一個(gè)進(jìn)程里可以啟動(dòng)多條線程,各個(gè)線程可共享該進(jìn)程的資源(內(nèi)存地址,文件I0等),又可以獨(dú)立調(diào)度。線程是CPU調(diào)度的基本單位。

    主流OS都提供線程實(shí)現(xiàn)。Java語言提供對(duì)線程操作的同一API,每個(gè)已經(jīng)執(zhí)行start(),且還未結(jié)束的java.lang.Thread類的實(shí)例,代表了一個(gè)線程。

    Thread類的關(guān)鍵方法,都聲明為Native。這意味著這個(gè)方法無法或沒有使用平臺(tái)無關(guān)的手段來實(shí)現(xiàn),也可能是為了執(zhí)行效率。

    實(shí)現(xiàn)線程的方式:

    使用內(nèi)核線程實(shí)現(xiàn),內(nèi)核線程(Kernel-Level Thread, KLT)就是直接由操作系統(tǒng)內(nèi)核支持的線程。

    9. JVM最大內(nèi)存限制是多少?

    (1) 堆內(nèi)存分配

    JVM初始分配的內(nèi)存由-Xms指定,默認(rèn)是物理內(nèi)存的1/64;JVM最大分配的內(nèi)存由-Xmx指定,默認(rèn)是物理內(nèi)存的1/4。默認(rèn)空余堆內(nèi)存小于40%時(shí),JVM就會(huì)增大堆直到-Xmx的最大限制;空余堆內(nèi)存大于70%時(shí),JVM會(huì)減少堆直到-Xms的最小限制。因此服務(wù)器一般設(shè)置-Xms、一Xmx相等以避免在每次GC后調(diào)整堆的大小。

    (2) 非堆內(nèi)存分配

    JVM使用-XX:PermSize 設(shè)置非堆內(nèi)存初始值,默認(rèn)是物理內(nèi)存的1/64;由XX:MaxPermSize設(shè)置最大非堆內(nèi)存的大小,默認(rèn)是物理內(nèi)存的1/4。

    (3) VM最大內(nèi)存

    首先JVM內(nèi)存限制于實(shí)際的最大物理內(nèi)存,假設(shè)物理內(nèi)存無限大的話,JVM內(nèi)存的最大值跟操作系統(tǒng)有很大的關(guān)系。簡(jiǎn)單的說就32位處理器雖然可控內(nèi)存空間有4GB,但是具體的操作系統(tǒng)會(huì)給一個(gè)限制,這個(gè)限制一般是2GB-3GB(一般來說Windows系統(tǒng)下為1.5G-2G,Linux系統(tǒng)下為2G-3G),而64bit 以上的處理器就不會(huì)有限制了。

    10. 描述一下JVM加載class文件的原理機(jī)制?

    JVM中類的裝載是由ClassLoader和它的子類來實(shí)現(xiàn)的,Java ClassLoader是一個(gè)重要的Java運(yùn)行時(shí)系統(tǒng)組件。它負(fù)責(zé)在運(yùn)行時(shí)查找和裝入類文件的類。

    Java中的所有類,都需要由類加載器裝載到JVM中才能運(yùn)行。類加載器本身也是一個(gè)類,而它的工作就是把class文件從硬盤讀取到內(nèi)存中。在寫程序的時(shí)候,我們幾乎不需要關(guān)心類的加載,因?yàn)檫@些都是隱式裝載的,除非我們有特殊的用法,像是反射,就需要顯式的加載所需要的類。

    11. 除了哪個(gè)區(qū)域外,虛擬機(jī)內(nèi)存其它運(yùn)行區(qū)域都會(huì)發(fā)生OutOfMemoryError? 什么情況下會(huì)出現(xiàn)堆內(nèi)存溢出?

    程序計(jì)數(shù)器。

    堆內(nèi)存存儲(chǔ)對(duì)象的實(shí)例。我們只要不斷的創(chuàng)建對(duì)象,并保證gc roots到對(duì)象之間有可達(dá)路徑來避免垃圾回收機(jī)制清除這些對(duì)象,就會(huì)在對(duì)象數(shù)量達(dá)到最大,堆容量限制后,產(chǎn)生內(nèi)存溢出異常。

    空間什么情況下會(huì)拋出OutOfMemoryError?

    如果虛擬機(jī)在擴(kuò)展棧時(shí)無法申請(qǐng)到足夠的內(nèi)存空間,則拋出OutOfMemoryError。

    12. Java中內(nèi)存泄漏是什么?什么時(shí)候會(huì)出現(xiàn)內(nèi)存泄漏?

    Java中的內(nèi)存泄漏,廣義并通俗的說:不再會(huì)被使用的對(duì)象的內(nèi)存不能被回收,就是內(nèi)存泄漏。

    如果長生命周期的對(duì)象持有短生命周期的引用,就可能出現(xiàn)內(nèi)存泄漏。

    13. 垃圾回收器的原理是什么?

    對(duì)于GC來說,當(dāng)程序員創(chuàng)建對(duì)象時(shí),GC就開始監(jiān)控這個(gè)對(duì)象的地址、大小以及使用情況。通常,GC采用有向圖的方式記錄和管理堆(heap)中的所有對(duì)象。通過這種方式確定哪些對(duì)象是”可達(dá)的”,哪些對(duì)象是”不可達(dá)的”。當(dāng)GC確定一些對(duì)象為”不可達(dá)”時(shí),GC就有責(zé)任回收這些內(nèi)存空間。可以。程序員可以手動(dòng)執(zhí)行System.gc(),通知GC運(yùn)行,但是Java 語言規(guī)范并不保證GC一定會(huì)執(zhí)行。

    六、Java 多線程

    1. 什么是進(jìn)程?什么是線程?

    進(jìn)程是系統(tǒng)中正在運(yùn)行的一個(gè)程序,程序一旦運(yùn)行就是進(jìn)程。

    進(jìn)程可以看成程序執(zhí)行的一個(gè)實(shí)例。進(jìn)程是系統(tǒng)資源分配的獨(dú)立實(shí)體,每個(gè)進(jìn)程都擁有獨(dú)立的地址空間。一個(gè)進(jìn)程無法訪問另一個(gè)進(jìn)程的變量和數(shù)據(jù)結(jié)構(gòu),如果想讓一個(gè)進(jìn)程訪問另一個(gè)進(jìn)程的資源,需要使用進(jìn)程間通信,比如管道,文件,套接字等。

    線程是操作系統(tǒng)能夠進(jìn)行運(yùn)算調(diào)度的最小單位。它被包含在進(jìn)程之中,是進(jìn)程中的實(shí)際運(yùn)作單位。一條線程指的是進(jìn)程中一個(gè)單一順序的控制流,一個(gè)進(jìn)程中可以并發(fā)多個(gè)線程,每條線程并行執(zhí)行不同的任務(wù)。

    2. 線程的實(shí)現(xiàn)方式?創(chuàng)建線程的方法?

    創(chuàng)建線程有如下三種方式:

    一、繼承Thread類創(chuàng)建線程類

    • (1)定義Thread類的子類,并重寫該類的run方法,該run方法的方法體就代表了線程要完成的任務(wù)。因此把run()方法稱為執(zhí)行體。
    • (2)創(chuàng)建Thread子類的實(shí)例,即創(chuàng)建了線程對(duì)象。
    • (3)調(diào)用線程對(duì)象的start()方法來啟動(dòng)該線程。

    二、通過Runnable接口創(chuàng)建線程類

    • (1)定義runnable接口的實(shí)現(xiàn)類,并重寫該接口的run()方法,該run()方法的方法體同樣是該線程的線程執(zhí)行體。
    • (2)創(chuàng)建Runnable實(shí)現(xiàn)類的實(shí)例,并依此實(shí)例作為Thread的target來創(chuàng)建Thread對(duì)象,該Thread對(duì)象才是真正的線程對(duì)象。
    • (3)調(diào)用線程對(duì)象的start()方法來啟動(dòng)該線程。

    三、通過Callable和 Future創(chuàng)建線程

    • (1)創(chuàng)建Callable接口的實(shí)現(xiàn)類,并實(shí)現(xiàn)call()方法,該call()方法將作為線程執(zhí)行體,并且有返回值。

    • (2)創(chuàng)建Callable實(shí)現(xiàn)類的實(shí)例,使用FutureTask類來包裝Callable對(duì)象,該FutureTask對(duì)象封裝了該Callable對(duì)象的call()方法的返回值。

    • (3)使用FutureTask對(duì)象作為Thread對(duì)象的target創(chuàng)建并啟動(dòng)新線程。(4)調(diào)用FutureTask對(duì)象的get()方法來獲得子線程執(zhí)行結(jié)束后的返回值。

    3. Thread類中的 start() 和 run() 方法有什么區(qū)別?啟動(dòng)一個(gè)線程是用哪個(gè)方法?

    start() 和 run() 方法的區(qū)別:

  • start ()方法來啟動(dòng)線程,真正實(shí)現(xiàn)了多線程運(yùn)行。這時(shí)無需等待run方法體代碼執(zhí)行完畢,可以直接繼續(xù)執(zhí)行下面的代碼;通過調(diào)用Thread類的start()方法來啟動(dòng)一個(gè)線程,這時(shí)此線程是處于就緒狀態(tài),并沒有運(yùn)行。然后通過此Thread類調(diào)用方法run()來完成其運(yùn)行操作的,這里方法run()稱為線程體,它包含了要執(zhí)行的這個(gè)線程的內(nèi)容, Run方法運(yùn)行結(jié)束,此線程終止。然后CPU再調(diào)度其它線程。
  • run ()方法當(dāng)作普通方法的方式調(diào)用。程序還是要順序執(zhí)行,要等待run方法體執(zhí)行完畢后,才可繼續(xù)執(zhí)行下面的代碼;程序中只有主線程——這一個(gè)線程,其程序執(zhí)行路徑還是只有一條,這樣就沒有達(dá)到寫線程的目的。
  • 啟動(dòng)一個(gè)線程是調(diào)用 start()方法,使線程所代表的虛擬處理機(jī)處于可運(yùn)行狀態(tài),這意味著它可以由JVM調(diào)度并執(zhí)行。這并不意味著線程就會(huì)立即運(yùn)行。run() 方法可以產(chǎn)生必須退出的標(biāo)志來停止一個(gè)線程。

    4. 解釋下線程的幾種可用狀態(tài)?

    1.新建( new ) : 新創(chuàng)建了一個(gè)線程對(duì)象。

    2.可運(yùn)行( runnable ) : 線程對(duì)象創(chuàng)建后,其他線程(比如 main線程)調(diào)用了該對(duì)象的start()方法。該狀態(tài)的線程位于可運(yùn)行線程池中,等待被線程調(diào)度選中,獲取cpu的使用權(quán)。

    3.運(yùn)行( running ) : 可運(yùn)行狀態(tài)( runnable )的線程獲得了cpu時(shí)間片( timeslice ) ,執(zhí)行程序代碼。

    4.阻塞( block ) : 阻塞狀態(tài)是指線程因?yàn)槟撤N原因放棄了cpu使用權(quán),也即讓出了cpu timeslice ,暫時(shí)停止運(yùn)行。直到線程進(jìn)入可運(yùn)行( runnable )狀態(tài),才有機(jī)會(huì)再次獲得cputimeslice轉(zhuǎn)到運(yùn)行( running )狀態(tài)。阻塞的情況分三種:

    • (一).等待阻塞:運(yùn)行( running )的線程執(zhí)行o . wait()方法,JVM 會(huì)把該線程放入等待隊(duì)列( waitting queue )中。
    • (二).同步阻塞:運(yùn)行(running )的線程在獲取對(duì)象的同步鎖時(shí),若該同步鎖被別的線程占用,則JVM會(huì)把該線程放入鎖池( lock pool )中。
    • (三).其他阻塞:運(yùn)行( running )的線程執(zhí)行Thread . sleep ( long ms )或t . join()方法,或者發(fā)出了I/0請(qǐng)求時(shí),JVM會(huì)把該線程置為阻塞狀態(tài)。當(dāng)sleep()狀態(tài)超時(shí)、join()等待線程終止或者超時(shí)、或者Ⅰ/0處理完畢時(shí),線程重新轉(zhuǎn)入可運(yùn)行(runnable)狀態(tài)。

    5.死亡( dead ) : 線程run ()、 main()方法執(zhí)行結(jié)束,或者因異常退出了run()方法,則該線程結(jié)束生命周期。死亡的線程不可再次復(fù)生。

    5. 多線程同步的方法?

    可以使用synchronized、lock、volatile和ThreadLocal來實(shí)現(xiàn)同步。

    6. 如何在兩個(gè)線程間共享數(shù)據(jù)?

    如果一個(gè)類繼承Thread,則不適合資源共享。但是如果實(shí)現(xiàn)了Runable接口的話,則很容易的實(shí)現(xiàn)資源共享。實(shí)現(xiàn)Runnable接口或callable接口,適合多個(gè)相同或不同的程序代碼的線程去共享同一個(gè)資源。

    多個(gè)線程共享數(shù)據(jù)分兩種情況:

  • 如果多個(gè)線程執(zhí)行同一個(gè)Runnable實(shí)現(xiàn)類中的代碼,此時(shí)共享的數(shù)據(jù)放在Runnable實(shí)現(xiàn)類中;
  • 如果多個(gè)線程執(zhí)行不同的Runnable實(shí)現(xiàn)類中的代碼,此時(shí)共享數(shù)據(jù)和操作共享數(shù)據(jù)的方法封裝到一個(gè)對(duì)象中,在不同的Runnable實(shí)現(xiàn)類中調(diào)用操作共享數(shù)據(jù)的方法。
  • 7. 如何線程安全的實(shí)現(xiàn)一個(gè)計(jì)數(shù)器?

    可以使用加鎖, 比如 synchronized 或者 lock。 也可以使用 Concurrent 包下的原子類。

    8. 線程池的種類?運(yùn)行流程,參數(shù),策略?線程池有什么好處?

    線程池的種類:

    1、newFixedThreadPool 創(chuàng)建一個(gè)指定工作線程數(shù)量的線程池。每當(dāng)提交一個(gè)任務(wù)就創(chuàng)建一個(gè)工作線程,如果工作線程數(shù)量達(dá)到線程池初始的最大數(shù),則將提交的任務(wù)存入到池隊(duì)列中。

    2、newCachedThreadPool 創(chuàng)建一個(gè)可緩存的線程池。這種類型的線程池特點(diǎn)是:

    • 工作線程的創(chuàng)建數(shù)量幾乎沒有限制(其實(shí)也有限制的,數(shù)目為Interger. MAX_VALUE), 這樣可靈活的往線程池中添加線程。
    • 如果長時(shí)間沒有往線程池中提交任務(wù),即如果工作線程空閑了指定的時(shí)間(默認(rèn)為1分鐘),則該工作線程將自動(dòng)終止。終止后,如果你又提交了新的任務(wù),則線程池重新創(chuàng)建一個(gè)工作線程。

    3、newSingleThreadExecutor 創(chuàng)建一個(gè)單線程化的Executor,即只創(chuàng)建唯一的工作者線程來執(zhí)行任務(wù),如果這個(gè)線程異常結(jié)束,會(huì)有另一個(gè)取代它,保證順序執(zhí)行(我覺得這點(diǎn)是它的特色)。單工作線程最大的特點(diǎn)是可保證順序地執(zhí)行各個(gè)任務(wù),并且在任意給定的時(shí)間不會(huì)有多個(gè)線程是活動(dòng)的。

    4、newScheduleThreadPool 創(chuàng)建一個(gè)定長的線程池,而且支持定時(shí)的以及周期性的任務(wù)執(zhí)行,類似于Timer。(這種線程池原理暫還沒完全了解透徹)

    線程池的運(yùn)行流程,參數(shù),策略:

    線程池主要就是指定線程池核心線程數(shù)大小,最大線程數(shù),存儲(chǔ)的隊(duì)列,拒絕策略,空閑線程存活時(shí)長。當(dāng)需要任務(wù)大于核心線程數(shù)時(shí)候,就開始把任務(wù)往存儲(chǔ)任務(wù)的隊(duì)列里,當(dāng)存儲(chǔ)隊(duì)列滿了的話,就開始增加線程池創(chuàng)建的線程數(shù)量,如果當(dāng)線程數(shù)量也達(dá)到了最大,就開始執(zhí)行拒絕策略,比如說記錄日志,直接丟棄,或者丟棄最老的任務(wù)。

    線程池的好處:

    第一:降低資源消耗。通過重復(fù)利用己創(chuàng)建的線程降低線程創(chuàng)建和銷毀造成的消耗。

    第二:提高響應(yīng)速度。當(dāng)任務(wù)到達(dá)時(shí),任務(wù)可以不需要等到線程創(chuàng)建就能執(zhí)行。

    第三:提高線程的可管理性,線程是稀缺資源,如果無限制地創(chuàng)建,不僅會(huì)消耗系統(tǒng)資源,還會(huì)降低系統(tǒng)的穩(wěn)定性,使用線程池可以進(jìn)行統(tǒng)一分配、調(diào)優(yōu)和監(jiān)控。

    9. 講一下AQS?

    AQS其實(shí)就是一個(gè)可以給我們實(shí)現(xiàn)鎖的框架

    內(nèi)部實(shí)現(xiàn)的關(guān)鍵是:先進(jìn)先出的隊(duì)列、state狀態(tài)定義了內(nèi)部類ConditionObject

    擁有兩種線程模式獨(dú)占模式和共享模式。

    在LOCK包中的相關(guān)鎖(常用的有ReentrantLock、 ReadWriteLock)都是基于AQS來構(gòu)建,一般我們叫 AQS為同步器。

    10. 如何理解Java多線程回調(diào)的方法?

    所謂回調(diào),就是客戶程序C調(diào)用服務(wù)程序S中的某個(gè)方法A,然后S又在某個(gè)時(shí)候反過來調(diào)用C中的某個(gè)方法B,對(duì)于C來說,這個(gè)B便叫做回調(diào)方法。

    11. 同步方法和同步代碼塊的區(qū)別?

    區(qū)別:

    同步方法默認(rèn)用 this 或者當(dāng)前類 class 對(duì)象作為鎖;

    同步代碼塊可以選擇以什么來加鎖,比同步方法要更細(xì)粒度,我們可以選擇只同步會(huì)發(fā)生同步的問題的部分代碼而不是整個(gè)方法。

    12. sleep() 和 wait() 有什么區(qū)別?

    sleep 是線程類(Thread )的方法,導(dǎo)致此線程暫停執(zhí)行指定時(shí)間,把執(zhí)行機(jī)會(huì)給其他線程,但是監(jiān)控狀態(tài)依然保持,到時(shí)后會(huì)自動(dòng)恢復(fù)。調(diào)用sleep不會(huì)釋放對(duì)象鎖。

    waitObject類的方法,對(duì)此對(duì)象調(diào)用wait方法導(dǎo)致本線程放棄對(duì)象鎖,進(jìn)入等待此對(duì)象的等待鎖定池,只有針對(duì)此對(duì)象發(fā)出 notify 方法(或notifyAll)后本線程才進(jìn)入對(duì)象鎖定池準(zhǔn)備獲得對(duì)象鎖進(jìn)入運(yùn)行狀態(tài)。

    sleep用Thread調(diào)用,在非同步狀態(tài)下就可以調(diào)用, wait用同步監(jiān)視器調(diào)用,必須在同名代碼中調(diào)用。

    13. 在監(jiān)視器(Monitor)內(nèi)部,如何做到線程同步的?程序應(yīng)該做哪種級(jí)別的同步?

    監(jiān)視器和鎖在Java 虛擬機(jī)中是一塊使用的。監(jiān)視器監(jiān)視一塊同步代碼塊,確保一次只有一個(gè)線程執(zhí)行同步代碼塊。每一個(gè)監(jiān)視器都和一個(gè)對(duì)象引用相關(guān)聯(lián)。線程在獲取鎖之前不允許執(zhí)行同步代碼。

    14. 同步和異步有何異同,在什么情況下分別使用他們?舉例說明。

    如果數(shù)據(jù)將在線程間共享。例如正在寫的數(shù)據(jù)以后可能被另一個(gè)線程讀到,或者正在讀的數(shù)據(jù)可能已經(jīng)被另一個(gè)線程寫過了,那么這些數(shù)據(jù)就是共享數(shù)據(jù),必須進(jìn)行同步存取。

    當(dāng)應(yīng)用程序在對(duì)象上調(diào)用了一個(gè)需要花費(fèi)很長時(shí)間來執(zhí)行的方法,并且不希望讓程序等待方法的返回時(shí),就應(yīng)該使用異步編程,在很多情況下采用異步途徑往往更有效率。

    15. 說出所知道的線程同步的方法?

    wait()∶使一個(gè)線程處于等待狀態(tài),并且釋放所持有的對(duì)象的lock。

    sleep(): 使一個(gè)正在運(yùn)行的線程處于睡眠狀態(tài),是一個(gè)靜態(tài)方法,調(diào)用此方法要捕捉InterruptedException異常。

    notify(): 喚醒一個(gè)處于等待狀態(tài)的線程,注意的是在調(diào)用此方法的時(shí)候,并不能確切的喚醒某一個(gè)等待狀態(tài)的線程,而是由JVM確定喚醒哪個(gè)線程,而且不是按優(yōu)先級(jí)。

    Allnotity(): 喚醒所有處入等待狀態(tài)的線程,注意并不是給所有喚醒線程一個(gè)對(duì)象的鎖,而是讓它們競(jìng)爭(zhēng)。

    16. 線程的sleep() 和yield() 方法有什么區(qū)別?

  • sleep()方法給其他線程運(yùn)行機(jī)會(huì)時(shí)不考慮線程的優(yōu)先級(jí),因此會(huì)給低優(yōu)先級(jí)的線程以運(yùn)行的機(jī)會(huì);yield()方法只會(huì)給相同優(yōu)先級(jí)或更高優(yōu)先級(jí)的線程以運(yùn)行的機(jī)會(huì);
  • 線程執(zhí)行sleep()方法后轉(zhuǎn)入阻塞(blocked)狀態(tài),而執(zhí)行yield()方法后轉(zhuǎn)入就緒(ready)狀態(tài);
  • sleep()方法聲明拋出InterruptedException,而yield()方法沒有聲明任何異常;
  • sleep()方法比yield()方法(跟操作系統(tǒng)CPU調(diào)度相關(guān))具有更好的可移植性。
  • 17. 如何保證線程安全?

    通過合理的時(shí)間調(diào)度,避開共享資源的存取沖突。另外,在并行任務(wù)設(shè)計(jì)上可以通過適當(dāng)?shù)牟呗?#xff0c;保證任務(wù)與任務(wù)之間不存在共享資源,設(shè)計(jì)一個(gè)規(guī)則來保證一個(gè)客戶的計(jì)算工作和數(shù)據(jù)訪問只會(huì)被一個(gè)線程或一臺(tái)工作機(jī)完成,而不是把一個(gè)客戶的計(jì)算工作分配給多個(gè)線程去完成。

    18. CyclicBarrier 和 CountDownLatch的區(qū)別?

    CountDownLatch:

    計(jì)數(shù)器: 計(jì)數(shù)器只能使用一次。

    等待: 一個(gè)線程或多個(gè)等待另外n個(gè)線程完成之后才能執(zhí)行。

    CyclicBarrier:

    計(jì)數(shù)器: 計(jì)數(shù)器可以重置(通過reset()方法)。

    等待: n個(gè)線程相互等待,任何一個(gè)線程完成之前,所有的線程都必須等待。

    19. 講一下公平鎖和非公平鎖在reetranklock里的實(shí)現(xiàn)。

    如果一個(gè)鎖是公平的,那么鎖的獲取順序就應(yīng)該符合請(qǐng)求的絕對(duì)時(shí)間順序,FIFO。

    對(duì)于非公平鎖,只要CAS設(shè)置同步狀態(tài)成功,則表示當(dāng)前線程獲取了鎖,而公平鎖還需要判斷當(dāng)前節(jié)點(diǎn)是否有前驅(qū)節(jié)點(diǎn),如果有,則表示有線程比當(dāng)前線程更早請(qǐng)求獲取鎖,因此需要等待前驅(qū)線程獲取并釋放鎖之后才能繼續(xù)獲取鎖。

    20. 講一下Synchronized,可重入是怎么實(shí)現(xiàn)的?

    每個(gè)鎖關(guān)聯(lián)一個(gè)線程持有者和一個(gè)計(jì)數(shù)器。

  • 當(dāng)計(jì)數(shù)器為0時(shí)表示該鎖沒有被任何線程持有,那么任何線程都都可能獲得該鎖而調(diào)用相應(yīng)方法。

  • 當(dāng)一個(gè)線程請(qǐng)求成功后,JVM會(huì)記下持有鎖的線程,并將計(jì)數(shù)器計(jì)為1。此時(shí)其他線程請(qǐng)求該鎖,則必須等待。而該持有鎖的線程如果再次請(qǐng)求這個(gè)鎖,就可以再次拿到這個(gè)鎖,同時(shí)計(jì)數(shù)器會(huì)遞增。

  • 當(dāng)線程退出一個(gè)synchronized方法/塊時(shí),計(jì)數(shù)器會(huì)遞減,如果計(jì)數(shù)器為0, 則釋放該鎖。

  • 21. 鎖和同步的區(qū)別。

    (1)用法上的不同:

    synchronized 既可以加在方法上,也可以加載特定代碼塊上,而lock需要顯示地指定起始位置和終止位置。

    synchronized是托管給JVM執(zhí)行的,lock的鎖定是通過代碼實(shí)現(xiàn)的,它有比synchronized更精確的線程語義。

    (2)性能上的不同:
    lock接口的實(shí)現(xiàn)類ReentrantLock,不僅具有和synchronized相同的并發(fā)性和內(nèi)存語義,還多了超時(shí)的獲取鎖、定時(shí)鎖、等候和中斷鎖等。

    在競(jìng)爭(zhēng)不是很激烈的情況下,synchronized的性能優(yōu)于ReentrantLock,競(jìng)爭(zhēng)激烈的情況下synchronized 的性能會(huì)下降的非常快,而ReentrantLock 則基本不變。

    (3)鎖機(jī)制不同:

    synchronized獲取鎖和釋放鎖的方式都是在塊結(jié)構(gòu)中,當(dāng)獲取多個(gè)鎖時(shí),必須以相反的順序釋放,并且是自動(dòng)解鎖。而Lock則需要開發(fā)人員手動(dòng)釋放,并且必須在finally中釋放,否則會(huì)引起死鎖。

    22. 什么是死鎖?如何確保N個(gè)線程可以訪問N個(gè)資源同時(shí)又不導(dǎo)致死鎖?

    什么是死鎖?

    兩個(gè)線程或兩個(gè)以上線程都在等待對(duì)方執(zhí)行完畢才能繼續(xù)往下執(zhí)行的時(shí)候就發(fā)生了死鎖。結(jié)果就是這些線程都陷入了無限的等待中。

    例如,如果線程1鎖住了A,然后嘗試對(duì)B進(jìn)行加鎖,同時(shí)線程2已經(jīng)鎖住了B,接著嘗試對(duì)A進(jìn)行加鎖,這時(shí)死鎖就發(fā)生了。線程1永遠(yuǎn)得不到B,線程2也永遠(yuǎn)得不到A,并且它們永遠(yuǎn)也不會(huì)知道發(fā)生了這樣的事情。為了得到彼此的對(duì)象(A和B),它們將永遠(yuǎn)阻塞下去。這種情況就是一個(gè)死鎖。

    如何確保N個(gè)線程可以訪問N個(gè)資源同時(shí)又不導(dǎo)致死鎖?

    使用多線程的時(shí)候,一種非常簡(jiǎn)單的避免死鎖的方式就是: 指定獲取鎖的順序,并強(qiáng)制線程按照指定的順序獲取鎖。因此,如果所有的線程都是以同樣的順序加鎖和釋放鎖,就不會(huì)出現(xiàn)死鎖了。

    預(yù)防死鎖,預(yù)先破壞產(chǎn)生死鎖的四個(gè)條件。互斥不可能破壞,所以有如下三種方法:

  • 破壞請(qǐng)求和保持條件,進(jìn)程必須等所有要請(qǐng)求的資源都空閑時(shí)才能申請(qǐng)資源,這種方法會(huì)使資源浪費(fèi)嚴(yán)重(有些資源可能僅在運(yùn)行初期或結(jié)束時(shí)才使用,甚至根本不使用).允許進(jìn)程獲取初期所需資源后,便開始運(yùn)行,運(yùn)行過程中再逐步釋放自己占有的資源,比如有一個(gè)進(jìn)程的任務(wù)是把數(shù)據(jù)復(fù)制到磁盤中再打印,前期只需獲得磁盤資源而不需要獲得打印機(jī)資源,待復(fù)制完畢后再釋放掉磁盤資源。這種方法比第一種方法好,會(huì)使資源利用率上升。
  • 破壞不可搶占條件,這種方法代價(jià)大,實(shí)現(xiàn)復(fù)雜。
  • 破壞循壞等待條件,對(duì)各進(jìn)程請(qǐng)求資源的順序做一個(gè)規(guī)定,避免相互等待。這種方法對(duì)資源的利用率比前兩種都高,但是前期要為設(shè)備指定序號(hào),新設(shè)備加入會(huì)有一個(gè)問題,其次對(duì)用戶編程也有限制。
  • 23. 簡(jiǎn)述Synchronized 和 java.util.cncurrent.locks.Lock的異同?

    主要相同點(diǎn): Lock 能完成synchronized 所實(shí)現(xiàn)的所有功能。

    主要不同點(diǎn): Lock有比 synchronized更精確的線程語義和更好的性能。synchronized會(huì)自動(dòng)釋放鎖,而Lock一定要求程序員手工釋放,并且必須在finally 從句中釋放。

    七、IO 和 NIO

    1. 什么是IO流?

    它是一種數(shù)據(jù)的流從源頭流到目的地。比如文件拷貝,輸入流和輸出流都包括了。輸入流從文件中讀取數(shù)據(jù)存儲(chǔ)到進(jìn)程(process)中,輸出流從進(jìn)程中讀取數(shù)據(jù)然后寫入到目標(biāo)文件。

    2. Java中有幾種類型的流?

    按照單位大小:字符流、字節(jié)流。
    按照流的方向:輸入流、輸出流。

    3. 字節(jié)流和字符流哪個(gè)好?怎么選擇?

  • 絕大多數(shù)情況下使用字節(jié)流會(huì)更好,因?yàn)樽止?jié)流是字符流的包裝,而大多數(shù)時(shí)候IO操作都是直接操作磁盤文件,所以這些流在傳輸時(shí)都是以字節(jié)的方式進(jìn)行的(圖片都是按字節(jié)存儲(chǔ)的)。
  • 如果對(duì)于操作需要通過IO在內(nèi)存中頻繁處理字符串的情況使用字符流會(huì)好些,因?yàn)樽址骶邆渚彌_區(qū),提高了性能。
  • 字節(jié)流是, 選擇BufferedInputStream 和 BufferedOutputStream。
    字符流是,選擇BufferedReader 和 BufferedWriter。

    4. IO模型有幾種?分別介紹一下。

    阻塞IO、非阻塞IO、多路復(fù)用IO、信號(hào)驅(qū)動(dòng)IO以及異步IO。

    阻塞IO(blocking IO)

    應(yīng)用程序調(diào)用一個(gè)IO函數(shù),導(dǎo)致應(yīng)用程序阻塞,如果數(shù)據(jù)已經(jīng)準(zhǔn)備好,從內(nèi)核拷貝到用戶空間,否則一直等待下去

    一個(gè)典型的讀操作流程大致如下圖,當(dāng)用戶進(jìn)程調(diào)用recvfrom這個(gè)系統(tǒng)調(diào)用時(shí),kernel就開始了IO的第一個(gè)階段:準(zhǔn)備數(shù)據(jù),就是數(shù)據(jù)被拷貝到內(nèi)核緩沖區(qū)中的一個(gè)過程〈很多網(wǎng)絡(luò)IO數(shù)據(jù)不會(huì)那么快到達(dá),如沒收一個(gè)完整的UDP包),等數(shù)據(jù)到操作系統(tǒng)內(nèi)核緩沖區(qū)了,就到了第二階段:將數(shù)據(jù)從內(nèi)核緩沖區(qū)拷貝到用戶內(nèi)存,然后kernel返回結(jié)果,用戶進(jìn)程才會(huì)解除block狀態(tài),重新運(yùn)行起來。

    blocking IO的特點(diǎn)就是在IO執(zhí)行的兩個(gè)階段用戶進(jìn)程都會(huì)block住
    非阻塞IO(nonblocking IO)

    非阻塞I/О模型,我們把一個(gè)套接口設(shè)置為非阻塞就是告訴內(nèi)核,當(dāng)所請(qǐng)求的I/O操作無法完成時(shí),不要將進(jìn)程睡眠,而是返回一個(gè)錯(cuò)誤。這樣我們的I/O操作函數(shù)將不斷的測(cè)試數(shù)據(jù)是否已經(jīng)準(zhǔn)備好,如果沒有準(zhǔn)備好,繼續(xù)測(cè)試,直到數(shù)據(jù)準(zhǔn)備好為止。在這個(gè)不斷測(cè)試的過程中,會(huì)大量的占用CPU的時(shí)間

    當(dāng)用戶進(jìn)程發(fā)出read操作時(shí),如果kernel中數(shù)據(jù)還沒準(zhǔn)備好,那么并不會(huì)block用戶進(jìn)程,而是立即返回error,用戶進(jìn)程判斷結(jié)果是error,就知道數(shù)據(jù)還沒準(zhǔn)備好,用戶可以再次發(fā)read,直到kernel中數(shù)據(jù)準(zhǔn)備好,并且用戶再一次發(fā)read操作,產(chǎn)生system call,那么kernel 馬上將數(shù)據(jù)拷貝到用戶內(nèi)存,然后返回;

    所以nonblocking IO的特點(diǎn)是用戶進(jìn)程需要不斷的主動(dòng)詢問kernel數(shù)據(jù)好了沒有。

    阻塞IO一個(gè)線程只能處理一個(gè)IO流事件,要想同時(shí)處理多個(gè)IO流事件要么多線程要么多進(jìn)程,這樣做效率顯然不會(huì)高,而非阻塞IO可以一個(gè)線程處理多個(gè)流事件,只要不停地詢所有流事件即可,當(dāng)然這個(gè)方式也不好,當(dāng)大多數(shù)流沒數(shù)據(jù)時(shí),也是會(huì)大量浪費(fèi)CPU資源;為了避免CPU空轉(zhuǎn),引進(jìn)代理(select和poll,兩種方式相差不大),代理可以觀察多個(gè)流I/O事件,空閑時(shí)會(huì)把當(dāng)前線程阻塞掉,當(dāng)有一個(gè)或多個(gè)I/O事件時(shí),就從阻塞態(tài)醒過來,把所有IO流都輪詢一遍,于是沒有IO事件我們的程序就阻塞在select方法處,即便這樣依然存在問題,我們從select出只是知道有IO事件發(fā)生,卻不知道是哪幾個(gè)流,還是只能輪詢所有流,epoll這樣的代理就可以把哪個(gè)流發(fā)生怎樣的IO事件通知我們。

    多路復(fù)用IO模型(IO multiplexing)

    I/O多路復(fù)用就在于單個(gè)進(jìn)程可以同時(shí)處理多個(gè)網(wǎng)絡(luò)連接IO,基本原理就是select,poll,epoll這些個(gè)函數(shù)會(huì)不斷輪詢所負(fù)責(zé)的所有socket,當(dāng)某個(gè)socket有數(shù)據(jù)到達(dá)了,就通知用戶進(jìn)程,這三個(gè)functon會(huì)阻塞進(jìn)程,但和IO阻塞不同,這些函數(shù)可以同時(shí)阻塞多個(gè)IO操作,而且可以同時(shí)對(duì)多個(gè)讀操作,寫操作IO進(jìn)行檢驗(yàn),直到有數(shù)據(jù)到達(dá),才真正調(diào)用IO操作函數(shù),調(diào)用過程如下圖;

    所以IO多路復(fù)用的特點(diǎn)是通過一種機(jī)制一個(gè)進(jìn)程能同時(shí)等待多個(gè)文件描述符,而這些文件描述符(套接字描述符)其中任意一個(gè)進(jìn)入就緒狀態(tài),select函數(shù)就可以返回。

    IO多路復(fù)用的優(yōu)勢(shì)在于并發(fā)數(shù)比較高的IO操作情況,可以同時(shí)處理多個(gè)連接,和bloking IO一樣socket是被阻塞的,只不過在多路復(fù)用中socket是被select阻塞,而在阻塞IO中是被socket IO給阻塞。

    信號(hào)驅(qū)動(dòng)IO模型

    可以用信號(hào),讓內(nèi)核在描述符就緒時(shí)發(fā)送SIGIO信號(hào)通知我們,通過sigaction系統(tǒng)調(diào)用安裝一個(gè)信號(hào)處理函數(shù)。該系統(tǒng)調(diào)用將立即返回,我們的進(jìn)程繼續(xù)工作,也就是說它沒有被阻塞。當(dāng)數(shù)據(jù)報(bào)準(zhǔn)備好讀取時(shí),內(nèi)核就為該進(jìn)程產(chǎn)生一個(gè)SIGIO信號(hào)。我們隨后既可以在信號(hào)處理函數(shù)中調(diào)用recvfrom讀取數(shù)據(jù)報(bào),并通知主循環(huán)數(shù)據(jù)已經(jīng)準(zhǔn)備好待處理。

    特點(diǎn):等待數(shù)據(jù)報(bào)到達(dá)期間進(jìn)程不被阻塞。主循環(huán)可以繼續(xù)執(zhí)行,只要等待來自信號(hào)處理函數(shù)的通知:既可以是數(shù)據(jù)已準(zhǔn)備好被處理,也可以是數(shù)據(jù)報(bào)已準(zhǔn)備好被讀取。

    異步IO(asynchronous IO)

    異步IO告知內(nèi)核啟動(dòng)某個(gè)操作,并讓內(nèi)核在整個(gè)操作(包括將內(nèi)核數(shù)據(jù)復(fù)制到我們自己的緩沖區(qū))完成后通知我們,調(diào)用aio_read (Posix異步I/O函數(shù)以aio或lio開頭)函數(shù),給內(nèi)核傳遞描述字、緩沖區(qū)指針、緩沖區(qū)大小(與read相同的3個(gè)參數(shù))、文件偏移以及通知的方式,然后系統(tǒng)立即返回。我們的進(jìn)程不阻塞于等待I/O操作的完成。當(dāng)內(nèi)核將數(shù)據(jù)拷貝到緩沖區(qū)后,再通知應(yīng)用程序。

    用戶進(jìn)程發(fā)起read操作之后,立刻就可以開始去做其它的事。而另一方面,從kernel的角度,當(dāng)它受到一個(gè)asynchronous read之后,首先它會(huì)立刻返回,所以不會(huì)對(duì)用戶進(jìn)程產(chǎn)生任何block。然后,kernel會(huì)等待數(shù)據(jù)準(zhǔn)備完成,然后將數(shù)據(jù)拷貝到用戶內(nèi)存,當(dāng)這一切都完成之后,kernel會(huì)給用戶進(jìn)程發(fā)送一個(gè)signal,告訴它read操作完成了。

    5. NIO和IO的區(qū)別?以及使用場(chǎng)景?

    NIO即New IO,這個(gè)庫是在JDK1.4中才引入的。NIO和IO有相同的作用和目的,但實(shí)現(xiàn)方式不同,NIO主要用到的是塊,所以NIO的效率要比IO高很多。在Java API中提供了兩套NIO,一套是針對(duì)標(biāo)準(zhǔn)輸入輸出NIO,另一套就是網(wǎng)絡(luò)編程N(yùn)IO。

    NIO是為彌補(bǔ)傳統(tǒng)IO的不足而誕生的,但是尺有所短寸有所長,**NIO也有缺點(diǎn),因?yàn)镹IO是面向緩沖區(qū)的操作,每一次的數(shù)據(jù)處理都是對(duì)緩沖區(qū)進(jìn)行的,那么就會(huì)有一個(gè)問題,在數(shù)據(jù)處理之前必須要判斷緩沖區(qū)的數(shù)據(jù)是否完整或者已經(jīng)讀取完畢,如果沒有,假設(shè)數(shù)據(jù)只讀取了一部分,那么對(duì)不完整的數(shù)據(jù)處理沒有任何意義。**所以每次數(shù)據(jù)處理之前都要檢測(cè)緩沖區(qū)數(shù)據(jù)。

    那么NIO和IO各適用的場(chǎng)景是什么呢?

    如果需要管理同時(shí)打開的成千上萬個(gè)連接,這些連接每次只是發(fā)送少量的數(shù)據(jù),例如聊天服務(wù)器,這時(shí)候用NIO處理數(shù)據(jù)可能是個(gè)很好的選擇。

    而如果只有少量的連接,而這些連接每次要發(fā)送大量的數(shù)據(jù),這時(shí)候傳統(tǒng)的IO更合適。使用哪種處理數(shù)據(jù),需要在數(shù)據(jù)的響應(yīng)等待時(shí)間和檢查緩沖區(qū)數(shù)據(jù)的時(shí)間上作比較來權(quán)衡選擇。

    6. NIO的核心組件是什么,分別介紹一下?

    channel、buffer、selector

    什么是Channel?

    一個(gè)Channel(通道)代表和某一實(shí)體的連接,這個(gè)實(shí)體可以是文件、網(wǎng)絡(luò)套接字等。也就是說,通道是Java NIO提供的一座橋梁,用于我們的程序和操作系統(tǒng)底層I/O服務(wù)進(jìn)行交互。

    通道是一種很基本很抽象的描述,和不同的I/O服務(wù)交互,執(zhí)行不同的I/O操作,實(shí)現(xiàn)不一樣,因此具體的有FileChannel、SocketChannel等。

    通道使用起來跟Stream比較像,可以讀取數(shù)據(jù)到Buffer中,也可以把Buffer中的數(shù)據(jù)寫入通道。


    當(dāng)然,也有區(qū)別,主要體現(xiàn)在如下兩點(diǎn):

  • 一個(gè)通道,既可以讀又可以寫,而一個(gè)Stream是單向的(所以分InputStream 和 OutputStream)
  • 通道有非阻塞 I/O 模式
  • Java NIO 中最常用的通道實(shí)現(xiàn)?

  • FileChannel: 讀寫文件
  • DatagramChannel:UDP協(xié)議網(wǎng)絡(luò)通信
  • SocketChannel:TCP協(xié)議網(wǎng)絡(luò)通信
  • ServerSocketChannel:監(jiān)聽TCP連接
  • Buffer是什么?

    NIO中所使用的緩沖區(qū)不是一個(gè)簡(jiǎn)單的byte數(shù)組,而是封裝過的Buffer類,通過它提供的API,我們可以靈活的操縱數(shù)據(jù)。

    與Java基本類型相對(duì)應(yīng),NIO提供了多種Buffer類型,如ByteBuffer、CharBuffer、IntBuffer等,區(qū)別就是讀寫緩沖區(qū)時(shí)的單位長度不一樣(以對(duì)應(yīng)類型的變量為單位進(jìn)行讀寫)。

    核心Buffer的實(shí)現(xiàn)有哪些?

    核心的buffer實(shí)現(xiàn)有哪些:ByteBuffer、CharBuffer、DoubleBuffer、FloatBufferr、IntBuffer、LongBuffer、ShortBuffer,涵蓋了所有的基本數(shù)據(jù)類型(4類8種,除了Boolean)。也有其他的buffer如MappedByteBuffer。

    Buffer讀寫數(shù)據(jù)基本操作:

  • 將數(shù)據(jù)寫入buffer
  • 調(diào)用buffer.flip()
  • 將數(shù)據(jù)從buffer中讀取出來
  • 調(diào)用buffer.clear()或者buffer.compact()
  • 在寫buffer的時(shí)候,buffer會(huì)跟蹤寫入了多少數(shù)據(jù),需要讀buffer的時(shí)候,需要調(diào)用flip()來將buffer從寫模式切換成讀模式,讀模式中只能讀取寫入的數(shù)據(jù),而非整個(gè)buffer。

    當(dāng)數(shù)據(jù)都讀完了,你需要清空buffer以供下次使用,可以有2種方法來操作:調(diào)用clear()或者調(diào)用compact()

    區(qū)別: clear方法清空整個(gè)buffer,compact方法只清除你已經(jīng)讀取的數(shù)據(jù),未讀取的數(shù)據(jù)會(huì)被移到buffer的開頭,此時(shí)寫入數(shù)據(jù)會(huì)從當(dāng)前數(shù)據(jù)的末尾開始。

    Selector是什么?

    Selector(選擇器)是一個(gè)特殊的組件,用于采集各個(gè)通道的狀態(tài)(或者說事件)。我們先將通道注冊(cè)到選擇器,并設(shè)置好關(guān)心的事件,然后就可以通過調(diào)用select() 方法,靜靜地等待事件發(fā)生。

    通道可以監(jiān)聽哪幾個(gè)事件?

    通道有4個(gè)事件可供監(jiān)聽:

  • Accept:有可以接受的連接
  • Connect:連接成功
  • Read:有數(shù)據(jù)可讀
  • Write:可以寫入數(shù)據(jù)
  • 為什么要用Selector?

    如果用阻塞I/О,需要多線程(浪費(fèi)內(nèi)存)﹐如果用非阻塞I/O需要不斷重試(耗費(fèi)CPU)。Selector的出現(xiàn)解決了這尷尬的問題,非阻塞模式下,通過Selector,我們的線程只為已就緒的通道工作,不用盲目的重試了。比如,當(dāng)所有通道都沒有數(shù)據(jù)到達(dá)時(shí),也就沒有Read事件發(fā)生,我們的線程會(huì)在select()方法處被掛起,從而讓出了CPU資源。

    Selector處理多Channel

    要使用一個(gè)Selector,要注冊(cè)這個(gè)Selector的Channels。然后調(diào)用Selector的select()方法。這個(gè)方法會(huì)阻塞,直到它注冊(cè)的Channels當(dāng)中有一個(gè)準(zhǔn)備好了的事件發(fā)生。 當(dāng)select() 方法返回的時(shí)候,線程可以處理這些事件,如新的連接的到來,數(shù)據(jù)收到了等。

    總結(jié)

    以上是生活随笔為你收集整理的Java面试手册——高频问题总结(二)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。

    主站蜘蛛池模板: 久久大香 | 日韩一区二区三区在线看 | 欧美视频在线观看视频 | 人人看超碰 | 美女网站免费观看 | 精品一区二区三区入口 | 国产日b视频| 在线免费观看视频黄 | 精品国产影院 | 国产伦精品一区二区三区视频1 | 亚洲第一男人天堂 | 国产吞精囗交久久久 | 国产精品777 | sm调教羞耻姿势图片 | 日本特黄网站 | 影音先锋 日韩 | 久久com | 九九热九九热 | 亚洲福利一区二区三区 | 深爱婷婷网 | 看黄色一级 | 九九热国产在线 | 肉色超薄丝袜脚交一区二区图片 | 麻豆chinese极品少妇 | 国产91在线播放精品91 | 天天干天天要 | 天天做天天爱夜夜爽 | 中文字幕av解说 | 亚洲黄色a级片 | 午夜精品久久久久久久久 | 黄色三级小视频 | 九七超碰在线 | www四虎精品视频免费网站 | 给我看高清的视频在线观看 | 日韩av中文字幕在线免费观看 | 宅男午夜在线 | 中文字幕xxxx | 清草视频 | 男人天堂新地址 | www.狠狠艹| 无法忍受在线观看 | 中文字幕在线观看免费 | 免费日韩在线视频 | 精品爆乳一区二区三区 | 欧美激精品 | 欧美午夜精品一区 | 丝袜毛片| 捆绑无遮挡打光屁股调教女仆 | 成人黄色动漫在线观看 | 日韩欧美高清片 | 欧美浪妇xxxx高跟鞋交 | 欧美视频第一区 | 欧美aa级| 性xxx法国hd极品 | 天天操天天插天天射 | 免费网站在线观看黄色 | 亚洲乱妇老熟女爽到高潮的片 | 成人黄色网 | 欧美精品 在线观看 | 国产免费一区二区三区视频 | 黄色在线免费观看 | 精品xxxxx| 久久久久久久久久久91 | 亚洲欧美日韩天堂 | 四虎影院免费视频 | 欧美综合在线一区 | 中文字幕国产日韩 | 99久久精品久久久久久清纯 | 毛片在线免费观看视频 | 一级全黄毛片 | 中国特级黄色大片 | 免费av软件 | 午夜在线一区二区三区 | 国产精品手机在线 | 国产精品欧美亚洲 | 国产精品久久久久久免费免熟 | 男人深夜网站 | 97超碰伊人 | 午夜不卡视频 | 中文字幕35页 | 性欧美又大又长又硬 | 久久国产精品无码网站 | 极品在线观看 | 国产精品网站视频 | 亚洲成人偷拍 | 成人免费在线小视频 | 天堂网在线看 | 操人网| 午夜av片 | 一级黄毛片 | 精品人妻一区二区三区四区久久 | 真人一及毛片 | 久久亚洲电影 | 校园春色综合 | 欧美日韩一区二区不卡 | 黄色网址在线视频 | 清冷学长被爆c躁到高潮失禁 | 久久只有这里有精品 | 丰满人妻av一区二区三区 |