今日内容

  • 多线程------------->必须掌握
    • 线程的状态
    • 等待唤醒机制
  • Lambda表达式------------->建议掌握
    • Lambda表达式的使用场景
    • Lambda表达式的格式(标准\省略)
  • Stream流
    • 流式思想的概述
    • 使用Stream流------------->建议掌握
      • 获取流-->操作流-->收集结果

第一章 线程状态

1.1 线程状态

线程状态概述

线程由生到死的完整过程:技术素养和面试的要求。

线程从创建到销毁的过程称为线程的生命周期,在线程的生命周期内一共有六种状态:

线程状态 导致状态发生条件
NEW(新建) 线程刚被创建,但是并未启动。还没调用start方法。MyThread t = new MyThread()只有线程对象,没有线程特征。创建线程对象时
Runnable(可运行) 调用了 start() 方法,此时线程可能正在执行,也可能没有,这取决于操作系统的调度。调用start方法时
Blocked(锁阻塞) 当线程试图获取锁对象,而该锁对象被其他的线程持有,则该线程进入锁阻塞状态;当该线程获取到锁对象时,该线程将变成可运行状态。等待锁对象时
Waiting(无限等待) 一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。锁对象调用wait()方法时
Timed Waiting(计时等待) 同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep 、Object.wait。调用sleep()方法时
Teminated(被终止) 因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。run方法执行结束时

线程状态的切换

我们不需要去研究这几种状态的实现原理,我们只需知道在做线程操作中存在这样的状态。那我们怎么去理解这几个状态呢,新建与被终止还是很容易理解的,我们就研究一下线程从Runnable(可运行)状态与非运行状态之间的转换问题。

1.2 计时等待和无限等待

  • 计时等待: 调用线程类的 sleep() 方法可使当前线程进入睡眠状态,当睡觉时间达到时线程会被自动唤醒。

    • public static void sleep(long time) 让当前线程进入到睡眠状态,到毫秒后自动醒来继续执行

      /**
       * Created by PengZhiLin on 2021/8/9 9:35
       */
      public class Test {
          public static void main(String[] args) throws InterruptedException {
              for (int i = 0; i < 10; i++) {
                  System.out.println("Hello World "+i);
                  // 暂停1秒
                  Thread.sleep(1000);
              }
          }
      }
      
      
  • 无限等待

    • Object类的方法:

      • public void wait() : 让当前线程进入到等待状态 此方法必须锁对象调用.
      • public void notify() : 唤醒当前锁对象上等待状态的线程 此方法必须锁对象调用.
      • public void notifyAll() : 唤醒当前锁对象上所有等待状态的线程 此方法必须锁对象调用.
    • 案例1: 无限等待线程

      
      /**
       * Created by PengZhiLin on 2021/8/9 9:39
       */
      public class Test1 {
      
          // 锁对象
          static Object lock = new Object();
      
          public static void main(String[] args) {
              // 创建并启动线程
              new Thread(new Runnable() {
                  @Override
                  public void run() {
                      synchronized (lock) {
                          System.out.println("线程1: 获取锁对象,调用wait方法,准备进入无线等待...");
                          // 进入无限等待
                          try {
                              lock.wait();
                          } catch (InterruptedException e) {
                              e.printStackTrace();
                          }
                          System.out.println("线程1: 被唤醒了,并获取了锁对象,继续执行...");
                      }
                  }
              }).start();
          }
      }
      
      
    • 案例2: 等待和唤醒案例

      package com.itheima.demo2_无限等待;
      
      

    /**

    • Created by PengZhiLin on 2021/8/9 9:39
      */
      public class Test2 {

      // 锁对象
      static Object lock = new Object();

      public static void main(String[] args) {
      // 创建并启动线程--->等待
      new Thread(new Runnable() {
      @Override
      public void run() {
      synchronized (lock) {
      System.out.println("线程1: 获取锁对象,调用wait方法,准备进入无线等待...");
      // 进入无限等待
      try {
      lock.wait();
      } catch (InterruptedException e) {
      e.printStackTrace();
      }
      System.out.println("线程1: 被唤醒了,并获取了锁对象,继续执行...");
      }
      }
      }).start();

       // 创建并启动线程--->唤醒
       new Thread(new Runnable() {
           @Override
           public void run() {
               synchronized (lock) {
                   // 唤醒无限等待线程
                   System.out.println("线程2: 3秒后唤醒无限等待线程...");
                   try {
                       Thread.sleep(3000);
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }
                   lock.notify();
               }
           }
       }).start();
      

      }
      }

    
    - 等待唤醒:
      - 必须使用锁对象调用wait()\notfiy()\notifyAll()方法
      - 调用wait()\notfiy()\notifyAll()方法的锁对象必须一致
    
    
    

1.3 等待唤醒机制

什么是等待唤醒机制

  • 概述: 使用等待和唤醒实现多条线程之间有规律的执行
  • 例如: 子线程打印i循环,主线程打印j循环
  • 不使用等待唤醒机制: 结果是主线程和子线程随机交替打印输出----->没有规律
  • 使用等待唤醒机制: 结果就要有规律的打印输出
    • 打印1次i循环,然后打印1次j循环....依次循环打印输出....---->有规律
    • 如何实现:
      • 子线程打印1次i循环,然后唤醒主线程来执行, 子线程就进入无限等待
      • 主线程打印1次j循环,然后唤醒子线程来执行,主线程就进入无限等待
      • 子线程打印1次i循环,然后唤醒主线程来执行,子线程就进入无限等待
      • 主线程打印1次j循环,然后唤醒子线程来执行,主线程就进入无限等待
      • ....

如何实现等待唤醒机制:

  • 1.使用锁对象调用wait()方法进入无限等待

  • 2.使用锁对象调用notify()方法唤醒无限等待线程

  • 3.调用wait(),notify()方法的锁对象要一致

  • 案例: 主线程和子线程有规律的交替打印输出

    /**
     * Created by PengZhiLin on 2021/8/9 9:55
     */
    public class MyThread extends Thread {
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                synchronized (Test.lock) {
                    // 条件(flag=true): 子线程进入无限等待
                    if (Test.flag == true) {
                        try {
                            Test.lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    // 条件(flag=false): 子线程被唤醒,执行任务代码
                    if (Test.flag == false) {
                        System.out.println("子线程:第" + i + "次i循环");
                        // 唤醒主线程
                        Test.lock.notify();
                        // 修改旗帜变量的值
                        Test.flag = true;
                    }
                }
            }
        }
    }
    
    
    /**
     * Created by PengZhiLin on 2021/8/9 9:55
     */
    public class Test {
    
        // 共享锁对象
        static Object lock = new Object();
        // 共享条件变量
        static boolean flag = false;
    
        public static void main(String[] args) {
            // 创建并启动子线程
            new MyThread().start();
    
            // 主线程任务
            for (int j = 0; j < 100; j++) {
                synchronized (lock) {
                    // 条件(flag=false): 主线程进入无限等待
                    if (flag == false) {
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    // 条件(flag=true): 主线程被唤醒,执行任务代码
                    if (flag == true) {
                        System.out.println("主线程:第" + j + "次j循环");
                        // 唤醒子线程
                        lock.notify();
                        // 修改旗帜变量的值
                        flag = false;
                    }
                }
            }
        }
    }
    
    

分析等待唤醒机制程序的执行

  • 1.不管是否使用等待唤醒机制,线程的调度都是抢占式
  • 2.线程进入无限等待,线程就会释放锁,cpu,也不会再去争夺
  • 3.唤醒其他线程,当前唤醒线程是不会释放锁,cpu的
  • 4.无限等待线程被唤醒,拿到锁对象后,会从进入无限等待的位置继续往下执行

1.4 等待唤醒案例

需求

  • 等待唤醒机制其实就是经典的“生产者与消费者”的问题。

  • 就拿生产包子消费包子来说等待唤醒机制如何有效利用资源:

    1588527361222

分析

包子铺线程生产包子,生产完了,包子就有了,唤醒吃货线程来吃包子,然后包子铺线程进入无限等待;
吃货线程吃包子,吃完了,包子就没有了,唤醒包子铺线程来生产包子,然后吃货线程进入无限等待;
包子铺线程生产包子,生产完了,包子就有了,唤醒吃货线程来吃包子,然后包子铺线程进入无限等待;
吃货线程吃包子,吃完了,包子就没有了,唤醒包子铺线程来生产包子,然后吃货线程进入无限等待;
.....
共享旗帜变量(有包子:true,没有包子:false)
包子铺线程:
	有包子,就进入无限等待
    没有包子,就执行任务代码(生产包子),唤醒吃货线程,修改包子的状态
吃货线程:
	没有包子,就进入无限等待
    有包子,就执行任务代码(吃包子),唤醒包子铺线程,修改包子的状态

实现

  • 包子铺线程

    /**
     * Created by PengZhiLin on 2021/8/9 10:15
     */
    public class BaoZiPu extends Thread {
        @Override
        public void run() {
            // 循环生产包子
            while (true) {
                synchronized (Test.lock) {
                    //有包子,就进入无限等待
                    if (Test.flag == true) {
                        try {
                            Test.lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    //没有包子,就执行任务代码(生产包子),唤醒吃货线程,修改包子的状态
                    if (Test.flag == false) {
                        System.out.println("包子铺线程: 开始做包子...");
                        System.out.println("包子铺线程: 包子做好了,吃货快来吃包子...");
                        // 唤醒吃货线程
                        Test.lock.notify();
                        //修改包子的状态
                        Test.flag = true;
                    }
                }
            }
        }
    }
    
    
  • 吃货线程

    /**
     * Created by PengZhiLin on 2021/8/9 10:15
     */
    public class ChiHuo extends Thread {
    
        @Override
        public void run() {
            // 循环吃包子
            while (true) {
                synchronized (Test.lock) {
                    //没有包子,就进入无限等待
                    if (Test.flag == false) {
                        try {
                            Test.lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    //有包子,就执行任务代码(吃包子),唤醒包子铺线程,修改包子的状态
                    if (Test.flag == true) {
                        System.out.println("吃货线程:开始吃包子...");
                        System.out.println("吃货线程:吃完了包子,包子铺快来做包子...");
                        // 唤醒包子铺线程
                        Test.lock.notify();
                        //修改包子的状态
                        Test.flag = false;
                    }
                }
            }
        }
    }
    
    
  • 测试类

    /**
     * Created by PengZhiLin on 2021/8/9 10:15
     */
    public class Test {
        // 共享锁对象
        static Object lock = new Object();
        // 共享旗帜变量
        static boolean flag = false;
    
        public static void main(String[] args) {
            // 创建并启动线程
            new BaoZiPu().start();
            new ChiHuo().start();
        }
    }
    
    

第二章 Lambda表达式

2.1 函数式编程思想概述

面向对象编程思想

面向对象强调的是对象 , “必须通过对象的形式来做事情”,相对来讲比较复杂,有时候我们只是为了做某件事情而不得不创建一个对象 , 例如线程执行任务,我们不得不创建一个实现Runnable接口对象,但我们真正希望的是将run方法中的代码传递给线程对象执行

函数编程思想

在数学中,函数就是有输入量、输出量的一套计算方案,也就是“拿什么东西做什么事情”。相对而言,面向对象过分强调“必须通过对象的形式来做事情”,而函数式思想则尽量忽略面向对象的复杂语法——强调做什么,而不是以什么形式做。例如线程执行任务 , 使用函数式思想 , 我们就可以通过传递一段代码给线程对象执行,而不需要创建任务对象

2.2 Lambda表达式的体验

  • 实现Runnable接口的方式创建线程执行任务

  • 匿名内部类方式创建线程执行任务

    • 以上2种方式,其实都是通过Runnable的实现类对象来传递任务给线程执行
    • 思考: 是否能不通过实现类对象传递任务给线程执行呢?--->函数式编程
  • Lambda方式创建线程执行任务

    /**
     * Created by PengZhiLin on 2021/8/9 10:39
     */
    public class Test {
        public static void main(String[] args) {
            // 需求:创建并启动线程,执行任务代码
            // 面向对象:
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println("线程任务代码1...");
                }
            }).start();
    
            // 函数式编程:
            new Thread(()->System.out.println("线程任务代码2...")).start();
        }
    }
    
    
  • Lambda表达式没有特殊功能,就是用来简化代码的

2.3 Lambda表达式的标准格式

  • 标准格式

    • 格式: (参数类型 参数名,参数类型 参数名,...)->{ 代码块 }
  • Lambda的使用前提条件:

    • 接口中只有一个抽象方法的接口,才可以使用Lambda表达式

    • 只有一个抽象方法的接口叫做函数式接口,函数式接口可以使用@FunctionalInterface注解标识

    • eg:

      @FunctionalInterface
      interface A{
          void method();
      }
      
      @FunctionalInterface
      interface B{
          void method1();
          default void method2(){
              System.out.println("默认方法...");
          }
      }
      
      //@FunctionalInterface // 编译报错
      interface C{
          void method1();
          void method2();
      }
      
      
  • 格式说明

    • 小括号中的参数类型,参数个数,参数顺序要和函数式接口中抽象方法的形参列表一致
    • -> 固定格式,表示指向
    • 大括号中的内容其实就是之前重写接口中抽象方法的方法体
  • 案例演示

    • Runnable函数式接口

    • Comparator函数式接口

      /**
       * Created by PengZhiLin on 2021/8/9 10:48
       */
      public class Test {
          public static void main(String[] args) {
              // 创建并启动线程
              new Thread(()->{
                  System.out.println("任务代码..");
                  System.out.println("任务代码..");
                  System.out.println("任务代码..");
                  System.out.println("任务代码..");
              }).start();
      
      
              // 创建ArrayList集合,限制集合元素类型为Integer
              ArrayList<Integer> list = new ArrayList<>();
              // 添加元素
              list.add(500);
              list.add(100);
              list.add(400);
              list.add(200);
              list.add(300);
              System.out.println("排序前:"+list);
              // 指定规则排序: 降序
             /* Collections.sort(list, new Comparator<Integer>() {
                  @Override
                  public int compare(Integer o1, Integer o2) {
                      return o2 - o1;
                  }
              });*/
             Collections.sort(list,(Integer o1, Integer o2)->{ return o2 - o1;});
      
              System.out.println("排序后:"+list);
          }
      }
      
      
  • Lambda使用套路

    • 1.判断该位置上是否可以使用Lambda表达式--->使用前提
    • 2.如果可以使用,直接写上()->{}
    • 3.填充小括号中的内容--->函数式接口中抽象方法的形参一致
    • 4.填充大括号中的内容--->重写函数式接口抽象方法需要的方法体

2.4 Lambda表达式省略格式

  • 省略规则

    • 小括号中参数类型可以省略不写
    • 小括号中只有一个参数,那么小括号也可以省略
    • 大括号中如果只有一条语句,那么大括号,return关键字,分号也可以省略(三个要一起省略)
  • 案例演示

    /**
     * Created by PengZhiLin on 2021/8/9 10:48
     */
    public class Test {
        public static void main(String[] args) {
            // 创建并启动线程
            // 标准格式
            new Thread(() -> {
                System.out.println("任务代码1..");
            }).start();
    
            // 省略格式
            new Thread(() ->
                    System.out.println("任务代码2..")
            ).start();
    
    
            // 创建ArrayList集合,限制集合元素类型为Integer
            ArrayList<Integer> list = new ArrayList<>();
            // 添加元素
            list.add(500);
            list.add(100);
            list.add(400);
            list.add(200);
            list.add(300);
            System.out.println("排序前:" + list);
            // 指定规则排序: 降序
            // 标准格式
            /*Collections.sort(list, (Integer o1, Integer o2) -> {
                return o2 - o1;
            });*/
    
            // 省略格式
            Collections.sort(list, (o1,  o2) -> o2 - o1);
    
            System.out.println("排序后:" + list);
        }
    }
    
    

2.5 Lambda的表现形式

  • Lambda的表现形式: Lambda表达式会出现在哪些位置

    • 变量形式: 赋值一个Lambda表达式

    • 参数形式: 传入Lambda表达式作为实参

    • 返回值形式: 返回一个Lambda表达式(返回值)

      • 案例:

        
        /**
         * Created by PengZhiLin on 2021/8/9 11:01
         */
        public class Test {
            public static void main(String[] args) {
                // 变量形式
                Runnable r = ()->{System.out.println("任务代码1...");};
                new Thread(r).start();
        
                // 参数形式
                new Thread(()->{System.out.println("任务代码2...");}).start();
        
                // 返回值形式
                Runnable runnable = getRunnable();
                new Thread(runnable).start();
            }
        
            public static Runnable getRunnable(){
                return ()->{System.out.println("任务代码3...");};
            }
        
        }
        
        

第三章 Stream

在Java 8中,得益于Lambda所带来的函数式编程,引入了一个全新的Stream概念,用于解决已有集合类库既有的弊端。

3.1 体验Stream流的使用

  • 传统方式操作集合

    • 需求:

       List<String> one = new ArrayList<>();
              one.add("迪丽热巴");
              one.add("宋远桥");
              one.add("苏星河");
              one.add("老子");
              one.add("庄子");
      		one.add("黄祺龙");
              one.add("孙子");
              one.add("洪七公");
      需求:
      1. 队伍中只要名字为3个字的成员姓名;
      2. 队伍中筛选之后只要前3个人;
      
  • Stream流操作集合

    
    /**
     * Created by PengZhiLin on 2021/8/9 11:06
     */
    public class Test1 {
        public static void main(String[] args) {
            List<String> one = new ArrayList<>();
            one.add("迪丽热巴");
            one.add("宋远桥");
            one.add("苏星河");
            one.add("老子");
            one.add("庄子");
            one.add("张无忌");
            one.add("孙子");
            one.add("洪七公");
    
            //1. 队伍中只要名字为3个字的成员姓名;
            // 1.1 创建一个新的集合用来存储名字为3个字的姓名
            ArrayList<String> list1 = new ArrayList<>();
            // 1.2 循环遍历集合,进行筛选
            for (String name : one) {
                if (name.length() == 3) {
                    list1.add(name);
                }
            }
            System.out.println("list1:" + list1);
    
            //2. 队伍中筛选之后只要前3个人;
            // 2.1 创建一个新的集合用来存储名字为3个字的姓名
            ArrayList<String> list2 = new ArrayList<>();
            // 2.2 循环获取前三个姓名
            for (int i = 0; i < 3; i++) {
                String name = list1.get(i);
                list2.add(name);
            }
            System.out.println("list2:" + list2);
    
    
            // Stream流的使用
            // 获取流---->操作流
            one.stream().filter(name->name.length()==3).limit(3).forEach(name-> System.out.println(name));
        }
    }
    
    

3.2 流式思想概述

  • 概述: 可以将流式思想类比成工厂车间的流水线\河流...

  • 特点:

    • 流一定要搭建好完整的函数模型,函数模型中必须要有终结方法
    • Stream流不能重复操作,也就是一个Stream流只能使用一次
    • Stream流不会存储数据的
    • Stream流不会修改数据源

    image-20210328145120792

3.3 获取流方式

  • 根据集合获取流---->Collection集合中有一个获取流的方法public default Stream<E> stream();

    • 根据Collection获取流

      /**
       * Created by PengZhiLin on 2021/8/9 11:39
       */
      public class Test {
          public static void main(String[] args) {
              List<String> list = new ArrayList<>();
              list.add("迪丽热巴");
              list.add("宋远桥");
              list.add("苏星河");
              list.add("老子");
              // 通过List集合获取流
              Stream<String> stream1 = list.stream();
              stream1.forEach(name-> System.out.println(name));
      
              System.out.println("------------------");
      
              Set<String> set = new HashSet<>();
              set.add("庄子");
              set.add("张无忌");
              set.add("孙子");
              set.add("洪七公");
              // 通过Set集合获取流
              Stream<String> stream2 = set.stream();
              stream2.forEach(name-> System.out.println(name));
      
          }
      }
      
    • 根据Map获取流

      • 根据Map集合的键获取流

      • 根据Map集合的值获取流

      • 根据Map集合的键值对对象获取流

        /**
         * Created by PengZhiLin on 2021/8/9 11:43
         */
        public class Test {
            public static void main(String[] args) {
                // 创建Map集合,限制键的类型为Integer,值的类型为String
                Map<Integer,String> map = new HashMap<>();
                // 添加键值对
                map.put(1,"张三");
                map.put(2,"李四");
                map.put(3,"王五");
                map.put(4,"赵六");
                map.put(5,"田七");
        
                //- 根据Map集合的键获取流
                Stream<Integer> stream1 = map.keySet().stream();
        
                //- 根据Map集合的值获取流
                Stream<String> stream2 = map.values().stream();
        
                //- 根据Map集合的键值对对象获取流
                Stream<Map.Entry<Integer, String>> stream3 = map.entrySet().stream();
        
                stream1.forEach(i-> System.out.println(i));
                System.out.println("--------------");
                stream2.forEach(str-> System.out.println(str));
                System.out.println("--------------");
                stream3.forEach(e-> System.out.println(e));
                System.out.println("--------------");
            }
        }
        
        
  • 根据数组获取流---->使用Stream流的静态of方法

    • public static <T> Stream<T> of(T... values);

      /**
       * Created by PengZhiLin on 2021/8/9 11:47
       */
      public class Test {
          public static void main(String[] args) {
              String[] names = {"张三","李四","王五"};
              // 根据数组获取流
              Stream<String> stream1 = Stream.of(names);
              stream1.forEach(name-> System.out.println(name));
      
              // 直接传元素
              Stream<String> stream2 = Stream.of("张三", "李四", "王五");
              stream2.forEach(name-> System.out.println(name));
      
          }
      }
      
      

3.4 Stream流常用方法

  • 终结方法: 方法的返回值类型不是Stream流, 函数模型中一定要有终结方法,否则无法执行

  • 延迟方法\非终结方法: 方法的返回值类型是Stream流

  • 常用方法:

    • forEach: 逐一处理流中的元素

      • `void forEach(Consumer action);
      /**
       * Created by PengZhiLin on 2021/8/9 11:53
       */
      public class Test1_forEach {
          public static void main(String[] args) {
              // 获取流--->调用forEach方法
              Stream<String> stream = Stream.of("张三", "李四", "王五");
              // stream.forEach((String name)->{System.out.println(name);});
              stream.forEach(name->System.out.println(name));
          }
      }
      
      
    • count: 统计流中元素的个数

      • long count();
      /**
       * Created by PengZhiLin on 2021/8/9 11:53
       */
      public class Test2_count {
          public static void main(String[] args) {
              // 获取流--->调用count方法
              Stream<String> stream = Stream.of("张三", "李四", "王五");
              long count = stream.count();
              System.out.println("流中元素个数:" + count);
          }
      }
      
      
    • filter: 根据条件过滤

      • Stream<T> filter(Predicate<? super T> predicate);
      /**
       * Created by PengZhiLin on 2021/8/9 12:02
       */
      public class Test3_filter {
          public static void main(String[] args) {
              // 获取流--调用filter方法
              Stream<String> stream = Stream.of("张三", "张无忌", "李四", "王五");
              // 过滤出姓张的元素,并打印输出
              stream.filter((String name)->{return name.startsWith("张");}).forEach(name-> System.out.println(name));
          }
      }
      
      
    • limit: 取流中前几个元素

      • Stream<T> limit(long maxSize);

      • 注意:
        1.参数一般开发中,设置大于0小于流中元素的个数
        2.如果参数设置小于0的数,就会报异常
        3.如果参数设置为0,返回的流中没有元素
        4.如果参数设置大于流中元素个数,返回的流中就包含了流中所有的元素

      • 案例:

        /**
         * Created by PengZhiLin on 2021/8/9 12:07
         */
        public class Test4_limit {
            public static void main(String[] args) {
                /*
                    `Stream<T> limit(long maxSize);`取流中前几个元素
                        1.参数一般开发中,设置大于0小于流中元素的个数
                         2.如果参数设置小于0的数,就会报异常
                         3.如果参数设置为0,返回的流中没有元素
                         4.如果参数设置大于流中元素个数,返回的流中就包含了流中所有的元素
                 */
                // 获取流----调用limit方法
                Stream<String> stream = Stream.of("张三", "李四", "王五","赵六","田七","王八");
                stream.limit(16).forEach(name-> System.out.println(name));
            }
        }
        
        
    • skip: 跳过流中前几个元素

      • Stream<T> skip(long n);跳过流中前几个元素

      • 注意:
        1.参数一般设置大于0或者小于元素个数
        2.如果参数设置为0,返回新的流中包含了所有元素
        3.如果参数设置为大于或者等于元素个数,返回新的流中没有元素
        4.如果参数设置为小于0,报异常

      • 案例:

        
        /**
         * Created by PengZhiLin on 2021/8/9 12:12
         */
        public class Test5_skip {
            public static void main(String[] args) {
                /*
                    - `Stream<T> skip(long n);跳过流中前几个元素`
                    - 注意:
                          1.参数一般设置大于0或者小于元素个数
                          2.如果参数设置为0,返回新的流中包含了所有元素
                          3.如果参数设置为大于或者等于元素个数,返回新的流中没有元素
                          4.如果参数设置为小于0,报异常
                 */
                // 获取流----调用limit方法
                Stream<String> stream = Stream.of("张三", "李四", "王五","赵六","田七","王八");
                stream.skip(3).forEach(name-> System.out.println(name));
            }
        }
        
        
    • map: 映射\转换

      • <R> Stream<R> map(Function<? super T, ? extends R> mapper) 把流中T类型的元素转换为R类型;
      /**
       * Created by PengZhiLin on 2021/8/9 12:16
       */
      public class Test6_map {
          public static void main(String[] args) {
              /*
                  `<R> Stream<R>  map(Function<? super T, ? extends R>   mapper) 把流中T类型的元素转换为R类型;`
               */
              // 获取流--->调用map方法
              Stream<String> stream = Stream.of("110", "120", "119", "114");
      
              // 把stream流中的元素转换为Integer类型,然后+1打印输出
              stream.map((String str)->{ return Integer.parseInt(str);}).forEach(i-> System.out.println(i+1));
          }
      }
      
      
    • concat: 拼接2个流

      • `static Stream concat(Stream<? extends T> a,Stream<? extends T> b);

        
        /**
         * Created by PengZhiLin on 2021/8/9 12:22
         */
        public class Test7_concat {
            public static void main(String[] args) {
                // 获取流
                Stream<String> stream1 = Stream.of("110", "120", "119", "114");
                Stream<String> stream2 = Stream.of("张三", "李四", "王五","赵六","田七","王八");
                // 调用concat方法
                Stream.concat(stream1,stream2).forEach(str-> System.out.println(str));
            }
        }
        
        

3.5 Stream综合案例

需求

现在有两个ArrayList集合存储队伍当中的多个成员姓名,要求使用Stream流,依次进行以下若干操作步骤:

  1. 第一个队伍只要名字为3个字的成员姓名;
  2. 第一个队伍筛选之后只要前3个人;
  3. 第二个队伍只要姓张的成员姓名;
  4. 第二个队伍筛选之后不要前2个人;
  5. 将两个队伍合并为一个队伍;
  6. 根据姓名创建Person对象;
  7. 打印整个队伍的Person对象信息。

两个队伍(集合)的代码如下:

public class DemoArrayListNames {
    public static void main(String[] args) {
        List<String> one = new ArrayList<>();
        one.add("迪丽热巴");
        one.add("宋远桥");
        one.add("苏星河");
        one.add("老子");
        one.add("庄子");
        one.add("孙子");
        one.add("洪七公");

        List<String> two = new ArrayList<>();
        two.add("古力娜扎");
        two.add("张无忌");
        two.add("张三丰");
        two.add("赵丽颖");
        two.add("张二狗");
        two.add("张天爱");
        two.add("张三");
		// ....
    }
}

实现

/**
 * Created by PengZhiLin on 2021/8/9 14:30
 */
public class Test {
    public static void main(String[] args) {
        List<String> one = new ArrayList<>();
        one.add("迪丽热巴");
        one.add("宋远桥");
        one.add("苏星河");
        one.add("老子");
        one.add("庄子");
        one.add("张无忌");
        one.add("孙子");
        one.add("洪七公");

        List<String> two = new ArrayList<>();
        two.add("古力娜扎");
        two.add("张无忌");
        two.add("张三丰");
        two.add("赵丽颖");
        two.add("张二狗");
        two.add("张天爱");
        two.add("张三");

        //1. 第一个队伍只要名字为3个字的成员姓名;
        //2. 第一个队伍筛选之后只要前3个人;
        Stream<String> stream1 = one.stream().filter(name -> name.length() == 3).limit(3);

        //3. 第二个队伍只要姓张的成员姓名;
        //4. 第二个队伍筛选之后不要前2个人;
        Stream<String> stream2 = two.stream().filter(name -> name.startsWith("张")).skip(2);

        //5. 将两个队伍合并为一个队伍;
        //6. 根据姓名创建`Person`对象;
        //7. 打印整个队伍的Person对象信息。
        Stream.concat(stream1,stream2).map(name->new Person(name)).forEach(p->System.out.println(p));
    }
}

3.6 收集Stream结果

收集到数组中

  • public Object[] toArray(); 把流中的元素收集到数组中

    /**
     * Created by PengZhiLin on 2021/8/9 14:45
     */
    public class Test1_数组 {
        public static void main(String[] args) {
            // 获取流
            Stream<String> stream = Stream.of("张三丰", "张无忌", "张翠山", "郭靖", "杨过");
    
            // 操作流: 过滤出姓张的元素,并收集到数组中
            Object[] arr = stream.filter(name -> name.startsWith("张")).toArray();
    
            // 打印数组
            System.out.println(Arrays.toString(arr));
        }
    }
    
    

收集到集合中

  • <R, A> R collect(Collector<? super T, A, R> collector);把流中的元素收集到集合中

    • R: 返回值类型,也就是说R是什么类型,就返回什么类型的集合

    • 参数Collector里面的泛型R确定了该方法的返回值类型

    • 如何得到Collector呢?????-----> 工具类Collectors

      • public static <T> Collector<T, ?, List<T>> toList()
      • public static <T> Collector<T, ?, Set<T>> toSet()
      • eg; stream.collect(Collectors.toList()) 收集到List集合
      • eg: ``stream.collect(Collectors.toSet()) 收集到Set集合`
      /**
       * Created by PengZhiLin on 2021/8/9 14:55
       */
      public class Test2_收集到List集合 {
          public static void main(String[] args) {
              // 获取流
              Stream<String> stream = Stream.of("张三丰", "张无忌", "张翠山", "郭靖", "杨过");
      
              // 操作流: 过滤出姓张的元素,并收集到List集合中
              List<String> list = stream.filter(name -> name.startsWith("张")).collect(Collectors.toList());
              System.out.println(list);
          }
      }
      
      
      /**
       * Created by PengZhiLin on 2021/8/9 14:55
       */
      public class Test3_收集到Set集合 {
          public static void main(String[] args) {
              // 获取流
              Stream<String> stream = Stream.of("张三丰", "张无忌", "张翠山", "郭靖", "杨过");
      
              // 操作流: 过滤出姓张的元素,并收集到Set集合中
              Set<String> set = stream.filter(name -> name.startsWith("张")).collect(Collectors.toSet());
              System.out.println(set);
      
          }
      }
      
      

总结

必须练习:
	1.线程6种状态之间的相互切换----->画图
    2.等待唤醒机制--->如何实现等待唤醒机制,如何分析等待唤醒机制案例的执行流程
        2.1 有规律的打印i循环和j循环
        2.2 有规律的生产和吃包子
    3.Lambda表达式: 
		3.1 默写使用Lambda表达式的套路---->4步
        3.2 默写使用前提
        3.3 默写省略规则
    4.Stream流:
		综合案例---->把结果收集到数组或者集合中
            
- 能够说出线程6个状态的名称
    新建,可运行,锁阻塞,无限等待,计时等待,被终止
            
- 能够理解等待唤醒案例
  如何实现等待唤醒机制:
    - 1.使用锁对象调用wait()方法进入无限等待
    - 2.使用锁对象调用notify()方法唤醒线程
    - 3.调用wait(),notify()方法的锁对象要一致
  分析等待唤醒机制程序的执行
    - 1.不管是否使用等待唤醒机制,线程的调度都是抢占式
    - 2.线程进入无限等待,线程就会释放锁,cpu,也不会再去争夺
    - 3.唤醒其他线程,当前唤醒线程是不会释放锁,cpu的
    - 4.无限等待线程被唤醒,拿到锁对象后,会从进入无限等待的位置继续往下执行
  
   
- 能够掌握Lambda表达式的标准格式与省略格式
   Lambda使用套路
    - 1.判断该位置上是否可以使用Lambda表达式--->使用前提
    - 2.如果可以使用,直接写上()->{}
    - 3.填充小括号中的内容--->函数式接口中抽象方法的形参一致
    - 4.填充大括号中的内容--->重写函数式接口抽象方法需要的方法体
  省略规则
    - 小括号中参数类型可以省略不写
    - 小括号中只有一个参数,那么小括号也可以省略
    - 大括号中如果只有一条语句,那么大括号,return关键字,分号也可以省略(三个要一起省略)
  使用前提: 函数式接口

- 能够通过集合、映射或数组方式获取流
   使用Collection的stream方法
   使用Stream流的of方法
      
- 能够掌握常用的流操作
   forEach,count,filter,limit,skip,concat,map
      
- 能够将流中的内容收集到集合和数组中
  Object[] toArray();
  stream.collect(Collectors.toList()) 收集到List集合
  stream.collect(Collectors.toSet()) 收集到Set集合

扩展多条线程实现等待唤醒

共享的旗帜变量  static int num = 1;
共享的锁对象
线程1:
    如果num!=1,线程1就进入无限等待
    如果num=1,线程1就执行任务代码,唤醒其他所有无限等待线程,修改旗帜变量num的值为2

线程2:
    如果num!=2,线程2就进入无限等待
    如果num=2,线程2就执行任务代码,唤醒其他所有无限等待线程,修改旗帜变量num的值为3

线程3:
    如果num!=3,线程3就进入无限等待
    如果num=3,线程3就执行任务代码,唤醒其他所有无限等待线程,修改旗帜变量num的值为1

/**
 * Created by PengZhiLin on 2021/8/9 15:20
 */
public class MyThread1 extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            synchronized (Test.lock) {
                //如果num!=1,线程1就进入无限等待
                while (Test.num != 1) {
                    try {
                        Test.lock.wait();// 无限
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                //如果num=1,线程1就执行任务代码,唤醒其他所有无限等待线程,修改旗帜变量num的值为2
                if (Test.num == 1) {
                    System.out.println("黑马程序员--1");
                    // 唤醒其他所有无限等待线程
                    Test.lock.notifyAll();
                    // 修改旗帜变量的值
                    Test.num = 2;
                }
            }
        }
    }
}

/**
 * Created by PengZhiLin on 2021/8/9 15:20
 */
public class MyThread2 extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            synchronized (Test.lock) {
                //如果num!=2,线程2就进入无限等待
                while (Test.num != 2) {
                    try {
                        Test.lock.wait();// 无限
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //如果num=2,线程2就执行任务代码,唤醒其他所有无限等待线程,修改旗帜变量num的值为3
                if (Test.num == 2) {
                    System.out.println("传智播客--2");
                    // 唤醒其他所有无限等待线程
                    Test.lock.notifyAll();
                    // 修改旗帜变量的值
                    Test.num = 3;
                }
            }
        }
    }
}


/**
 * Created by PengZhiLin on 2021/8/9 15:20
 */
public class MyThread3 extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            synchronized (Test.lock) {
                // 如果num!=3,线程3就进入无限等待
                while (Test.num != 3) {
                    try {
                        Test.lock.wait();// 无限
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                // 如果num=3,线程3就执行任务代码,唤醒其他所有无限等待线程,修改旗帜变量num的值为1
                if (Test.num == 3) {
                    System.out.println("javaee--3---i:" + i);
                    // 唤醒其他所有无限等待线程
                    Test.lock.notifyAll();
                    // 修改旗帜变量的值
                    Test.num = 1;
                }
            }
        }
    }
}

/**
 * Created by PengZhiLin on 2021/8/9 15:18
 */
public class Test {
    //共享的旗帜变量
    static int num = 1;

    //共享的锁对象
    static Object lock = new Object();

    public static void main(String[] args) {
        // 创建并启动三条线程
        new MyThread1().start();
        new MyThread2().start();
        new MyThread3().start();

    }
}

posted on 2022-04-24 23:42  ofanimon  阅读(43)  评论(0编辑  收藏  举报
// 侧边栏目录 // https://blog-static.cnblogs.com/files/douzujun/marvin.nav.my1502.css