【020 標記接口】
標記接口沒有任何成員和方法,表明的是能夠具備某種功能,通常是在編譯器進行類型校驗。
① Cloneable接口,代表類可以被拷貝,否則調用對象的clone方法會拋出異常;另外clone方法是Object類的方法,底層實現通過本地方法,效率更高,因此重寫clone方法最好先調用super.clone方法;
② Serializable接口,代表類可以被序列化,否則使用ObjectInputStream的readObject和ObjectOutputStream的writeObject方法會拋出異常;
③ RandomAccess接口,代表類對象是否支持快速訪問,例如ArrayList和LinkedList,前者是連續存儲數據的,可以聲明為RandomAccess接口,則編譯器對于foreach的處理,前者會使用for+下標遍歷,而后者只能使用迭代器。
【021 序列化與反序列化】
① 序列化的對象建議自定義SerializableID成員的值,否則編譯器會自動分配,其作用是用來比較序列化前的類和序列化后的類是否是同一個版本;
② 序列化的過程,可以調用ObjectOutputStream的writeObject方法,static和transient類型的數據不會被寫入;寫入時,首先會寫入類類型,然后寫入父類類型,接著依次寫入各個成員類型和賦值;
③ 反序列化的類型,可以調用ObjectIutputStream的readObject方法,首先利用反射的方法,根據類型名稱生成對象,對于成員對象,也會根據成員類型,生成成員對象;
④ 父類類型不支持可序列化,則必須提供無參構造函數,成員必須支持可序列化。
【022 Java異常處理機制】
Java異??梢苑譃镋rror和Exception。Error是虛擬機錯誤,如OutOfMemoryError;Exception可以分為其他異常和RunTimeException,RunTimeException可以通過虛擬機JVM捕獲處理,如空指針異常、數組越界異常等,而其他異常必須主動捕獲,否則編譯不過,如IOException。
【023 Java動態代理】
動態代理可以分為Java代理和CGLib代理,Java代理是以接口為基礎的,被代理的類需要繼承某個接口,然后InvocationHandler的invoke方法實際上是對接口方法的封裝;而CGLib代理是以抽象類為基礎的.
【024 Java注解】
Java注解可以理解為一種特殊的接口類型@Interface,是描述類/方法/成員的一種元數據。注解可以擁有成員并為其指定默認值,通過注解形式標注的類/方法/成員,可以通過反射,獲取注解的成員并做出適當的操作。
【025 String原理】
String的內部核心成員是private final char[],編碼格式是UNICODE,意味著String的內容不可修改,但是String的引用可以替換的。另外String是final類型的,說明String不可以被繼承。此外,StringBuilder和StringBuffer也是final類型的,但是StringBuilder是非線程安全的,而StringBuffer通過Synchronized實現了線程安全。
【026 String的基本操作】
① String是唯一實現了運算符+重載的類,其底層實現是通過StringBuilder的appender方法完成的,最后再通過toString轉回字符串;
② 常量類型或者final類型的字符串進行+運算,編譯器會進行優化,直接記錄最終結果。
【027 集合的分類】
集合可以分為Collection和Map,Collection又可以分為List和Set。List是有序可重復的,通過繼承AbstractCollection和AbstractList抽象類,實現了三個分支:ArrayList、Vector和LinkedList,其中Vector是棧Stack的父類,而LinkedList是隊列Queue的實現類。Set的實現是基于Map的Key值,因此是無順序不可重復的,通過繼承AbstractCollection和AbstractSet實現了TreeSet、HashSet和LinkedHashSet。
【028 Iterator接口】
Iterator是集合的內部實現類,幫助遍歷集合容器。其提供了一種Fast-Fail的線程遍歷安全機制,使用modeCount記錄當前的版本號,如果在遍歷過程中對集合進行過增刪改操作,會導致版本號變化,和遍歷的版本號不相同,從而拋出異常。Iterator內部還有兩個成員cursor游標和lastRet,分別指向下一個要訪問的位置和最后訪問的位置,通過這兩個成員實現方法hasNext、next和remove等。
【029 ArrayList的內部結構】
ArrayList是非線程安全的,允許空值,其內部核心是private transient Object[] elementData成員,默認大小為10,每次擴容為1.5倍的原有size+1。transient的作用是對象在序列化的過程,不會直接把elementData直接序列化,而是提供了readObject和writeObject方法,序列化的是elementData已使用的空間大小和保存的數據,避免序列化無意義的元素。同時ArrayList聲明了Cloneable接口,并重寫clone方法實現深拷貝。
【030 Vector的內部結構】
Vector與ArrayList最大的區別是通過Synchronized實現了線程安全,其內部核心是不帶有屬性transient的elementData成員,可以直接序列化,默認大小為10,每次擴容為2倍。
【031 LinkedList的內部結構】
LinkedList本質上是一個非線程安全的帶頭指針的雙向鏈表,核心成員是transient? Entry<E> header。Transient同樣表明其不可直接序列化,而是直接保存Entry中的數據內容,節省序列化空間。雙向鏈表在查找指定位置的元素時,可以根據index與size的關系,決定是從頭開始查找還是從尾倒序查找。
【032 HashMap的內部結構】
HashMap是非線程安全的,無序不可重復,Key和Value都允許是Null。核心成員transient Entry[] table,初始化大小為2的指數,數組的每個元素都是一個單向鏈表的首元素,沒有頭指針。Transient序列化是直接保存key和value的值。元素存儲時首先根據key值計算hashcode,對hsahcode二次hash后(盡量避免沖突),找到table中對應的元素,然后在對應的鏈表中依次通過equals比較key是否相同,key為NULL默認保存在table[0]中。HashMap的遍歷只能通過迭代器,依次對數組中的每個鏈表元素進行訪問,數組擴容和插入時,都是從鏈表元素單向訪問,因此是無序的。
【033 LinkedHashMap的內部結構】
LinkedHashMap是在HashMap的基礎上,提供了一種快速訪問機制,也就是在HashMap原有的數據結構上,添加一個帶頭指針的雙向鏈表,每次插入元素到數值的同時,也會插入一份到這個雙向鏈表,在對元素進行遍歷時直接使用??梢灾付ㄔ卦陔p向鏈表的遍歷順序,默認按照插入順序,也可以按照訪問順序,在每次訪問元素后,把元素移動到鏈表最前端。
【034 HashTable的內部結構】
HashTable與HashMap最大的區別是通過Synchronized實現了線程安全,由于支持多線程并發,因此Key和Value都不允許是Null。假設允許,通過get方法返回NULL的兩種可能性:元素不存在或者value為Null無法判斷,而單線程通過consistKey可以判斷。
【035 ConcurrentHashMap的內部結構】
ConcurrentHashMap在HashMap的基礎上,增加了Segments數組,每個元素都是可重入互斥鎖ReentrantLock的子類,擁有HashEntry數組的成員,與HashMap的核心成員一致??梢岳斫鉃?,Segments的每一個元素都是一個加鎖HashMap,不同的HashMap之間支持并發訪問。不過Segment的HashMap中鏈表的next是final的,這會導致的刪除元素時,該元素鏈表中排在元素之前的元素會被從頭復制一份并調用插入函數倒序插入鏈表。
【036 TreeMap的內部結構】
TreeMap的底層是紅黑樹,是在二叉搜索樹的基礎上,對有序數列的優化。通常使用的鏈表或者數組,要么插入耗時,要么增刪耗時,而平衡二叉樹可以均衡這一現象。
【037 IO流分類】
IO流可以分為字節流/字符流,以讀取的內容是原始字節還是封裝后的編碼字符來區分;或者節點流和處理流,節點流直接從指定位置獲取內容,而處理流作為封裝,對字節流添加輔助功能。
【038 FileInputStream和FileOutputStream的工作原理】
字節流,節點流。FileInputStream的方法read,通過參數設置讀取一個字節或者讀取一個字節數組,但核心都是調用native方法的read0,一個一個讀取字節,效率較低。FileOutputStream的方法write,每次輸出一個字節,核心也是調用native的write0,可以通過append屬性指定是追加到文件末尾還是重頭寫入。多個流可以同時訪問一個文件,各自維護一套對文件的索引。此外,文件流支持NIO操作,通過getChannel來獲取通道,但還是會阻塞在read和write方法上。
【039 BufferedInputStream和BufferedOutputStream的工作原理】
字節流,處理流。可以封裝在FileInputStream和FileOutputStream上,通過提供一個volatile的字節數組來支持緩存,默認大小是8M。與FileInputStream和FileOutputStream的read和write帶字節參數方法相比,好處是主動維護了一個可控制的字節數組,通過pos和count來記錄數組的讀寫,寫時一定要通過flush主動輸出,否則會等到緩沖區滿時再一次性輸出。其read和write方法是synchronized的,保證一次輸入輸出的字節流是安全的。
【040 DataInputStream和DataOutputStream的工作原理】
字節流,處理流。提供了一次讀寫多個字節并按編碼轉為int、char等各種類型的方法,例如readUTF,但是在調用過時方法readLine時,由于不需要指定編碼,會把2個byte直接轉化為char,然后把char數組轉化為String,出現亂碼;同時對于System.In輸入流,還會因為沒有讀到\r\n阻塞。
【041 ObjectInputStream和ObjectOutputStream的工作原理】
字節流,處理流。主要是實現對象序列化使用,readObject和writeObject。
【042 FileReader和FileWriter的工作原理】
字符流,節點流。底層實現主要是靠父類InputStreamReader和OutputStreamWriter來完成,通過StreamEncoder和StreamDecoder對FileInputStream和FileOutputStream讀取的字節進行編解碼。
【043 RandomAccessFile的工作原理】
RandomAccessFile支持雙向讀寫,同時實現接口DataInput和DataOutput接口,底層的read和write都是通過native方法實現,按照byte讀寫,可以通過seek(pos)支持快速訪問。
【044 NIO的工作原理】
NIO由Buffer、Channel和Selector三部分組成。Channel是一個雙向通道,從Buffer中讀取或寫入數據。Buffer是一塊連續的內存空間,子類可以指定數據類型,通過capacity、position和limit三個變量控制。Capacity指Buffer可以讀寫多少個類型數據,position表示下一個讀或寫的位置,從寫模式切換到讀模式時,會置為0;limit讀模式指向能讀取多少個數據,寫模式等于capacity。Selector是NIO的核心,通過把channel注冊到Selector上,通過select方法輪詢訪問各個channel,直到channel上有響應事件。
【045 BIO、NIO和AIO的區別】
BIO是同步阻塞的,NIO是同步非阻塞的,而AIO是異步非阻塞的。以Tomcat為例,BIO會使用ServerSocket的accept方法,一直阻塞到請求到來后,分配給線程池的Processor,然后重新accept阻塞;NIO使用ServerSocketChannel,并注冊到selector上,通過selector的select輪詢獲取請求;AIO采用主動通知的方式,獲取到的請求會主動推送給Proceesor。
【046 Java的線程狀態】
線程狀態可以分為就緒、運行、等待、結束。當調用Thread類的start方法,可以啟動一個線程進入就緒狀態;就緒狀態的線程通過系統調度,可以進入運行狀態;運行狀態的線程調用stop或destroy方法,直接進入結束狀態;而調用sleep或者wait等方法,進入等待狀態,等待事件通知后重新回到就緒隊列。
【047 線程中斷的方式及原理】
線程中斷的stop方法,會立即釋放線程持有的鎖進入結束狀態,但不會釋放所占有的資源;而destroy更加粗暴,直接結束進程,鎖和資源都不會被釋放;suspend是把線程掛起,直到resume后重新回到就緒狀態,但是鎖和資源也都不會被釋放。因此,以上三種方法都不推薦。Interrupt中斷本質上是設置線程的中斷標志位,如果線程在等待狀態,則拋出中斷異常后,進入就緒狀態,而在運行狀態的線程繼續運行,可以通過檢查isInterrupt,來主動退出。另外在實際的線程控制中,也是采用標志位來輪詢決定是否退出運行狀態的。
【048 線程休眠的方式及原理】
Thread的休眠方法有sleep、yield和join。Sleep是使線程自身休眠指定的ms,然后重新回到就緒狀態,不會釋放鎖;yield相當于sleep(0),直接進入休眠狀態,也不會釋放鎖;t.join是掛起線程t,等待調用該方法的線程執行結束或一段時間后,重新進入就緒狀態,join函數最大的特點在于它是一個synchronized的方法,內部實現通過wait函數來實現,因此會釋放占用的鎖。wait是Object的方法,需要結合notify或notifyAll來使用,必須包含在synchronized或者lock來使用,會釋放占用的鎖。
【049 生產者-消費者案例】
思路:生產者和消費者共享產品的內存隊列。多個生產者存放產品時要競爭隊列鎖,確保資源有序進入隊列,然后去判斷是否有空間,如果有,存放完產品,通知消費者并退出,如果沒有,進入wait等待狀態,直到消費者發送notifyAll喚醒;多個消費者消費產品時同樣也需要競爭隊列鎖,確保產品被依次取出。獲得鎖的消費者判斷是否有可用資源,如果有,則取出產品,通知生產者并退出,如果沒有,進入wait等待狀態,直到生產者發送notifyAll喚醒。只有一把隊列鎖,可以保證同一時間,最多只有一個生產者或消費者可以訪問隊列。
【050 啟動單個線程的方法】
第一種是繼承Thread類或者實現Runable接口,重寫run方法,然后調用Thread的start方法,通過底層的native實現多線程。Thread類本身就實現了Runable接口,并且內部有一個Runable的成員,通過判斷Runable成員是否為NULL,決定是調用Thread本身還是Runable對象的run方法。這種方法比較常用,缺點是缺少返回值。
第二種是實現Callable接口的call方法,并通過FutureTask的run方法和get方法實現線程的運行以及得到內部成員result的返回值。FutureTask是同時實現了Future和Runable接口。get是一個阻塞方法。
【051 Synchronized和Volatile關鍵字】
Synchronized在編譯階段會在包含的代碼塊前后分別加上monitorenter和monitorexit監視器,本質上也是通過鎖實現的,在Java對象的內存分配中會通過對象頭來標記鎖狀態,之前提到的偏向鎖、自旋鎖等都是對synchronized底層實現的優化。Monitor鎖對象會擁有_WaitSet和_EntryList,用來保存ObjectWaiter對象列表,ObjectWaiter對象就是封裝的等待線程對象。
Volatile是通過JVM的內存模型來實現的,擁有該關鍵字的對象每次修改后都會把對象的副本寫會內存中,而其余線程讀取該對象都必須從內存而不是ThreadLocal中加載。
【052 Lock和Synchronized】
雖然Synchronized本質是可重入鎖,但是運用上不直接使用鎖的lock和unlock方法方便。另外,鎖的lock方法可以指定獲取鎖的時間,如果獲取不了,不必阻塞。
【053 Lock的常用類】
ReentrantLock是最常用的可重入鎖,核心成員private final Sync sync是一個隊列同步器,由一個等待獲取鎖的隊列和一個鎖重用計算器count組成。ReentrantLock的lock方法最后會調用sync.lock方法實現。可以指定鎖為公平鎖還是非公平鎖。公平鎖是指排在等待隊列最前面的線程會獲取鎖,不會出現餓漢問題,但是效率不是最高的。而非公平鎖有JVM根據最優分配,決定下一個獲取鎖的線程,例如剛剛釋放鎖的線程馬上重新獲取,可以避免上下問切換,但會出現餓漢線程。
ReentrantReadAndWriterLock,讀寫鎖,同時擁有一把讀鎖和一把寫鎖,讀鎖之間不互斥,是共享鎖,而寫鎖是排他鎖。
【054 線程池的工作原理】
線程池的幾個重要變量:分為核心線程數、最大線程數、阻塞隊列。但需要一個線程時,首先會判斷線程池的核心線程數是否全部創建,沒有則直接創建線程放入核心線程池直接使用;如果線程池的核心線程數已全部創建,則先判斷核心線程是否有空閑,有空閑則調用空閑線程;如果沒有空閑線程,則判斷阻塞隊列是否有空閑可以放入線程,有則放入,沒有則判斷非核心線程是否有可以使用的,有則使用,沒有則交給系統的RejectExceptHandler處理;同時,完成調用的線程會去阻塞隊列中提取等待的線程任務。
【054擴展線程池的調用方法】
線程池的頂層抽象類是Executor,子類ExecutorService,常用的是ThreadPoolExecutor??梢酝ㄟ^構造函數創建常用的4類線程池,然后通過execute方法或者submit方法來實現具體的調用。execute方法可以接收一個runable對象,而submit方法接收一個callable對象并返回Future返回值。
【055 線程池的阻塞隊列】
① ArrayBlockingQueue,底層是Array,因此是有界FIFO的,只有一把ReentrantLock,通過putIndex和getIndex維護索引;
② LinkedBlockingQueue,底層是單鏈表結構,但是有head和last兩個節點分別標識頭尾,實現尾插入和頭取出,各有一把ReentrantLock;
③SynchronizedBlockingQueue,底層沒有任何阻塞隊列,每個操作必須等待前一個操作執行后才能調用;
④PriorityBlockingQueue,底層是Array,但是會通過comparator排序后,按照優先級放入。
【056 常見線程池】
① FixedThreadPoolExecutor,核心線程數=最大線程數=n,阻塞隊列LinkedBlockingQueue;
② CachedThreadPoolExecutor,核心線程數=0,最大線程數=INTEGER_MAX,非核心等待時間60s,阻塞隊列SynchronizedBlockingQueue,適用于異步的短任務;
③SingleThreadPoolExecutor,核心線程數=最大線程數=1,阻塞隊列LinkedBlockingQueue;
④ScheduledThreadPoolExecutor,阻塞隊列是封裝了PriorityBlockingQueue的DelayQueue。