今日目标

  • Lambda表达式
  • Stream流
  • 线程的入门使用

    1 Lambda表达式

    在数学中,函数就是有输入量、输出量的一套计算方案。在编程技术中,函数式编程(Functional Programming)是把函数作为基本运算单元,函数可以作为变量,可以接收函数,还可以返回函数。我们经常把支持函数式编程的编码风格称为Lambda表达式。

    1.1 体验Lambda表达式

    用匿名内部类对象传参,代码复杂度高,用Lambda传参代码更精简,关注点更加明确了。
    我们可以这么理解,Lambda表达式的出现是为了简化匿名内部类复杂语法结构而出现的。
    当然这里要注意,只能简化函数式接口的匿名子类,什么是函数式接口我们后续会讲解到!
    如下:
    1. package com.itheima.lambda_demo;
    2. import java.util.ArrayList;
    3. import java.util.Collections;
    4. import java.util.Comparator;
    5. import java.util.List;
    6. /*
    7. Lambda表达式体验 :
    8. */
    9. public class LambdaDemo {
    10. public static void main(String[] args) {
    11. //将集合要以降序排序
    12. List<Integer> list = new ArrayList<>();
    13. list.add(100);
    14. list.add(300);
    15. list.add(200);
    16. //集合以降序排序(以前的做法:匿名对象)
    17. /* Collections.sort(list, new Comparator<Integer>() {
    18. @Override
    19. public int compare(Integer o1, Integer o2) {
    20. return o2 - o1;
    21. }
    22. });*/
    23. //集合以降序排序(新做法:Lambda表达式)
    24. Collections.sort(list, (Integer o1, Integer o2) -> {
    25. return o2 - o1;
    26. });//先知道了参数是函数式接口,才会使用lambda表达式
    27. System.out.println("list = " + list);
    28. }
    29. }

lambda表达式可以理解为对匿名内部类的一种简化 , 但是本质是有区别的
函数式编程思想:把函数当做变量,参数,返回值使用的一种思想。
Lambda表达式:符合函数式编程思想语法规则的式子。

1.2 函数式接口

  • 只有一个抽象方法需要重写的接口,函数式接口。函数式接口是允许有其他的非抽象方法的存在例如静态方法,默认方法,私有方法。
  • 为了标识接口是一个函数式接口,可以在接口之上加上一个注解: _@_FunctionalInterface 以示区别
  • 在JDK中 java.util.function 包中的所有接口都是函数式接口。我们之前学习线程时学习的Runnable也是函数式接口
    day08 Lambda,Stream,线程入门 - 图1

    1.3 Lambda表达式的使用

  • 使用前提
    必须存在函数式接口(接口只有一个需要强制重写的抽象方法)

  • Lambda表达式其实就是对函数式接口匿名内部类的简写,简写到只有一个方法的重写。因此,Lambda表达式所表示的其实就是对函数式接口中抽象方法的重写。
  • 格式 : ( 参数列表) -> { 方法体 }
    1. 参数列表:就是函数式接口抽象方法的参数列表
    2. ->:固定语法
    3. 方法体:就是对函数式接口抽象方法的重写
  • 就是将匿名内部类一直简化到只有一个方法的存在,而且方法可以进一步简化到只有参数列表,和方法体,中间加上语法箭头
    day08 Lambda,Stream,线程入门 - 图2

    1.4 Lambda表达式的案例

    案例1:

    1. package com.itheima.lambda_demo.lambda_test1;
    2. /*
    3. Lambda表达式的使用前提 :
    4. 1 有一个接口
    5. 2 接口中有且仅有一个抽象方法
    6. 练习1:
    7. 1 编写一个接口(ShowHandler)
    8. 2 在该接口中存在一个抽象方法(show),该方法是无参数无返回值
    9. 3 在测试类(LambdaTest1)中存在一个方法(useShowHandler),
    10. 方法的的参数是ShowHandler类型的,在方法内部调用了ShowHandler的show方法
    11. */
    12. public class LambdaTest1 {
    13. public static void main(String[] args) {
    14. //匿名内部类实现函数式接口
    15. useShowHandler(new ShowHandler() {
    16. @Override
    17. public void show() {
    18. System.out.println("这是匿名内部实现");
    19. }
    20. });
    21. //Lambda表达式实现函数式接口
    22. useShowHandler(()->{
    23. System.out.println("这是Lambda实现");
    24. });
    25. }
    26. public static void useShowHandler(ShowHandler showHandler) {
    27. showHandler.show();
    28. //该对象调用的show方法,
    29. // 就是从参数传入的 匿名内部类中实现的show方法,或者Lambda表达式所表示的show方法
    30. }
    31. }
    32. @FunctionalInterface
    33. interface ShowHandler{ //函数式接口
    34. void show();
    35. }

    案例2

    1. package com.itheima.lambda_demo.lambda_test1;
    2. /*
    3. 1 首先存在一个接口(StringHandler)
    4. 2 在该接口中存在一个抽象方法(printMessage),该方法是有参数无返回值
    5. 3 在测试类(LambdaTest2)中存在一个方法(useStringHandler),
    6. 方法的的参数是StringHandler类型的,
    7. 在方法内部调用了StringHandler的printMessage方法
    8. */
    9. public class LambdaTest2 {
    10. public static void main(String[] args) {
    11. // Lambda 表达式 就是对 函数式接口的抽象方法进行重写
    12. // 对方法进行简化为: (参数列表)->{方法体}
    13. useStringHandler((String msg) -> {
    14. System.out.println("收到数据:" + msg);
    15. });
    16. }
    17. public static void useStringHandler(StringHandler stringHandler) {//参数是函数式接口
    18. stringHandler.printMessage("HelloWorld!");//printMessage方法执行的就是传入的Lambda表达式
    19. }
    20. }
    21. @FunctionalInterface
    22. interface StringHandler {
    23. void printMessage(String msg);
    24. }

    案例3

    1. package com.itheima.lambda_demo.lambda_test1;
    2. import java.util.Random;
    3. /*
    4. 1 首先存在一个接口(RandomNumHandler)
    5. 2 在该接口中存在一个抽象方法(getNumber),该方法是无参数但是有返回值,返回int数据
    6. 3 在测试类(LambdaTest3)中存在一个方法(useRandomNumHandler),方法的的参数是RandomNumHandler类型的
    7. 在方法内部调用了RandomNumHandler的getNumber方法
    8. */
    9. public class LambdaTest3 {
    10. public static void main(String[] args) {
    11. useRandomNumHandler(()->{
    12. //return 100;
    13. //返回[0,100]之间的随机数
    14. return new Random().nextInt(101);
    15. });
    16. }
    17. public static void useRandomNumHandler(RandomNumHandler handler) {
    18. int number = handler.getNumber();
    19. System.out.println("number = " + number);
    20. }
    21. }
    22. @FunctionalInterface
    23. interface RandomNumHandler{
    24. int getNumber();
    25. }

    案例4

    1. package com.itheima.lambda_demo.lambda_test1;
    2. /*
    3. 1 首先存在一个接口(Calculator)
    4. 2 在该接口中存在一个抽象方法(calc),该方法是有参数也有返回值
    5. 3 在测试类(LambdaTest4)中存在一个方法(useCalculator)
    6. 方法的的参数是Calculator类型的
    7. 在方法内部调用了Calculator的calc方法
    8. */
    9. public class LambdaTest4 {
    10. public static void main(String[] args) {
    11. //加法实现
    12. useCalculator((int a, int b)->{
    13. return a+b;
    14. });
    15. //乘法法实现
    16. useCalculator((int a, int b)->{
    17. return a*b;
    18. });
    19. //减法法实现
    20. //除法法实现
    21. }
    22. public static void useCalculator(Calculator calculator) {
    23. int result = calculator.calc(100, 20);
    24. System.out.println("result = " + result);//具体运算结果,由传入的Calculator具体实现而定
    25. }
    26. }
    27. @FunctionalInterface
    28. interface Calculator{
    29. int calc(int a, int b);
    30. }

    1.5 Lambda的省略格式

    省略规则:

  • 参数类型可以省略,但是有多个参数的情况下,不能只省略一个(一省全省)

  • 如果参数有且仅有一个,那么小括号和类型可以省略
  • 如果参数为空,括号不能省略
  • 如果方法体中语句只有一条,可以省略大括号和分号,甚至是return

例如:

package com.itheima.lambda_demo.shenlue;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/*
    Lambda表达式体验 :
 */
public class LambdaDemo {
    public static void main(String[] args) {
        //将集合要以降序排序
        List<Integer> list = new ArrayList<>();
        list.add(100);
        list.add(300);
        list.add(200);
        //集合以降序排序(以前的做法:匿名对象)
       /* Collections.sort(list, new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o2 - o1;
            }
        });*/
        //集合以降序排序(新做法:Lambda表达式)
        Collections.sort(list, (Integer o1, Integer o2) ->{
            return  o2 - o1;
        });
        //省略后
        Collections.sort(list, (o1, o2) -> o2 - o1);
        //先知道了参数是函数式接口,才会使用lambda表达式
        System.out.println("list = " + list);
    }
}

1.6 Lambda表达式和匿名内部类的区别

1. 所需类型不同
匿名内部类:可以是接口,也可以是抽象类,还可以是具体类
Lambda表达式:只能是函数式接口
2. 使用限制不同
如果接口中有且仅有一个抽象方法需要重写,可以使用Lambda表达式,也可以使用匿名内部类
如果接口中多于一个抽象方法,只能使用匿名内部类,而不能使用Lambda表达式
能够使用lambda的地方,一定可以使用匿名内部类。
3.实现原理不同
匿名内部类:编译之后,产生一个单独的.class字节码文件
Lambda表达式:编译之后,没有一个单独的.class字节码文件。对应的字节码会在运行的时候动态生成(不是以文件形式存在,而是在内存中动态生成)

2 Stream流

2.1 Stream的体验

Stream流在JDK1.8开始,新出来的API用来简化容器(数组,集合)数据操作。
是一种流水线作业思想。

package com.itheima.stream_demo;
import java.util.ArrayList;
/*
    体验Stream流的好处
    需求:按照下面的要求完成集合的创建和遍历
        1 创建一个集合,存储多个字符串元素
            "张无忌" , "张翠山" , "张三丰" , "谢广坤" , "赵四" , "刘能" , "小沈阳" , "张良"
        2 把集合中所有以"张"开头的元素存储到一个新的集合
        3 把"张"开头的集合中的长度为3的元素存储到一个新的集合
        4 遍历上一步得到的集合
 */
public class StreamDemo {
    public static void main(String[] args) {
        // 传统方式完成
        ArrayList<String> list = new ArrayList<>();
        list.add("张无忌");
        list.add("张翠山");
        list.add("张三丰");
        list.add("谢广坤");
        list.add("赵四");
        list.add("刘能");
        list.add("小沈阳");
        list.add("张良");
        // 把集合中所有以"张"开头的元素存储到一个新的集合
        ArrayList<String> list2 = new ArrayList<>();
        for (String s : list) {
            if (s.startsWith("张")) {
                list2.add(s);
            }
        }
        // 把"张"开头的集合中的长度为3的元素存储到一个新的集合
        ArrayList<String> list3 = new ArrayList<>();
        for (String s : list2) {
            if (s.length() == 3) {
                list3.add(s);
            }
        }
        // 遍历list3集合
        for (String s : list3) {
            System.out.println(s);
        }
        System.out.println("================================");
        // Stream流的方式
        list
            .stream()
            .filter(s -> s.startsWith("张") && s.length() == 3)
            .forEach(s -> System.out.println(s));
    }
}

2.2 Stream流的三类方法

  • 获取Stream流
    • 创建一条流水线,并把数据放到流水线上准备进行操作
  • 中间方法
    • 流水线上的操作。
    • 一次操作完毕之后,还可以继续进行其他操作
  • 终结方法

    • 一个Stream流只能有一个终结方法
    • 是流水线上的最后一个操作

      2.3 第一类 : 获取流方法

      1 单列集合
      可以使用Collection接口中的默认方法stream()生成流
      default Stream<E> stream()
      2 双列集合
      双列集合不能直接获取 , 需要间接的生成流
      可以先通过keySet或者entrySet获取一个Set集合,再获取Stream流
      3 数组
      Arrays中的静态方法stream生成流
      4 把同一种数据类型多个数据放到stream
      Stream类中的of方法 , 把多个数据放在stream流上
      
      package com.itheima.stream_demo;
      import java.util.*;
      import java.util.stream.LongStream;
      import java.util.stream.Stream;
      public class StreamDemo2 {
      public static void main(String[] args) {  
         /*
         可以使用Collection接口中的默认方法stream()生成流
         default Stream<E> stream()
         Collection
      
            List
                 ArrayList
                 Vector
                 LinkedList
            Set
                 HashSet
                 LinkedHashSet
                 TreeSt
          */
         ArrayList<String> c1 = new ArrayList<>();
         HashSet<String> c2 = new HashSet<>();
         Stream<String> s1 = c1.stream();
         Stream<String> s2 = c2.stream();
         // 双列集合不能直接获取 , 需要间接的生成流
         // 可以先通过keySet或者entrySet获取一个Set集合,再获取Stream流
         Map<String, String> map = new HashMap<>();
         //map.steam();
         //使用流操作键的数据 keySet
         Stream<String> s3 = map.keySet().stream();
         //使用流操作Map集合的键值对对象 entrySet
         Stream<Map.Entry<String, String>> s4 = map.entrySet().stream();
         //使用流操作集合中所有的值 values
         Stream<String> s5 = map.values().stream();
         // Arrays中的静态方法stream生成流
         String[] arr1 = {"a", "b", "c"};
         Stream<String> s6 = Arrays.stream(arr1);
         long[] arr2 = {1, 2, 3, 4, 5};
         LongStream s7 = Arrays.stream(arr2);
         // 把一种数据类型多个值 放入Stream上
         Stream<String> s8 = Stream.of("A", "B", "C", "D");
      }
      }
      

      2.4 第二类 : 中间方法

      1 Stream<T> filter(Predicate predicate):用于对流中的数据进行过滤
      Predicate接口中的方法 : boolean test(T t):对给定的参数进行判断,返回一个布尔值
      2 Stream<T> limit(long maxSize):截取指定参数个数的数据
      3 Stream<T> skip(long n):跳过指定参数个数的数据
      4 static <T> Stream<T> concat(Stream a, Stream b):合并a和b两个流为一个流
      5 Stream<T> distinct():去除流中重复的元素。依赖(hashCode和equals方法)
      6 Stream<T> sorted () : 将流中元素按照自然排序的规则排序
      7 Stream<T> sorted (Comparator<? super T> comparator) : 将流中元素按照自定义比较器规则排序
      8   8 <R> Stream<R>    map(Function<? super T,? extends R> mapper)  可以实现流中数据的转换
         Function 也是一个函数式接口,抽象方法  R apply(T t);
      

      使用示例:

      package com.itheima.stream_demo;
      import java.util.ArrayList;
      import java.util.Collections;
      import java.util.List;
      import java.util.function.Consumer;
      import java.util.function.Predicate;
      import java.util.stream.Stream;
      public class StreamDemo3 {
      public static void main(String[] args) {
         //ArrayList<String> list = new ArrayList<>(List.of("曹操", "曹孟德", "曹阿瞒", "曹阿瞒", "曹阿瞒", "刘备", "刘玄德", "刘皇叔"));
         ArrayList<String> list = new ArrayList<>();
         Collections.addAll(list, "曹操", "曹孟德", "曹阿瞒", "曹阿瞒", "曹阿瞒", "刘备", "刘玄德", "刘皇叔");
         //filter体验,对三个字名字筛选
         // Predicate 抽象方法  boolean test(T t);
         //list.stream().filter((String name)->{ return name.length()==3;})
         //Consumer  抽象方法 void accept(T t);  遍历元素,元素要处理的逻辑,定义在accept方法上
         list
                 .stream()
                 .filter(name -> name.length() == 3)//定义筛选逻辑
                 .forEach(name -> System.out.println(name));
         //注意:1.中间方法返回的Stream对象,是新的对象,2.Stream对象一生只用一次
         Stream<String> s1 = list.stream();
         Stream<String> s2 = s1.filter(name -> name.length() == 3);
         //s1.forEach(name-> System.out.println(name)); //报错,流对象只用一次
         s2.forEach(name -> System.out.println(name));
         System.out.println("===============");
         //流中一共8个字符串数据
         // Stream<T> limit(long maxSize):截取指定参数个数的数据,如果maxSize比流中元素个数还大,相当于全要
         list.stream()
                 .limit(5) // 取流中前5个数据
                 .forEach(name -> System.out.println(name));
         System.out.println("===============");
         // Stream<T> skip(long n):跳过指定参数个数的数据,,如果n比流中元素个数还大,相当于全不要
         list.stream()
                 .skip(5) // 跳过流中前5个数据
                 .forEach(name -> System.out.println(name));
         //  4 static <T> Stream<T> concat(Stream a, Stream b):合并a和b两个流为一个流
         Stream<String> s3 = Stream.of("A", "B", "C");
         Stream<String> s4 = Stream.of("1", "2", "3");
         Stream.concat(s3, s4)
                 .forEach(str -> System.out.println(str));
         System.out.println("============");
         // 5 Stream<T> distinct():去除流中重复的元素。依赖(hashCode和equals方法)
         Stream
                 .of("A", "B", "A", "B", "C")
                 .distinct()// 去重
                 .forEach(str -> System.out.println(str));
         System.out.println("============");
         //  6 Stream<T> sorted () : 将流中元素按照自然排序的规则排序
         Stream.of("B", "C", "A")
                 .sorted()
                 .forEach(str -> System.out.println(str));
         System.out.println("============");
         // 7 Stream<T> sorted (Comparator<? super T> comparator) : 将流中元素按照自定义比较器规则排序
         Stream.of("B", "C", "A")
                 .sorted((o1, o2) -> o2.compareTo(o1))
                 .forEach(str -> System.out.println(str));
      }
      }
      

      map方法使用示例

      public class StreamDemo3_1 {
      public static void main(String[] args) {
         //ArrayList<String> list = new ArrayList<>(List.of("曹操", "曹孟德", "曹阿瞒", "曹阿瞒", "曹阿瞒", "刘备", "刘玄德", "刘皇叔"));
         ArrayList<String> list = new ArrayList<>();
         Collections.addAll(list, "曹操", "曹孟德", "曹阿瞒", "曹阿瞒", "曹阿瞒", "刘备", "刘玄德", "刘皇叔");
         //将流中字符串数据转换为学生数据
         list.stream()
                 .filter(name -> name.length() == 3)
                 /* .map(new Function<String, Student>() {
                      @Override
                      public Student apply(String name) {
                          return new Student(name);
                      }
                  })*/
                 .map(name -> new Student(name))
                 .forEach((Student stu) -> System.out.println(stu));
      }
      }
      class Student {
      String name;
      public Student(String name) {
         this.name = name;
      }
      @Override
      public String toString() {
         return "Student{" +
                 "name='" + name + '\'' +
                 '}';
      }
      }
      

      2.5 第三类 : 终结方法

      Stream流中三类方法之一 :  终结方法
      1 void forEach(Consumer action):对此流的每个元素执行操作
         Consumer接口中的方法 void accept(T t):对给定的参数执行此操作
      2 long count():返回此流中的元素的个数
      
      package com.itheima.stream_demo;
      import java.util.ArrayList;
      import java.util.List;
      public class StreamDemo4 {
      public static void main(String[] args) {
         ArrayList<String> list = new ArrayList<>(List.of("曹操", "曹孟德", "曹阿瞒", "曹阿瞒", "曹阿瞒", "刘备", "刘玄德", "刘皇叔"));
         //  1 void forEach(Consumer action):对此流的每个元素执行操作
         list.stream()
                 .forEach(name-> System.out.println(name));
         System.out.println("-----");
         //  2 long count():返回此流中的元素个数
         long count = list.stream()
                 .filter(name -> name.length() == 3)
                 .count();
         System.out.println("count = " + count);
      }
      }
      

      2.6 Stream流中的收集方法

      使用Stream流的方式操作完毕之后,我想把流中的数据收集起来,该怎么办呢?
      Stream流的收集方法

      R collect(Collector collector) : 此方法只负责收集流中的数据 , 创建集合添加数据动作需要依赖于参数
      

      工具类Collectors提供了具体的收集方式

      public static <T> Collector toList():把元素收集到List集合中
      public static <T> Collector toSet():把元素收集到Set集合中
      public static Collector toMap(Function keyMapper,Function valueMapper):把元素收集到Map集合中
      

      收集为List集合,Set集合示例:

      package com.itheima.stream_demo;
      import java.util.ArrayList;
      import java.util.List;
      import java.util.Set;
      import java.util.stream.Collector;
      import java.util.stream.Collectors;
      /*
      Stream流的收集操作 : 第二部分
      使用Stream流的方式操作完毕之后,我想把流中的数据起来,该怎么办呢?
      Stream流的收集方法
      R collect(Collector collector) : 此方法只负责收集流中的数据 , 创建集合添加数据动作需要依赖于参数
      工具类Collectors提供了具体的收集方式
      public static <T> Collector toList():把元素收集到List集合中
      public static <T> Collector toSet():把元素收集到Set集合中
      public static  Collector toMap(Function keyMapper,Function valueMapper):把元素收集到Map集合中
      需求 :
         定义一个集合,并添加一些整数1,2,3,4,5,6,7,8,9,10
         将集合中的奇数删除,只保留偶数。
         遍历集合得到2,4,6,8,10
      */
      public class StreamDemo6 {
      public static void main(String[] args) {
         ArrayList<String> list = new ArrayList<>(List.of("曹操", "曹孟德", "曹阿瞒", "曹阿瞒", "刘备", "刘玄德", "刘皇叔"));
         // 使用stream流 , 过滤性 曹 三个字的
         //  R collect(Collector collector) : 此方法只负责收集流中的数据 , 创建集合添加数据动作需要依赖于参数
         // collect负责收集元素 , 不负责创建List集合对象
         // 如果需要创建List集合对象需要依赖于Collectors.toList()
         List<String> caoList = list.stream()
                 .filter(name -> name.startsWith("曹") && name.length() == 3)//过滤
                 .collect(Collectors.toList());//搜集
         System.out.println("caoList = " + caoList);
         //caoList = [曹孟德, 曹阿瞒, 曹阿瞒]
         System.out.println("===============");
         //   public static <T> Collector toSet():把元素收集到Set集合中
         Set<String> caoSet = list.stream()
                 .filter(name -> name.startsWith("曹") && name.length() == 3)//过滤
                 .collect(Collectors.toSet());//搜集
         System.out.println("caoSet = " + caoSet);
         //caoSet = [曹阿瞒, 曹孟德]
      }
      }
      

      收集为Map集合示例

      package com.itheima.stream_demo;
      import java.util.ArrayList;
      import java.util.Arrays;
      import java.util.Collections;
      import java.util.Map;
      import java.util.function.Function;
      import java.util.stream.Collectors;
      import java.util.stream.Stream;
      /*
      Stream流的收集操作 : 第三部分
      1 创建一个ArrayList集合,并添加以下字符串。字符串中前面是姓名,后面是年龄
         "zhangsan,23"
         "lisi,24"
         "wangwu,25"
      2 保留年龄大于等于24岁的人,并将结果收集到Map集合中,姓名为键,年龄为值
         需求1 : 保留年龄大于等于24岁的人
         需求2 : 并将结果收集到Map集合中,姓名为键,年龄为值
      收集方法 :
      collect(Collector collector)
      Collectors的静态方法:
      public static  Collector toMap(Function keyMapper  ,   Function valueMapper):把元素收集到Map集合中
      Map map = 流.collect(Collectors.toMap(键转换,值的转换));
      */
      public class StreamDemo7 {
      public static void main(String[] args) {
         ArrayList<String> list = new ArrayList<>();
         list.add("zhangsan,23");
         list.add("lisi,24");
         list.add("wangwu,25");
         //  需求1 : 保留年龄大于等于24岁的人
         Map<String, String> map = list.stream()
                 //.filter(str -> Integer.parseInt(str.split(",")[1]) >=24)
                 .filter(str -> {
                     String[] arr = str.split(",");//["姓名","年龄"]
                     String ageStr = arr[1];
                     int age = Integer.parseInt(ageStr);
                     return age >= 24;
                 })
                 .collect(Collectors.toMap(
                         str -> str.split(",")[0],//键
                         str -> str.split(",")[1]));//值
         System.out.println("map = " + map);
         Character[] arr = {'a', 'b'};
         Stream<Character> stream = Arrays.stream(arr);
      }
      }
      

      3 多线程入门

      3.1 多线程相关的概念

  • 并发与并行

    • 并行:在同一时刻,有多个任务在多个CPU上同时执行。
    • 并发:在同一时刻,有多个任务在单个CPU上交替执行。
  • 进程与线程

    • 进程:就是操作系统中正在运行的一个应用程序。
    • 线程:就是应用程序中做的事情。比如:360软件中的杀毒,扫描木马,清理垃圾。

      3.2 什么是多线程

  • 是指从软件或者硬件上实现多个线程并发执行的技术。
    具有多线程能力的计算机因有硬件支持而能够在同一时间执行多个线程,提升性能。

  • 好处 : 提高任务的执行性能

    3.3 多线程的创建方式

    3.3.1 继承Thread方式

  • 基本步骤:

    1. 创建一个类继承Thread类。
    2. 在类中重写run方法(线程执行的任务放在这里)
    3. 创建线程对象,调用线程的start方法开启线程。
    4. 执行程序,观察控制台的打印数据的现象
      package com.itheima.thread_demo;
      /*
      线程的创建方式1:继承Thread方式
      基本步骤 :
        1 创建一个类继承Thread类。
        2 在类中重写run方法(线程执行的任务放在这里)
        3 创建线程对象,调用线程的start方法开启线程。
      需求 :
        我们启动一个Java程序,其实默认就存在一个主线程(main方法所在线程)
        接下来,我们在主线程启动一个线程,打印1到100的数字,主线程启动完线程后又打印1到100的数字。
        此时主线程和启动的线程在并发执行,观察控制台打印的结果。
      */
      public class MyThread01 {
      public static void main(String[] args) {
        // 创建线程对象,调用线程的start方法开启线程。
        MyThread mt = new MyThread();
        mt.start();
        // main方法中的任务
        for (int i = 1; i <= 100; i++) {
            System.out.println("i:" + i);
        }
      }
      }
      // 创建一个类继承Thread类。
      class MyThread extends Thread {
      // 在类中重写run方法(线程执行的任务放在这里)
      @Override
      public void run() {
        for (int i = 1; i <= 100; i++) {
            System.out.println("i:" + i);
        }
      }
      }
      

      3.3.2 实现Runable方式

  • 构造方法

    public Thread(Runnable target)
    public Thread(Runnalbe target , String name)
    
  • 实现步骤

    1. 定义任务类实现Runnable,并重写run方法
    2. 创建任务对象
    3. 使用含有Runnable参数的构造方法,创建线程对象并指定任务。
    4. 调用线程的start方法,开启线程
  • 案例演示:
    需求 :启动一个Java程序,其实默认就存在一个主线程(main方法所在线程)
    接下来,我们在主线程启动一个线程,打印1到100的数字,主线程启动完线程后又打印1到100的数字。
    此时主线程和启动的线程在并发执行,观察控制台打印的结果。

    package com.itheima.thread_demo;
    public class MyThread02 {
      public static void main(String[] args) {
          // 创建线程对象,调用线程的start方法开启线程。
          MyRunnable mr = new MyRunnable();
          Thread thread= new Thread(mr);
          thread.start();
          // main方法中的任务
          for (int i = 1; i <= 100; i++) {
              System.out.println("i:" + i);
          }
      }
    }
    // 1 定义任务类实现Runnable,并重写run方法
    class MyRunnable implements Runnable {
      // 在类中重写run方法(线程执行的任务放在这里)
      @Override
      public void run() {
          for (int i = 1; i <= 100; i++) {
              System.out.println("i:" + i);
          }
      }
    }
    

    3.3.3 Thread类中常用方法

    常用方法如下:

    public String getName():返回此线程的名称
    public void setName(String name):将此线程的名称更改为等于参数 name,通过构造方法也可以设置线程名称
    public static Thread currentThread():返回对当前正在执行的线程对象的引用
    public static void sleep(long time):让线程休眠指定的时间,单位为毫秒
    public void join() : 具备阻塞作用 , 等待这个线程死亡,才会执行其他线程
    public final void setPriority(int newPriority)    设置线程的优先级
    public final int getPriority()        获取线程的优先级
    线程优先级1最低到10最高,默认是5
    

    线程有两种调度模型

  • 分时调度模型:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片

  • 抢占式调度模型:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些
    线程对象获取及名字设置示例:
    需求:获取主线程名字并设置主线程的名字
    public class Demo {
      public static void main(String[] args) {
          //看看主线程叫什么名字
          //获取主线程对象
          Thread thread = Thread.currentThread();//获取执行main方法的线程对象(主线程)
          String name = thread.getName();
          System.out.println("name = " + name);//main  就是主线程默认的名字
          //给主线程设置新的名字
          thread.setName("主线程");
          name = thread.getName();
          System.out.println("name = " + name);//主线程
      }
    }
    
    sleep方法示例:
    package com.itheima.thread_demo.thread_method;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    /*
      需求 : 在主线程中创建一个新的线程(实现Runnable接口)
      在新线程中 , 打印当前时间 "yyyy年MM月dd HH:mm:ss"
      2022年01月05 16:31:56
      2022年01月05 16:31:57
      2022年01月05 16:31:58
      2022年01月05 16:31:59
      2022年01月05 16:32:00
      ...
      Thread类
          public static void sleep(long time) : 当前线程进行休眠 , time代表的是毫秒值
    */
    public class SleepDemo {
      public static void main(String[] args) throws InterruptedException {
          new Thread(new TimeTask()).start();
      }
    }
    class TimeTask implements Runnable {
      SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd HH:mm:ss");
      @Override
      public void run() {
          while (true) {
              //sdf.format(new Date())
              String timeStr = sdf.format(System.currentTimeMillis());
              System.out.println("timeStr = " + timeStr);
              //  public static void sleep(long time) : 当前线程进行休眠 , time代表的是毫秒值
              try {
                  //让线程暂停执行1秒
                  Thread.sleep(1000); //1000毫秒 = 1 秒
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
          }
      }
    }
    
    join方法示例
    package com.itheima.thread_demo.thread_method;
    /*
      Thread类
          public void join() : 具备阻塞作用 , 等待这个线程死亡,才会执行其他线程
    */
    public class JoinDemo {
      public static void main(String[] args) throws InterruptedException {
          Thread t = new Thread(new Runnable() {
              @Override
              public void run() {
                  for (int i = 1; i <= 10; i++) {
                      System.out.println(Thread.currentThread().getName() + ":" + i);
                      try {
                          Thread.sleep(300);
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
                  }
              }
          }, "新线程");
          t.start();
          t.join();// 阻塞,等待t线程执行完
          for (int i = 1; i <= 100; i++) {
              System.out.println(Thread.currentThread().getName() + ":" + i);
          }
      }
    }
    
    线程优先级示例:
    package com.itheima.thread_demo.thread_method;
    /*
      线程有两种调度模型
      分时调度模型:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
      抢占式调度模型:让优先级高的线程优先使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程
             获取的 CPU 时间片相对多一些
      public final void setPriority(int newPriority) 设置线程的优先级
      public final int getPriority()    获取线程的优先级
    */
    public class PriorityDemo {
      public static void main(String[] args) {
          Thread thread1 = new Thread(() -> {
              for (int i = 0; i <= 1000; i++) {
                  System.out.println(Thread.currentThread().getName() + ":" + i);
              }
          }, "线程1");
          thread1.setPriority(1); //设定优先级为1
          thread1.start();
          System.out.println(thread1.getPriority());//1  如果没有设定优先级,默认为5
          Thread thread2 = new Thread(() -> {
              for (int i = 0; i <= 1000; i++) {
                  System.out.println(Thread.currentThread().getName() + ":" + i);
              }
          }, "线程2");
          thread2.setPriority(10);
          thread2.start();
      }
    }