线程池的创建和使用步骤:
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表达式的使用前提有哪些
总结:
省略原则:
"可推导的都可省略" (凡是能根据前后代码能猜测出来的代码, 都可以省略不写)