第一章 函数式接口
函数式接口是Java 8中引入的一个新特性,只包含一个抽象方法
的接口。
函数式接口可以使用Lambda表达式
来实现,从而实现函数式编程的特性。
使用 @FunctionalInterface
标识接口是函数式接口,编译器才会检查接口是否符合函数式接口
的定义。
四大核心函数式接口
自定义函数式接口
java
@FunctionalInterface
interface MyFunction {
int calculate(int a, int b); // 抽象方法
}
实现函数式接口
java
public class Main {
public static void main(String[] args) {
// 使用Lambda表达式实现函数式接口的抽象方法
MyFunction add = (a, b) -> a + b;
System.out.println("Addition: " + add.calculate(3, 5));
}
}
第二章 Lambda表达式
Lambda表达式是在Java 8引入的,Lambda表达式是一种匿名函数
,可以作为参数传递给方法
或存储在变量
中,使用Lambda表达式可以简化代码编写,使代码更加简洁和易读。
基本语法
Lambda表达式的基本语法包括参数列表
、箭头符号
和方法体
:
java
(params) -> expression 或者 {statements;}
- params :参数列表,可以为空或非空。
- -> :箭头符号,将参数列表和Lambda表达式的主体分开。
- expression :单行Lambda表达式的
返回值表达式
。 - {statements;} :Lambda表达式的方法体,可以是一个
表达式
或一段代码块
。
第三章 方法引用
方法引用是一种简化Lambda表达式
的语法,可以直接引用已有方法的实现,是Java8的新特性之一。
(1)引用类方法
格式:类名::静态方法
示例代码:
java
List<String> list = new ArrayList<>();
list.add("11");
list.add("22");
Integer result = list.stream().map(Integer::parseInt).reduce(0, Integer::sum);
System.out.println(result);
(2)引用成员方法
格式:对象::成员方法
java
List<User> userList = userService.list();
userList.stream().map(User::getName).forEach(item->{
System.out.println(item);
});
(3)引用构造器
格式:类名::new
第四章 Stream流式编程
Java Stream流是 Java 8 引入的一个新的抽象概念,代表着一种处理数据的序列,比如对 Stream 中的元素进行数据转换、过滤、映射、聚合等操作,从而实现对数据的处理和操作。 函数式编程
java.util.stream
Stream 对象是一种一次性使用的对象,只能被消费一次,一旦对 Stream 执行了终止操作(如收集结果、遍历元素),Stream 就会被关闭,后续无法再使用。
Stream流式操作主要有 3 个步骤:
- 创建Stream对象:通过一些数据源创建流对象
- 中间操作:对数据源的数据进行处理(过滤、排序等)
- 终止操作:一旦执行终止操作, 就执行中间的链式操作,并产生结果。
在Java中,Stream流分为两种类型:
-
流(Stream):表示顺序流,按照数据源的顺序进行操作,适用于串行操作。
-
并行流(ParallelStream):表示并行流,可以同时对数据源的多个元素进行操作,适用于并行计算。
注意线程安全和性能问题。
4.1 创建 Stream 对象
(1)数据源为集合:调用集合的 stream()
方法来创建Stream 对象
(2)数据源为数组:Arrays类的 stream()
方法
(3)Stream.of()
创建 数据已知
(4)Stream.builder()
创建 数据未知
NOTE:Stream流是不会改变源对象的,而是返回一个新的持有结果的Stream。
4.2 中间操作
filter(Predicate)筛选
根据给定的条件(Predicate)过滤流中的元素,只保留符合条件
的元素。
java
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> evenNumbers = numbers.stream()
.filter(num -> num % 2 == 0)
.collect(Collectors.toList()); //[2, 4]
map(Function)映射
将流中的每个元素映射为另一个元素,生成一个新的流。
java
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
List<Integer> nameLengths = names.stream()
.map(String::length) //映射为长度
.collect(Collectors.toList()); // [5, 3, 7]
sorted()排序
对流中的元素进行排序,默认是自然顺序
排序,也可以传入Comparator
进行自定义排序。
java
List<Integer> numbers = Arrays.asList(3, 1, 4, 1, 5, 9, 2, 6, 5);
List<Integer> sortedNumbers = numbers.stream()
.sorted()
.collect(Collectors.toList());
// [1, 1, 2, 3, 4, 5, 5, 6, 9]
distinct()去重
去除流中重复
的元素,得到一个去重后的新流。
java
List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 3, 3, 4, 4, 4, 4);
List<Integer> distinctNumbers = numbers.stream()
.distinct()
.collect(Collectors.toList());// [1, 2, 3, 4]
limit(long)截取
截取流中的前几个元素
,生成一个新的流。
java
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> limitedNumbers = numbers.stream()
.limit(3)
.collect(Collectors.toList());// [1, 2, 3]
skip(long)跳过
跳过
流中的前几个元素,生成一个新的流。
java
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> skippedNumbers = numbers.stream()
.skip(2)
.collect(Collectors.toList());// [3, 4, 5]
4.3 中止操作
Stream的终止操作用于触发流的计算
(延迟执行)并得到最终的结果
。
forEach(Consumer)
对流中的每个元素
执行指定的操作
。
java
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.stream()
.forEach(System.out::println);//控制台输出每个字符串
reduce(BinaryOperator)
对流中的元素进行归约操作
,得到一个最终的结果。
java
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream()
.reduce(0, Integer::sum); // 对流中的元素求和
count()
返回流中元素的个数
。
java
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
long count = names.stream()
.count();// 返回3
anyMatch(Predicate)
判断流中是否存在任意一个
元素满足给定条件
。
java
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
boolean hasEvenNumber = numbers.stream()
.anyMatch(num -> num % 2 == 0);// 返回true
allMatch(Predicate)
判断流中的所有元素
是否都满足给定条件
。
java
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
boolean allPositive = numbers.stream()
.allMatch(num -> num > 0);// 返回true,因为流中所有元素都是正数
第五章 线程池
线程池(Thread Pool)是一种基于池化思想管理线程的工具,经常出现在多线程服务器中,如MySQL。
池化,顾名思义,是为了最大化收益并最小化风险,而将资源统一在一起管理的一种思想。
(1)内存池(Memory Pooling):预先申请内存,提升申请内存速度,减少内存碎片。
(2)连接池(Connection Pooling):预先申请数据库连接,提升申请连接的速度,降低系统的开销。
(3)实例池(Object Pooling):循环使用对象,减少资源在初始化和释放时的昂贵损耗。
线程池的优点:
- 降低资源消耗:通过池化技术重复利用已创建的线程,降低线程创建和销毁造成的损耗。
- 提高响应速度:任务到达时,无需等待线程创建即可立即执行。
- 提高线程的可管理性:线程是稀缺资源,如果无限制创建,不仅会消耗系统资源,还会因为线程的不合理分布导致资源调度失衡,降低系统的稳定性。使用线程池可以进行统一的分配、调优和监控。
- 提供更多更强大的功能:线程池具备可拓展性,允许开发人员向其中增加更多的功能。比如延时定时线程池ScheduledThreadPoolExecutor,就允许任务延期执行或定期执行。
Java中的线程池核心实现类是ThreadPoolExecutor
,
构造器如下:
java
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {...}
参数说明:
-
corePoolSize
: 线程池的核心线程数量。任务队列未达到队列容量时,最大可以同时运行的线程数量。 -
maximumPoolSize
:线程池的最大线程数。任务队列中存放的任务达到队列容量的时候,当前可以同时运行的线程数量变为最大线程数。 -
keepAliveTime
:线程池中的线程数量大于corePoolSize
的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了keepAliveTime
才会被回收销毁。 -
unit
:keepAliveTime
参数的时间单位。 -
workQueue
: 任务队列,用来储存等待执行任务的队列。新任务来的时候会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,新任务就会被存放在队列中。 -
threadFactory
:线程工厂,用来创建线程,一般默认即可。 -
handler
:拒绝策略。当提交的任务过多而不能及时处理时,可以定制策略来处理任务。
线程池思想
阻塞队列
新任务来的时候会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,新任务就会被存放在队列中。
LinkedBlockingQueue
(无界队列)
SynchronousQueue
(同步队列)
DelayedWorkQueue
(延迟阻塞队列)
四种拒绝策略
如果当前同时运行的线程数量达到最大线程数量并且队列也已经被放满了任务时,ThreadPoolExecutor
定义一些策略:
(1)ThreadPoolExecutor.AbortPolicy
:抛出 RejectedExecutionException
来拒绝新任务的处理。
(2)ThreadPoolExecutor.CallerRunsPolicy
:调用执行自己的线程运行任务,也就是直接在调用execute
方法的线程中运行(run
)被拒绝的任务,如果执行程序已关闭,则会丢弃该任务。因此这种策略会降低对于新任务提交速度,影响程序的整体性能。如果您的应用程序可以承受此延迟并且你要求任何一个任务请求都要被执行的话,你可以选择这个策略。
(3)ThreadPoolExecutor.DiscardPolicy
:不处理新任务,直接丢弃掉。
(4)ThreadPoolExecutor.DiscardOldestPolicy
:此策略将丢弃最早的未处理的任务请求。
线程安全的方式
避免共享状态
如果可能,最好是完全避免共享状态。可以设计无状态类或者确保线程之间没有数据共享。
不可变对象
不可变对象一旦创建就不可以被更改。因此在多线程环境中,不可变对象是线程安全的。
例如,使用 final
关键字声明的对象。
synchronized关键字
同步方法
和 同步代码块
Lock
java.util.concurrent.locks.Lock
分布式锁
如果是在单机的情况下,使用synchronized
和Lock
保证线程安全是没有问题的。
如果在分布式的环境中,即某个应用如果部署了多个节点,每一个节点可以使用synchronized
和Lock
保证线程
安全,但不同的节点之间,没法保证线程安全。解决方式:分布式锁
分布式锁有很多种,比如:数据库分布式锁,zookeeper分布式锁,redis分布式锁等。
volatile
需求:只要求多个线程间的可见性
,不要求原子性
。
可见性是指当一个线程修改了共享变量的值,这种修改对于其他线程是立即可见的。
原子性是指一个操作或者多个操作要么全部完成,要么全部不完成。
线程安全集合
JDK提供了能够保证线程安全的集合,比如:ConcurrentHashMap、ArrayBlockingQueue等等。
CAS比较再交换
CAS内部包含了四个值:旧数据
、期望数据
、新数据
和 地址
,比较旧数据 和 期望的数据,如果一样的话,就把旧数据改成新数据。如果不一样的话,当前线程不断自旋
,一直到成功为止。
原子类
通过使用 java.util.concurrent.atomic
包中的原子类(如 AtomicInteger
, AtomicLong
等),针对基本数据类型提供了线程安全的操作。保证可见性。
本质是:volatile关键字
高级同步工具
java.util.concurrent
包提供了高级的同步工具,比如 CountDownLatch
等。
示例代码
需要多运行几次,才能看到效果。
java
@SpringBootTest
public class ThreadTest {
//共享变量,存在线程安全问题
private static Integer count=10;
//原子类,底层volatile关键字,保证可见性
private static AtomicInteger count2 = new AtomicInteger(10);
public static void subtract(){
synchronized (ThreadTest.class){
count-=1;
}
}
@Test
public void testThreadPool() throws Exception{
int corePoolSize=4;
int maximumPoolSize=6;
int capacity = 2;
long keepAliveTime = 1L;
ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize,
maximumPoolSize, keepAliveTime, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(capacity),
new ThreadPoolExecutor.CallerRunsPolicy()
);
executor.execute(new Runnable() {
@Override
public void run() {
subtract();
System.out.println(Thread.currentThread().getName()+",当前count为:"+count);
}
});
executor.execute(() -> {
subtract();
System.out.println(Thread.currentThread().getName()+",当前count为:"+count);
});
Integer result1 = executor.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
subtract();
System.out.println(Thread.currentThread().getName()+",当前count为:"+count);
return count;
}
}).get();
Integer result2 = executor.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
subtract();
System.out.println(Thread.currentThread().getName()+",当前count为:"+count);
return count;
}
}).get();
//终止线程池
executor.shutdown();
}
}
待完善...