1200字范文,内容丰富有趣,写作的好帮手!
1200字范文 > Java学习笔记18:Java_Map集合_HashMap集合_可变参数_Stream流_多线程_线程同步_生产者消费者

Java学习笔记18:Java_Map集合_HashMap集合_可变参数_Stream流_多线程_线程同步_生产者消费者

时间:2022-12-25 18:59:29

相关推荐

Java学习笔记18:Java_Map集合_HashMap集合_可变参数_Stream流_多线程_线程同步_生产者消费者

文章目录

1.Map集合1.1Map集合概述和特点[理解]1.2Map集合的基本功能[应用]1.3Map集合的获取功能[应用]1.4Map集合的遍历(方式1)[应用]1.5Map集合的遍历(方式2)[应用] 2.HashMap集合2.1HashMap集合概述和特点[理解]2.2HashMap集合应用案例[应用] 3.TreeMap集合3.1TreeMap集合概述和特点[理解]3.2TreeMap集合应用案例一[应用]3.3TreeMap集合应用案例二[应用] 4.可变参数4.1可变参数[应用]4.2创建不可变集合[理解] 5.Stream流5.1体验Stream流[理解]5.2Stream流的常见生成方式[应用]5.3Stream流中间操作方法[应用]5.4Stream流终结操作方法[应用]5.5Stream流的收集操作[应用]5.6Stream流综合练习[应用] 6.实现多线程6.1简单了解多线程【理解】6.2并发和并行【理解】6.3进程和线程【理解】6.4实现多线程方式一:继承Thread类【应用】6.5实现多线程方式二:实现Runnable接口【应用】6.6实现多线程方式三: 实现Callable接口【应用】6.7设置和获取线程名称【应用】6.8线程休眠【应用】6.9线程优先级【应用】6.10守护线程【应用】 7.线程同步7.1卖票【应用】7.2卖票案例的问题【理解】7.3同步代码块解决数据安全问题【应用】7.4同步方法解决数据安全问题【应用】7.5Lock锁【应用】7.6死锁【理解】 8.生产者消费者8.1生产者和消费者模式概述【应用】8.2生产者和消费者案例【应用】3.3生产者和消费者案例优化【应用】8.4阻塞队列基本使用【理解】8.5阻塞队列实现等待唤醒机制【理解】

1.Map集合

1.1Map集合概述和特点[理解]

Map集合概述

interface Map<K,V> K:键的类型;V:值的类型

Map集合的特点

双列集合,一个键对应一个值键不可以重复,值可以重复

Map集合的基本使用

public class MapDemo01 {public static void main(String[] args) {//创建集合对象Map<String,String> map = new HashMap<String,String>();//V put(K key, V value) 将指定的值与该映射中的指定键相关联map.put("itheima001","林青霞");map.put("itheima002","张曼玉");map.put("itheima003","王祖贤");map.put("itheima003","柳岩");//输出集合对象System.out.println(map);}}

1.2Map集合的基本功能[应用]

方法介绍

示例代码

public class MapDemo02 {public static void main(String[] args) {//创建集合对象Map<String,String> map = new HashMap<String,String>();//V put(K key,V value):添加元素map.put("张无忌","赵敏");map.put("郭靖","黄蓉");map.put("杨过","小龙女");//V remove(Object key):根据键删除键值对元素// System.out.println(map.remove("郭靖"));// System.out.println(map.remove("郭襄"));//void clear():移除所有的键值对元素// map.clear();//boolean containsKey(Object key):判断集合是否包含指定的键// System.out.println(map.containsKey("郭靖"));// System.out.println(map.containsKey("郭襄"));//boolean isEmpty():判断集合是否为空// System.out.println(map.isEmpty());//int size():集合的长度,也就是集合中键值对的个数System.out.println(map.size());//输出集合对象System.out.println(map);}}

1.3Map集合的获取功能[应用]

方法介绍

示例代码

public class MapDemo03 {public static void main(String[] args) {//创建集合对象Map<String, String> map = new HashMap<String, String>();//添加元素map.put("张无忌", "赵敏");map.put("郭靖", "黄蓉");map.put("杨过", "小龙女");//V get(Object key):根据键获取值// System.out.println(map.get("张无忌"));// System.out.println(map.get("张三丰"));//Set<K> keySet():获取所有键的集合// Set<String> keySet = map.keySet();// for(String key : keySet) {// System.out.println(key);// }//Collection<V> values():获取所有值的集合Collection<String> values = map.values();for(String value : values) {System.out.println(value);}}}

1.4Map集合的遍历(方式1)[应用]

遍历思路

我们刚才存储的元素都是成对出现的,所以我们把Map看成是一个夫妻对的集合 把所有的丈夫给集中起来遍历丈夫的集合,获取到每一个丈夫根据丈夫去找对应的妻子

步骤分析

获取所有键的集合。用keySet()方法实现遍历键的集合,获取到每一个键。用增强for实现根据键去找值。用get(Object key)方法实现

代码实现

public class MapDemo01 {public static void main(String[] args) {//创建集合对象Map<String, String> map = new HashMap<String, String>();//添加元素map.put("张无忌", "赵敏");map.put("郭靖", "黄蓉");map.put("杨过", "小龙女");//获取所有键的集合。用keySet()方法实现Set<String> keySet = map.keySet();//遍历键的集合,获取到每一个键。用增强for实现for (String key : keySet) {//根据键去找值。用get(Object key)方法实现String value = map.get(key);System.out.println(key + "," + value);}}}

1.5Map集合的遍历(方式2)[应用]

遍历思路

我们刚才存储的元素都是成对出现的,所以我们把Map看成是一个夫妻对的集合 获取所有结婚证的集合遍历结婚证的集合,得到每一个结婚证根据结婚证获取丈夫和妻子

步骤分析

获取所有键值对对象的集合 Set<Map.Entry<K,V>> entrySet():获取所有键值对对象的集合 遍历键值对对象的集合,得到每一个键值对对象 用增强for实现,得到每一个Map.Entry 根据键值对对象获取键和值 用getKey()得到键用getValue()得到值

代码实现

public class MapDemo02 {public static void main(String[] args) {//创建集合对象Map<String, String> map = new HashMap<String, String>();//添加元素map.put("张无忌", "赵敏");map.put("郭靖", "黄蓉");map.put("杨过", "小龙女");//获取所有键值对对象的集合Set<Map.Entry<String, String>> entrySet = map.entrySet();//遍历键值对对象的集合,得到每一个键值对对象for (Map.Entry<String, String> me : entrySet) {//根据键值对对象获取键和值String key = me.getKey();String value = me.getValue();System.out.println(key + "," + value);}}}

2.HashMap集合

2.1HashMap集合概述和特点[理解]

HashMap底层是哈希表结构的依赖hashCode方法和equals方法保证键的唯一如果键要存储的是自定义对象,需要重写hashCode和equals方法

2.2HashMap集合应用案例[应用]

案例需求

创建一个HashMap集合,键是学生对象(Student),值是居住地 (String)。存储多个元素,并遍历。要求保证键的唯一性:如果学生对象的成员变量值相同,我们就认为是同一个对象

代码实现

学生类

public class Student {private String name;private int age;public Student() {}public Student(String name, int age) {this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;Student student = (Student) o;if (age != student.age) return false;return name != null ? name.equals(student.name) : student.name == null;}@Overridepublic int hashCode() {int result = name != null ? name.hashCode() : 0;result = 31 * result + age;return result;}}

测试类

public class HashMapDemo {public static void main(String[] args) {//创建HashMap集合对象HashMap<Student, String> hm = new HashMap<Student, String>();//创建学生对象Student s1 = new Student("林青霞", 30);Student s2 = new Student("张曼玉", 35);Student s3 = new Student("王祖贤", 33);Student s4 = new Student("王祖贤", 33);//把学生添加到集合hm.put(s1, "西安");hm.put(s2, "武汉");hm.put(s3, "郑州");hm.put(s4, "北京");//遍历集合Set<Student> keySet = hm.keySet();for (Student key : keySet) {String value = hm.get(key);System.out.println(key.getName() + "," + key.getAge() + "," + value);}}}

3.TreeMap集合

3.1TreeMap集合概述和特点[理解]

TreeMap底层是红黑树结构依赖自然排序或者比较器排序,对键进行排序如果键存储的是自定义对象,需要实现Comparable接口或者在创建TreeMap对象时候给出比较器排序规则

3.2TreeMap集合应用案例一[应用]

案例需求

创建一个TreeMap集合,键是学生对象(Student),值是籍贯(String),学生属性姓名和年龄,按照年龄进行排序并遍历要求按照学生的年龄进行排序,如果年龄相同则按照姓名进行排序

代码实现

学生类

public class Student implements Comparable<Student>{private String name;private int age;public Student() {}public Student(String name, int age) {this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +'}';}@Overridepublic int compareTo(Student o) {//按照年龄进行排序int result = o.getAge() - this.getAge();//次要条件,按照姓名排序。result = result == 0 ? o.getName().compareTo(this.getName()) : result;return result;}}

测试类

public class Test1 {public static void main(String[] args) {// 创建TreeMap集合对象TreeMap<Student,String> tm = new TreeMap<>();// 创建学生对象Student s1 = new Student("xiaohei",23);Student s2 = new Student("dapang",22);Student s3 = new Student("xiaomei",22);// 将学生对象添加到TreeMap集合中tm.put(s1,"江苏");tm.put(s2,"北京");tm.put(s3,"天津");// 遍历TreeMap集合,打印每个学生的信息tm.forEach((Student key, String value)->{System.out.println(key + "---" + value);});}}

3.3TreeMap集合应用案例二[应用]

案例需求

给定一个字符串,要求统计字符串中每个字符出现的次数。举例: 给定字符串是“aababcabcdabcde”,在控制台输出: “a(5)b(4)c(3)d(2)e(1)”

代码实现

public class Test2 {public static void main(String[] args) {// 给定字符串String s = "aababcabcdabcde";// 创建TreeMap集合对象,键是Character,值是IntegerTreeMap<Character,Integer> tm = new TreeMap<>();//遍历字符串,得到每一个字符for (int i = 0; i < s.length(); i++) {//c依次表示字符串中的每一个字符char c = s.charAt(i);// 判断当前遍历到的字符是否在集合中出现过if(!tm.containsKey(c)){//表示当前字符是第一次出现。tm.put(c,1);}else{//存在,表示当前字符已经出现过了//先获取这个字符已经出现的次数Integer count = tm.get(c);//自增,表示这个字符又出现了依次count++;//将自增后的结果再次添加到集合中。tm.put(c,count);}}// a(5)b(4)c(3)d(2)e(1)//System.out.println(tm);tm.forEach((Character key,Integer value)->{System.out.print(key + "(" + value + ")");});}}

4.可变参数

4.1可变参数[应用]

可变参数介绍

可变参数又称参数个数可变,用作方法的形参出现,那么方法参数个数就是可变的了方法的参数类型已经确定,个数不确定,我们可以使用可变参数

可变参数定义格式

修饰符 返回值类型 方法名(数据类型… 变量名) {}

可变参数的注意事项

这里的变量其实是一个数组如果一个方法有多个参数,包含可变参数,可变参数要放在最后

可变参数的基本使用

public class ArgsDemo01 {public static void main(String[] args) {System.out.println(sum(10, 20));System.out.println(sum(10, 20, 30));System.out.println(sum(10, 20, 30, 40));System.out.println(sum(10,20,30,40,50));System.out.println(sum(10,20,30,40,50,60));System.out.println(sum(10,20,30,40,50,60,70));System.out.println(sum(10,20,30,40,50,60,70,80,90,100));}// public static int sum(int b,int... a) {// return 0;// }public static int sum(int... a) {int sum = 0;for(int i : a) {sum += i;}return sum;}}

4.2创建不可变集合[理解]

方法介绍

在List、Set、Map接口中,都存在of方法,可以创建一个不可变的集合 这个集合不能添加,不能删除,不能修改但是可以结合集合的带参构造,实现集合的批量添加 在Map接口中,还有一个ofEntries方法可以提高代码的阅读性 首先会把键值对封装成一个Entry对象,再把这个Entry对象添加到集合当中

示例代码

public class MyVariableParameter4 {public static void main(String[] args) {// static <E> List<E> of(E…elements) 创建一个具有指定元素的List集合对象//static <E> Set<E> of(E…elements) 创建一个具有指定元素的Set集合对象//static <K , V> Map<K,V> of(E…elements) 创建一个具有指定元素的Map集合对象//method1();//method2();//method3();//method4();}private static void method4() {Map<String, String> map = Map.ofEntries(Map.entry("zhangsan", "江苏"),Map.entry("lisi", "北京"));System.out.println(map);}private static void method3() {Map<String, String> map = Map.of("zhangsan", "江苏", "lisi", "北京", "wangwu", "天津");System.out.println(map);}private static void method2() {//传递的参数当中,不能存在重复的元素。Set<String> set = Set.of("a", "b", "c", "d","a");System.out.println(set);}private static void method1() {List<String> list = List.of("a", "b", "c", "d");System.out.println(list);//list.add("Q");//list.remove("a");//list.set(0,"A");//System.out.println(list);// ArrayList<String> list2 = new ArrayList<>();// list2.add("aaa");// list2.add("aaa");// list2.add("aaa");// list2.add("aaa");//集合的批量添加。//首先是通过调用List.of方法来创建一个不可变的集合,of方法的形参就是一个可变参数。//再创建一个ArrayList集合,并把这个不可变的集合中所有的数据,都添加到ArrayList中。ArrayList<String> list3 = new ArrayList<>(List.of("a", "b", "c", "d"));System.out.println(list3);}}

5.Stream流

5.1体验Stream流[理解]

案例需求

按照下面的要求完成集合的创建和遍历

创建一个集合,存储多个字符串元素把集合中所有以"张"开头的元素存储到一个新的集合把"张"开头的集合中的长度为3的元素存储到一个新的集合遍历上一步得到的集合

原始方式示例代码

public class StreamDemo {public static void main(String[] args) {//创建一个集合,存储多个字符串元素ArrayList<String> list = new ArrayList<String>();list.add("林青霞");list.add("张曼玉");list.add("王祖贤");list.add("柳岩");list.add("张敏");list.add("张无忌");//把集合中所有以"张"开头的元素存储到一个新的集合ArrayList<String> zhangList = new ArrayList<String>();for(String s : list) {if(s.startsWith("张")) {zhangList.add(s);}}// System.out.println(zhangList);//把"张"开头的集合中的长度为3的元素存储到一个新的集合ArrayList<String> threeList = new ArrayList<String>();for(String s : zhangList) {if(s.length() == 3) {threeList.add(s);}}// System.out.println(threeList);//遍历上一步得到的集合for(String s : threeList) {System.out.println(s);}System.out.println("--------");//Stream流来改进// list.stream().filter(s -> s.startsWith("张")).filter(s -> s.length() == 3).forEach(s -> System.out.println(s));list.stream().filter(s -> s.startsWith("张")).filter(s -> s.length() == 3).forEach(System.out::println);}}

使用Stream流示例代码

public class StreamDemo {public static void main(String[] args) {//创建一个集合,存储多个字符串元素ArrayList<String> list = new ArrayList<String>();list.add("林青霞");list.add("张曼玉");list.add("王祖贤");list.add("柳岩");list.add("张敏");list.add("张无忌");//Stream流来改进list.stream().filter(s -> s.startsWith("张")).filter(s -> s.length() == 3).forEach(System.out::println);}}

Stream流的好处

直接阅读代码的字面意思即可完美展示无关逻辑方式的语义:获取流、过滤姓张、过滤长度为3、逐一打印Stream流把真正的函数式编程风格引入到Java中代码简洁

5.2Stream流的常见生成方式[应用]

Stream流的三类方法

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

生成Stream流的方式

Collection体系集合

使用默认方法stream()生成流, default Stream stream()

Map体系集合

把Map转成Set集合,间接的生成流

数组

通过Arrays中的静态方法stream生成流

同种数据类型的多个数据

通过Stream接口的静态方法of(T… values)生成流

代码演示

public class StreamDemo {public static void main(String[] args) {//Collection体系的集合可以使用默认方法stream()生成流List<String> list = new ArrayList<String>();Stream<String> listStream = list.stream();Set<String> set = new HashSet<String>();Stream<String> setStream = set.stream();//Map体系的集合间接的生成流Map<String,Integer> map = new HashMap<String, Integer>();Stream<String> keyStream = map.keySet().stream();Stream<Integer> valueStream = map.values().stream();Stream<Map.Entry<String, Integer>> entryStream = map.entrySet().stream();//数组可以通过Arrays中的静态方法stream生成流String[] strArray = {"hello","world","java"};Stream<String> strArrayStream = Arrays.stream(strArray);//同种数据类型的多个数据可以通过Stream接口的静态方法of(T... values)生成流Stream<String> strArrayStream2 = Stream.of("hello", "world", "java");Stream<Integer> intStream = Stream.of(10, 20, 30);}}

5.3Stream流中间操作方法[应用]

概念

中间操作的意思是,执行完此方法之后,Stream流依然可以继续执行其他操作

常见方法

filter代码演示

public class StreamDemo01 {public static void main(String[] args) {//创建一个集合,存储多个字符串元素ArrayList<String> list = new ArrayList<String>();list.add("林青霞");list.add("张曼玉");list.add("王祖贤");list.add("柳岩");list.add("张敏");list.add("张无忌");//需求1:把list集合中以张开头的元素在控制台输出list.stream().filter(s -> s.startsWith("张")).forEach(System.out::println);System.out.println("--------");//需求2:把list集合中长度为3的元素在控制台输出list.stream().filter(s -> s.length() == 3).forEach(System.out::println);System.out.println("--------");//需求3:把list集合中以张开头的,长度为3的元素在控制台输出list.stream().filter(s -> s.startsWith("张")).filter(s -> s.length() == 3).forEach(System.out::println);}}

limit&skip代码演示

public class StreamDemo02 {public static void main(String[] args) {//创建一个集合,存储多个字符串元素ArrayList<String> list = new ArrayList<String>();list.add("林青霞");list.add("张曼玉");list.add("王祖贤");list.add("柳岩");list.add("张敏");list.add("张无忌");//需求1:取前3个数据在控制台输出list.stream().limit(3).forEach(System.out::println);System.out.println("--------");//需求2:跳过3个元素,把剩下的元素在控制台输出list.stream().skip(3).forEach(System.out::println);System.out.println("--------");//需求3:跳过2个元素,把剩下的元素中前2个在控制台输出list.stream().skip(2).limit(2).forEach(System.out::println);}}

concat&distinct代码演示

public class StreamDemo03 {public static void main(String[] args) {//创建一个集合,存储多个字符串元素ArrayList<String> list = new ArrayList<String>();list.add("林青霞");list.add("张曼玉");list.add("王祖贤");list.add("柳岩");list.add("张敏");list.add("张无忌");//需求1:取前4个数据组成一个流Stream<String> s1 = list.stream().limit(4);//需求2:跳过2个数据组成一个流Stream<String> s2 = list.stream().skip(2);//需求3:合并需求1和需求2得到的流,并把结果在控制台输出// Stream.concat(s1,s2).forEach(System.out::println);//需求4:合并需求1和需求2得到的流,并把结果在控制台输出,要求字符串元素不能重复Stream.concat(s1,s2).distinct().forEach(System.out::println);}}

5.4Stream流终结操作方法[应用]

概念

终结操作的意思是,执行完此方法之后,Stream流将不能再执行其他操作

常见方法

代码演示

public class StreamDemo {public static void main(String[] args) {//创建一个集合,存储多个字符串元素ArrayList<String> list = new ArrayList<String>();list.add("林青霞");list.add("张曼玉");list.add("王祖贤");list.add("柳岩");list.add("张敏");list.add("张无忌");//需求1:把集合中的元素在控制台输出// list.stream().forEach(System.out::println);//需求2:统计集合中有几个以张开头的元素,并把统计结果在控制台输出long count = list.stream().filter(s -> s.startsWith("张")).count();System.out.println(count);}}

5.5Stream流的收集操作[应用]

概念

对数据使用Stream流的方式操作完毕后,可以把流中的数据收集到集合中

常用方法

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

代码演示

public class CollectDemo {public static void main(String[] args) {//创建List集合对象List<String> list = new ArrayList<String>();list.add("林青霞");list.add("张曼玉");list.add("王祖贤");list.add("柳岩");/*//需求1:得到名字为3个字的流Stream<String> listStream = list.stream().filter(s -> s.length() == 3);//需求2:把使用Stream流操作完毕的数据收集到List集合中并遍历List<String> names = listStream.collect(Collectors.toList());for(String name : names) {System.out.println(name);}*///创建Set集合对象Set<Integer> set = new HashSet<Integer>();set.add(10);set.add(20);set.add(30);set.add(33);set.add(35);/*//需求3:得到年龄大于25的流Stream<Integer> setStream = set.stream().filter(age -> age > 25);//需求4:把使用Stream流操作完毕的数据收集到Set集合中并遍历Set<Integer> ages = setStream.collect(Collectors.toSet());for(Integer age : ages) {System.out.println(age);}*///定义一个字符串数组,每一个字符串数据由姓名数据和年龄数据组合而成String[] strArray = {"林青霞,30", "张曼玉,35", "王祖贤,33", "柳岩,25"};//需求5:得到字符串中年龄数据大于28的流Stream<String> arrayStream = Stream.of(strArray).filter(s -> Integer.parseInt(s.split(",")[1]) > 28);//需求6:把使用Stream流操作完毕的数据收集到Map集合中并遍历,字符串中的姓名作键,年龄作值Map<String, Integer> map = arrayStream.collect(Collectors.toMap(s -> s.split(",")[0], s -> Integer.parseInt(s.split(",")[1])));Set<String> keySet = map.keySet();for (String key : keySet) {Integer value = map.get(key);System.out.println(key + "," + value);}}}

5.6Stream流综合练习[应用]

案例需求

现在有两个ArrayList集合,分别存储6名男演员名称和6名女演员名称,要求完成如下的操作

男演员只要名字为3个字的前三人女演员只要姓林的,并且不要第一个把过滤后的男演员姓名和女演员姓名合并到一起把上一步操作后的元素作为构造方法的参数创建演员对象,遍历数据

演员类Actor已经提供,里面有一个成员变量,一个带参构造方法,以及成员变量对应的get/set方法

代码实现

演员类

public class Actor {private String name;public Actor(String name) {this.name = name;}public String getName() {return name;}public void setName(String name) {this.name = name;}}

测试类

public class StreamTest {public static void main(String[] args) {//创建集合ArrayList<String> manList = new ArrayList<String>();manList.add("周润发");manList.add("成龙");manList.add("刘德华");manList.add("吴京");manList.add("周星驰");manList.add("李连杰");ArrayList<String> womanList = new ArrayList<String>();womanList.add("林心如");womanList.add("张曼玉");womanList.add("林青霞");womanList.add("柳岩");womanList.add("林志玲");womanList.add("王祖贤");/*//男演员只要名字为3个字的前三人Stream<String> manStream = manList.stream().filter(s -> s.length() == 3).limit(3);//女演员只要姓林的,并且不要第一个Stream<String> womanStream = womanList.stream().filter(s -> s.startsWith("林")).skip(1);//把过滤后的男演员姓名和女演员姓名合并到一起Stream<String> stream = Stream.concat(manStream, womanStream);//把上一步操作后的元素作为构造方法的参数创建演员对象,遍历数据// stream.map(Actor::new).forEach(System.out::println);stream.map(Actor::new).forEach(p -> System.out.println(p.getName()));*/Stream.concat(manList.stream().filter(s -> s.length() == 3).limit(3),womanList.stream().filter(s -> s.startsWith("林")).skip(1)).map(Actor::new).forEach(p -> System.out.println(p.getName()));}}

6.实现多线程

6.1简单了解多线程【理解】

是指从软件或者硬件上实现多个线程并发执行的技术。

具有多线程能力的计算机因有硬件支持而能够在同一时间执行多个线程,提升性能。

6.2并发和并行【理解】

并行:在同一时刻,有多个指令在多个CPU上同时执行。

并发:在同一时刻,有多个指令在单个CPU上交替执行。

6.3进程和线程【理解】

进程:是正在运行的程序

独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位

动态性:进程的实质是程序的一次执行过程,进程是动态产生,动态消亡的

并发性:任何进程都可以同其他进程一起并发执行

线程:是进程中的单个顺序控制流,是一条执行路径

​ 单线程:一个进程如果只有一条执行路径,则称为单线程程序

​ 多线程:一个进程如果有多条执行路径,则称为多线程程序

6.4实现多线程方式一:继承Thread类【应用】

方法介绍

实现步骤

定义一个类MyThread继承Thread类在MyThread类中重写run()方法创建MyThread类的对象启动线程

代码演示

public class MyThread extends Thread {@Overridepublic void run() {for(int i=0; i<100; i++) {System.out.println(i);}}}public class MyThreadDemo {public static void main(String[] args) {MyThread my1 = new MyThread();MyThread my2 = new MyThread();// my1.run();// my2.run();//void start() 导致此线程开始执行; Java虚拟机调用此线程的run方法my1.start();my2.start();}}

两个小问题

为什么要重写run()方法?

因为run()是用来封装被线程执行的代码

run()方法和start()方法的区别?

run():封装线程执行的代码,直接调用,相当于普通方法的调用

start():启动线程;然后由JVM调用此线程的run()方法

6.5实现多线程方式二:实现Runnable接口【应用】

Thread构造方法

实现步骤

定义一个类MyRunnable实现Runnable接口在MyRunnable类中重写run()方法创建MyRunnable类的对象创建Thread类的对象,把MyRunnable对象作为构造方法的参数启动线程

代码演示

public class MyRunnable implements Runnable {@Overridepublic void run() {for(int i=0; i<100; i++) {System.out.println(Thread.currentThread().getName()+":"+i);}}}public class MyRunnableDemo {public static void main(String[] args) {//创建MyRunnable类的对象MyRunnable my = new MyRunnable();//创建Thread类的对象,把MyRunnable对象作为构造方法的参数//Thread(Runnable target)// Thread t1 = new Thread(my);// Thread t2 = new Thread(my);//Thread(Runnable target, String name)Thread t1 = new Thread(my,"坦克");Thread t2 = new Thread(my,"飞机");//启动线程t1.start();t2.start();}}

6.6实现多线程方式三: 实现Callable接口【应用】

方法介绍

实现步骤

定义一个类MyCallable实现Callable接口在MyCallable类中重写call()方法创建MyCallable类的对象创建Future的实现类FutureTask对象,把MyCallable对象作为构造方法的参数创建Thread类的对象,把FutureTask对象作为构造方法的参数启动线程再调用get方法,就可以获取线程结束之后的结果。

代码演示

public class MyCallable implements Callable<String> {@Overridepublic String call() throws Exception {for (int i = 0; i < 100; i++) {System.out.println("跟女孩表白" + i);}//返回值就表示线程运行完毕之后的结果return "答应";}}public class Demo {public static void main(String[] args) throws ExecutionException, InterruptedException {//线程开启之后需要执行里面的call方法MyCallable mc = new MyCallable();//Thread t1 = new Thread(mc);//可以获取线程执行完毕之后的结果.也可以作为参数传递给Thread对象FutureTask<String> ft = new FutureTask<>(mc);//创建线程对象Thread t1 = new Thread(ft);String s = ft.get();//开启线程t1.start();//String s = ft.get();System.out.println(s);}}

三种实现方式的对比

实现Runnable、Callable接口 好处: 扩展性强,实现该接口的同时还可以继承其他的类缺点: 编程相对复杂,不能直接使用Thread类中的方法 继承Thread类 好处: 编程比较简单,可以直接使用Thread类中的方法缺点: 可以扩展性较差,不能再继承其他的类

6.7设置和获取线程名称【应用】

方法介绍

代码演示

public class MyThread extends Thread {public MyThread() {}public MyThread(String name) {super(name);}@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println(getName()+":"+i);}}}public class MyThreadDemo {public static void main(String[] args) {MyThread my1 = new MyThread();MyThread my2 = new MyThread();//void setName(String name):将此线程的名称更改为等于参数 namemy1.setName("高铁");my2.setName("飞机");//Thread(String name)MyThread my1 = new MyThread("高铁");MyThread my2 = new MyThread("飞机");my1.start();my2.start();//static Thread currentThread() 返回对当前正在执行的线程对象的引用System.out.println(Thread.currentThread().getName());}}

6.8线程休眠【应用】

相关方法

代码演示

public class MyRunnable implements Runnable {@Overridepublic void run() {for (int i = 0; i < 100; i++) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "---" + i);}}}public class Demo {public static void main(String[] args) throws InterruptedException {/*System.out.println("睡觉前");Thread.sleep(3000);System.out.println("睡醒了");*/MyRunnable mr = new MyRunnable();Thread t1 = new Thread(mr);Thread t2 = new Thread(mr);t1.start();t2.start();}}

6.9线程优先级【应用】

线程调度

两种调度方式

分时调度模型:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片抢占式调度模型:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些

Java使用的是抢占式调度模型

随机性

假如计算机只有一个 CPU,那么 CPU 在某一个时刻只能执行一条指令,线程只有得到CPU时间片,也就是使用权,才可以执行指令。所以说多线程程序的执行是有随机性,因为谁抢到CPU的使用权是不一定的

优先级相关方法

代码演示

public class MyCallable implements Callable<String> {@Overridepublic String call() throws Exception {for (int i = 0; i < 100; i++) {System.out.println(Thread.currentThread().getName() + "---" + i);}return "线程执行完毕了";}}public class Demo {public static void main(String[] args) {//优先级: 1 - 10 默认值:5MyCallable mc = new MyCallable();FutureTask<String> ft = new FutureTask<>(mc);Thread t1 = new Thread(ft);t1.setName("飞机");t1.setPriority(10);//System.out.println(t1.getPriority());//5t1.start();MyCallable mc2 = new MyCallable();FutureTask<String> ft2 = new FutureTask<>(mc2);Thread t2 = new Thread(ft2);t2.setName("坦克");t2.setPriority(1);//System.out.println(t2.getPriority());//5t2.start();}}

6.10守护线程【应用】

相关方法

代码演示

public class MyThread1 extends Thread {@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println(getName() + "---" + i);}}}public class MyThread2 extends Thread {@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println(getName() + "---" + i);}}}public class Demo {public static void main(String[] args) {MyThread1 t1 = new MyThread1();MyThread2 t2 = new MyThread2();t1.setName("女神");t2.setName("备胎");//把第二个线程设置为守护线程//当普通线程执行完之后,那么守护线程也没有继续运行下去的必要了.t2.setDaemon(true);t1.start();t2.start();}}

7.线程同步

7.1卖票【应用】

案例需求

某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票

实现步骤

定义一个类SellTicket实现Runnable接口,里面定义一个成员变量:private int tickets = 100;

在SellTicket类中重写run()方法实现卖票,代码步骤如下

判断票数大于0,就卖票,并告知是哪个窗口卖的

卖了票之后,总票数要减1

票卖没了,线程停止

定义一个测试类SellTicketDemo,里面有main方法,代码步骤如下

创建SellTicket类的对象

创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名称

启动线程

代码实现

public class SellTicket implements Runnable {private int tickets = 100;//在SellTicket类中重写run()方法实现卖票,代码步骤如下@Overridepublic void run() {while (true) {if(ticket <= 0){//卖完了break;}else{try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}ticket--;System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticket + "张票");}}}}public class SellTicketDemo {public static void main(String[] args) {//创建SellTicket类的对象SellTicket st = new SellTicket();//创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名称Thread t1 = new Thread(st,"窗口1");Thread t2 = new Thread(st,"窗口2");Thread t3 = new Thread(st,"窗口3");//启动线程t1.start();t2.start();t3.start();}}

7.2卖票案例的问题【理解】

卖票出现了问题

相同的票出现了多次

出现了负数的票

问题产生原因

线程执行的随机性导致的,可能在卖票过程中丢失cpu的执行权,导致出现问题

7.3同步代码块解决数据安全问题【应用】

安全问题出现的条件

是多线程环境

有共享数据

有多条语句操作共享数据

如何解决多线程安全问题呢?

基本思想:让程序没有安全问题的环境

怎么实现呢?

把多条语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可

Java提供了同步代码块的方式来解决

同步代码块格式:

synchronized(任意对象) {多条语句操作共享数据的代码 }

synchronized(任意对象):就相当于给代码加锁了,任意对象就可以看成是一把锁

同步的好处和弊端

好处:解决了多线程的数据安全问题

弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率

代码演示

public class SellTicket implements Runnable {private int tickets = 100;private Object obj = new Object();@Overridepublic void run() {while (true) {synchronized (obj) {// 对可能有安全问题的代码加锁,多个线程必须使用同一把锁//t1进来后,就会把这段代码给锁起来if (tickets > 0) {try {Thread.sleep(100);//t1休息100毫秒} catch (InterruptedException e) {e.printStackTrace();}//窗口1正在出售第100张票System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");tickets--; //tickets = 99;}}//t1出来了,这段代码的锁就被释放了}}}public class SellTicketDemo {public static void main(String[] args) {SellTicket st = new SellTicket();Thread t1 = new Thread(st, "窗口1");Thread t2 = new Thread(st, "窗口2");Thread t3 = new Thread(st, "窗口3");t1.start();t2.start();t3.start();}}

7.4同步方法解决数据安全问题【应用】

同步方法的格式

同步方法:就是把synchronized关键字加到方法上

修饰符 synchronized 返回值类型 方法名(方法参数) {方法体;}

同步方法的锁对象是什么呢?

​ this

静态同步方法

同步静态方法:就是把synchronized关键字加到静态方法上

修饰符 static synchronized 返回值类型 方法名(方法参数) {方法体;}

同步静态方法的锁对象是什么呢?

​ 类名.class

代码演示

public class MyRunnable implements Runnable {private static int ticketCount = 100;@Overridepublic void run() {while(true){if("窗口一".equals(Thread.currentThread().getName())){//同步方法boolean result = synchronizedMthod();if(result){break;}}if("窗口二".equals(Thread.currentThread().getName())){//同步代码块synchronized (MyRunnable.class){if(ticketCount == 0){break;}else{try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}ticketCount--;System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticketCount + "张票");}}}}}private static synchronized boolean synchronizedMthod() {if(ticketCount == 0){return true;}else{try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}ticketCount--;System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticketCount + "张票");return false;}}}public class Demo {public static void main(String[] args) {MyRunnable mr = new MyRunnable();Thread t1 = new Thread(mr);Thread t2 = new Thread(mr);t1.setName("窗口一");t2.setName("窗口二");t1.start();t2.start();}}

7.5Lock锁【应用】

虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock

Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化

ReentrantLock构造方法

加锁解锁方法

代码演示

public class Ticket implements Runnable {//票的数量private int ticket = 100;private Object obj = new Object();private ReentrantLock lock = new ReentrantLock();@Overridepublic void run() {while (true) {//synchronized (obj){//多个线程必须使用同一把锁.try {lock.lock();if (ticket <= 0) {//卖完了break;} else {Thread.sleep(100);ticket--;System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticket + "张票");}} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}// }}}}public class Demo {public static void main(String[] args) {Ticket ticket = new Ticket();Thread t1 = new Thread(ticket);Thread t2 = new Thread(ticket);Thread t3 = new Thread(ticket);t1.setName("窗口一");t2.setName("窗口二");t3.setName("窗口三");t1.start();t2.start();t3.start();}}

7.6死锁【理解】

概述

线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行

什么情况下会产生死锁

资源有限同步嵌套

代码演示

public class Demo {public static void main(String[] args) {Object objA = new Object();Object objB = new Object();new Thread(()->{while(true){synchronized (objA){//线程一synchronized (objB){System.out.println("小康同学正在走路");}}}}).start();new Thread(()->{while(true){synchronized (objB){//线程二synchronized (objA){System.out.println("小薇同学正在走路");}}}}).start();}}

8.生产者消费者

8.1生产者和消费者模式概述【应用】

概述

生产者消费者模式是一个十分经典的多线程协作的模式,弄懂生产者消费者问题能够让我们对多线程编程的理解更加深刻。

所谓生产者消费者问题,实际上主要是包含了两类线程:

​ 一类是生产者线程用于生产数据

​ 一类是消费者线程用于消费数据

为了解耦生产者和消费者的关系,通常会采用共享的数据区域,就像是一个仓库

生产者生产数据之后直接放置在共享数据区中,并不需要关心消费者的行为

消费者只需要从共享数据区中去获取数据,并不需要关心生产者的行为

Object类的等待和唤醒方法

8.2生产者和消费者案例【应用】

案例需求

桌子类(Desk):定义表示包子数量的变量,定义锁对象变量,定义标记桌子上有无包子的变量

生产者类(Cooker):实现Runnable接口,重写run()方法,设置线程任务

1.判断是否有包子,决定当前线程是否执行

2.如果有包子,就进入等待状态,如果没有包子,继续执行,生产包子

3.生产包子之后,更新桌子上包子状态,唤醒消费者消费包子

消费者类(Foodie):实现Runnable接口,重写run()方法,设置线程任务

1.判断是否有包子,决定当前线程是否执行

2.如果没有包子,就进入等待状态,如果有包子,就消费包子

3.消费包子后,更新桌子上包子状态,唤醒生产者生产包子

测试类(Demo):里面有main方法,main方法中的代码步骤如下

创建生产者线程和消费者线程对象

分别开启两个线程

代码实现

public class Desk {//定义一个标记//true 就表示桌子上有汉堡包的,此时允许吃货执行//false 就表示桌子上没有汉堡包的,此时允许厨师执行public static boolean flag = false;//汉堡包的总数量public static int count = 10;//锁对象public static final Object lock = new Object();}public class Cooker extends Thread {// 生产者步骤:// 1,判断桌子上是否有汉堡包// 如果有就等待,如果没有才生产。// 2,把汉堡包放在桌子上。// 3,叫醒等待的消费者开吃。@Overridepublic void run() {while(true){synchronized (Desk.lock){if(Desk.count == 0){break;}else{if(!Desk.flag){//生产System.out.println("厨师正在生产汉堡包");Desk.flag = true;Desk.lock.notifyAll();}else{try {Desk.lock.wait();} catch (InterruptedException e) {e.printStackTrace();}}}}}}}public class Foodie extends Thread {@Overridepublic void run() {// 1,判断桌子上是否有汉堡包。// 2,如果没有就等待。// 3,如果有就开吃// 4,吃完之后,桌子上的汉堡包就没有了//叫醒等待的生产者继续生产// 汉堡包的总数量减一//套路://1. while(true)死循环//2. synchronized 锁,锁对象要唯一//3. 判断,共享数据是否结束. 结束//4. 判断,共享数据是否结束. 没有结束while(true){synchronized (Desk.lock){if(Desk.count == 0){break;}else{if(Desk.flag){//有System.out.println("吃货在吃汉堡包");Desk.flag = false;Desk.lock.notifyAll();Desk.count--;}else{//没有就等待//使用什么对象当做锁,那么就必须用这个对象去调用等待和唤醒的方法.try {Desk.lock.wait();} catch (InterruptedException e) {e.printStackTrace();}}}}}}}public class Demo {public static void main(String[] args) {/*消费者步骤:1,判断桌子上是否有汉堡包。2,如果没有就等待。3,如果有就开吃4,吃完之后,桌子上的汉堡包就没有了叫醒等待的生产者继续生产汉堡包的总数量减一*//*生产者步骤:1,判断桌子上是否有汉堡包如果有就等待,如果没有才生产。2,把汉堡包放在桌子上。3,叫醒等待的消费者开吃。*/Foodie f = new Foodie();Cooker c = new Cooker();f.start();c.start();}}

3.3生产者和消费者案例优化【应用】

需求

将Desk类中的变量,采用面向对象的方式封装起来生产者和消费者类中构造方法接收Desk类对象,之后在run方法中进行使用创建生产者和消费者线程对象,构造方法中传入Desk类对象开启两个线程

代码实现

public class Desk {//定义一个标记//true 就表示桌子上有汉堡包的,此时允许吃货执行//false 就表示桌子上没有汉堡包的,此时允许厨师执行//public static boolean flag = false;private boolean flag;//汉堡包的总数量//public static int count = 10;//以后我们在使用这种必须有默认值的变量// private int count = 10;private int count;//锁对象//public static final Object lock = new Object();private final Object lock = new Object();public Desk() {this(false,10); // 在空参内部调用带参,对成员变量进行赋值,之后就可以直接使用成员变量了}public Desk(boolean flag, int count) {this.flag = flag;this.count = count;}public boolean isFlag() {return flag;}public void setFlag(boolean flag) {this.flag = flag;}public int getCount() {return count;}public void setCount(int count) {this.count = count;}public Object getLock() {return lock;}@Overridepublic String toString() {return "Desk{" +"flag=" + flag +", count=" + count +", lock=" + lock +'}';}}public class Cooker extends Thread {private Desk desk;public Cooker(Desk desk) {this.desk = desk;}// 生产者步骤:// 1,判断桌子上是否有汉堡包// 如果有就等待,如果没有才生产。// 2,把汉堡包放在桌子上。// 3,叫醒等待的消费者开吃。@Overridepublic void run() {while(true){synchronized (desk.getLock()){if(desk.getCount() == 0){break;}else{//System.out.println("验证一下是否执行了");if(!desk.isFlag()){//生产System.out.println("厨师正在生产汉堡包");desk.setFlag(true);desk.getLock().notifyAll();}else{try {desk.getLock().wait();} catch (InterruptedException e) {e.printStackTrace();}}}}}}}public class Foodie extends Thread {private Desk desk;public Foodie(Desk desk) {this.desk = desk;}@Overridepublic void run() {// 1,判断桌子上是否有汉堡包。// 2,如果没有就等待。// 3,如果有就开吃// 4,吃完之后,桌子上的汉堡包就没有了//叫醒等待的生产者继续生产// 汉堡包的总数量减一//套路://1. while(true)死循环//2. synchronized 锁,锁对象要唯一//3. 判断,共享数据是否结束. 结束//4. 判断,共享数据是否结束. 没有结束while(true){synchronized (desk.getLock()){if(desk.getCount() == 0){break;}else{//System.out.println("验证一下是否执行了");if(desk.isFlag()){//有System.out.println("吃货在吃汉堡包");desk.setFlag(false);desk.getLock().notifyAll();desk.setCount(desk.getCount() - 1);}else{//没有就等待//使用什么对象当做锁,那么就必须用这个对象去调用等待和唤醒的方法.try {desk.getLock().wait();} catch (InterruptedException e) {e.printStackTrace();}}}}}}}public class Demo {public static void main(String[] args) {/*消费者步骤:1,判断桌子上是否有汉堡包。2,如果没有就等待。3,如果有就开吃4,吃完之后,桌子上的汉堡包就没有了叫醒等待的生产者继续生产汉堡包的总数量减一*//*生产者步骤:1,判断桌子上是否有汉堡包如果有就等待,如果没有才生产。2,把汉堡包放在桌子上。3,叫醒等待的消费者开吃。*/Desk desk = new Desk();Foodie f = new Foodie(desk);Cooker c = new Cooker(desk);f.start();c.start();}}

8.4阻塞队列基本使用【理解】

阻塞队列继承结构

常见BlockingQueue:

ArrayBlockingQueue: 底层是数组,有界

LinkedBlockingQueue: 底层是链表,无界.但不是真正的无界,最大为int的最大值

BlockingQueue的核心方法:

put(anObject): 将参数放入队列,如果放不进去会阻塞

take(): 取出第一个数据,取不到会阻塞

代码示例

public class Demo02 {public static void main(String[] args) throws Exception {// 创建阻塞队列的对象,容量为 1ArrayBlockingQueue<String> arrayBlockingQueue = new ArrayBlockingQueue<>(1);// 存储元素arrayBlockingQueue.put("汉堡包");// 取元素System.out.println(arrayBlockingQueue.take());System.out.println(arrayBlockingQueue.take()); // 取不到会阻塞System.out.println("程序结束了");}}

8.5阻塞队列实现等待唤醒机制【理解】

案例需求

生产者类(Cooker):实现Runnable接口,重写run()方法,设置线程任务

1.构造方法中接收一个阻塞队列对象

2.在run方法中循环向阻塞队列中添加包子

3.打印添加结果

消费者类(Foodie):实现Runnable接口,重写run()方法,设置线程任务

1.构造方法中接收一个阻塞队列对象

2.在run方法中循环获取阻塞队列中的包子

3.打印获取结果

测试类(Demo):里面有main方法,main方法中的代码步骤如下

创建阻塞队列对象

创建生产者线程和消费者线程对象,构造方法中传入阻塞队列对象

分别开启两个线程

代码实现

public class Cooker extends Thread {private ArrayBlockingQueue<String> bd;public Cooker(ArrayBlockingQueue<String> bd) {this.bd = bd;}// 生产者步骤:// 1,判断桌子上是否有汉堡包// 如果有就等待,如果没有才生产。// 2,把汉堡包放在桌子上。// 3,叫醒等待的消费者开吃。@Overridepublic void run() {while (true) {try {bd.put("汉堡包");System.out.println("厨师放入一个汉堡包");} catch (InterruptedException e) {e.printStackTrace();}}}}public class Foodie extends Thread {private ArrayBlockingQueue<String> bd;public Foodie(ArrayBlockingQueue<String> bd) {this.bd = bd;}@Overridepublic void run() {// 1,判断桌子上是否有汉堡包。// 2,如果没有就等待。// 3,如果有就开吃// 4,吃完之后,桌子上的汉堡包就没有了//叫醒等待的生产者继续生产// 汉堡包的总数量减一//套路://1. while(true)死循环//2. synchronized 锁,锁对象要唯一//3. 判断,共享数据是否结束. 结束//4. 判断,共享数据是否结束. 没有结束while (true) {try {String take = bd.take();System.out.println("吃货将" + take + "拿出来吃了");} catch (InterruptedException e) {e.printStackTrace();}}}}public class Demo {public static void main(String[] args) {ArrayBlockingQueue<String> bd = new ArrayBlockingQueue<>(1);Foodie f = new Foodie(bd);Cooker c = new Cooker(bd);f.start();c.start();}}

💗💗💗

print("如果文章对你有用,请点个赞呗O(∩_∩)O~")

System.out.println("如果文章对你有用,请点个赞呗O(∩_∩)O~");

cout<<"如果文章对你有用,请点个赞呗O(∩_∩)O~"<<endl;

💗💗💗

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。