1. 程式人生 > 實用技巧 >《Java核心技術(卷1)》筆記:第12章 併發

《Java核心技術(卷1)》筆記:第12章 併發

執行緒

  1. (P 552)多程序多執行緒的本質區別:每一個程序都擁有自己的一整套變數,而執行緒共享資料

  2. (P 555)執行緒具有6種狀態

    • New(新建):使用new操作符建立執行緒時
    • Runnable(可執行):呼叫start方法
    • Blocked(阻塞)
    • Waiting(等待)
    • Timed waiting(計時等待)
    • Terminated(終止):run方法正常退出、沒有捕獲異常
  3. (P 558)interrupt方法用來請求終止一個執行緒。當對一個執行緒呼叫interrupt方法時,就會設定執行緒的中斷狀態。每個執行緒都應該不時地檢查這個標誌,以判斷執行緒是否被中斷

  4. (P 559)如果執行緒被阻塞,就無法檢查中斷狀態,因此引入InterruptedException

    異常。當在一個被sleepwait呼叫阻塞的執行緒上呼叫interrupt方法時,那個阻塞呼叫將被一個InterrupedtException異常中斷

  5. (P 559)如果設定了中斷狀態,此時倘若呼叫sleep方法,它不會休眠。實際上,它會清除中斷狀態並丟擲InterruptedException。因此,如果迴圈裡呼叫了sleep,不要檢測中斷狀態,而應當捕獲InterruptedException異常

  6. (P 560)interruptedisInterrupted以及interrupt方法的區別:

    方法 性質 作用 影響
    interrupted Thread的靜態方法 檢查當前執行緒是否被中斷 會清除該執行緒的中斷狀態
    isInterrupted Thread的例項方法 測試執行緒是否被中斷 不會改變中斷狀態
    interrupt Thread的例項方法 向執行緒傳送中斷請求 會設定執行緒的中斷狀態
  7. (P 561)守護執行緒:唯一用途是為其他執行緒提供服務,當只剩下守護執行緒時,虛擬機器就會退出。可通過setDaemon方法將執行緒設為守護執行緒

  8. (P 561)執行緒的run方法不能丟擲任何檢查型異常,線上程死亡之前,異常會傳遞到一個用於處理未捕獲異常的處理器(必須實現Thread.UncaughtExceptionHandler介面)

    方法 性質 作用
    setUncaughtExceptionHandler Thread的例項方法 為任何執行緒安裝一個處理器
    setDefaultUncaughtExceptionHandler Thread的靜態方法 為所有執行緒安裝一個預設的處理器

同步

  1. (P 568)Java提供的兩種可防止併發訪問程式碼塊的機制:

    • synchronized關鍵字

    • ReentrantLock類(重入鎖)

      myLock.lock();	// 一個ReentrantLock物件
      try {
          ...
      } finally {
          myLock.unlock();	// 必須放在finally裡,不能使用try-with-resources
      }
      
  2. (P 570)重入(reentrant)鎖:執行緒可以反覆獲得已擁有的鎖,被一個鎖保護的程式碼可以呼叫另一個使用相同鎖的方法。注意確保臨界區中的程式碼不要因為丟擲異常而跳出臨界區

  3. (P 572)一個鎖物件可以有一個或多個相關聯的條件物件,可以使用newCondition方法獲得一個條件物件。

    方法 性質 作用
    newCondition ReentrantLock的例項方法 獲得一個條件物件
    await Condition的例項方法 當前執行緒現在暫停,並放棄鎖
    signalAll Condition的例項方法 解除等待這個條件的所有執行緒的阻塞狀態
    signal Condition的例項方法 隨機選擇一個執行緒解除其阻塞狀態

    使用形式:

    class A {
        private var lock = new ReentrantLock();
        private Condition condition;
        ...
        
        private A() {
            ...
            condition = lock.newCondition();
        }
        
        private someMethod() {
            lock.lock();
            try {
                ...
                while(!(OK to proceed)) {	// await呼叫通常放在迴圈中
                    condition.await();
                }
                ...
                condition.signalAll();	// signalAll只是通知等待的執行緒:現在有可能滿足條件,值得再次檢查條件
                						// 只要一個物件的狀態有變化,而且可能有利於等待的執行緒,就可以呼叫signalAll
                						// signalAll只是解除等待執行緒的阻塞,使這些執行緒可以在當前執行緒釋放鎖之後競爭訪問物件
            } finally {
                lock.unlock();
            }
        }
        
        ...
    }
    
  4. (P 576)Java中的每個物件都有一個內部鎖只有一個關聯條件)。如果一個方法宣告時有synchronized關鍵字,那麼物件的鎖將保護整個方法

    方法 性質 作用 等價於
    wait Object的例項方法 將一個執行緒增加到等待集中 await
    notify/notifyAll Object的例項方法 解除等待執行緒的阻塞 signal/signalAll
  5. (P 577)將靜態方法宣告為同步也是合法的,如果呼叫這樣一個方法,它會獲得相關類物件(Class物件)的內部鎖

  6. (P 577)內部鎖和條件存在一些限制

    • 不能中斷一個正在嘗試獲得鎖的執行緒
    • 不能指定嘗試獲得鎖時的超時時間
    • 每個鎖僅有一個條件可能是不夠的
  7. (P 579)同步塊:

    synchronized(obj) {	// 會獲得obj物件的鎖
        ...
    }
    
  8. (P 580)監視器的特性:

    • 監視器是隻包含私有欄位的類
    • 監視器類的每個物件有一個關聯的
    • 所有方法有這個鎖鎖定
    • 鎖可以有任意多個相關聯的條件
  9. (P 581)volatile關鍵字為例項欄位的同步訪問提供了一種免鎖機制,volatile變數不能提供原子性

    • 另一種安全訪問共享欄位的情況:將欄位宣告為final
  10. (P 582)java.util.concurrent.atomic包中有很多類使用了很高效的機器級指令來保證其他操作的原子性

  11. (P 586)執行緒區域性變數:ThreadLocal

    public static final ThreadLocal<SimpleDateFormat> dataFormat = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
    // 在一個給定執行緒中首次呼叫get時,會呼叫構造器中的lambda表示式
    // 在此之後,get方法會返回屬於當前執行緒的那個例項
    String dateStamp = dataFormat.get().format(new Date());
    

執行緒安全的集合

  1. (P 589)阻塞佇列(blocking queue):addelementofferpeekpollputremovetake

  2. (P 595)高效的對映、集和佇列:java.util.concurrent包提供了ConcurrentHashMapConcurrentSkipListMapConcurrentSkipListSetConcurrentLinkedQueue

  3. (P 602)同步包裝器(synchronization wrapper):任何集合類都可以通過使用同步包裝器變成執行緒安全的

    List<E> synchArrayList = Collections.synchronizedList(new ArrayList<E>());
    Map<K, V> synchHashMap = Collections.synchronizedMap(new HashMap<K, V>());
    

執行緒池

  1. (P 603)CallableRunnable類似,但是有返回值,只有一個call方法

  2. (P 604)Future儲存非同步計算的結果

  3. (P 604)執行Callable的一種方法是使用FutureTask,它實現了FutureRunnable介面

    Callable<Integer> task = ...;
    var futureTask = new FutureTask<Integer>(task);
    var t = new Thread(futureTask);		// it's a Runnable
    t.start();
    ...
    Integer result = futureTask.get();	// it's a Future
    
  4. (P 605)執行器(Executors)類有許多靜態工廠方法,用來構造執行緒池

  5. (P 606)使用執行緒池時所做的工作:

    1. 呼叫Executors類的靜態方法newCachedThreadPoolnewFixedThreadPool
    2. 呼叫submit提交RunnableCallable物件
    3. 儲存好返回的Future物件,以便得到結果或者取消任務
    4. 當不想再提交任何任務時,呼叫shutdown
  6. (P 607)控制任務組

    方法 性質 作用 備註
    invokeAny ExecutorService的例項方法 提交一個Callable物件集合中的所有物件,並返回某個已完成任務的結果
    invokeAll ExecutorService的例項方法 提交一個Callable物件集合中的所有物件,並返回表示所有任務答案的一個Future物件列表 這個方法會阻塞,直到所有任務都完成
  7. (P 612)fork-join框架:專門用來支援計算密集型任務,假設有一個處理任務,它可以很自然地分解為子任務

非同步計算

  1. (P 615)CompletableFuture類實現了Future介面,它提供了獲得結果的另一種機制。你要註冊一個回撥,一旦結果可用,就會(在某個執行緒中)利用該結果呼叫這個回撥(與之不同的是,Future中的get方法會阻塞)
  2. (P 615)Supplier<T>Callable<T>:都描述了無引數而且返回值型別為T的函式,不過Supplier函式不能丟擲檢查型異常

程序

  1. (P 628)Process類在一個單獨的作業系統程序中執行一個命令,允許我們與標準輸入、輸出和錯誤流互動。ProcessBuilder類則允許我們配置Process物件
  2. (P 631)ProcessHandle介面:要獲得程式啟動的一個程序的更多資訊,或者想更多地瞭解你的計算機上正在執行的任何其他程序,可以使用ProcessHandle介面
  3. (P 631)得到ProcessHandle的4種方式:
    • 給定一個Process物件pp.toHandle()會生成它的ProcessHandle
    • 給定一個long型別的作業系統程序IDProcessHandle.of(id)可以生成這個程序的控制代碼
    • Process.current()是執行這個java虛擬機器的程序控制代碼
    • ProcessHandle.allProcesses()可以生成對當前程序可見的所有作業系統程序的Stream<ProcessHandle>