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.流只能一次性使用,不能多次使用
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值