| 
 
| 多线程 线程间通信 (等待唤醒案例)
 线程池
 函数式编程
 Lambda表达式
 标准格式
 省略格式线程间通信线程间通信介绍知识点:    为什么要进行线程间通信
 进行线程间通信要使用什么技术总结:    线程间通信:
 多个线程在处理同一个资源, 但是多个线程的处理动作却不相同(线程的任务不同, 需要协调合作)
 为什么要进行线程间通信:
 通常是竞争关系: 多个线程并发执行时, 在默认情况下CPU是随机切换线程的.
 有时也"需要合作": 当我们需要多个线程来共同完成一件任务, 并且我们希望他们有规律的执行, 那么多线程之间需要一些协调通信, 以此来帮我们达到多线程共同操作一份数据
 如何实现线程间通信:
 "等待唤醒机制"补充:等待唤醒机制介绍知识点:    什么是等待唤醒机制
 了解 wait/notify 的执行流程总结:    等待唤醒机制:
 wait/notify, 就是"线程间的一种协作机制", 用于实现线程间通信
 
 等待唤醒中的方法
 java.lang.Object类:
 // 成员方法 (只能通过"锁对象"调用)
 void notify(): 随机唤醒在同一个锁对象上的某一个处于等待状态的线程
 void notifyAll(): 唤醒所有在同一个锁对象上处于等待状态的线程
 void wait(): 让当前线程处于无限等待状态, 同时释放锁
 
 wait和notify/notifyAll的执行原理:
 wait:
 线程不再活动, 不再参与调度, 进入 wait set 中, 因此不会浪费 CPU 资源, 也不会去竞争锁, 这时的线程状态即是"WAITING". 它还要等着别的线程执行"通知(notify)", 让在锁对象上等待的线程从 wait set 中释放出来, 重新进入到调度队列(ready queue)中
 notify/notifyAll:
 哪怕只通知了一个等待的线程, 被通知线程也不能立即恢复执行, 因为它当初中断的地方是在同步块内, 而
 此刻它已经不持有锁, 所以它需要"再次尝试去获取锁"(很可能面临其它线程的竞争), 成功后才能在当初调用 wait() 之后的地方恢复执行
 总结如下:
 如果能获取锁, 线程就从"WAITING"状态变成"RUNNABLE"状态
 否则, 从 wait set 出来, 又进入entry set, 线程就从"WAITING"状态又变成"BLOCKED"状态
 
 调用 wait() 和 notify() 需要注意的细节:
 1. wait() 与 notify() 必须要由"同一个锁对象"调用
 因为对应的锁对象可以通过 notify() 唤醒使用同一个锁对象调用的 wait() 后的线程
 2. wait() 与 notify() 是属于Object类的方法
 因为锁对象可以是任意对象, 而任意对象的所属类都是继承了Object类的
 3. wait() 与 notify() 必须要在"同步代码块"或者是"同步方法"中使用
 因为必须要通过锁对象调用这2个方法补充:    等待唤醒机制: 吃包子(需求分析)知识点:    生产者消费者问题: 模拟包子铺卖包子给吃货, 需要哪些类总结:    资源: 包子类
 属性:
 皮
 馅
 是否有包子: true有, false没有
 生产者: 包子铺类, 继承Thread
 任务:
 对包子状态进行判断:
 true: 有包子, 则wait()
 false: 没有包子, 则交替生产不同皮和馅的包子, 修改包子状态为true, 并notify()吃货来吃
 消费者: 吃货类, 继承Thread
 任务:
 对包子状态进行判断:
 false: 没有包子, 则wait()
 true: 有包子, 则吃, 修改包子状态为false, 并notify()包子铺生产
 测试类:
 创建包子对象(作为共享的锁对象)
 创建包子铺对象, 启动线程
 创建吃货对象, 启动线程补充:    等待唤醒机制: 吃包子代码实现(包子类, 包子铺类)5分钟练习: 定义包子类和包子铺类    需求:
 定义包子类: Baozi
 属性: 为了写代码方便不用加private
 皮儿: String pi;
 馅儿: String xian;
 是否有包子: boolean you = false; 默认没有
 定义包子铺类: BaoziPu, 继承Thread类
 属性: 为了写代码方便不用加private
 Baozi baozi;
 有参构造: public BaoziPu(Baozi baozi)
 重写run()方法:
 定义变量 int count = 0; 用于每次做不同的包子
 死循环 while(true)做包子
 循环中定义同步代码块, 锁对象是baozi对象
 同步代码块中, 先if判断是否有包子, 有则wait()
 if代码块外, 判断 count 的奇偶, 奇数将包子改为"冰皮牛肉馅", 偶数将包子改为"薄皮三鲜馅"
 count++ 增加
 打印输出包子铺生产的包子
 sleep 3 秒模拟生产包子的耗时
 修改baozi属性为 true 有包子
 notify()通知吃货来吃
 
 包子铺任务:
 if (有包子) {
 则wait()等待吃货通知
 }
 醒了之后, 生产包子
 修改包子状态为: 有包子
 notify()通知吃货来吃代码:    public class BaoziPu extends Thread {
 // 定义包子成员变量, 作为共享的数据
 Baozi baozi;
 // 定义有参构造方法, 便于将共享的同一个包子对象传入
 public BaoziPu(Baozi baozi) {
 this.baozi = baozi;
 }
 // 定义包子铺的任务
 @Override
 public void run() {
 // 定义一个计数器变量, 用于判断生产什么包子
 int count = 0;
 // 循环生产包子
 while (true) {
 // 使用同步代码块
 synchronized (baozi) {
 // 先判断是不是有包子, 有包子则先等待
 if (baozi.you) {
 try {
 baozi.wait();
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 }
 // 如果能执行到这里, 说明被吃货唤醒了, 要开始做包子. 先判断做什么馅
 if (count % 2 == 0) {
 // 做薄皮三鲜
 baozi.pi = "薄皮";
 baozi.xian = "三鲜馅";
 } else {
 // 做冰皮牛肉
 baozi.pi = "冰皮";
 baozi.xian = "牛肉馅";
 }
 // 将计数器增加
 count++;
 // 打印一句话模拟包子制作过程
 System.out.println("[包子铺] 正在生产" + baozi.pi + baozi.xian + "的包子");
 try {
 Thread.sleep(2000);
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 // 包子做好了, 要修改包子的状态
 baozi.you = true;
 // 通知吃货来吃
 System.out.println("[包子铺] 做好了" + baozi.pi + baozi.xian + "的包子, 快来取餐!");
 baozi.notify();
 }
 }
 }
 }
 public class Baozi {
 String pi;
 String xian;
 boolean you = false;  // 一开始没有包子
 }等待唤醒机制: 吃包子代码实现 (吃货类, 测试类)5分钟练习: 定义吃货类和测试类    需求:
 定义吃货类: Chihuo, 继承Thread类
 属性: 为了写代码方便不用加private
 Baozi baozi;
 有参构造: public ChiHuo(Baozi baozi)
 重写run()方法:
 while(true)循环吃包子
 循环内定义同步代码块, 锁对象为baozi对象
 if判断包子状态, 如果没有包子, 则 wait() 等待
 if代码块外, 打印吃货吃包子
 修改包子状态为 false 没有包子
 notify()通知包子铺做包子
 打印分割线
 定义测试类:
 创建baozi对象
 创建包子铺对象, 传入包子, 调用start()启动线程
 创建吃货对象, 传入包子, 调用start()启动线程
 
 吃货任务:
 if (没有包子) {
 则wait()等待包子铺通知
 }
 醒了之后, 吃包子
 修改包子状态为: 没有包子
 notify()通知包子铺生产包子代码:    public class ChiHuo extends Thread {
 Baozi baozi;
 public ChiHuo(Baozi baozi) {
 this.baozi = baozi;
 }
 @Override
 public void run() {
 // 循环吃包子
 while (true) {
 // 使用同步代码块
 synchronized (baozi) {
 // 先判断是不是没有包子, 没有包子则等待
 if (!baozi.you) {
 try {
 baozi.wait();
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 }
 // 如果代码能执行到这里 说明被包子铺唤醒了, 则有包子了, 开始吃包子
 System.out.println("[吃货] 我正在吃" + baozi.pi + baozi.xian + "的包子, 真香!");
 // 吃完了修改状态
 baozi.you = false;
 // 通知包子铺再去生产包子
 System.out.println("[吃货] 吃完了, 老板再来一锅!");
 baozi.notify();
 System.out.println("---------------------------");
 }
 }
 }
 }
 public class Test {
 public static void main(String[] args) {
 // 创建一个包子对象, 同时传入包子铺和吃货, 作为共享的锁对象
 Baozi baozi = new Baozi();
 // 创建包子铺, 并开始
 new BaoziPu(baozi).start();
 // 创建吃货, 并开始
 new ChiHuo(baozi).start();
 }
 }线程池线程池概念和原理知识点:    普通创建线程方式有什么缺点
 什么是线程池
 线程池可以解决什么问题
 线程池的工作原理是怎样的总结:    普通创建线程方式的缺点:
 "创建"线程和"销毁"线程都是比较占用内存和CPU的操作.
 对于一些数量多, 执行时间短的任务, 频繁的创建和销毁线程来执行, 会降低程序运行效率.
 
 线程池:
 一个容纳多个线程的容器(集合)
 
 线程池可以解决的问题:
 其中的线程可以反复使用, 省去了频繁创建线程对象的操作, 无需反复创建线程而消耗过多资源
 
 线程池的工作原理:
 提前创建好多个线程对象, 放在集合中. 多个任务来了反复使用这些线程对象来执行补充:    线程池的代码实现知识点:    JDK 5 中提供了哪些类/接口和方法, 可以实现线程池
 如何使用总结:                         一哥栽Q特儿斯
 java.util.concurrent.Executors类: 线程池工厂类, 用于创建和管理线程池
 // 静态方法:
 static ExecutorService newFixedThreadPool(int nThreads): 创建固定数量线程的线程池(常用)
 
 一哥载Q特儿 色儿喂斯
 java.util.concurrent.ExecutorService接口: 真正执行任务的线程池服务
 // 成员方法:
 Future submit(Runnable task): 提交一个Runnable任务
 void shutdown(): 通知线程执行完任务后关闭. 如不调此方法, 则线程执行完任务后仍在运行以便重复使用
 
 线程池的创建和使用步骤:
 1. 使用Executors的静态方法 newFixedThreadPool(int nThreads) 创建线程池ExecutorService
 2. 创建一个任务类, 实现Runnable接口, 重写run()方法
 3. 调用ExecutorService对象的 submit(Runnable task) 方法, 传递任务给线程池, 执行任务
 4. 调用ExecutorService对象的 shutdown() 方法, 销毁线程池 (不建议执行)补充:    // 创建线程池对象
 ExecutorService es = Executors.newFixedThreadPool(2);
 // 提交任务
 RunnableImpl r = new RunnableImpl();  // 1个任务
 es.submit(new RunnableImpl());
 es.submit(new RunnableImpl());
 es.submit(new RunnableImpl());
 // 销毁线程池
 es.shutdown();
 
 单独创建的线程 和 线程池 的适用场景:
 单独创建的线程: 适合任务执行时间长, 但数量少的场景 (包子铺吃货)
 线程池: 适合任务执行时间短, 但数量多的场景 (网络请求, 10000个用户分别查看一个网页页面)5分钟练习: 使用线程池模拟银行服务员    需求: 模拟银行柜台办理业务
 银行有2个柜台, 现在来了10个客户. 同一时间只能有2个客户办理业务, 其余客户要等待
 定义RunnableImpl类, 实现Runnable接口, 模拟办理业务:
 重写run()方法:
 打印: 线程名称+"号柜台为您服务"
 打印: "顾客正在办理业务..."
 sleep()2秒模拟客户办理业务耗时
 打印: 线程名称+"号柜台期待您再次光临"
 打印: 分割线--------
 定义测试类:
 Executors.newFixedThreadPool(2) 创建有2个线程的线程池ExecutorService
 for循环10次, 循环内使用ExecutorService对象的 submit() 提交任务对象, 用来模拟10个用户
 循环结束后使用ExecutorService对象的 shutdown() 方法结束线程池代码:    public class RunnableImpl implements Runnable {
 @Override
 public void run() {
 System.out.println(Thread.currentThread().getName() + "号柜台为您服务");
 System.out.println("顾客正在办理业务...");
 try {
 Thread.sleep(2000);
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 System.out.println(Thread.currentThread().getName() + "号柜台期待您再次光临");
 System.out.println("--------------------------------------------");
 }
 }
 public class Test {
 public static void main(String[] args) {
 // 创建线程池
 ExecutorService executorService = Executors.newFixedThreadPool(2);
 // 提交10个任务
 for (int i = 0; i < 10; i++) {
 executorService.submit(new RunnableImpl());
 }
 // 可以关闭也可以不关闭
 executorService.shutdown();
 }
 }函数式编程思想: Lambda表达式函数式编程思想概述知识点:    函数式编程思想强调什么
 函数式编程思想与面向对象编程思想有什么区别
 函数式编程思想有什么好处总结:    函数式:
 在数学中, 函数就是有输入量, 输出量的一套计算方案, 也就是"传入什么东西, 得到什么结果"
 y = f(x)
 面向对象: 强调"用哪个对象的哪个方法"来做事 (注重语法形式: 继承 方法重写)
 函数式: 强调"传入的参数 和 要执行的代码"
 函数式编程的好处:
 简化代码编写 (使用 λ Lambda表达式, 简化匿名内部类的代码)补充:    冗余的Runnable代码知识点:    之前使用的匿名内部类方式创建线程对象的代码, 有哪些是按照面向对象思想不得不写出来的多余步骤总结:    // 我们要通过Lambda表达式简化以下的代码
 new Thread(new Runnable() {
 @Override
 public void run() {
 // 要执行的代码才是重要的
 }
 }).start();
 关键代码是: run()方法中要执行的任务
 而其他代码都只是形式补充:    编程思想的转换, 体验Lambda更优写法知识点:    Lambda表达式是哪个JDK版本中加入的
 体会Lambda表达式简化匿名内部类的代码的简洁性总结:    JDK 8 中, 加入的Lambda表达式, 是函数式编程思想中的重点
 对比:
 // 面向对象方式的代码
 new Thread(new Runnable() {
 @Override
 public void run() {
 System.out.println(Thread.currentThread().getName() + "新线程创建了");
 }
 }).start();
 
 // 函数式编程的代码
 new Thread( ()-> {
 System.out.println(Thread.currentThread().getName() + "新线程创建了");
 }
 ).start();补充:    Lambda标准格式知识点:    Lambda表达式的标准格式是什么样的, 由哪3部分组成
 总结:    Lambda表达式的3个部分:
 1. 一些参数 ()
 接口中抽象方法的参数列表. 没参数就空着; 有参数就写, 多个参数用逗号分隔
 2. 一个箭头 ->
 将参数传递给方法体
 3. 一段代码 {}
 重写接口抽象方法的方法体
 格式:
 // 写成多行
 (数据类型 变量名, 数据类型 变量名) -> {
 一些重写方法的代码
 一些重写方法的代码
 ...
 }
 // 如果代码只有一行, 也可以合并写成一行
 (参数列表) -> {一些重写方法的代码}补充:    5分钟练习: 使用Lambda标准格式(简化线程创建)    需求:
 定义测试类, 使用实现Runnable接口方式创建线程
 1. 创建Thread对象, 在构造方法参数中, 使用匿名内部类方式传入Runnable实现类对象
 重写 run() 方法, 打印 线程名字+"新线程创建了", 并启动线程
 2. 创建Thread对象, 在构造方法参数中, 使用Lambda表达式方式传入Runnable实现类对象
 重写 run() 方法, 打印 线程名字+"新线程创建了", 并启动线程代码:    public class Test {
 public static void main(String[] args) {
 // 匿名内部类方式
 new Thread(new Runnable() {
 @Override
 public void run() {
 System.out.println(Thread.currentThread().getName()+"新线程创建了");
 }
 }).start();
 
 // Lambda标准格式
 new Thread(
 // 为了避免括号嵌套混乱看不清关系, 可以将参数括号单独换一行, 再写Lambda参数
 () -> {
 System.out.println(Thread.currentThread().getName()+"新线程创建了");
 }
 ).start();
 }
 }练习: 使用Lambda标准格式(重写"无参无返回值"的方法)5分钟练习: 厨师喊吃饭    需求:
 已经提供了一个厨子 Cook 接口, 接口中只有唯一一个抽象方法 makeFood(), 且无参数, 无返回值. 如下:
 public interface Cook {
 void makeFood();
 }
 在下面的代码中, 请分别使用匿名内部类方式, 和Lambda的标准格式, 调用 invokeCook() 方法, 打印输出"吃饭啦!"字样:
 public class Test {
 public static void main(String[] args) {
 //?TODO?请在此使用匿名内部类方式调用invokeCook方法
 //?TODO?请在此使用Lambda【标准格式】调用invokeCook方法
 }
 private static void invokeCook(Cook cook) {
 cook.makeFood();
 }
 }代码:    public interface Cook {
 // 做饭的方法
 void makeFood();
 }
 public class Test {
 public static void main(String[] args) {
 // 匿名内部类调用方法
 invokeCook(new Cook() {
 @Override
 public void makeFood() {
 System.out.println("匿名内部类方式: 吃饭啦");
 }
 });
 // Lambda标准格式调用方法
 invokeCook(()->{
 System.out.println("Lambda标准格式: 吃饭啦");
 });
 }
 private static void invokeCook(Cook cook) {
 cook.makeFood();
 }
 }练习: 使用Lambda标准格式(重写"有参有返回值"的方法)5分钟练习: 数组排序    需求:
 定义Person类:
 属性: private String name, private int age
 生成无参, 有参构造, set/get方法, toString()
 定义测试类:
 定义Person数组存储3个Person对象:
 柳岩, 38
 迪丽热巴, 18
 古力娜扎, 19
 使用Arrays类的静态方法sort(T[] a, Comparator<? super T> c), 根据年龄升序对数组排序
 传入Comparator对象时, 分别使用匿名内部类比较器和Lambda表达式比较器2种方式来实现
 打印集合查看排序后的效果代码:    public class Test {
 public static void main(String[] args) {
 // 创建3个Person对象
 Person p1 = new Person("柳岩", 38);
 Person p2 = new Person("迪丽热巴", 18);
 Person p3 = new Person("古力娜扎", 19);
 // 创建数组
 Person[] arr = new Person[]{p1, p2, p3};
 // 对数组排序
 // 匿名内部类方式
 /*Arrays.sort(arr, new Comparator<Person>() {
 @Override
 public int compare(Person o1, Person o2) {
 return o1.getAge() - o2.getAge();
 }
 });*/
 // Lambda标准格式
 Arrays.sort(arr, (Person o1, Person o2) -> {
 return o1.getAge() - o2.getAge();
 });
 // 打印排序后的结果
 for (Person person : arr) {
 System.out.println(person);
 }
 }
 }补充:    函数式接口:
 函数式接口: "有且仅有一个抽象方法的接口"
 但函数式接口对于 哪些方法算作抽象方法 有特殊规定:
 1. 有方法体的方法"不算作"抽象方法, 如默认方法, 静态方法, 私有方法
 2. 如果一个抽象方法 与 java.lang.Object类中的方法 定义相同的, 也"不算作"抽象方法
 因为任何实现本接口的实现类, 都会直接或间接继承java.lang.Object类的public的方法, 所以在创建实现类时其实不用重写该抽象方法, 也就不算作抽象方法练习: 使用Lambda标准格式(自定义接口, 抽象方法有参有返回值)5分钟练习: 计算器    需求:
 已经提供了一个计算器接口 Calculator, 接口中只有唯一一个抽象方法 calc(), 计算两个int数字:
 // 计算器
 public interface Calculator {
 int calc(int a, int b);  // 计算a和b的结果, 并返回
 }
 在下面的代码中,请使用匿名内部类和Lambda的标准格式调用 invokeCalc() 方法, 完成120和130的相加计算:
 public class Demo08InvokeCalc {
 public static void main(String[] args) {
 // TODO 请在此使用匿名内部类方式调用invokeCalc方法来计算120+130的结果
 
 // TODO 请在此使用Lambda【标准格式】调用invokeCalc方法来计算120+130的结果
 }
 private static void invokeCalc(int a, int b, Calculator calculator) {
 int result = calculator.calc(a, b);
 System.out.println("结果是:" + result);
 }
 }代码:    // 函数式接口, 模拟Comparator接口
 public interface Calculator {
 // 计算2个整数的结果并返回
 int calc(int a, int b);
 }
 public class Test {
 public static void main(String[] args) {
 // 匿名内部类
 invokeCalc(120, 130, new Calculator() {
 @Override
 public int calc(int a, int b) {
 return a + b;
 }
 });
 // Lambda标准格式
 invokeCalc(120, 130, (int a, int b)->{
 return a + b;
 });
 }
 // 模拟 Arrays.sort(T[] arr, Comparator c) 方法
 private static void invokeCalc(int a, int b, Calculator calculator) {
 int result = calculator.calc(a, b);
 System.out.println("结果是:" + result);
 }
 }Lambda省略格式和使用前提知识点:    Lambda表达式的省略原则是什么, 哪些部分可以省略
 Lambda表达式的使用前提有哪些
 总结:    省略原则:
 "可推导的都可省略" (凡是能根据前后代码能猜测出来的代码, 都可以省略不写)
 可以省略的部分:
 1. (参数列表): 参数"类型"可以省略 (a, b) -> {}
 2. (参数列表): 如果参数只有1个, 则"类型"和"小括号"都可以省略  a -> sout(a)
 3. {一些代码}: 如果只有一条代码, 则"大括号", "return", "分号"都可以"一起省略"
 *函数式接口:
 函数式接口: "有且仅有一个抽象方法的接口"
 但函数式接口对于 哪些方法算作抽象方法 有特殊规定:
 1. 有方法体的方法"不算作"抽象方法, 如默认方法, 静态方法, 私有方法
 2. 如果一个抽象方法 与 java.lang.Object类中的方法 定义相同的, 也"不算作"抽象方法
 因为任何实现本接口的实现类, 都会直接或间接继承java.lang.Object类的public的方法, 所以在创建实现类时其实不用重写该抽象方法, 也就不算作抽象方法
 
 Lambda表达式的使用前提:
 1. Lambda只能用于接口, 且"接口中有且仅有一个抽象方法"(也称为"函数式接口")
 普通类, 抽象类不能用
 2. 使用Lambda必须具有上下文推断
 接口中只能有一个抽象方法, 才能推断出来重写的是这个抽象方法
 简而言之: 参数类型必须是函数式接口
 比如以下构造方法
 Thread(Runnable r): 该构造方法的参数类型是Runnable
 // Runnable接口中, 有且仅有一个抽象方法, 该接口就是一个函数式接口, 可以使用Lambda表达式
 public interface Runnable {
 public abstract void run();
 }
 // Comparator接口中, 有且仅有一个抽象方法
 public interface Comparator<T> {
 // 这是该接口中有且仅有的一个抽象方法
 int compare(T o1, T o2);
 // 该方法与Object类中equals定义相同, 所以不算抽象方法
 boolean equals(Object obj);
 // 一些default方法, 有方法体, 不算抽象方法
 // 一些静态方法, 有方法体, 不算抽象方法
 }
 // 我们今天自己定义的接口, 也满足函数式接口的要求
 public?interface?Cook?{
 void?makeFood();
 }补充:    10分钟练习: 使用Lambda的省略格式    需求:
 将今日使用Lambda标准格式写的4个练习(创建线程, 厨师喊吃饭, 数组对Person排序, 计算器)的代码, 使用Lambda的"省略格式"再次实现代码:    // Lambda省略格式
 new Thread(() -> System.out.println(Thread.currentThread().getName() + "新线程创建了")).start();
 // Lambda省略格式
 invokeCook(()-> System.out.println("Lambda标准格式: 吃饭啦"));
 // Lambda省略格式
 Arrays.sort(arr, (o1, o2) -> o1.getAge() - o2.getAge());
 // Lambda省略格式
 invokeCalc(120, 130, (a, b)->a + b);---今日API    java.lang.Object类:
 // 成员方法
 void wait(): 使用锁对象调用, 当前线程进入WAITING无限等待状态, 直到被其他线程唤醒
 void notify(): 使用锁对象调用, 随机唤醒一个处于等待状态的线程
 void notifyAll(): 使用锁对象调用, 唤醒所有处于等待状态的线程
 java.util.concurrent.Executors类: 线程池工厂类, 用于管理线程池
 // 静态方法:
 static ExecutorService newFixedThreadPool(int nThreads): 创建固定数量线程的线程池(常用)
 
 java.util.concurrent.ExecutorService接口: 真正执行任务的线程池服务
 // 成员方法:
 Future submit(Runnable task): 提交一个Runnable任务
 void shutdown(): 通知线程执行完任务后关闭. 如不调此方法, 则线程执行完任务后仍在运行以便重复使用今日目标能够理解线程通信概念    多个线程操作共享的资源, 每个线程执行的任务不同, 需要相互协作, 依赖线程间通信能够理解等待唤醒机制    wait/notify
 调用wait: 1. 释放锁 2.进入WAITING状态
 离开WAITING: 1. 获取锁 2.等待CPU调用执行
 wait()
 wait(long millis)
 notify()
 notifyAll()能够描述Java中线程池运行原理    集合装着提前创建好的线程对象
 有任务时, 从集合中取出线程来执行任务
 执行完任务后, 线程会被归还到线程池, 以备复用
 ExecutorService es = Executors.newFixedThreadPool(2)
 es.submit(new RunnableImpl());能够理解函数式编程相对于面向对象的优点    面向对象: 注重对象调用方法(语法格式)
 函数式: 注重输入什么(参数), 得到什么结果(运行代码)
 简化代码
 y = f(x);能够掌握Lambda表达式的标准格式    (参数列表) -> {一段代码}能够使用Lambda标准格式使用Runnable与Comparator接口    创建线程
 Person数组排序能够掌握Lambda表达式的省略格式与规则    规则:
 1.(参数): 省略"数据类型"
 2.(参数): 如果参数只有1个, 省略"数据类型"和"小括号"
 3.{一段代码}: 如果代码只有一条, 可以"同时省略": "大括号{}", "return关键字", ";分号"能够使用Lambda省略格式使用Runnable与Comparator接口    创建线程
 Person数组排序能够通过Lambda的标准格式使用自定义的接口(有且仅有一个抽象方法)    Cook 厨师
 Calculator 计算器能够通过Lambda的省略格式使用自定义的接口(有且仅有一个抽象方法)    Cook 厨师
 Calculator 计算器能够明确Lambda的两项使用前提    1. Lambda只适用于"函数式接口" (有且仅有一个抽象方法)
 2. 必须具有上下文的推导
 
 
 
 | 
 |