Java知識積累-高級篇

Java高級

內容正在完善中……線程池inghtml

1. Collection

List

  1. ArrayList數組實現,按下標訪問效率高(隨機訪問),增刪操做費事java

  2. Victor:在ArrayList基礎上添加了線程安全特性,效率下降面試

  3. LinkedList雙向鏈表實現,增刪效率高,不支持下標訪問(隨機訪問),只能順序查找express

Set

  1. HashSet:哈希表實現,查找效率高 O(1),無序ubuntu

  2. LinkedHashSet:雙向鏈表維護元素的插入順序,查詢效率高,有序api

  3. TreeSet:紅黑樹實現,有序( 甚至拿來排序orz ),查找效率較低 O(logN)數組

Queue

  • 待補充

2. Map

1. HashMap( 面試必問 )

  • 實現:哈希表
  • 非線程安全
  • 做用:存Key-Value類型的值,每一對造成一組Entry<K, V>
  • 結構:傳說中的拉鍊法(手寫練習,加深印象orz
  • 使用:
    • 存值: put(K key, V value))安全

    • 經過key取value: get(K key),返回valuebash

    • 取全部keys: keySet(),返回key的集合併發

    • 遍歷: (常考)

      • KeySet
      private static String keySetTest(HashMap<Integer, String> hashMap){
          StringBuilder result = new StringBuilder("KeySetTest: ");
          for ( Integer i : hashMap.keySet() ){
              result.append(i).append(": ").append(hashMap.get(i));
          }
          return result.toString();
      }
      複製代碼
      • EntrySet
      private static String entrySetTest(HashMap<Integer, String> hashMap){
          StringBuilder result = new StringBuilder("EntrySetTest: ");
          for ( Map.Entry<Integer, String> entry: hashMap.entrySet() ){
              result.append(entry.getKey()).append(": ").append(entry.getValue());
          }
          return result.toString();
      }
      複製代碼
      • Iterator
      private static String iteratorTest(HashMap<Integer, String> hashMap){
          StringBuilder result = new StringBuilder("IteratorTest: ");
          Iterator iterator = hashMap.entrySet().iterator();
          while( iterator.hasNext() ){
              Map.Entry entry = (Map.Entry) iterator.next();
              result.append(entry.getKey()).append(": ").append(entry.getValue());
          }
          return result.toString();
      }
      複製代碼
      • ForeachIteratorTest(idea給上面的方法報warning,推薦了foreach寫法,如下爲自動修改後的方案)

      注意:經檢驗這就是EntrySet同樣的作法……idea會把此種方案視爲可優化爲foreach循環的可優化點

      private static String foreachIteratorTest(HashMap<Integer, String> hashMap){
          StringBuilder result = new StringBuilder("ForeachIteratorTest: ");
          for ( Map.Entry<Integer, String> entry: hashMap.entrySet() ) {
              result.append(entry.getKey()).append(": ").append(entry.getValue());
          }
          return result.toString();
      }
      複製代碼
      • IteratorWithoutType
      private static String iteratorWithoutTypeTest(HashMap<Integer, String> hashMap){
          StringBuilder result = new StringBuilder("IteratorWithoutTypeTest: ");
          Iterator<Map.Entry<Integer, String>> iterator = hashMap.entrySet().iterator();
          while( iterator.hasNext() ){
              Map.Entry entry = iterator.next();
              result.append(entry.getKey()).append(": ").append(entry.getValue());
          }
          return result.toString();
      }
      複製代碼
      • ForeachIteratorWithoutType(同理爲idea優化)
      private static String foreachIteratorWithoutTypeTest(HashMap<Integer, String> hashMap){
          StringBuilder result = new StringBuilder("ForeachIteratorWithoutTypeTest: ");
          for (Map.Entry entry : hashMap.entrySet()) {
              result.append(entry.getKey()).append(": ").append(entry.getValue());
          }
          return result.toString();
      }
      複製代碼
    • 測速: 目前測速方案致使結果波動驗證,仍在調整中……

      • 測試類:
      public static void main(String[] args){
          HashMap<Integer, String> hashMap = new HashMap<>();
          for (int i = 0; i < 1000000; i++) {
              hashMap.put(i, String.valueOf(i));
          }
          for (int i = 1; i <= 6; i++) {
              System.out.println(runTestWithClock(i, hashMap).substring(0, 30));
          }
      }
      private static String runTestWithClock(int n, HashMap<Integer, String> hashMap){
          String result = null;
          long clock = System.currentTimeMillis();
          long timer = 0;
          System.out.println("--------"+n+"-------");
          switch (n){
              case 1:
                  result = keySetTest(hashMap);
                  break;
              case 2:
                  result = entrySetTest(hashMap);
                  break;
              case 3:
                  result = iteratorTest(hashMap);
                  break;
              case 4:
                  result = foreachIteratorTest(hashMap);
                  break;
              case 5:
                  result = coolerIteratorTest(hashMap);;
                  break;
              case 6:
                  result = foreachCoolerIteratorTest(hashMap);
                  break;
          }
          timer = System.currentTimeMillis() - clock ;
          System.out.println("Timer: "+timer);
          return result;
      }
      複製代碼
      • 運行結果:(安全起見運行了3次,結果排在Timer行)
      --------1-------
      Timer: 1232 1234 1207
      KeySetTest: 0: 01: 12: 23: 34:
      --------2-------
      Timer: 96 112 100
      EntrySetTest0: 01: 12: 23: 34:
      --------3-------
      Timer: 127 120 109
      IteratorTest: 0: 01: 12: 23: 3
      --------4-------
      Timer: 98 91 111
      ForeachIteratorTest: 0: 01: 12
      --------5-------
      Timer: 112 99 104
      CoolerIteratorTest: 0: 01: 12:
      --------6-------
      Timer: 105 106 122
      ForeachCoolerIteratorTest: 0: 
      複製代碼
      • 結果統計:
      method 1 2 3
      KeySet 1232 1234 1207
      EntrySet 96 112 100
      Iterator 127 120 109
      ForeachIterator 98 91 111
      CoolerIterator 112 99 104
      ForeachCoolerIterator 105 106 122

      結果波動性比較大,安全起見從新制定測試計劃

      • 測試條件:①HashMap大小 ②測試次數 ③測試方法的順序

      先對①和②進行驗證

size 2000k 1500k 1000k 100k
times 1 2 3 4 5 1 2 3 1 2 3 1 2 3
KeySet 2315 2271 2303 2328 2258 813 816 819 1232 1234 1207 25 30 25
EntrySet 203 1010 201 991 201 735 738 745 96 112 100 23 25 23
Iterator 178 155 165 165 170 180 173 188 127 120 109 24 26 24
ForeachIterator 986 147 983 194 971 138 134 137 98 91 111 27 27 37
IteratorWithoutType 185 187 179 162 169 134 122 130 112 99 104 18 19 19
ForeachIteratorWithoutType 183 188 180 156 150 145 112 121 105 106 122 19 21 20

表格渲染結果不佳,另附https://paste.ubuntu.com/p/VKmyxsGbdh/

  • 由此表大體能夠認爲在這次測試中,HashMap<Integer,String>存儲簡單類型的k-v值:

    • 在容量在100k級別時,各類方法差異並不大
    • 在容量到1000k及以上的級別時,執行效率開始呈現較大差異
      • KeySet方式是同數量級中最低效率的遍歷方式
      • Iterator方式效率相對較高
    • 特殊狀況
      • KeySet方式在size爲1500k時的效率明顯異常,或許有特殊優化?(又去跑了幾回:1869,844,787,的確超過1秒的不多,待調查* 存在③影響的可能)
      • EntrySet和ForeachIterator在2000k時居然在900ms和100ms之間交替orz,回過頭一看,ForeachIterator通過idea優化以後居然就是EntrySet的方法,測試方案需進行簡化*
  • 猜測:

    • ForEach是經過Iterator實現的,否則在idea中不該該會推薦進行此優化,而且在運行時間上也存在類似之處
  • 進一步推測:

    • 不指定Entry類型的Iterator和不指定Entry類型的for-each循環 實現類似
    • 指定Entry類型的Iterator和指定Entry類型的for-each循環 實現類似
  • 驗證:

    • 經過反編譯.class文件得到EntrySet測試的代碼:
    private static String entrySetTest(HashMap<Integer, String> hashMap) {
        StringBuilder result = new StringBuilder("EntrySetTest: ");
        Iterator var2 = hashMap.entrySet().iterator();
    
        while(var2.hasNext()) {
            Entry<Integer, String> entry = (Entry)var2.next();
            result.append(entry.getKey()).append(": ").append((String)entry.getValue());
        }
    
        return result.toString();
    }
    複製代碼
    • 同理查看foreachIteratorTest
    private static String foreachIteratorTest(HashMap<Integer, String> hashMap) {
        StringBuilder result = new StringBuilder("ForeachIteratorTest: ");
        Iterator var2 = hashMap.entrySet().iterator();
    
        while(var2.hasNext()) {
            Entry<Integer, String> entry = (Entry)var2.next();
            result.append(entry.getKey()).append(": ").append((String)entry.getValue());
        }
    
        return result.toString();
    }
    複製代碼

    果真是同樣的,這些for-each是基於iterator實現的

  • 結論:

    • size較大時,iterator進行map遍歷時的效率最高,KeySet效率最低
    • 相對來講,指定iterator的Entry類型能夠增長遍歷的效率

2. LinkedHashMap

  • 雙向鏈表來維護元素的順序
  • 順序:插入順序或者LRU(Least recently used)順序

文檔 docs.oracle.com/javase/8/do…

3. HashTable

  • 線程安全√
  • 不推薦使用,僅做了解,官方文檔推薦高併發場景使用→ConcurrentHashMap進行替代

Thread

基礎使用

  1. 實現 Runnable 接口
  • 經常使用且高效
public class RunnableThreadTest implements Runnable{
    @Override
    public void run() {
        // do sth.
    }
}
複製代碼

運行:

public static void main(String[] args) {
    RunnableThreadTest threadTest = new RunnableThreadTest();
    Thread thread = new Thread(threadTest);
    thread.start();
}}
複製代碼
  1. 繼承 Thread 類
  • 使用簡單但過於繁重
class ThreadTestExtentsThread extends Thread{
    public static void main(String[] args) {
        ThreadTestExtentsThread testExtentsThread = new ThreadTestExtentsThread();
        testExtentsThread.start();
    }
}
複製代碼
  1. 實現Callable接口
class CallableThreadTest implements Callable<Integer>{
    @Override
    public Integer call() throws Exception {
        Integer result = 123;
        // do sth.
        return result;
    }
}
複製代碼

使用:

public static void main(String[] args) throws ExecutionException, InterruptedException {
    CallableThreadTest threadTest = new CallableThreadTest();
    // 指派一項要完成的任務,目標得到物件類型爲Integer
    FutureTask<Integer> task = new FutureTask<>(threadTest);
    // 把task交託給thread
    Thread thread007 = new Thread(task);
    // 特工007開始執行任務:D (Target is locked)
    thread007.start();
    // 得到任務執行結果
    Integer resultOfTask = task.get();
    System.out.println(resultOfTask);
}
複製代碼

咳咳,使用007來理解FutureTask可能更清晰一些XD

唔,乾脆發揮一下神盾局AOS……

// 衆所周知神盾局致力於尋找o-8-4,隨叫隨到:D
class AOS implements Callable<O84>{
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 固然,這事兒運做得先有神盾局
        AOS aos = new AOS();
        // 還須要有任務
        FutureTask<O84> mission = new FutureTask<>(aos);
        // 任務須要指派特工去完成
        Thread agent = new Thread(mission);
        // 特工開始執行任務
        agent.start();
        /** 任務執行中 **/
        // 獲取行動結果-o84
        O84 target = mission.get();
        System.out.println(target);
    }
    @Override
    public O84 call() throws Exception {
        O84 target = new O84();
        // 行動中
        // Find out what is o84
        // Like Thor's hammer XD
        return target;
    }
}
class O84 {
    private String name;
    public String getName() {return name;}
    public void setName(String name) {this.name = name;}
}
複製代碼

這樣一想是否是清楚了不少咧:D (別趁如今回去看一次吼,時間瞬間……等等怎麼一天過去了)

  1. 快速建立運行(jdk1.8+, lambda)
new Thread(() -> {
    // do sth.
}).start();
複製代碼

實際是用runnable的:

new Thread(new Runnable() {
    @Override
    public void run() {
        // do sth.
    }
}).start();
複製代碼

線程通訊

1. wait->notify

public class WaitNotifyTest {

    private static Object goods = null;

    private void waitNotify() throws InterruptedException {
        new Thread(() -> {
            synchronized (this){
                while( goods == null ){
                    try{
                        System.out.println("1. [Consumer]Waiting");
                        this.wait();
                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }
                }
            }
            System.out.println("3. [Consumer]Done");
        }).start();

        Thread.sleep(1000L);

        new Thread(()->{
            goods = new Object();
            synchronized (this){
                this.notifyAll();
                System.out.println("2. [Consumer]NotifyAll");
            }
        }).start();
    }
    
    public static void main(String[] args) throws InterruptedException {
        new WaitNotifyTest().waitNotify();
    }
}
複製代碼

2. park->unpark

import java.util.concurrent.locks.LockSupport;

public class ParkUnparkTest {

    private static Object goods = null;

    public static void main(String[] args) throws InterruptedException {
        new ParkUnparkTest().parkUnpark();
    }

    private void parkUnpark() throws InterruptedException {
        Thread consumerThread = new Thread( ()->{
           while( goods == null ){
               System.out.println("1. [Consumer]Park");
               LockSupport.park();
           }
           System.out.println("3. [Consumer]Done");
        });
        consumerThread.start();

        Thread.sleep(1000L);
        goods = new Object();
        LockSupport.unpark(consumerThread);
        System.out.println("2. [Consumer]Unpark");
    }

}
複製代碼

3. resume->suspend (已棄用)

線程池

1. ThreadPoolExecutor

測試類

public class ThreadPoolTest {
    public static void main(String[] args) throws Exception {
        ThreadPoolTest threadPoolTest =  new ThreadPoolTest();
        threadPoolTest.threadPoolExecutorTest01();
        threadPoolTest.threadPoolExecutorTest02(); // 分別執行請自行添加註釋
        threadPoolTest.threadPoolExecutorTest03();
    }

    private void submitMission(ThreadPoolExecutor threadPoolExecutor) throws Exception{
        for (int i = 0; i < 15; i++) {
            int n = i; 
            threadPoolExecutor.submit(() -> {
            // 提交15項任務,每項需執行3秒
                try{
                    System.out.println("Start: "+n);
                    Thread.sleep(3000L);
                    System.out.println("Finish: "+n);
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            });
            System.out.println("Submitted: "+i);
        }
        // 在提交完任務到任務執行完成之間,輸出線程池狀態
        Thread.sleep(500L);
        System.out.println("PoolSize: "+threadPoolExecutor.getPoolSize());
        System.out.println("QueueSize: "+threadPoolExecutor.getQueue().size());
        // 預期爲5項任務所有執行完成以後,輸出線程池狀態
        Thread.sleep(15000L);
        System.out.println("PoolSize: "+threadPoolExecutor.getPoolSize());
        System.out.println("QueueSize: "+threadPoolExecutor.getQueue().size());
    }
}
複製代碼

測試01代碼:

  • 池子核心線程數:5
  • 最大數量:10
  • 超限策略:等待隊列(不指定容量)
private void threadPoolExecutorTest01() throws Exception {
    ThreadPoolExecutor tpe = new ThreadPoolExecutor( 
            5,  // corePoolSize
            10, // maximumPoolSize
            5,  // keepAliveTime
            TimeUnit.SECONDS, 
            new LinkedBlockingDeque<Runnable>());
    submitMission(tpe);
}
複製代碼

測試01結果:

Submitted: 0
Start: 0
Submitted: 1
Submitted: 2
Start: 1
Submitted: 3
Start: 2
Submitted: 4    ← 5項任務提交
Start: 3
Start: 4        ← 5項任務啓動
Submitted: 5
Submitted: 6
Submitted: 7
Submitted: 8
Submitted: 9
Submitted: 10
Submitted: 11
Submitted: 12
Submitted: 13
Submitted: 14   ← 全部任務提交
PoolSize: 5     ← 池子中線程數
QueueSize: 10   ← 等待隊列中任務數量:10
Finish: 4
Finish: 1
Finish: 3
Finish: 0
Finish: 2       ← 5項任務結束
Start: 8
Start: 7
Start: 6
Start: 5
Start: 9        ← 後5項任務啓動
Finish: 7
Finish: 8
Finish: 5
Finish: 6
Finish: 9       ← 後5項任務結束
Start: 13
Start: 12
Start: 11
Start: 10
Start: 14       ← 最後5項任務啓動
Finish: 12
Finish: 13
Finish: 10
Finish: 14
Finish: 11      ← 最後5項任務結束
PoolSize: 5
QueueSize: 0    ← 等待隊列爲空
複製代碼

測試02代碼:

  • 池子核心線程數:5
  • 最大數量:10
  • 超限策略:容量爲3的等待隊列
  • 異常條件:線程數量>池子中線程數最大數量+等待隊列容量
  • 異常策略:
private void threadPoolExecutorTest02() throws Exception {
    ThreadPoolExecutor tpe = new ThreadPoolExecutor(
            5,  // corePoolSize
            10, // maximumPoolSize
            5,  // keepAliveTime
            TimeUnit.SECONDS,
            new LinkedBlockingQueue<Runnable>(3), new RejectedExecutionHandler() {
        @Override
        public void rejectedExecution(Runnable runnable, ThreadPoolExecutor executor) {
            System.err.println("Mission rejected");
        }
    });
    submitMission(tpe);
}
複製代碼

測試02結果:

Submitted: 0
Start: 0
Submitted: 1
Submitted: 2
Start: 1
Start: 2
Submitted: 3
Start: 3
Submitted: 45項任務提交
Submitted: 5
Submitted: 6
Start: 45項任務啓動
Submitted: 7
Submitted: 8
Submitted: 9
Start: 8
Submitted: 10
Start: 9
Submitted: 11
Start: 10
Submitted: 12   ← 等待隊列中任務數量:3
Start: 11
Mission rejected ← 此處提交數已經達到14>最大線程數10+等待隊列容量3,拒絕任務
Submitted: 13
Mission rejected ← 同理拒絕任務
Start: 12
Submitted: 14
PoolSize: 10    ← 已經達到池子最大線程數,可是核心仍是5
QueueSize: 3
Finish: 0
Finish: 4
Finish: 1
Finish: 3
Finish: 2       ← 核心數量5因此先執行5項任務
Start: 7        ← 開始取出等待隊列中的任務
Start: 5
Start: 6        ← 取完等待隊列中的任務
Finish: 10
Finish: 11
Finish: 9
Finish: 8
Finish: 12
Finish: 5
Finish: 7
Finish: 6       ← 執行結束,不包含任務1415,他們被rejected了
複製代碼

以上兩則測試演示了ThreadPoolExecutor中池子最大線程數和等待隊列的效果,注意點:

  1. 在設置了等待隊列但沒有指定隊列容量時,線程池會一直只用核心線程數(即 最大線程數配置項maximumPoolSize無效)
  2. 在給定了等待隊列容量後,線程池在開到最大線程數後才向等待隊列裏聽任務,最終提交任務的數量超過最大線程數+等待隊列容量時,任務會使用RejectedExecutionHandler回絕
  3. 方案權衡:
  • 不指定等待隊列容量時,投入過多的任務會一直在隊列裏等待執行,若是出現外部中斷則會丟失任務信息,在外力不可拒絕的狀況下需提早保證任務數量不會太多
  • 指定等待隊列容量時,超出限額的任務會被拒絕,需安排拒絕策略

簡圖:

等待隊列可視爲右方的蓄水箱,不設定大小則至關於無限積水,左側水箱maxSize永遠不會滿; 可是若是等待隊列有容量,左側水箱天然也就會出現溢出到maxSize的狀況(即num-capacity-corePoolSize需≤maximumPoolSize)

2. ScheduledThreadPoolExecutor

複製代碼

Network

語法糖

[Lambda表達式]

參考

泛型類型擦除

Streams API

參考

相關文章
相關標籤/搜索