【Java劝退师】Concurrent 知识脑图 - 并发编程

1,503 阅读24分钟

并发编程

一、概念

1. 并发编程三要素

  • 原子性 : 操作要么全部成功,要么全部失败
  • 有序性 : 进程按照代码的先后顺序运行
  • 可见性 : 多线程访问同一个变量时,其中一个线程对变量进行修改,其他线程能立刻获取到最新的值

2. 锁分类

  • 悲观锁

    • 在数据【修改前】,就先锁定,造成线程阻塞

    • 保证同一时间只能有一个线程访问特定代码段

    • 实际案例

      • synchronized

        • 等待策略

          • 如果拿不到锁,先自旋,自旋还拿不到锁,再阻塞
        • 原理

          • 锁是对象
            • 内部有一个 state 变量(标志位),用于纪录锁有没有被线程占用
              • 0 : 没有线程占用
              • 1 : 有某个线程占用
            • 如果锁被线程占用,纪录该线程的 Thread ID
            • 维护一个 Thread Id List,纪录等待获取这个锁的线程(锁池),在当前线程释放锁后,从 List 中取一个线程继续运行
          // 两者等价
          
          public void synchronized method1() {
          } 
          
          public void method1() {
            synchronized(this) {
            }
          } 
          
          // 两者等价
          public static void synchronized method2() {
          	
          }
          public static void method2() {
            synchronized(MyClass.class) {
            }
          }
          
      • ReentrantLock

  • 乐观锁

    • 在数据【提交更新时】,才进行版本检测,线程不阻塞

    • 实际案例

      • CAS : 如果 预期原值 与 内存值 相匹配,则 CPU 自动将该位置更新为 新值,返回 true;否则则不做操作,返回 false

        Compare And Swap 比较替换

      • AtomicInteger : 内部使用 CAS、自旋、volatile 语意 实现

3. synchronized 修饰

  • 代码块
    • 作用范围 : 大括号 { } 括起来的代码
    • 作用对象 : 调用这个代码块的对象
  • 方法
    • 作用范围 : 整个方法
    • 作用对象 : 调用这个方法的对象
  • 静态方法
    • 作用范围 : 整个静态方法
    • 作用对象 : 该类的所有对象
    • 作用范围 : 类的 { } 括起来的代码
    • 作用对象 : 该类的所有对象

二、线程

  • 所有 Java 进程,都有一个 Main 线程

  • 所有线程都有优先级,默认 5,最高 10 - 通常 高优先级 在 低优先级 线程之前运行

  • 分类

    • 守护线程 : 通常作为 垃圾搜集器 或 缓存管理器 存在于应用中,运行辅助任务

    • 非守护线程【默认】: 所有 非守护线程 结束运行,则应用结束 - 无论守护线程是否正在运行

      当 非守护线程 都不存在时,进程结束,守护线程 也会一起结束

  • 状态

    给定时间内,线程只能处于其中一种状态

    • NEW : Thread 对象已创建,但还没有开始运行

    • RUNNABLE : Thread 对象正在运行

    • BLOCKING : Thread 对象正在被阻塞

      重量级阻塞,不能被中断 interrupt() - synchronized

    • WAITING : Thread 对象正在等待另一个线程的动作

    • TIME_WAITING : Thread 对象正在等待另一个线程的动作,但有时间限制

      WAITING、TIME_WAITING : 轻量级阻塞,能够被中断 interrupt() - wait()、sleep()、join()、park()

    • TERMINATED : Thread 对象已完成运行

  • 常用方法

    • 信息
      • getId() : 获取 Thread 对象的标示符,一个正整数,在整个线程生命中唯一,且无法改变
      • getName() / setName() : 获取 / 设置 Thread 对象 String 类型的名称
      • getPriority() / setPriority() : 获取 / 设置 Thread 对象的优先级
      • isDaemon() / setDaemon() : 获取 / 设置 Thread 对象是否为守护线程
      • getState() : 获取 Thread 对象的当前状态
    • interrupt() : 唤醒轻量级阻塞,给目标线程打上中断标记,如果该线程正在运行的方法有声明抛出 InterruptedException,则会抛出异常 - wait()、join()、sleep()
    • interrupted() : 返回目标线程是否有中断标记,并清除中断标记
    • isinterrupted() : 返回目标线程是否有中断标记,但不清除中断标记
    • sleep(ms) : 线程暂停 ms 时间,且不释放锁
    • join() : 调用另一个线程运行,直到另一个线程运行结束,再接着运行
    • setUncaughtExceptionHandler() : 创建未校验异常的控制器
    • currentThread() : 返回实际运行该代码的 Thread 对象
  • 创建方式

    • 继承 Thread 类

      public class MyThread extends Thread { 
      	@Override
      	public void run() {
      	}
      }
      public class Main {
        public static void main(String[] args) {
          MyThread thread = new MyThread();
          thread.start();
        }
      }
      
    • 实现 Runnable 接口

      public class MyRunnable implements Runnable {
        @Override
        public void run() {
        }
      }
      public class Main {
        public static void main(String[] args) {
          Thread thread = new Thread(new MyRunnable());
          thread.start();
        }
      }
      
    • 实现 Callable 接口 - 可获取线程返回值

      public class MyCallable implements Callable<String> {
        @Override
        public String call() throws Exception {
          Thread.sleep(5000);
          return "hello world call() invoked!";
        }
      }
      public class Main {
        public static void main(String[] args) {
          MyCallable myCallable = new MyCallable();
          // 设置 Callable 对象,泛型表示 Callable 的返回类型
          FutureTask<String> futureTask = new FutureTask<String>(myCallable);
          // 启动处理线程
          new Thread(futureTask).start();
          // 同步等待线程运行的结果
          String result = futureTask.get();
          // 5s 后得到结果
          System.out.println(result);
        }
      }
      public class Main2 {
        public static void main(String[] args) {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 5, 1, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10)) {
            @Override
            protected void afterExecute(Runnable r, Throwable t) {
              // 如果在call方法运行过程中有错误,则可以在此处进行处理
              super.afterExecute(r, t);
            }
          };
          Future<String> future = executor.submit(new MyCallable());
          String s = future.get();
          System.out.println(s);
          executor.shutdown();
        }
      }
      
  • 沟通

    • Object 类函数

      • 和 synchronized 一起使用

      • wait() : 阻塞当前线程,并释放锁,并将该线程加入 等待池

      • notify() : 随机唤醒一个在等待 synchronized 代码块锁的线程,并将该线程加入 锁池

      • notifyAll() : 唤醒全部在等待 synchronized 代码块锁的线程,并将线程加入 锁池

      ○ 等待池 : 池中的线程 不能 参与锁竞争

      ○ 锁池 : 池中的线程 可以 参与锁竞争

    • Condition

      • 和 Lock 一起使用

      • await() : 阻塞当前线程,并释放锁,并将该线程加入 等待池

      • signal() : 精确唤醒被阻塞的一类线程,并将该线程加入 锁池

三、并发

1. 概念

  • 并发 : 在单核处理器上运行多个任务

  • 并行 : 同一时间内,在 不同计算机 或 不同处理器 上同时运行多个任务

  • 临界段 : 一段代码,给定时间内,只能被一个任务运行

  • 不可变对象 : 初始化后,不能修改其可视状态(属性值) - 线程安全

    如果想修改,则必须创建一个新的对象

  • 原子操作 : 操作要么都成功,要么都失败,可以透过临界段实现

  • 原子变量

    • 透过原子操作 设置 或 获取 值的操作
    • 实现方式
      • 同步机制
      • CAS : 无锁,不需要同步机制

2. 同步

  • 控制同步 : 任务的开始依赖于另一个任务的结束
  • 数据访问同步 : 多个任务同时访问共享变量时,任意时间里,只有一个任务可以访问该变量
  • 机制
    • 信号量
      • 存放可用资源数量的变量,采用两种原子操作管理该变量
        • 获取
        • 释放
      • 互斥(特殊的信号量)
        • 状态
          • 空闲
          • 忙 : 只有将状态设置为忙的任务才能释放它
    • 监视器 : 有 一个互斥、一个条件变量、两种操作(等待、通报) - 一旦通报该条件,等待它的任务中只有一个会继续运行

3. 任务通信

  • 共享内存 : 任务读取、写入使用相同的内存区域 - 为避免问题,共享内存的访问需在临界段完成
  • 消息传递

4. 问题

  • 数据竞争 : 多个任务在临界段之外对一个共享变量进行写入操作
  • 死锁
    • 一个线程等待另一个线程释放共享资源,而该线程又等待前述之线程释放另一共享资源
    • 发生条件 Coffman
      • 互斥 : 死锁涉及的资源是不可共享的
      • 占有并等待 : 线程占有某一互斥资源,同时请求另一互斥资源,且在等待时不会释放资源
      • 不可剥夺 : 资源只能被线程持有者释放
      • 循环等待 : 线程1 等待 线程2 占有的资源,线程2 等待 线程3 占有的资源 ...,线程n 等待 线程1 占有的资源
    • 避免方式
      • 忽略【常用】: 假设应用不会出现死锁,如果发生死锁则重启应用
      • 检测 : 创建专门分析应用状态的任务,如检测到死锁,则采取措施修复 - 结束线程、强制释放资源
      • 预防 : 避免 Coffman 条件一条或多条出现
      • 规避 : 线程开始前,得到该线程所有使用的资源
  • 活锁
    • 应用中的两个线程,总因为对方行为而改变自己的状态,导致双方陷入状态变更循环而无法向下运行
  • 资源不足
    • 解决 : 确保公平原则,所有等待该资源的线程必须在给定时间内占有该资源
  • 优先权反转
    • 低优先权线程 持有 高优先权线程 所需的资源,将发生优先权反转,低优先权线程 将在 高优先权线程 之前运行

四、内存模型 JMM

Java Memory Model

1. 内存可见性

  • 问题产生原因
    • L1、L2 缓存为 CPU 私有,L3 缓存为 CPU 共有,但因为有 CPU 缓存一致性协议 MESI,所以多核 CPU 间不会有内存可见性问题,但因为缓存一致性协议造成巨大性能损耗,故又增加了许多 Buffer
    • L1、L2、L3 与 主内存 之间是同步的,但 Store Buffer、Load Buffer 与 L1 之间是异步的 - 每个逻辑 CPU 有自己的缓存,缓存 和 主内存 不完全同步,造成在其他线程看来,该线程的运行顺序是颠倒的

2. 重排序

  • 类型
    • 编译器 : 没有先后依赖关系的语句,编译器可以重新调整语句的运行顺序
    • CPU 指令 : 没有依赖关系的多条指令并行
    • CPU 内存 : CPU 指令的运行顺序 和 写入主内存的顺序 不完全一致 - 造成内存可见性问题

3. 内存屏障

  • 告诉编译器不要对指令进行重排序
  • 种类
    • LoadLoad : 禁止 读 和 读 重排序
    • StoreStore : 禁止 写 和 写 重排序
    • LoadStore : 禁止 读 和 写 重排序
    • StoreLoad : 禁止 写 和 读 重排序
  • 使用方式
    • volatile 关键字
    • Unsafe 类
      • loadFence : LoadLoad + LoadStore
      • storeFence : StoreStore + LoadStore
      • fullFence : LoadFence + StoreFence + StoreLoad

4. as-if-serial

运行结果不会改变,代码看起来就像是完全串行的从头运行到尾

  • 单线程重排序规则
    • 单线程运行结果不能改变 - 操作之间没有数据依赖性,可以任意重排序
  • 多线程重排序规则
    • 保证每个线程 as-if-serial 语意

5. happen-before

  • A happen-before B
    • A 的运行结果对 B 可见
    • 不代表 A 一定在 B 之前运行
  • JMM 承诺
    • 单线程中的操作,happen-before 对应线程中任意后续操作 - as-if-serial 语意保证
    • volatile 变量写入,happen-before 对应后续该变量的读取 - volatile 变量不能重排序,非 volatile 变量可任意重排序
    • synchronized 的解锁,happen-before 对应后续该锁的加锁
    • final 变量的写入,happen-before 对应对象的读取,happen-before final 变量的读取
  • 传递性
    • A happen-before B,B happen-before C,则 A happed-before C

6. volatile

  • 功能
    • 64 位写入原子性
    • 保证内存可见性
    • 禁止重排序

五、并发容器

BlockingQueue 为接口,其他类为实现类

1. BlockingQueue

  • 带阻塞功能的队列

  • 当队列已满,阻塞发送者

  • 当队列为空,阻塞消费者

  • 方法

    • add : 非阻塞,当队列满时抛出异常

    • remove : 非阻塞,当队列空时抛出异常

    • offer : 非阻塞,当队列满时返回 false

    • poll : 非阻塞,当队列空时返回 null

    • put : 当队列满时阻塞

    • take : 当队列空时阻塞

  • ArrayBlockingQueue

    • 数组实现的环形队列,需传入数组容量

    • 实现方式

      • 1 把锁 ReentrantLock

      • 2 个条件 Condition

        • notEmpty

        • notFull

  • LinkedBlockingQueue

    • 单向列表的阻塞队列
    • 默认空间大小 Integer.MAX_VALUE
    • 实现方式
      • 2 把锁 ReentrantLock
        • takeLock
        • putLock
      • 2 个条件 Condition
        • notEmpty
        • notFull
  • PriorityQueue

    • 按照元素的优先级大小出队列
    • 默认空间大小 11,超过则自动扩容
    • 实现方式
      • 1 把锁 ReentrantLock
      • 1 个条件 Condition - notEmpty
  • DelayQueue

    • 按照延迟时间从小到大出队列

      延迟时间 : 未来将要运行的时间 - 当前时间

    • 当 队列空、堆顶元素延迟时间未到 阻塞

    • 实现方式

      • 1 把锁 ReentrantLock
      • 1 个条件 Condition - available
  • SynchronousQueue

    • 没有容量
    • 当调用 put() 线程阻塞,直到另一个线程调用 take(),两个线程才同时解锁
    • 模式
      • 公平模式 : 先到先配对
      • 非公平模式 : 后到先配对
    • 实现方式
      • 单向链表
      • put 节点、take 节点 相遇则出队列

2. BlockingDeque

Deque : Double End Queue 双端队列

  • 阻塞的双端队列接口

  • 继承 BlockingQueue、Deque

  • LinkedBlockingDeque

    • 实现方式
      • 双向链表
      • 1 把锁 ReentrantLock
      • 2 个条件 Condition
        • notEmpty
        • notFull

3. CopyOnWrite

  • 在写的时候,不是直接写数据,而是把数据拷贝一份进行修改,再透过乐观锁或悲观锁写回 - 【目的】读不加锁

  • CopyOnWriteArrayList

  • CopyOnWriteArraySet

    • 使用 Array 实现 Set,保证元素不重复
    • 内部封装 CopyOnWriteArrayList

4. ConcurrentLinkedQueue/Deque

  • 基于 双向链表,对 head/tail 进行 CAS 操作,实现 入队 和 出队

5. ConcurrentHashMap

  • key 无序
  • 头节点是 Node 类型,则后面的为 链表
  • 头节点是 TreeNode 类型,则后面的为 红黑树
  • 对每个 头节点 加锁
  • 初始数组长度 16
  • 当数组长度小于 64 时,不会将链表转换成红黑树,而是直接扩容
  • 链表元素超过 8 时,将链表转换成红黑树

6. ConcurrentSkipListMap

  • key 可排序,基于 SkipList(跳查表) 实现

7. ConcurrentSkipListSet

  • key 可排序,基于 SkipList(跳查表) 实现

六、同步工具

1. Semaphore 信号量

  • 资源数量的并发访问控制
  • 当初始资源数为 1 时,退化成排他锁
  • 有 公平、非公平 之分

2. CountDownLatch

  • 等待多个 Worker 线程运行完才能退出
  • 没有 公平、非公平 之分

3. CyclicBarrier

  • 协调多个线程同步运行操作
  • 可以被重用
  • 可以使用响应中断 breakBarrier(),唤醒所有阻塞线程,count 重置为初始值
  • 可以设置回调方法,在线程批量完成时运行一次

4. Exchanger

  • 用于线程间交换数据

5. Phaser

  • 当未达到线程数减到 0 时,唤醒主线程

  • 用于替代 CyclicBarrier 和 CountDownLatch

  • 等待所有人都到达这个同步点

  • 可以在运行期间动态调整要同步的线程个数

  • 多个 Phaser 可以组成树状结构

    • Phaser 知道自己的父节点,不知道自己的子节点,所以对父节点的操作,是透过子节点来实现的
    • 当子 Phaser 中注册的参与者数量等于 0 时,自动向父节点解除注册

七、Atomic 类

1. AtomicInteger、AtomicLong

  • 对整数进行加减操作,如需保证线程安全,则 需要加锁(synchronized) 或 使用 Atomic 类

  • 内部使用 CAS、自旋、volatile 语意 实现

    ○【目的】CAS、自旋 : 降低锁范围

    ○【目的】volatile : 运行结果在多线程间可见

2. AtomicBoolean、AtomicReference

  • 实现 Compare 和 Set 操作合在一起的原子性

3. AtomicStampedReference、AtomicMarkableReference

  • 解决 ABA 问题 - 不仅比较值,还要比较版本号

    从 A 改到 B,再从 B 改到 A

  • AtomicMarkableReference 与 AtomicStampedReference 类似,只是 AtomicMarkableReference 中的版本号是 boolean 类型,而不是整形

4. AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater

  • 解决已经存在的类,不能更改其源代码,对其实现成员变量的原子操作
  • 限制 : 成员变量必须是 volatile 的基础类型,不能是包装类型

5. AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray

  • 实现数组中一个元素的原子操作

6. Striped64、LongAdder、LongAccumulator、DoubleAdder、DoubleAccumulator

  • 将变量拆分为多个变量,性能相对 Atomic 再提升
  • Accumulator 与 Adder 原理相似,只是功能更强大

八、Condition、Lock

1. Condition

  • 本身是一个接口,需和 Lock 一起使用

    synchronized 需和 wait()、notify() 一起使用

  • 使用 Condition 本身还是只有一把锁(同时只有一个线程能运行),但可以精确通知被阻塞的线程

  • 使用 await() 方法,阻塞线程

  • 使用 signal() 方法,通知被阻塞的线程运行

  • 实现原理

    • 使用 双向链表 组成队列,维护被阻塞的线程
  • 使用方式

    public class ArrayBlockingQueue<E> extends AbstractQueue<E>
    			implements BlockingQueue<E>, java.io.Serializable {
      
      //...
      final Object[] items;
      int takeIndex;
      int putIndex;
      int count;
      
      // 一把锁+两个条件
      final ReentrantLock lock;
      private final Condition notEmpty;
      private final Condition notFull;
      
      public ArrayBlockingQueue(int capacity, boolean fair) {
        if (capacity <= 0)
        	throw new IllegalArgumentException();
        this.items = new Object[capacity];
        // 构造器中创建一把锁加两个条件
        lock = new ReentrantLock(fair);
        // 构造器中创建一把锁加两个条件
        notEmpty = lock.newCondition();
        // 构造器中创建一把锁加两个条件
        notFull = lock.newCondition();
      } 
      
      public void put(E e) throws InterruptedException {
        Objects.requireNonNull(e);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
          while (count == items.length)
            // 非满条件阻塞,队列容量已满
            notFull.await();
          enqueue(e);
        } finally {
        	lock.unlock();
      	}
    	} 
      
      private void enqueue(E e) {
        // assert lock.isHeldByCurrentThread();
        // assert lock.getHoldCount() == 1;
        // assert items[putIndex] == null;
        final Object[] items = this.items;
        items[putIndex] = e;
        if (++putIndex == items.length) putIndex = 0;
        count++;
        
        // put数据结束,通知消费者非空条件
        notEmpty.signal();
      } 
      
      public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
          while (count == 0)
            // 阻塞于非空条件,队列元素个数为0,无法消费
            notEmpty.await();
          return dequeue();
        } finally {
        	lock.unlock();
        }
      } 
      
      private E dequeue() {
        // assert lock.isHeldByCurrentThread();
        // assert lock.getHoldCount() == 1;
        // assert items[takeIndex] != null;
        final Object[] items = this.items;
        @SuppressWarnings("unchecked")
        E e = (E) items[takeIndex];
        items[takeIndex] = null;
        if (++takeIndex == items.length) takeIndex = 0;
        count--;
        if (itrs != null)
        	itrs.elementDequeued();
        
        // 消费成功,通知非满条件,队列中有空间,可以生产元素了。
        notFull.signal();
        
        return e;
      } 
      // ...
    }
    

2. 互斥锁 ReentrantLock

  • 防止两线程同时对一公共资源进行读写的机制

  • 读读互斥、读写互斥、写写互斥

  • 支持 Condition

  • 概念

    • 可重入
      • 当一个线程调用 object.lock() 获取到锁,再次调用 object.lock(),仍可获取到该锁
      • 通常锁都要设计成可重入,否则将发生死锁
      • synchronized
    • 公平
      • 新线程进来,看到很多线程在排队,自己排到队伍末尾
    • 非公平
      • 新线程来了之后,直接去抢锁
      • 【目的】提高效率,减少线程切换
  • 实现类

    • NonfairSync【默认】- 非公平锁
    • FairSync - 公平锁
  • 实现原理

    • AbstractOwnableSynchronizer

      • 纪录当前是哪个线程持有锁
    • AbstractQueuedSynchronizer(AQS)

      • 一个 state 变量,纪录锁状态,并使用 CAS 保证线程安全,因支持可重入锁,故 state 值可大于 1
    • 一个线程安全的无锁 队列 维护所有阻塞线程 - 使用 双向链表 + CAS 实现

    • 底层支持一个线程进行 阻塞 或 唤醒 操作

      • Unsafe 类提供 park() 阻塞、unpark() 唤醒

        unpark(thread) 实现对一个线程的精准唤醒,notify() 只是唤醒某一个线程

3. 读写锁 ReadWriteLock

  • 读读不互斥,读写互斥,写写互斥

  • 采用 悲观读 策略,当第一个线程拿到读锁后,第二个、第三个线程仍可以拿到读锁,可能导致写线程一直拿不到锁,造成写线程 饿死

  • 实现类

    • ReadLock - 不支持 Condition
    • WriteLock - 支持 Condition
  • 实现原理

    • 使用一把锁,将线程分为 读线程 和 写线程
    • 读线程 和 写线程 之间不互斥(可同时拿到这把锁),读线程之间不互斥,写线程之间互斥
    • 当 state != 0 时,要么有线程持有读锁,要么有线程持有写锁,两者不能同时成立,因为读和写互斥
  • 使用方式

    ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    Lock readLock = readWriteLock.readLock();
    readLock.lock();
    // 进行读取操作
    readLock.unlock();
    Lock writeLock = readWriteLock.writeLock();
    writeLock.lock();
    // 进行写操作
    writeLock.unlock();
    

4. StampedLock

  • 读读不互斥、读写不互斥、写写互斥

  • 采用 乐观读 策略,读不加读锁,读出来时发现数据被修改,再升级为 悲观读,降低 读 的地位,避免写线程被饿死

  • 实现原理

    • 在读之前给数据状态做一个快照,拷贝到内存中
  • 使用方式

    • 在读之前再比对一次版本号,如果版本号改变,则说明在读期间有其他线程修改该数据,则将读出来的数据废弃,重新获取悲观读锁,再次读取
    class Point {
      
    	private double x, y;
    	private final StampedLock sl = new StampedLock();
      
    	// 多个线程调用该方法,修改x和y的值
    	void move(double deltaX, double deltaY) {
        long stamp = sl.writeLock();
        try {
          x += deltaX;
          y += deltaY;
        } finally {
        	sl.unlockWrite(stamp);
        }
      } 
      
      // 多个线程调用该方法,求距离
    	double distenceFromOrigin() {
    
      // 使用“乐观读”
      long stamp = sl.tryOptimisticRead();
      // 将共享变量拷贝到线程栈
      double currentX = x, currentY = y;
        
      // 读期间有其他线程修改数据
      if (!sl.validate(stamp)) {
        // 读到的是脏数据,丢弃。
        // 重新使用“悲观读”
        stamp = sl.readLock();
        try {
          currentX = x;
          currentY = y;
        } finally {
        	sl.unlockRead(stamp);
        }
      } 
      return Math.sqrt(currentX * currentX + currentY * currentY);
    }
    

九、ThreadPool、Future

1. 线程池

  • 原理

    • 调用方不断向线程池中的任务队列提交任务,线程池中有一组线程,不断地从任务队列中取任务来运行 - 生产者 - 消费者 模型
    • 使用阻塞队列 管理任务 与 唤醒线程
  • 继承关系

  • 实现类

    • ThreadPoolExecutor
      • 参数
        • corePoolSize : 始终维护的线程个数
        • maxPoolSize : 在 corePoolSize 已满、队列也满的情况下,扩充线程至此值
        • keepAliveTime : maxPoolSize 中的线程,空闲多长时间将进行销毁,总线程数收缩回 corePoolSize
        • timeUnit : keepAliveTime 的时间单位
        • blockingQueue : 使用的任务队列类型
        • threadFactory : 线程创建工厂 - 【默认】Executors.defaultThreadFactory()
        • rejectedExecutionHandler
          • 当线程数到达 maxPoolSize,且 blockingQueue 已满的拒绝策略
          • 策略
            • AbortPolicy【默认】: 抛出异常
            • CallerRunsPolicy : 调用者直接在自己的线程里运行
            • DiscardPolicy : 丢弃任务
            • DiscardOldestPolicy : 丢弃任务队列中最早的任务
    • ScheduledThreadPoolExecutor
      • 可以延迟运行任务 - 【原理】依靠 DelayQueue
      • 可以周期性地运行任务
        • scheduleAtFixedRate() : 按照固定频率运行任务,与任务运行时间无关
        • scheduleWithFixedDelay() : 按照固定频率运行任务,与任务运行时间有关
        • 【原理】周期性地运行完任务后,再将任务扔回任务队列中
  • 任务提交流程

    1. 判断当前线程数是否小于 corePoolSize,如小於则创建新线程
    2. 判断队列是否已满,如未满则放入队列
    3. 判断当前线程数是否小于 maxPoolSize,如小於则创建新线程,如大於则根据拒绝策略,拒绝任务 - 如果任务队列是无界的,则永远没有机会走到此步骤
  • 正确关闭步骤

    1. 调用 Executor 的 shutdown() 或 shutdownNow() 方法

      ○ shutdown() : 不会清空任务队列、只会中断空闲线程

      ○ shutdownNow() : 清空任务队列、中断所有线程

    2. 循环调用 Executor 的 awaitTermination() 方法 - 判断线程池是否到达了最终状态

    // executor.shutdownNow();
    executor.shutdown();
    try {
    	boolean flag = true;
    do {
    	flag = ! executor.awaitTermination(500, TimeUnit.MILLISECONDS);
    } while (flag);
    } catch (InterruptedException e) {
    	// ...
    }
    
  • 使用方式

    • public class ThreadPoolExecutorDemo {
        public static void main(String[] args) {
          ThreadPoolExecutor executor = new ThreadPoolExecutor(
          	3,
          	5,
          	1,
          	TimeUnit.SECONDS,
          	new ArrayBlockingQueue<>(3),
            // new ThreadPoolExecutor.AbortPolicy()
            // new ThreadPoolExecutor.CallerRunsPolicy()
            // new ThreadPoolExecutor.DiscardOldestPolicy()
            new ThreadPoolExecutor.DiscardPolicy()
        	);
          
          for (int i = 0; i < 20; i++) {
          	int finalI = i;
        		executor.execute(new Runnable() {
              @Override
              public void run() {
                
                System.out.println(Thread.currentThread().getId() + "[" + finalI + "] -- 开始");
                try {
                	Thread.sleep(5000);
                } catch (InterruptedException e) {
                	e.printStackTrace();
                } 
                	System.out.println(Thread.currentThread().getId() + "["+ finalI + "] -- 结束");
                }
            });
            try {
              Thread.sleep(200);
            } catch (InterruptedException e) {
              e.printStackTrace();
            }
          } 
          executor.shutdown();
        	boolean flag = true;
          
          try {
            do {
              flag = !executor.awaitTermination(1, TimeUnit.SECONDS);
              System.out.println(flag);
            } while (flag);
          } catch (InterruptedException e) {
          	e.printStackTrace();
          } 
          
          System.out.println("线程池关闭成功。。。");
          System.out.println(Thread.currentThread().getId());
        }
      }
      
  • Executors 工具类

    阿里巴巴开发手册载明禁止使用

    • 单线程线程池 : newSingleThreadExecutor()
    • 固定数目线程池 : newFixedThreadPool()
    • 每接收一个请求,创建一个线程 : newCachedThreadPool()
    • 单线程,具有周期调度功能的线程池 : newSingleThreadScheduledExecutor()
    • 多线程,具有周期调度功能的线程池 : newSheduledThreadPool()

2. CompletableFuture

  • 用于提交多线程任务

  • 使用方式

    • complete() : 用于多线程间沟通,让计算结果可以用 get() 获取
    • get() : 运行被提交的任务,直到有返回结果
    public class CompletableFutureDemo {
      public static void main(String[] args) throws ExecutionException,InterruptedException {
        
        CompletableFuture<String> future = new CompletableFuture<>();
        
        new Thread(() -> {
          try {
          	Thread.sleep(1000);
          } catch (InterruptedException e) {
          	e.printStackTrace();
          } 
          future.complete("hello world");
        }).start();
        
        System.out.println("获取结果中。。。");
        String result = future.get();
        System.out.println("获取的结果:" + result);
      }
    }
    
    • runAsync(Runnable) : 提交没有返回值的任务
    public class CompletableFutureDemo2 {
      public static void main(String[] args) throws ExecutionException,InterruptedException {
        
        CompletableFuture<Void> voidCompletableFuture = CompletableFuture.runAsync(() -> {
          try {
            Thread.sleep(2000);
            System.out.println("任务执行完成");
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
        });
        // 阻塞,等待任务执行完成
        voidCompletableFuture.get();
        System.out.println("进程运行结束");
      }
    }
    
    • supplyAsync(Supplier) : 提交有返回值的任务
    public class CompletableFutureDemo3 {
      public static void main(String[] args) throws ExecutionException,InterruptedException {
        CompletableFuture<String> future = CompletableFuture.supplyAsync(new Supplier<String>() {
          @Override
          public String get() {
            try {
              TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
              e.printStackTrace();
            } 
            return "这是结果";
          }
        });
        String result = future.get();
        System.out.println("任务执行结果:" + result);
      }
    }
    
    • thenRun(Runnable) : 接着运行下一个任务,两个任务之间无任何关联
    public class CompletableFutureDemo4 {
      public static void main(String[] args) throws ExecutionException,InterruptedException {
        CompletableFuture voidCompletableFuture = CompletableFuture.supplyAsync(() -> {
          try {
            Thread.sleep(5);
          } catch (InterruptedException e) {
            e.printStackTrace();
          } 
          return "这是结果";
        }).thenRun(() -> {
          try {
            Thread.sleep(2);
          } catch (InterruptedException e) {
            e.printStackTrace();
          } 
          System.out.println("任务执行结束之后执行的语句");
        });
        // 阻塞等待任务执行完成 null
        voidCompletableFuture.get();
        System.out.println("任务执行结束");
      }
    }
    
    • thenAccept(Consumer) : 任务可以获取到前一个任务的返回值,但是本身没有返回值
    public class CompletableFutureDemo5 {
      public static void main(String[] args) throws ExecutionException,InterruptedException {
        
        CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> {
          
          try {
            Thread.sleep(5000);
          } catch (InterruptedException e) {
            e.printStackTrace();
          } 
          System.out.println("返回中间结果");
          return "这是中间结果";
        }).thenAccept((param) -> {
          try {
            Thread.sleep(2000);
          } catch (InterruptedException e) {
          	e.printStackTrace();
          } 
          System.out.println("任务执行后获得前面的中间结果:" + param);
        });
        // 阻塞等待任务执行完成
        future.get();
        System.out.println("任务执行完成");
      }
    }
    
    • thenApply(Function) : 任务可以获取到前一个任务的返回值,并且本身有返回值
    public class CompletableFutureDemo6 {
      
      public static void main(String[] args) throws ExecutionException,InterruptedException {
        
        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
          try {
            Thread.sleep(5000);
          } catch (InterruptedException e) {
            e.printStackTrace();
          } 
          System.out.println("返回中间结果");
          return "abcdefg";
        }).thenApply(new Function<String, Integer>() {
            @Override
            public Integer apply(String middle) {
              try {
                Thread.sleep(2000);
              } catch (InterruptedException e) {
                e.printStackTrace();
              } 
              System.out.println("获取中间结果,再次计算返回");
              return middle.length();
          }
        });
        Integer integer = future.get();
        System.out.println("最终的结果为:" + integer);
      }
    }
    
    • thenCompose(Function) : 嵌套 CompletableFuture
    CompletableFuture<Integer> future = CompletableFuture.supplyAsync(new Supplier<String>() {
      @Override
      public String get() {
      	return "hello world";
      }
    }).thenCompose(new Function<String, CompletionStage<Integer>>() {
      @Override
      public CompletionStage<Integer> apply(String s) {
      	return CompletableFuture.supplyAsync(new Supplier<Integer>() {
          @Override
          public Integer get() {
          	return s.length();
          }
      	});
      }
    });
    Integer integer = future.get();
    System.out.println(integer);
    
    • thenCombine() : 在 2 个 CompletableFuture 完成之后,把 2 个 CompletableFuture 的返回值传进去,再额外做一些事情
    CompletableFuture<Integer> future = CompletableFuture.supplyAsync(new Supplier<String>() {
      @Override
      public String get() {
      	return "hello";
      }
    }).thenCombine(CompletableFuture.supplyAsync(new Supplier<String>() {
      @Override
      public String get() {
      	return "lagou";
    	}
    }), new BiFunction<String, String, Integer>() {
      @Override
      public Integer apply(String s, String s2) {
      	return s.length() + s2.length();
      }
    });
    Integer result = future.get();
    System.out.println(result);
    
    • allOf() : 所有的 CompletableFuture 结束,返回 Void 类型
    • anyOf() : 任意一个 CompletableFuture 结束,返回 Object 类型
    public class CompletableFutureDemo11 {
    
        private static final Random RANDOM = new Random();
        private static volatile int result = 0;
    
        public static void main(String[] args) throws ExecutionException, InterruptedException {
    
            CompletableFuture[] futures = new CompletableFuture[10];
    
            for (int i = 0; i < 10; i++) {
    
                int finalI = i;
    
                CompletableFuture<Void> future = CompletableFuture.runAsync(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            Thread.sleep(1000 + RANDOM.nextInt(1000));
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        result++;
                    }
                });
                futures[i] = future;
            }
            System.out.println(result);
            // for (int i = 0; i < 10; i++) {
            // 		futures[i].get();
            // 		System.out.println(result);
            // }
            // Integer allResult = CompletableFuture.allOf(futures).thenApply(new Function<Void, Integer>() {
            // 		@Override
            // 		public Integer apply(Void unused) {
            // 		    return result;
            // 		}
            // }).get();
            //
            // System.out.println(allResult);
            Integer anyResult = CompletableFuture.anyOf(futures).thenApply(new Function<Object, Integer>() {
                @Override
                public Integer apply(Object o) {
                    return result;
                }
            }).get();
            System.out.println(anyResult);
        }
    }
    

十、ForkJoin

  • 基于多线程实现分治算法的并行计算框架 - 可以视为单机版的 Map/Reduce - 将一个大任务拆分成多个小任务,再将小任务结果合并

  • 相比 ThreadPoolExecutor,可以更好地实现计算的负载均衡,提高资源利用率

    • ForkJoinTask

      • RecursiveAction : 无返回值
      • RecursiveTask : 有返回值
      • 方法
        • compute() : 制定具体业务逻辑
    • ForkJoinPool

      • 具备一个全局任务队列,每个 Worker 线程还各具备一个局部任务队列

      • 当 Worker 线程运行完自己队列的任务后,会窃取其他线程队列中的任务来运行

        • 以防有的线程很闲,有的线程很忙
        • Worker 线程自己,在队列头部,透过对 top 指针运行加、减操作,实现任务的入队与出队
        • 其他 Worker 线程,在队列尾部,对 base 指针进行累加,实现任务出队操作
          • 由于是多线程,需要透过 CAS 操作
        • 任务队列是环形的,当 top - base = queue.length - 1 时,表示队列满,需要扩容
      • 使用 long 型变量保存状态

        • AC : 最高 16 比特位,Active 线程数 - parallelism
        • TC : 次高 16 比特位,Total 线程数 - parallelism
        • ST : 1 比特位,如果是 1,表示 ForkJoinPool 正在关闭
        • EC : 15 比特位,阻塞栈的栈顶线程的 wait count
        • ID : 16 比特位,阻塞栈的栈顶线程的 ID
      • 阻塞栈 Treiber Stack

        • 用一个无锁链表,实现多个线程的阻塞与唤醒
        • 当一个 Worker 线程窃取不到任何任务时,就会进入阻塞栈
        • 当有新任务时,唤醒 Worker 线程,并出阻塞栈
      • 线程状态

        • 空闲 : 放在 阻塞栈 里面
        • 活跃 : 正在运行某个 ForkJoinTask,但未阻塞
        • 阻塞 : 正在运行某个 ForkJoinTask,但因为正在等待子任务运行完成,所以被阻塞
      • 方法

        • fork()
          • 将子任务放入任务队列,并运行
          • 如果是 Worker 线程调用,则将任务放入当前线程的局部队列
          • 如果是其他线程调用,则将任务放入共享队列中
        • join()
          • 等待子任务运行完成,并返回子任务运行结果
          • 实现方式 : 每个 ForkJoinTask 有多个线程等待它完成,所以每个 ForkJoinTask 就是一个同步对象,当线程调用 join() 时,将阻塞在该 ForkJoinTask 上,当运行完成后,将通知所有等待的线程
        • get() : 获取任务结果
        • shutdown() : 拒绝新提交的任务,结束 ForkJoinPool
        • shutdownNow() : 取消现有 全局队列 与 局部队列 中的任务,并唤醒所有空闲线程,让其自动退出
  • 适用场景

    • 快速排序
    • 求 1 ~ n 总和

十一、设计模式

1. Single Threaded Execution

  • 以一个线程运行
  • 使用 synchronized 关键字实现,保护 unsafeMethod,使其同时只能由一个线程访问
  • 使用场景
    • 多线程访问共享资源
    • 需确保资源(集合)安全性

2. Immutable

  • 确保实例状态不会发生改变
  • 使用 final 关键字实现
  • 使用场景
    • 创建实例后,状态不再发生改变
    • 实例共享,且被频繁访问
  • 实际案例
    • java.lang.String
    • java.math.BigInteger
    • java.math.Decimal
    • java.util.regex.Pattern
    • java.lang.Boolean
    • java.lang.Byte
    • java.lang.Character
    • java.lang.Double
    • java.lang.Float
    • java.lang.Integer
    • java.lang.Long
    • java.lang.Short
    • java.lang.Void

3. Guarded Suspension

  • 当守护条件不成立时,就让线程继续等待

  • 使用 while、wait()、notify()/notifyAll() 实现

  • public class RequestQueue {
    
        private final Queue<Request> queue = new LinkedList<>();
    
        public synchronized Request getRequest() {
            // 守护条件
            while (queue.peek() == null) {
                try {
                    // 当守护条件不成立时,就继续等待
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            return queue.remove();
        }
    
        public synchronized void putRequest(Request request) {
            queue.offer(request);
            notifyAll();
        }
    }
    

4. Balking

  • 当守护条件不成立时,立即中断处理

  • public class Data {
    
        private final String filename;
        private String content;
        private boolean changed;
    
        public Data(String filename, String content) {
            this.filename = filename;
            this.content = content;
        }
    
        public synchronized void change(String newContent) {
            this.content = newContent;
            this.changed = true;
        }
    
        public synchronized void save() throws IOException {
    
            // 当守护条件成立,则正常运行
            if (changed) {
                doSave();
                changed = false;
            }
            // 当守护条件不成立,则中断处理
            else {
                System.out.println(Thread.currentThread().getName() + "不需要保存");
            }
        }
    
        private void doSave() throws IOException {
            System.out.println(Thread.currentThread().getName() + " 调用doSave, 内容为:" + content);
            Writer writer = new FileWriter(filename);
            writer.write(content);
            writer.close();
        }
    }
    
  • 使用场景

    • 当守护条件不成立,则不需要处理
    • 守护条件仅在第一次运行时成立

5. Producer-Consumer

  • 生产者 将数据交给 Channel,消费者 从 Channel 中获取数据

  • 生产者 与 消费者 都有多个,当 生产者 和 消费者 只有一个时,称为 管道(Pipeline) 模式

  • public class Table {
    
        private final String[] buffer;
        private int tail;
        private int head;
        private int count;
    
        public Table(int count) {
            this.buffer = new String[count];
            this.head = 0;
            this.tail = 0;
            this.count = 0;
        }
    
        public synchronized void put(String steamedBread) throws
                InterruptedException {
            System.out.println(Thread.currentThread().getName() + " 蒸出来 " + steamedBread);
            while (count >= buffer.length) {
                wait();
            }
            buffer[tail] = steamedBread;
            tail = (tail + 1) % buffer.length;
            count++;
            notifyAll();
        }
    
        public synchronized String take() throws InterruptedException {
            
            while (count <= 0) {
                wait();
            }
    
            String steamedBreak = buffer[head];
            head = (head + 1) % buffer.length;
            count--;
            notifyAll();
            System.out.println(Thread.currentThread().getName() + " 取走 " + steamedBreak);
            return steamedBreak;
        }
    }
    
    
  • 线程要协调放在 Channel 的东西

  • 线程要互斥应该保护的东西

  • 实际案例

    • BlockingQueue : 阻塞队列
    • ArrayBlockingQueue : 基于数组的 BlockingQueue
    • LinkedBlockingQueue : 基于链表的 BlockingQueue
    • PriorityBlockingQueue : 具有优先级的 BlockingQueue
    • DelayQueue : 特定时间后才可以 take 的 BlockingQueue
    • SynchronousQueue : 一直传递的 BlockingQueue
    • ConcurrentLinkedQueue : 不限制元素个数的线程安全 BlockingQueue

6. Read-Write Lock

  • 多个线程可同时读取,但读取时不可写入

  • 当线程正在写入时,其他线程不可 读取 或 写入

  • 利用线程读操作不会冲突的特性来提高性能

  • public class ReadWriteLock {
        
        private int readingReaders = 0;
        private int waitingWriters = 0;
        private int writingWriters = 0;
        private boolean preferWriter = true;
    
        /**
         * 对于读操作:
         * 1. 如果有正在写入的writer,则当前线程等待writer写入完成
         * 2. 如果没有正在写入的writer,但是有正在等待写入的writer,同时偏向写操作
         * 则当前线程等待writer写入完成
         *
         * @throws InterruptedException
         */
        public synchronized void readLock() throws InterruptedException {
            while (writingWriters > 0 || (preferWriter && waitingWriters > 0)) {
                // 当前线程等待
                wait();
            }
            readingReaders++;
        }
    
        public synchronized void readUnlock() {
            readingReaders--;
            preferWriter = true;
            // 唤醒在当前对象上等待的其他线程
            notifyAll();
        }
    
        public synchronized void writeLock() throws InterruptedException {
            waitingWriters++;
            try {
                // 如果有正在读取的线程,或者有正在写入的线程,则当前线程等待被唤醒
                while (readingReaders > 0 || writingWriters > 0) {
                    wait();
                }
            } finally {
                waitingWriters--;
            }
            writingWriters++;
        }
    
        public synchronized void writeUnlock() {
            writingWriters--;
            preferWriter = false;
            // 当前线程写入完成,唤醒其他等待的线程进行读写操作
            notifyAll();
        }
    }
    
  • readLock()、writeLock() 都采用了 Guarded Suspension 模式

  • 使用场景

    • 读操作负载大
    • 多读少写
  • 实际案例

    • ReadWriteLock

7. Thread-Per-Message

  • 为每个请求分配一个线程

  • 流程

    1. Client 向 Host 提交请求

    2. Host 启用线程,线程调用 Helper 处理请求

  • public class Host {
        private final Helper helper = new Helper();
    
        public void request(final int count, final char c) {
            System.out.println("\t请求:【" + count + "," + c + "】开始。。。");
            new Thread() {
                @Override
                public void run() {
                    helper.handle(count, c);
                }
            }.start();
            System.out.println("\t请求:【" + count + "," + c + "】结束!!!");
        }
    }
    
    public class Helper {
        
        public void handle(int count, char c) {
            System.out.println("\t\t处理:【" + count + "," + c + "】开始。。。");
            for (int i = 0; i < count; i++) {
                slowly();
                System.out.print(c);
            }
            System.out.println("");
            System.out.println("\t\t处理:【" + count + "," + c + "】结束!!!");
        }
    
        private void slowly() {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
  • 使用场景

    • 需要快速响应
    • 对操作顺序没有要求
    • 不需要返回值
  • 实际案例

    • Thread 类
    • Runnable 接口
    • ThreadFactory 接口
    • Executor 接口
    • Executors 类
    • ExecutorService 接口
    • ScheduledExecutorService 类

8. Worker Thread

  • Worker 线程逐个取回工作并处理

  • 也被称为 Thread Pool 模式

  • 流程

    1. Client 提交任务给 Channel
    2. Worker Thread 不断从 Channel 中获取任务来运行
  • public class Channel {
        
        private static final int MAX_REQUEST = 100;
        private final Request[] requestQueue;
        private int tail;
        private int head;
        private int count;
        private final WorkerThread[] threadPool;
    
        public Channel(int threads) {
            this.requestQueue = new Request[MAX_REQUEST];
            this.head = 0;
            this.tail = 0;
            this.count = 0;
            threadPool = new WorkerThread[threads];
            for (int i = 0; i < threadPool.length; i++) {
                threadPool[i] = new WorkerThread("Worker-" + i, this);
            }
        }
    
        public void startWorkers() {
            for (int i = 0; i < threadPool.length; i++) {
                threadPool[i].start();
            }
        }
    
        public synchronized void putRequest(Request request) {
            while (count >= requestQueue.length) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
            requestQueue[tail] = request;
            tail = (tail + 1) % requestQueue.length;
            count++;
            notifyAll();
        }
    
        public synchronized Request takeRequest() {
            while (count <= 0) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
            Request request = requestQueue[head];
            head = (head + 1) % requestQueue.length;
            count--;
            notifyAll();
            return request;
        }
    }
    
    
    public class WorkerThread extends Thread {
    
        private final Channel channel;
    
        public WorkerThread(String name, Channel channel) {
            super(name);
            this.channel = channel;
        }
    
        @Override
        public void run() {
            while (true) {
                Request reqeust = channel.takeRequest();
                reqeust.execute();
            }
        }
    }
    
  • 使用场景

    • 提高吞吐量
    • 控制 Worker 线程数量
    • 调用与运行分离
  • 实际案例

    • ThreadPoolExecutor
    • Executors

9. Future

  • 提交任务,先拿到一张提货卡,过段时间透过提货卡,获得任务处理结果

  • 流程

    1. Client 向 Host 提交任务,返回 FutureData
    2. Host 产生新线程,新线程在 RealData 运行任务,并将任务结果设置到 FutureData
    3. Client 访问 FutureData 获取任务结果
  • public class Host {
        
        public Data request(final int count, final char c) {
            
            System.out.println("\trequest(" + count + ", " + c + ") 开始");
            
            // 创建FutureData对象
            final FutureData future = new FutureData();
            
            // 启动新线程,创建RealData对象
            new Thread() {
                @Override
                public void run() {
                    RealData realData = new RealData(count, c);
                    future.setRealData(realData);
                }
            }.start();
            
            System.out.println("\trequest(" + count + ", " + c + ") 结束");
            
            // 返回提货单
            return future;
        }
    }
    
    public class FutureData implements Data {
        
        private RealData realData = null;
        private boolean ready = false;
        
        public synchronized void setRealData(RealData realData) {
            
            // balking,如果已经准备好,就返回
            if (ready) {
                return;
            } 
            
            this.realData = realData;
            this.ready = true;
            notifyAll();
        } 
        
        @Override
        public synchronized String getContent() {
            // guarded suspension
            while (!ready) {
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            } 
            return realData.getContent();
        }
    }
    
    public class RealData implements Data {
    
        private final String content;
    
        public RealData(int count, char c) {
            System.out.println("\t组装RealData(" + count + ", " + c + ") 开始");
            char[] buffer = new char[count];
            for (int i = 0; i < count; i++) {
                buffer[i] = c;
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("\t\t组装RealData(" + count + ", " + c + ") 结束");
            this.content = new String(buffer);
        }
    
        @Override
        public String getContent() {
            return content;
        }
    
    }
    
  • 使用场景

    • 提高响应,并需要获取任务结果

      Thread-Per-Message 只能提高响应,但不能获取任务结果

  • 实际案例

    • Callable
    • Future
    • FutureTask