Java进阶之线程池&lambda表达式&Stream流
一、线程池
之前使用多线程的方式是需要使用线程就新建一个线程,线程执行完任务后就退出销毁,如果再使用线程完成任务,又需要创建一个线程。这样频繁创建以及销毁线程对于性能的影响非常大。
Java给我们提供了一个工具类——线程池,可以把它看作一种容器,里面预先存放了很多线程,需要使用线程的时候,直接从这里面调用,当执行玩任务,再把线程放回去,因此就避免了频繁创建以及销毁线程带来的性能问题。
线程池中的线程具有复用性,可以执行多次任务。
1.1 线程池的基本使用
线程池相关API:
Executor
:接口,是所有线程池的根接口。这个接口中提供了提交线程任务的方法ExecutorService
:是Executor的子接口,也表示线程池。 这个接口中不仅提供了提交线程任务的方法,还提供了管理线程池的方法Executors
:线程池的工具类,里面提供了获取线程池的方法
注意:线程池对象不是由我们自己new的,而是要通过工具类Executors
进行获取
Executors中获取线程池的方法:
static ExecutorService newFixedThreadPool(int nThreads)
:获取一个定长的线程池,参数表示线程池的长度
ExecutorService表示线程池,里面方法:
submit(Runnable task)
:提交线程任务并执行
-shutdown()
:销毁线程池
线程池的使用步骤:
- 1.通过Executors工具类获取线程池。
- 2.调用线程池的submit方法,提交并执行线程任务。
- 3.销毁线程池(一般不做,频繁创建和销毁线程池非常影响性能,多线程一般在服务器上运行,可以一直开启)
public class Task implements Runnable{
//线程要执行的任务
@Override
public void run() {
//输出100次HelloWorld
for (int i = 1; i <= 100; i++) {
System.out.println(Thread.currentThread().getName() + "在输出HelloWorld:" + i);
}
}
}
public class Demo01ThreadPool {
public static void main(String[] args) {
//通过Executors工具类获取线程池。
ExecutorService threadPool = Executors.newFixedThreadPool(2);
//调用线程池的submit方法,提交并执行线程任务。
Task task = new Task();//线程任务对象
threadPool.submit(task);
threadPool.submit(task);
threadPool.submit(task);
//销毁线程池(一般不做)
//threadPool.shutdown();
}
}
1.2 Callable方式完成多线程
我们可以通过实现Callable接口的方式实现多线程,这是实现多线程的第三种方式。
步骤:
- 1.定义类,实现Callable接口
- 2.重写Callable接口中的call方法,在call方法中定义线程要执行的任务
- 3.获取线程池
- 4.通过线程池调用submit方法,传递Callable接口的实现类对象,去执行该任务
- 5.处理结果
线程池中提交任务的方法:
<T> Future<T> submit(Callable<T> task)
:提交线程任务,返回值是Future类型
Future类型封装了线程执行后的结果
V get()
:获取到线程执行后的结果
public class CallableImpl implements Callable {
@Override
public Object call() throws Exception {
for (int i = 0; i < 100; i++) {
System.out.println("HelloWorld"+i);
}
return "打印完成!";
}
}
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//获取线程池
ExecutorService threadPool = Executors.newFixedThreadPool(2);
//通过线程池调用submit方法,传递Callable接口的实现类对象,去执行该任务
Future<String> future = threadPool.submit(new CallableImpl());
//V get():获取到线程执行后的结果
String str = future.get();
System.out.println(str);//打印完成!
}
}
二、线程状态
线程状态
- 新建(NEW):刚刚创建出来但是没有运行的线程处于此状态。
- 运行(RUNNABLE):调用start方法启动后的线程处于运行状态。
- 受阻塞(BLOCKED):等待获取锁的线程处于此状态。
- 无限等待(WAITING):当线程调用wait()方法时,线程会处于无限等待状态【没有时间的等待】
- 计时等待(TIMED_WAITING):当线程调用wait(毫秒值)方法或sleep(毫秒值)时,线程会处于计时等待状态【有时间的等待】
- 退出(TERMINATED):当线程执行完了自己的run方法或者调用了stop方法,会进入退出状态
线程状态图:
2.1 wait和notify介绍
在Object中,有两种方法可以让线程等待以及唤醒等待的线程
void wait()
:让线程等待,直到其他线程唤醒它void wait(long timeout)
:让线程等待,直到其他线程唤醒它,或者时间到了void notify()
:唤醒一个线程,如果有多个线程在等待,就随机唤醒一个void notifyAll()
:唤醒所有的线程
注意:
- wait和notify不是Thread中的方法,而是Object中的方法
- wait和notify一定要放到【同步代码块】或【同步方法】,并且通过锁对象调用
- 通过哪个锁调用的notify,那么唤醒的就是通过哪个锁调用wait等待的线程
- 当线程调用wait方法后会等待并释放掉锁,而sleep方法不会释放锁
public class Demo02Wait {
public static void main(String[] args) {
//创建对象,当做锁
Object lock = new Object();
//创建线程【等待】
new Thread(new Runnable() {
@Override
public void run() {
//同步代码块
synchronized (lock) {
try {
//通过锁对象调用wait方法让线程等待
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("我醒了");
}
}).start();
//创建线程【唤醒】
new Thread(new Runnable() {
@Override
public void run() {
//同步代码块
synchronized (lock) {
try {
System.out.println("3秒后唤醒它");
//等3秒
Thread.sleep(3000);
//唤醒操作,继续往下执行
lock.notify();//线程被唤醒后也不会立即执行,因为没有锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}
三、定时器
Timer类:表示定时器,可以只执行一次,也可以周期性的执行。
构造方法:
Timer()
:创建一个定时器
其他方法:
void schedule(TimerTask task, long delay)
:指定delay毫秒后,执行task任务,只执行一次void schedule(TimerTask task, long delay, long period)
:指定delay毫秒后,执行task任务,每隔period周期性执行一次void schedule(TimerTask task, Date time)
:从指定时间开始,执行task任务,只执行一次
public class Demo01Test {
public static void main(String[] args) {
Timer timer = new Timer();
//设置定时器,1秒后启动,输出砰砰砰
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("砰砰砰");
}
}, 1000);
//设置定时器,5秒后启动,每一秒执行一次
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("滴滴滴");
}
}, 5000, 1000);
//设置执行时间后执行定时器
Calendar c = Calendar.getInstance();//设置为当天的23:35:50
c.set(Calendar.HOUR_OF_DAY, 23);
c.set(Calendar.MINUTE, 35);
c.set(Calendar.SECOND, 50);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("祝你生你生日快乐!");
}
//把日历对象转换成日期对象
}, c.getTime());//到时间后输出祝你生你生日快乐!
}
}
四、lambda表达式
4.1 冗余的匿名内部类
匿名内部类有很多地方是冗余的,如在使用匿名内部类完成多线程代码中,因为Thread构造方法中需要传递一个Runnable类型的参数,所以我们不得不写了new Runnable,因为匿名内部类中要重写方法,所以我们又不得不写了run方法的声明部分(public void run),而匿名内部类中最重要的是方法的前中后三点:
- 前:方法参数
- 中:方法体
- 后:返回值
最好的情况是只关注最关键的东西,也就是只关注匿名内部类中方法的参数,方法体,返回值。
使用lambda表达式,可以只关注最核心的内容,解决匿名内部类的冗余。
public class Demo01Inner {
public static void main(String[] args) {
//使用匿名内部类的方式完成多线程。
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "执行了");
}
}).start();
//Lambda表达式初体验
new Thread(() -> System.out.println(Thread.currentThread().getName() + "执行了")).start();
}
}
Lambda表达式使用的是函数式编程思想
- 面向对象思想:怎么做
- 函数式编程思想:做什么
4.2 Lambda表达式标准格式
Lambda标准格式:
(参数类型 参数名) -> {
方法体;
return 返回值;
}
格式解释:
- 1.小括号中的参数和之前传统方法参数写法一样,如果有多个参数,使用逗号隔开。
- 2.->是一个运算符,表示指向性动作。
- 3.大括号中的内容之前传统方法大括号中的内容写法一样的
Lambda表达式是函数式编程思想,函数式编程思想中,可推导,就是可省略
- 因为在Thread的构造方法中需要传递Runnable接口类型的参数,所以可以省略new Runnable
- 因为Runnable中只有一个抽象方法叫做run,所以在重写该方法时可以省略public void run
4.3 Lambda表达式简化写法
省略规则:
- 1.小括号中的参数类型可以省略。
- 2.如果小括号中只有一个参数,那么可以省略小括号。
- 3.如果大括号中只有一条语句,那么可以省略大括号,return,以及分号
public class Demo04SimpleLambda {
public static void main(String[] args) {
//使用Lambda标准格式完成多线程
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "执行了");
}).start();
//使用Lambda表达式省略格式完成多线程
new Thread(() -> System.out.println(Thread.currentThread().getName() + "执行了")).start();
}
}
4.4 Lambda表达式使用前提
Lambda表达式的使用前提
- 1.必须要有接口(不能是抽象类),接口中有且仅有一个需要被重写的抽象方法。(比如Runnable或Comparator)
- 2.必须支持上下文推导。 要能够推导出来Lambda表达式表示的是哪个接口中的哪个方法
注意:
如果一个接口中有且仅有一个需要被重写的抽象方法,那么该接口也叫做函数式接口
最常用的上下文推导的方式是使用函数式接口当做方法参数,然后传递Lambda表达式。
五、Stream流
5.1 传统方式操作集合的弊端
我们在之前对集合进行筛选和截取时,每次都要重新创建集合,然后通过遍历把新的值放进去,操作有些繁琐,而通过Stream流的方式,可以通过一条语句完成一些列操作,大大减少了代码量。
public class Demo01PrintList {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("张无忌");
list.add("周芷若");
list.add("赵敏");
list.add("张强");
list.add("张三丰");
//1. 首先筛选所有姓张的人;
//定义集合,保存本次筛选后的结果
List<String> zhangList = new ArrayList<>();
//遍历集合,拿到每一个元素,判断是否以张开头
for (String str : list) {
if(str.startsWith("张")) {
zhangList.add(str);
}
}
// 2. 然后筛选名字有三个字的人;
//定义集合,保存本次筛选后的结果
List<String> threeList = new ArrayList<>();
//遍历上次筛选后的结果,拿到里面的每一个元素,判断是否是三个字
for (String str : zhangList) {
if(str.length() == 3) {
threeList.add(str);
}
}
//3. 最后进行对结果进行打印输出。
for (String str : threeList) {
System.out.println(str);
}
System.out.println("=================================");
//Stream流初体验
list.stream().filter(s -> s.startsWith("张")).filter(s -> s.length() == 3).forEach(s -> System.out.println(s));
}
}
5.2 流式思想介绍
我们可以把数据当成一件待加工的产品,Stream流就相当于一条流水线,每次都可以对数据进行一定的操作,然后输出新的流到下一个环节,直到最后加工完成。
5.3 获取流
Stream<T>
是一个接口,该接口表示流
获取流的两种方式
- 1.通过Collection集合(单列集合)调用stream()方法获取。【根据单列集合获取】
- 2.通过Stream中的静态方法根据数组获取流。【根据数组获取】
5.3.1 单列集合获取流
Collection中获取流的方法:
Stream<E> stream()
:获取集合对应的流
public class Demo02CollectionGetStream {
public static void main(String[] args) {
//创建集合
List<String> list = new ArrayList<>();
//添加元素
list.add("hello");
list.add("world");
list.add("java");
//获取集合对应的流
Stream<String> stream = list.stream();
//通过流调用toArray将流转成数组,然后通过Arrays工具类将数组中的内容转成字符串,然后通过外面的输出语句输出。
System.out.println(Arrays.toString(stream.toArray()));
}
}
5.3.2 数组获取流
Stream中根据数组获取流的方法:
static <T> Stream<T> of(T... values)
:根据数组或多个元素获取Stream流
public class Demo03ArrayGetStream {
public static void main(String[] args) {
//创建数组
String[] strArr = {"hello", "world", "java"};
//通过Stream调用of方法,根据数组获取流
//Stream<String> stream = Stream.of(strArr);
//of方法不仅可以根据数组获取流,也可以根据多个元素获取流
Stream<String> stream = Stream.of("你好", "我好", "大家好");
//输出流中的内容
System.out.println(Arrays.toString(stream.toArray()));
}
}
5.4 Stream流中的方法
5.4.1 forEach方法
void forEach(Consumer action)
:对流中的每一个元素进行逐一操作,逐一处理。参数Consumer表示处理规则。
Consumer是一个函数式接口,这个接口中只有一个抽象方法
void accept(T t)
:对数据进行操作,进行处理
forEach方法的参数是Consumer函数式接口,那么可以传递Lambda表达式,这个Lambda表达式表示的是Consumer接口中唯一的一个抽象方法accept的内容,我们要在Lambda表达式中编写操作规则。
public class Demo04ForEach {
public static void main(String[] args) {
//获取Stream流
Stream<String> stream = Stream.of("hello", "world", "java");
//调用forEach方法,对流中的每一个元素进行逐一处理(输出)
//Lambda表达式中的参数s表示流中的每一个元素。
stream.forEach(s -> System.out.println(s));
}
}
5.4.2 filter
Stream<T> filter(Predicate predicate)
:用来对流中的元素进行过滤筛选,返回值是过滤后新的流;参数predicate表示过滤规则。
Predicate是一个函数式接口,里面只有一个抽象方法
boolean test(T t)
:判断数据是否符合要求
filter方法参数是Predicate函数式接口,所以可以传递Lambda表达式,该Lambda表达式表示Predicate接口中的唯一的抽象方法test的内容。我们要在Lambda表达式中编写验证(判断)规则。 如果我们希望某个数据留下,那么就返回true,如果不希望某个数据留下,那么就返回false。
public class Demo05Filter {
public static void main(String[] args) {
//获取Stream流
Stream<String> stream = Stream.of("aa", "bbbbbb", "cc", "ddddd");
//对流中的元素进行过滤,只留下长度小于3的元素。
//参数s表示流中的每一个元素,Lambda表达式的方法体是过滤的规则,如果结果是true,元素留下
Stream<String> newStream = stream.filter(s -> s.length() < 3);
//对过滤后新的流中的元素进行逐一处理,逐一输出
newStream.forEach(s -> System.out.println(s));
}
}
5.4.3 count
long count()
:获取流中的元素的个数
public class Demo06Count {
public static void main(String[] args) {
//获取流对象
Stream<String> stream = Stream.of("aa", "bb", "cc", "dd");
//long count():获取流中的元素的个数。
long c = stream.count();
System.out.println(c);
}
}
5.4.4 limit
Stream<T> limit(long n)
:获取流中的前n个元素然后放入到新的流中返回
public class Demo07Limit {
public static void main(String[] args) {
//获取Stream流
Stream<String> stream = Stream.of("aa", "bb", "cc", "dd", "ee");
//获取流中的前3个元素
Stream<String> newStream = stream.limit(3);
//逐一处理
newStream.forEach(s -> System.out.println(s));
}
}
5.4.5 skip
Stream<T> skip(long n)
:跳过流中前n个元素,获取剩下的元素放到一个新的流中返回
public class Demo08Skip {
public static void main(String[] args) {
//获取Stream流
Stream<String> stream = Stream.of("aa", "bb", "cc", "dd", "ee");
//调用skip方法,跳过流中的前3个元素,获取剩下的
Stream<String> newStream = stream.skip(3);
//输出newStream中的元素
newStream.forEach(s -> System.out.println(s));
}
}
5.4.6 concat
static Stream concat(Stream a, Stream b)
:对a和b这两个流进行合并,合并成新的流返回
public class Demo09Concat {
public static void main(String[] args) {
//获取两个流
Stream<String> streamOne = Stream.of("aa", "bb");
Stream<String> streamTwo = Stream.of("cc", "dd");
//对两个流合并,合并成新的流
Stream<String> stream = Stream.concat(streamOne, streamTwo);
//逐一处理
stream.forEach(s -> System.out.println(s));
}
}
5.4.7 map
Stream map(Function mapper)
:将流中的元素映射到新的流中并返回。参数Function表示映射规则。
Function是一个函数式接口,里面只有一个抽象方法叫做apply
R apply(T t)
:对数据进行处理,然后返回结果
map方法的参数是Function这个函数式接口,那么我们可以传递Lambda表达式, 这个Lambda表达式表示的Function中唯一的一个抽象方法map方法的内容,我们在Lambda表达式中编写处理的规则。
public class Demo10Map {
public static void main(String[] args) {
//获取Stream流
Stream<String> stream = Stream.of("10", "20", "30");
//将原来流中的每一个元素变成数字保存到新的流中(将原来流中的元素变成字符串映射到新的流)
//Lambda表达式中的参数s表示原来流中的每一个元素,然后将流中的每一个元素变成了数字保存在新的流中。
Stream<Integer> newStream = stream.map(s -> Integer.parseInt(s));
//输出结果
newStream.forEach(s -> System.out.println(s));
}
}
5.5 收集流中的元素
5.5.1 收集到集合中
在Stream中有一个方法叫做collect,可以将流中的元素收集到集合(将流转成集合)
R collect(Collector collector)
:参数collector表示将数据收集到哪种集合
Collector是一个接口,我们要使用这个接口的实现类对象,这个接口的实现类对象不是由我们去创建的,而是通过工具类获取,获取Collector的工具类叫做Collectors
Collectors中获取Collector的方法:
static Collector toList()
:通过该方法获取到的Collector对象表示将数据收集到List集合。static Collector toSet()
:通过该方法获取到的Collector对象表示将数据收集到Set集合
public class Demo01StreamToList {
public static void main(String[] args) {
//获取Stream流
Stream<String> stream = Stream.of("hello", "world", "java", "php");
//将流中的元素收集到List集合(将流转成List集合)
//List<String> list = stream.collect(Collectors.toList());
//System.out.println(list);
//将流中的元素收集到Set集合(将流转成Set集合)
Set<String> set = stream.collect(Collectors.toSet());
System.out.println(set);
}
}
5.5.2 收集到数组中
在Stream中有一个方法叫做toArray,可以将流中的内容收集到数组中(将流转成数组)
Object[] toArray()
:将流转成数组
public class Demo02StreamToArray {
public static void main(String[] args) {
//获取流
Stream<String> stream = Stream.of("hello", "world", "java", "php");
//将流转成数组,必须使用Object[]
Object[] objArr = stream.toArray();
//遍历打印
for (Object o : objArr) {
System.out.println(o);
}
}
}
5.6 stream流的小结与注意事项
Stream中的常见方法:
- (终结)forEach:逐一处理
- filter:过滤筛选。
- (终结)count:获取数量
- limit:获取前几个
- skip:跳过前几个
- map:映射
- concat:合并。
终结方法:如果方法返回值类型不是Stream本身类型,该方法可以称为终结方法。不支持链式调用。
非终结(函数拼接)方法:返回值类型是Stream本身类型,该方法可以称为非终结方法。可以链式调用。
注意:
- 1.流调用非终结方法返回值是Stream自身类型,但是返回的并不是自身对象。
- 2.流只能一次性使用,不能多次使用