1. 程式人生 > >Java 知識 - 集合、多執行緒、IO、JVM

Java 知識 - 集合、多執行緒、IO、JVM

GitHub 專案地址

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

  1. 給 result 賦予某個非 0 常量
  2. 對於每一個域,計算出一個雜湊碼 c
  3. 合併計算, result = result * 37 + c
  4. 返回 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 會不執行

終結任務

  • 小心謹慎

執行緒狀態

  1. new
  2. runnable
  3. blocked
  4. 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 採用,優點是訪問速度快(減少了一次指標訪問)