阅读 107

Java知识积累-高级篇

Java高级

内容正在完善中……线程池ing

1. Collection

List

  1. ArrayList数组实现,按下标访问效率高(随机访问),增删操作费事

  2. Victor:在ArrayList基础上添加了线程安全特性,效率降低

  3. LinkedList双向链表实现,增删效率高,不支持下标访问(随机访问),只能顺序查找

Set

  1. HashSet:哈希表实现,查找效率高 O(1),无序

  2. LinkedHashSet:双向链表维护元素的插入顺序,查询效率高,有序

  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),返回value

    • 取所有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类型可以增加遍历的效率
  • 如何在高并发环境下使用?

    • HashMap map = Collections.synchronizedMap(new HashMap(...));
    • 使用ConcurrentHashMap

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

参考

关注下面的标签,发现更多相似文章
评论