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

執行緒

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

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

    • New(新建):使用new操作符創建執行緒時
    • Runnable(可運行):調用start方法
    • Blocked(阻塞)
    • Waiting(等待)
    • Timed waiting(計時等待)
    • Terminated(終止):run方法正常退出、沒有捕獲異常

    image-20200630154201750

  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>