Java 知識 - 集合、多執行緒、IO、JVM
阿新 • • 發佈:2018-11-02
Collection
Java
Collection
- 新增、刪除等操作時可選操作,如 Arrays.asList,會產生固定大小的集合,會丟擲 UnsupportedOperationException
Set
- HashSet、TreeSet、LinkedHashSet
- HashSet、LinkedHashSet 注意需要對其中的元素定義 hashcode()
- SortedSet 有序集合
- NavigableSet 可導航集合,擁有 lower 等方法
Queue
- LinkedList、PriorityQueue,效能無差別
- DeQue,雙端佇列
- add 不能插入丟擲異常,offer 不會
- remove 移除空會丟擲異常,poll 不會
Map
- HashMap、TreeMap、LinkedHashMap、WeakHashMap、ConcurrentHashMap、IdentityHashMap
- LinkedHashMap,遍歷按照插入順序
- WeakHashMap 弱鍵對映,允許釋放對映所指的物件
- IdentityHashMap 用 == 進行比較
- SortedMap,排序 Map
Collections
- 對 Collection 的操作
- fill 填充物件陣列
- newSetFromMap 用法:實現 ConcurrentHashSet,newSetFromMap(new ConcurrentHashMap)
- disjoint 不相交集合
- checkedXXX 元素會檢查型別
- synchronizedXXX 同步集合
- unmodified 不可修改集合
- rotate 迴圈向後移動,最後的元素往前移
- shuffle 亂序
- sort 排序
- XXXBinarySearch 二分搜尋
Arrays
- 對陣列的操作
- sort 排序
- binarySearch 二分查詢元素
- stream 流式處理
System
- arrayCopy 記憶體級複製,淺複製
Comparator、Comparable
- Comparator 比較器,一般類直接實現
- Comparable 可比較的,一般作為引數傳入
XXXReference
- WeakReference System.GC() 就可以回收
- SoftReference 記憶體不足回收
- PhantomReference 呼叫 clear 方法才會清除
Spliterator
- splitable iterator,可分割迭代器
- 介面是Java為了並行遍歷資料來源中的元素而設計的迭代器
一些設計原則
- 將保持不變的事物和會改變的事物分離
正確的 equals 方法
- 自反性、對稱性、傳遞性、一致性、為 null 結果為 false
- 預設 equals 比較地址
hash
正確的 hashcode
- 給 result 賦予某個非 0 常量
- 對於每一個域,計算出一個雜湊碼 c
- 合併計算, result = result * 37 + c
- 返回 result
雜湊碼 c 計算公式
域型別 | 計算公式 |
---|---|
boolean | 0 1 |
byte char short int | (int) |
long | (int) f >> 32 |
float | Float.floatToIntBits |
double | (int)Double.doubleToIntBits >> 32 |
Object | hashcode |
陣列 | 每一項運用 |
hash 原理
- 先用 hashcode 計算,無衝突直接使用得到的值
- 有衝突,遍歷衝突所在的 list,equals 計算得出值
HashMap 的效能
- 負載因子,當前儲存 / 容量,預設 0.75
- 如果知道需要儲存多少資料,設定合適的容量
快速報錯
- 當非併發集合進行併發操作時,會快速丟擲 ConcurrentModificationException
Concurrent
Java
Description
- 併發提高在阻塞時的效能
- 函式式語言處理併發
- Java 執行緒機制是搶佔式
- 併發上下文切換代價高
- 實現:多執行緒
Priority
- 大多數情況下試圖操作執行緒優先順序是種錯誤
- 優先順序在各個作業系統實現不同,可靠的是 MAX_PRIORITY、MIN_PRIORITY、NORM_PRIORITY
KeyWord finally
- 在設定後臺程序時, 後臺執行緒的 finally 會不執行
終結任務
- 小心謹慎
執行緒狀態
- new
- runnable
- blocked
- dead
blocked 觸發條件
- sleep
- wait 掛起(notify 再次進入就緒狀態)
- 等待輸入輸出
- 試圖 synchronized,被其他執行緒鎖住
死鎖
- 潛伏期長,很難復現
產生條件
- 互斥
- 一個資源者持有資源
- 資源不可以被搶佔
- 迴圈等待
免鎖容器
- 讀取和寫入同時發生
- 寫入時複製建立讀取內容副本,讀取操作讀源陣列,寫入資料寫副本,完成之後以原子性的操作將副本換入源陣列
- 只能保證最終一致性
- CopyOnWriteXXX
Implementation
Runnable
- 定義任務
- 通常寫成無限迴圈的形式,除非有條件使得終止
Callable
- 可以返回
- Future
- get 獲取結果,如果結果為準備就緒,會阻塞
Thread
- 可以繼承實現 start 方法,也可以利用構造器設定 Runnable 的實現類的例項來執行任務
- start 啟動任務
- yield 向執行緒排程器生命發出已經執行完最重要的任務、可以切換執行緒的訊號,不能依賴
- registerNatives 執行緒註冊了自己,在 run 沒有執行完成之前無法被垃圾回收器清除
- sleep 休眠
- setPriority 設定優先順序
- setDaemon 設定後臺程序
- join 等待執行緒結束,可以設定超時時間
- setUncaughtExceptionHandler 設定異常處理器
- interrupted 可以查詢是否產生中斷,並清除中斷狀態
- wait 執行緒進入等待狀態,釋放鎖,可以從 notify 恢復,建議用 while 而不是 if 執行 wait 操作,因為別的操作可能又讓 wait 條件滿足
- wait 會釋放 synchronized 的鎖
- notify 喚醒一個等待的執行緒,保證所有執行緒 wait 條件相同,在一個類中可能有很多工,只喚醒當前任務相關的執行緒
- notifyAll 喚醒所有等待的執行緒
FutureTask
- 可取消
- 非同步計算
ThreadFactory
- Thread 工廠,生成 Thread
Executor
- 管理 Thread 物件
- 接收 ThreadFactory 作為引數
- execute 接受引數是實現 Runnable 類的例項,submit 接受引數是 Callable 類的例項
- shutdown 阻止後續提交任務
- CachedThreadPool 快取
- FixedThreadPool 固定大小
- SingleThreadExecutor 單執行緒,多工排隊
Lock
- 鎖物件,相當於 synchronized
- 在 finalize 中 unlock
- 實現鎖的高階功能,如超時
- lockInterruptibly 產生中斷
- 效能好(線性增長),可讀性略低
AtomicXXX
- 原子操作
- 鎖更安全一些,Atomic 系列類是為 java.util.concurrent 服務
- 樂觀鎖,效能一般很高,但併發量大時,CPU 消耗資源多
ThreadLocal
- 執行緒本地儲存
BlockingQueue
- 阻塞佇列
- 配合 Enum 使用,組建流程
PipedReader & PipedWriter
- 管道讀寫同步資料
Semaphore
- 訊號量,允許多個任務同時訪問一個資源
Exchanger
- 兩個任務交換物件的柵欄
synchronized
- 設定域為 private,保證只有方法可以訪問該欄位的值
- 共享域需要同步
- 鎖方法:當前物件只有一個執行緒能訪問該方法,效率低
- 鎖物件:用於臨界區,鎖方法中的部分程式碼片段,效率高
- 不具備超時等特性
- 具有鎖的物件可以訪問其他該物件加鎖的方法
- wait, notify, notifyAll 必須在 synchronized 下使用,如果不這麼使用,可能會丟失 notify()
- 資料量大效能低,但可讀性好
volatile
- 立即寫入主存中,所有執行緒都看得到,避免快取影響
- 保證 long、double 賦值操作的原子性
- 只有一個值會改變的情況下使用
- 不能保證執行緒安全
InterruptedException
- 中斷執行緒
- 注意清理資源
- IO、synchronized 不可中斷
- NIO 提供了新中斷方式
CountDownLatch
- 同步多個任務使用,首先新建 CountDownWatch 確定任務大小,各個任務 countDown,再 await 等待其他任務完成
CyclicBarrier
- 建立一組任務,並行工作,在所有所有任務完成之前等待
- 相比於 CountDownLatch,可以多次觸發
- 建構函式引數包括所需任務數、所有任務完成之後執行的操作
DelayedQueue
- 延遲佇列
PriorityBlockingQueue
- 優先順序佇列
ConcurrentHashMap
- 寫入機制非寫時複製,比寫時複製快
ScheduledExecutorService
- 定時器
- schedule 執行一次
- scheduleAtFixedRate 多次執行
ReadWriteLock
- 讀鎖可以在沒有寫鎖的時候被多個執行緒同時持有,寫鎖是獨佔的
- 多讀少寫效能高
Fork/Join
ForkJoinPool
- invoke 啟動任務
RecursiveTask
- 定義子任務
- invokeAll 呼叫子任務
TransferQueue
- 佇列滿時,阻塞生產者
有意思的問題:為什麼 System.out.println() 不會被中斷?
《Java 程式設計思想》提了一句 “System.out.println() 不會被中斷”,疑惑的我去看原始碼,恍然大悟。
public void println(boolean x) {
synchronized (this) {
print(x);
newLine();
}
}
System 包的 out 是靜態物件,只有一個例項,在執行 println,鎖住自己,下個執行緒想用 System.out 的方法,只能等當前操作結束。
這在多執行緒是個效能天坑。每個執行緒如果都有 System.out.println 方法,互相阻塞。可以參考專案下 com.example.concurrent.number.NumberMain 測試。
三個執行緒互相列印
三個執行緒按順序列印 A、B、C,參見 com.example.concurrent.print.PrinterTest。
承諾升級理論
執行緒組的啟示:繼續錯誤的代價由別人來承擔,而承認錯誤的代價由自己來承擔。
執行緒很簡單?
如果某個人表示執行緒機制很容易或者很簡單,那麼請確保這個人沒有對你的專案作出重要的決策。如果這個人已經在做了,那麼你就已經陷入麻煩中了。
雙檢鎖
雙重檢查,加鎖。雙重檢查防止多次例項化。
- 多執行緒下需要加鎖
- 在方法上加鎖影響效能
- 由於 JIT 編譯不確定性,需要在資源上加上 volatile 防止編譯器優化,導致獲取到的 obj 為空的問題
正確做法:
class SomeClass {
private volatile Resource resource = null;
public Resource getResource() {
if (resource == null) {
synchronized {
if (resource == null)
resource = new Resource();
}
}
return resource;
}
}
參考雙重檢查鎖定原理詳解。
IO
通道、緩衝器
- 通道是一個獲取資料的通路
- 緩衝器承載了資料
- 更接近於底層作業系統的操作機制,效能更高
Java
File
- 處理檔案、目錄
FileNameFilter
- 過濾檔名
InputStream
- 位元組流
- 輸入,包括位元組陣列、字串、檔案、管道、流、其他資料來源,包括 ByteArrayInputStream、StringBufferInputStream、FileInputStream、PipedInputStream、SequenceInputStream(合併兩個 InputStream)
- FilterInputStream 修改了內部的行為,或者是返回物件的方式,包括 DataInputStream、BuffedInputStream、LineNumberInputStream、PushbackInputStream
- DataInputStream 讀取資料,必須通過 DataOutputStream 寫入資料,專用於 JVM 平臺
OutputStream
- 位元組流
- 輸出,包括 ByteArrayOutputStream、FileOutputStream、PipedOutputStream
- FilterOutputStream 修改了寫入物件的方式,包括 DataOutputStream、PrintStream、BufferedOutputStream
- DataOutputStream 寫入資料,必須通過 DataInputStream 讀取資料,專用於 JVM 平臺
Reader
- 字元流
- 需要用 InputStreamReader 將 InputStream 轉換
- 主要是為了國際化
- XXXInputStream 對應 XXXReader
Writer
- 字元流
- 需要用 OutputStreamReader 將 OutputStream 轉換
- 主要是為了國際化
- XXXOutputStream 對應 XXXWriter
RandomAccessFile
- 隨機讀取
- seek 調到檔案某一個地方
Scanner
- 讀取器,傳入 InputStream
System.XXX
- in 輸入、out 輸出、err 錯誤
- setXXX 重定向
ProcessBuilder
- 程序控制
NIO
Channel
- FileInputStream、FileOutputStream、RandomAccessFile 增加了 Channel
- 操作 ByteBuffer
- transferTo、transferFrom 傳輸資料
- read 讀取資料
- write 寫入資料
- tryLock 非阻塞加鎖,lock 阻塞加鎖,position 加鎖位置,limit 結束位置
ByteBuffer
- 讀取資料的單元
- wrap 儲存位元組資料
- allocate 分配空間
- limit 讀寫閾值,capacity 容量,position 當前讀寫位置,工作流程
- 初始狀態,limit = capacity,position = 0
- 工作時,移動 position,直至 limit
- flip,limit = position,position = 0,一般是寫了很多資料,重新讀資料,才會這樣使用
- clear,limit = capacity,position = 0,寫完資料準備讀會使用
- rewind,position = 0
Java 記憶體
本文基於 JDK 8。
分類
程式計數器(Program Counter Register)
- 記憶體空間較小,儲存了當前執行緒執行程式碼的位置
- 執行緒私有
- 不會 OOM(Out Of Memory)
虛擬機器棧(VM Stack)
- 儲存了物件引用等
- 執行緒私有
- 會 OOM、StackOverflow
- 指定棧大小,-Xss128k
本地方法棧(Native Method Stack)
- 與虛擬機器棧基本相同,執行的事本地方法
- 執行緒私有
- 會 OOM、StackOverflow
- 指定棧大小,-Xss128k
堆(Heap)
- 物件的內容會在堆上分配,包括執行時常量池(Runtime Constant Pool)
- 執行緒公有
- 會 OOM
- 是 GC 管理的主要區域
- 用 Xmx、Xms 分別控制最大堆記憶體、最小堆記憶體,-Xms20m -Xmx20m,指定 OOM 時 dump,-XX:+HeapDumpOnOutOfMemory -XX:HeapDumpPath=e:/
方法區(Method Area)
- 儲存了類資訊、靜態變數等
- 執行緒公有
- 記憶體分配在 JVM 之中,會 OOM
- 俗稱永久代(Permanent Generation,簡稱 Perm Gen)
- 指定大小,-XX:PermSize=10M -XX:MaxPermSize=10m
- JDK 7 將永久代轉移到堆中
- JDK 8 已移除永久代,轉移到 Metaspace
Metaspace
- 原來的 MethodArea
- 記憶體分配在系統記憶體中,超過系統可用記憶體,會 OOM
- 指定大小,-XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m
直接記憶體
- 不是虛擬機器執行的一部分,DirectByteBuffer 會用,主要是從記憶體中直接讀取檔案
- 記憶體分配在系統記憶體中,超過系統可用記憶體,會 OOM
- 指定大小,-XX:MaxDirectMemorySize=10m
記憶體分配
物件建立流程
- 遇到 new 關鍵字,查詢對應類的符號引用,沒有就進行類載入
- 分配記憶體
- 初始化為零值
- 設定物件相應的屬性
- 執行物件的建構函式
記憶體分配方式
- 指標碰撞:計算出記憶體大小,指標向下移動
- 空閒列表:維護記憶體可用部分,進行分配
併發
- CAS 同步處理:併發大容易產生衝突,降低效率
- TLAB(Thread Local Allocation Buffer):線上程所處空間中分配記憶體
記憶體儲存
- 物件頭(Header):一部分是自身執行資料,考慮到空間效率,空間大小非固定,主要儲存雜湊碼、GC 年代等;另一部分型別指標,儲存物件型別資訊,陣列還要儲存陣列大小
- 例項資料(Instance Data):儲存例項資料,一般相同寬度的資料會被分配到一起,父類會在子類之前(CompactField 引數為 true,子類較窄的也可能插入到父類空隙之中)
- 對齊填充(Padding):佔位符,物件大小必須是 8 位元組的整數倍
訪問定位方式
- 控制代碼訪問:棧 reference -> 控制代碼池 物件例項資料指標 -> 例項池,優點是移動物件時只需要改例項指標
- 直接指標訪問: 棧 reference -> 物件例項資料指標(包括例項資料),HotSpot 採用,優點是訪問速度快(減少了一次指標訪問)