首页
碎碎念
技术分享
开发日志
学习日记
追剧
番
电视剧
电影
闲言碎语
精美壁纸
留言板
个人导航
关于我
归档
用户登录
用户名
密码
登录
注册
关键词搜索
搜索
标签搜索
java
追番~~
计网
c#
unity
脆皮大学生
spring AOP
博客更新
selenium
web测试
springboot
ES6
vue3
elementPlus
idea
近期浏览
热门文章
1
动漫分享---《想要成为影之实力者》
1.4k 阅读
2
在unity中使用c#语言实现人物的转身
1.4k 阅读
3
可恶的日本军国主义!我们一定不能遗忘这段历史,遗忘历史就意味着背叛!!!!
1.3k 阅读
4
Java与Selenium自动化测试全面指南
1.3k 阅读
5
Unity冲刺机制:如何实现角色冲刺
1.3k 阅读
6
SpringMVC 入门教程(基于Java配置类)
1.3k 阅读
7
子网划分与CIDR
1.2k 阅读
8
我的第一个blog
1.2k 阅读
9
分享键盘
1.1k 阅读
Charlotte
累计撰写
54
篇文章
累计添加
17
个标签
累计收到
50
条评论
首页
分类
碎碎念
技术分享
开发日志
学习日记
追剧
番
电视剧
电影
页面
闲言碎语
精美壁纸
留言板
个人导航
关于我
归档
用户登录
登录
注册
“java” 共(4)篇
Java中for循环、增强型for循环与forEach()方法的比较
Java 中 for 循环、增强型 foreach 循环与 forEach() 方法的比较传统 for 循环基本语法传统的 for 循环由三个部分组成:初始化部分、循环条件和迭代部分。其基本语法如下:for (初始化; 条件; 迭代) { // 循环体 }示例:for (int i = 0; i < array.length; i++) { System.out.println(array[i]); }优缺点优点:高度控制:可以精确控制循环变量、步进方式,以及在循环中灵活操作索引。适用广泛:适用于需要索引访问或复杂循环控制的场景。缺点:代码冗长:需要手动管理循环变量和边界条件,增加了代码的复杂性。易出错:手动管理索引可能导致越界错误或无限循环。增强型 foreach 循环基本语法增强型 foreach 循环简化了遍历数组或集合的过程,其基本语法如下:for (元素类型 元素变量 : 数组或集合) { // 循环体 }示例:for (int element : array) { System.out.println(element); }优缺点优点:简洁明了:语法更简洁,减少了循环控制变量和边界检查的代码。可读性高:清晰地表达了“对于每个元素”这一意图,使代码更易理解。缺点:灵活性较低:无法在循环中轻松获取元素的索引,或控制循环的步进。不适合修改集合:在遍历过程中直接修改集合(如删除元素)可能导致 ConcurrentModificationException。forEach() 方法基本语法自 Java 8 引入的 forEach() 方法提供了一种函数式编程的方式来遍历集合。它通常与 Lambda 表达式或方法引用一起使用。示例:List<String> list = Arrays.asList("A", "B", "C"); // 使用 Lambda 表达式 list.forEach(element -> System.out.println(element)); // 使用方法引用 list.forEach(System.out::println);{callout color="#4d7eef"}stream().map() 方法基本语法stream().map() 是 Java 8 引入的 Stream API 中的一个中间操作,用于将流中的每个元素映射成另一种形式。它常与终端操作如 forEach() 结合使用,实现数据的转换和处理。示例:List<String> names = Arrays.asList("alice", "bob", "charlie"); // 使用 map() 将名字转换为大写 names.stream() .map(String::toUpperCase) .forEach(System.out::println);优缺点优点:数据转换:map() 方法允许对流中的每个元素进行转换,如类型转换、数据格式转换等。链式操作:支持链式调用,可以轻松组合多个操作,如过滤、排序、映射等。高效处理:与 Stream API 结合,可以利用并行流进行高效的数据处理。函数式编程风格:促进更清晰、声明式的代码编写方式。缺点:学习曲线:需要理解函数式编程的概念和 Stream API 的使用。调试复杂:链式操作中的每一步不易单独调试,特别是在复杂的转换过程中。过度使用可能降低可读性:过长的链式调用可能导致代码难以阅读和维护。应用场景数据转换和处理:如将对象列表转换为特定字段的列表。复杂的数据操作:如筛选、分组、排序和聚合等。并行处理:利用多核处理器加快大数据量的处理速度。示例:List<User> users = getUsers(); // 提取所有用户的邮箱并转换为大写 List<String> emails = users.stream() .map(User::getEmail) .map(String::toUpperCase) .collect(Collectors.toList()); // 打印所有邮箱 emails.forEach(System.out::println);{/callout}三者的比较1. 语法与可读性传统 for 循环:需要显式管理循环变量和条件,代码较为冗长,但提供了高度的控制力。for (int i = 0; i < list.size(); i++) { System.out.println("Element at " + i + ": " + list.get(i)); }增强型 foreach 循环:语法简洁,适用于只需访问元素的场景,增强了代码的可读性。for (String element : list) { System.out.println(element); }forEach() 方法:结合 Lambda 表达式或方法引用,实现了更简洁和表达力强的遍历方式,适合函数式编程风格。list.forEach(System.out::println);stream().map() 方法:通过流式操作和中间操作(如 map()),实现数据的转换和处理,代码更加声明式和模块化。List<String> upperCaseNames = names.stream() .map(String::toUpperCase) .collect(Collectors.toList());2. 使用场景传统 for 循环:需要访问元素的索引。需要对循环变量进行复杂操作(如步进、跳跃)。需要在循环中进行控制(如 break、continue)。增强型 foreach 循环:仅需访问集合或数组中的元素。不需要元素的索引信息。遍历过程中不需要修改集合。forEach() 方法:适用于函数式编程风格的遍历。需要对每个元素执行相同的操作。结合 Stream API 进行复杂的数据处理和并行操作。stream().map() 方法:需要对数据进行转换或映射。需要进行复杂的数据操作,如过滤、排序、分组等。需要利用并行流提高数据处理性能。3. 性能表现在大多数情况下,这三种循环方式在性能上差异不大,尤其是在遍历数组时。然而,具体表现取决于集合类型和具体的使用场景:遍历数组:传统 for 和增强型 foreach 循环的性能相近。forEach() 方法的性能也相似,但可能略受 Lambda 表达式的影响。遍历 ArrayList:传统 for 循环可能略快,因为它通过索引直接访问元素。增强型 foreach 和 forEach() 方法依赖于迭代器,性能差异较小。遍历 LinkedList:传统 for 循环效率较低,因为 get(i) 操作需要线性时间。增强型 foreach 和 forEach() 方法使用迭代器,遍历效率更高。并行处理:forEach() 方法可与 Stream API 结合,利用并行流提高大数据量的处理性能。传统 for 循环和增强型 foreach 循环不直接支持并行处理。结论: 性能差异通常较小,除非在特殊情况下需要优化。选择哪种循环结构应更多基于代码的可读性和需求。4. 修改集合的能力传统 for 循环:允许在遍历过程中修改集合(如删除元素),但需要谨慎操作以避免 ConcurrentModificationException。for (int i = 0; i < list.size(); i++) { if (list.get(i).isInvalid()) { list.remove(i); i--; // 调整索引以避免跳过元素 } }增强型 foreach 循环:不推荐在遍历过程中直接修改集合,尤其是删除元素,因为这可能导致 ConcurrentModificationException。错误示例:for (String name : names) { if (name.isEmpty()) { names.remove(name); // 会抛出 ConcurrentModificationException } }forEach() 方法:同样不推荐在遍历过程中修改集合。使用 forEach() 方法时,如果需要修改集合,建议使用 Iterator 或其他安全的方式。错误示例:list.forEach(element -> { if (element.isInvalid()) { list.remove(element); // 会抛出 ConcurrentModificationException } });正确示例(使用 Iterator):Iterator<String> iterator = list.iterator(); while (iterator.hasNext()) { String element = iterator.next(); if (element.isInvalid()) { iterator.remove(); } }stream().map() 方法:通常用于数据转换,不建议在流操作中修改源集合。这不仅会导致并发修改异常,还可能引入不可预测的行为。错误示例:List<String> names = new ArrayList<>(Arrays.asList("Alice", "Bob", "Charlie")); names.stream() .map(name -> { if (name.equals("Bob")) { names.remove(name); // 会抛出 ConcurrentModificationException } return name.toUpperCase(); }) .forEach(System.out::println);正确示例:List<String> filteredNames = names.stream() .filter(name -> !name.equals("Bob")) .collect(Collectors.toList()); filteredNames.forEach(System.out::println);结论: 如果需要在遍历过程中修改集合,传统 for 循环或使用 Iterator 更为合适。forEach() 和流操作应避免直接修改源集合。举例说明示例 1:遍历数组传统 for 循环:int[] numbers = {1, 2, 3, 4, 5}; for (int i = 0; i < numbers.length; i++) { System.out.println("Index " + i + ": " + numbers[i]); }增强型 foreach 循环:int[] numbers = {1, 2, 3, 4, 5}; for (int number : numbers) { System.out.println(number); }forEach() 方法:int[] numbers = {1, 2, 3, 4, 5}; Arrays.stream(numbers).forEach(System.out::println);stream().map() 方法:int[] numbers = {1, 2, 3, 4, 5}; int[] squaredNumbers = Arrays.stream(numbers) .map(n -> n * n) .toArray(); Arrays.stream(squaredNumbers).forEach(System.out::println);分析:传统 for 循环可以同时获取元素值和索引。增强型 foreach 循环代码更简洁,但无法直接获取索引。forEach() 方法通过流式操作实现,语法简洁,适合函数式编程。stream().map() 方法不仅遍历元素,还对每个元素进行转换(如平方操作),并生成新的数组。示例 2:遍历 ArrayList传统 for 循环:List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C")); for (int i = 0; i < list.size(); i++) { System.out.println("Element at " + i + ": " + list.get(i)); }增强型 foreach 循环:List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C")); for (String element : list) { System.out.println(element); }forEach() 方法:List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C")); list.forEach(System.out::println);stream().map() 方法:List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C")); List<String> lowerCaseList = list.stream() .map(String::toLowerCase) .collect(Collectors.toList()); lowerCaseList.forEach(System.out::println);分析:传统 for 循环适用于需要元素索引的场景。增强型 foreach 和 forEach() 方法适用于只需访问元素值的场景,代码更为简洁。stream().map() 方法用于将所有元素转换为小写字母,并收集到新的列表中,然后遍历打印。示例 3:遍历 LinkedList传统 for 循环(低效):List<String> linkedList = new LinkedList<>(Arrays.asList("X", "Y", "Z")); for (int i = 0; i < linkedList.size(); i++) { System.out.println(linkedList.get(i)); }增强型 foreach 循环(高效):List<String> linkedList = new LinkedList<>(Arrays.asList("X", "Y", "Z")); for (String element : linkedList) { System.out.println(element); }forEach() 方法(高效):List<String> linkedList = new LinkedList<>(Arrays.asList("X", "Y", "Z")); linkedList.forEach(System.out::println);stream().map() 方法:List<String> linkedList = new LinkedList<>(Arrays.asList("X", "Y", "Z")); List<String> reversedList = linkedList.stream() .map(element -> new StringBuilder(element).reverse().toString()) .collect(Collectors.toList()); reversedList.forEach(System.out::println);分析:传统 for 循环在 LinkedList 中效率较低,因为 get(i) 操作需要线性时间。增强型 foreach 和 forEach() 方法使用迭代器,遍历效率更高。stream().map() 方法用于将每个元素反转,并收集到新的列表中,然后遍历打印。示例 4:使用 forEach() 方法进行复杂操作示例:List<String> names = Arrays.asList("Alice", "Bob", "Charlie"); // 使用 Lambda 表达式进行过滤和打印 names.stream() .filter(name -> name.startsWith("A")) .forEach(name -> System.out.println("Filtered Name: " + name)); // 使用方法引用进行打印 names.stream() .filter(name -> name.startsWith("A")) .forEach(System.out::println); // 使用 map() 进行转换后打印 names.stream() .map(String::toUpperCase) .forEach(System.out::println);分析:结合 Stream API,forEach() 方法能够实现复杂的数据处理和操作。使用 map() 方法将所有名字转换为大写,然后打印,展示了数据转换与遍历的结合。提高了代码的表达力和可读性,适用于需要对数据进行多步处理的场景。最佳实践建议优先使用增强型 foreach 循环和 forEach() 方法:在不需要索引的情况下,选择增强型 foreach 循环或 forEach() 方法,以提高代码的简洁性和可读性。在需要索引访问时使用传统 for 循环:当需要访问元素的索引,或根据索引进行计算时,传统 for 循环更为合适。结合 Stream API 使用 forEach() 和 map() 方法:在需要进行复杂的数据处理、过滤、转换或并行操作时,结合 Stream API 使用 forEach() 和 map() 方法,以发挥其优势。谨慎修改集合:在遍历过程中需要修改集合时,避免使用增强型 foreach 循环或 forEach() 方法,改用传统 for 循环或迭代器,以防止 ConcurrentModificationException。考虑集合类型:根据所使用的集合类型(如 ArrayList vs LinkedList),选择最合适的循环结构以优化性能。利用 stream().map() 进行数据转换:当需要对集合中的元素进行转换、映射或其他中间操作时,优先考虑使用 stream().map(),以实现更高效和更具可读性的代码。示例:List<User> users = getUsers(); // 使用 stream().map() 提取用户邮箱并转换为大写 List<String> upperCaseEmails = users.stream() .map(User::getEmail) .map(String::toUpperCase) .collect(Collectors.toList()); // 打印所有转换后的邮箱 upperCaseEmails.forEach(System.out::println);总结在 Java 编程中,选择合适的循环结构对于编写高效、可读性强的代码至关重要。传统的 for 循环、增强型 foreach 循环、forEach() 方法以及 stream().map() 方法各有优劣,适用于不同的场景:传统 for 循环:适用于需要高度控制、访问索引或在循环中修改集合的场景。增强型 foreach 循环:适用于只需访问元素值的简单遍历,代码更简洁。forEach() 方法:适用于函数式编程风格的遍历,结合 Stream API 实现复杂的数据处理和并行操作。stream().map() 方法:适用于需要对数据进行转换、映射和中间处理的场景,增强代码的表达力和模块化。
5个月前
188
0
0
Java 枚举中的参数化常量与遍历查找逻辑
在 Java 开发中,枚举(enum)不仅仅是用来定义一组常量。通过参数化枚举常量和实现特定的逻辑,枚举可以变得功能强大且灵活。本文将针对以下两段代码进行详细解析:枚举常量的参数化定义FAIL(500, "失败")枚举值的遍历与查找逻辑for (ResultCodeEnum resultCodeEnum : ResultCodeEnum.values()) { if (resultCodeEnum.code == codeVal) { return resultCodeEnum; } }枚举常量的参数化定义基本概念在 Java 中,枚举(enum)用于定义一组固定的常量。通常,我们会看到类似以下的简单枚举:public enum Status { ACTIVE, INACTIVE, PENDING }然而,Java 枚举的强大之处在于它允许为每个枚举常量定义参数,从而为每个常量关联特定的数据。这种方式使得枚举不仅仅是简单的常量集合,而是可以携带更多信息的对象。参数化枚举常量的定义以您的代码为例:FAIL(500, "失败")这里,FAIL 是枚举 ResultCodeEnum 的一个常量,它被参数化为两个值:状态码(code):500描述信息(desc):"失败"这种参数化的定义方式使得每个枚举常量不仅有名称(如 FAIL),还携带了额外的属性信息。构造方法与字段为了支持参数化的枚举常量,需要在枚举中定义相应的字段和构造方法。例如:@Getter public enum ResultCodeEnum { SUCCESS(200, "成功"), FAIL(500, "失败"); // 状态码 private final int code; // 描述 private final String desc; ResultCodeEnum(int code, String desc) { this.code = code; this.desc = desc; } }解析:字段定义:private final int code; 和 private final String desc; 分别用于存储状态码和描述信息。使用 final 修饰确保这些字段的不可变性。构造方法:ResultCodeEnum(int code, String desc) 是一个私有构造方法,用于初始化每个枚举常量的 code 和 desc 字段。Lombok 的 @Getter 注解:自动为所有字段生成 getter 方法,简化代码编写。优点清晰的关联关系:每个枚举常量与其对应的状态码和描述信息紧密关联,提升代码的可读性和维护性。类型安全:避免了使用魔法数字或字符串,减少了出错的可能性。封装性:通过 private final 字段和 getter 方法,确保了数据的封装与不可变性。枚举值的遍历与查找逻辑背景在实际应用中,常常需要根据某个值(如状态码)来查找对应的枚举常量。例如,当接收到一个状态码 500 时,需要找到对应的 ResultCodeEnum.FAIL 枚举实例。遍历查找的实现以下是您提供的遍历查找逻辑:public static ResultCodeEnum getByCode(int codeVal) { for (ResultCodeEnum resultCodeEnum : ResultCodeEnum.values()) { if (resultCodeEnum.code == codeVal) { return resultCodeEnum; } } return null; }解析:ResultCodeEnum.values() 方法:这是一个由 Java 自动为枚举类型生成的静态方法,返回包含所有枚举常量的数组。在本例中,ResultCodeEnum.values() 返回 [SUCCESS, FAIL]。增强型 for 循环:for (ResultCodeEnum resultCodeEnum : ResultCodeEnum.values()) 遍历所有枚举常量。每次循环中,resultCodeEnum 分别代表 SUCCESS 和 FAIL。条件判断:if (resultCodeEnum.code == codeVal) 检查当前枚举常量的 code 是否与传入的 codeVal 相等。如果相等,返回该枚举常量。返回 null:如果遍历完所有枚举常量后仍未找到匹配的 codeVal,则返回 null。优点与缺点优点:简单直观:逻辑清晰,易于理解和实现。通用性:适用于枚举常量数量较少的情况。缺点:性能问题:随着枚举常量数量的增加,遍历查找的效率会下降(时间复杂度为 O(n))。空指针风险:方法返回 null 可能导致调用者在未进行 null 检查时发生 NullPointerException。优化建议为了提升查找效率和代码的健壮性,可以考虑以下优化措施:使用静态缓存(Map):通过一个静态的 Map 来缓存 code 与枚举实例的映射关系,将查找时间复杂度降低到 O(1)。private static final Map<Integer, ResultCodeEnum> CODE_MAP = new HashMap<>(); static { for (ResultCodeEnum resultCodeEnum : ResultCodeEnum.values()) { CODE_MAP.put(resultCodeEnum.getCode(), resultCodeEnum); } } public static ResultCodeEnum getByCode(int codeVal) { return CODE_MAP.get(codeVal); }使用 Optional 避免返回 null:返回 Optional<ResultCodeEnum>,强制调用者处理可能的空值,减少 NullPointerException 风险。public static Optional<ResultCodeEnum> getByCode(int codeVal) { return Optional.ofNullable(CODE_MAP.get(codeVal)); }抛出自定义异常:在未找到匹配枚举时,抛出自定义异常,明确告知调用者错误情况。public static ResultCodeEnum getByCode(int codeVal) { ResultCodeEnum result = CODE_MAP.get(codeVal); if (result == null) { throw new IllegalArgumentException("无效的状态码: " + codeVal); } return result; }综合应用示例结合上述两部分的解析,以下是一个完整的 ResultCodeEnum 实现示例:package com.jingdianjichi.subject.common.enums; import lombok.Getter; import java.util.HashMap; import java.util.Map; import java.util.Optional; @Getter public enum ResultCodeEnum { SUCCESS(200, "成功"), FAIL(500, "失败"); // 状态码 private final int code; // 描述 private final String desc; // 静态缓存 Map,用于快速查找 private static final Map<Integer, ResultCodeEnum> CODE_MAP = new HashMap<>(); // 静态代码块,初始化缓存 Map static { for (ResultCodeEnum resultCodeEnum : ResultCodeEnum.values()) { CODE_MAP.put(resultCodeEnum.getCode(), resultCodeEnum); } } // 构造方法 ResultCodeEnum(int code, String desc) { this.code = code; this.desc = desc; } public static Optional<ResultCodeEnum> getByCode(int codeVal) { return Optional.ofNullable(CODE_MAP.get(codeVal)); } }使用示例以下是如何在实际项目中使用 ResultCodeEnum 的示例:public class ApiResponse { private int code; private String message; private Object data; // 构造方法 public ApiResponse(int code, String message, Object data) { this.code = code; this.message = message; this.data = data; } // 静态方法:成功响应 public static ApiResponse success(Object data) { return new ApiResponse(ResultCodeEnum.SUCCESS.getCode(), ResultCodeEnum.SUCCESS.getDesc(), data); } // 静态方法:失败响应 public static ApiResponse fail(ResultCodeEnum resultCodeEnum) { return new ApiResponse(resultCodeEnum.getCode(), resultCodeEnum.getDesc(), null); } // Getter 和 Setter 略 }调用示例:@RestController @RequestMapping("/api") public class UserController { @GetMapping("/user/{id}") public ResponseEntity<ApiResponse> getUser(@PathVariable Long id) { Optional<User> userOpt = userService.findById(id); if (userOpt.isPresent()) { return ResponseEntity.ok(ApiResponse.success(userOpt.get())); } else { return ResponseEntity.status(HttpStatus.NOT_FOUND) .body(ApiResponse.fail(ResultCodeEnum.FAIL)); } } }
5个月前
164
0
1
尚硅谷java多线程笔记(上)
java多线程 重点: 1.会使用多线程方法,主要是start() 2.会使用继承Thread的方式创建多线程 3.会使用实现Runnable接口的方式实现多线程 4.会使用同步代码块解决线程不安全问题 5.会使用同步方法解决线程不安全问题第一章.多线程基本了解1.多线程_线程和进程进程:在内存中执行的应用程序线程:是进程中最小的执行单元 线程作用:负责当前进程中程序的运行.一个进程中至少有一个线程,一个进程还可以有多个线程,这样的应用程序就称之为多线程程序 简单理解:一个功能就需要一条线程取去执行 1.使用场景: 软件中的耗时操作 -> 拷贝大文件, 加载大量的资源 所有的聊天软件 所有的后台服务器 一个线程可以干一件事,我们就可以同时做多件事了,提高了CPU的利用率2.并发和并行并行:在同一个时刻,有多个执行在多个CPU上(同时)执行(好比是多个人做不同的事儿) 比如:多个厨师在炒多个菜并发:在同一个时刻,有多个指令在单个CPU上(交替)执行 比如:一个厨师在炒多个菜细节: 1.之前CPU是单核,但是在执行多个程序的时候好像是在同时执行,原因是CPU在多个线程之间做高速切换 2.现在咱们的CPU都是多核多线程的了,比如2核4线程,那么CPU可以同时运行4个线程,此时不同切换,但是如果多了,CPU就要切换了,所以现在CPU在执行程序的时候并发和并行都存在 3.CPU调度1.分时调度:值的是让所有的线程轮流获取CPU使用权,并且平均分配每个线程占用CPU的时间片 2.抢占式调度:多个线程轮流抢占CPU使用权,哪个线程先抢到了,哪个线程先执行,一般都是优先级高的先抢到CPU使用权的几率大,我们java程序就是抢占式调度 4.主线程介绍主线程:CPU和内存之间开辟的转门为main方法服务的线程第二章.创建线程的方式(重点)1.第一种方式_extends Thread1.定义一个类,继承Thread 2.重写run方法,在run方法中设置线程任务(所谓的线程任务指的是此线程要干的具体的事儿,具体执行的代码) 3.创建自定义线程类的对象 4.调用Thread中的start方法,开启线程,jvm自动调用run方法public class Test01 { public static void main(String[] args) { //创建线程对象 MyThread t1 = new MyThread(); //调用start方法,开启线程,jvm自动调用run方法 t1.start(); for (int i = 0; i < 10; i++) { System.out.println("main线程..........执行了"+i); } } }public class MyThread extends Thread{ @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println("MyThread...执行了"+i); } } }2.多线程在内存中的运行原理注意:同一个线程对象不能连续调用多次start,如果想要再次调用start,那么咱们就new一个新的线程对象3.Thread类中的方法void start() -> 开启线程,jvm自动调用run方法 void run() -> 设置线程任务,这个run方法是Thread重写的接口Runnable中的run方法 String getName() -> 获取线程名字 void setName(String name) -> 给线程设置名字 static Thread currentThread() -> 获取正在执行的线程对象(此方法在哪个线程中使用,获取的就是哪个线程对象) static void sleep(long millis)->线程睡眠,超时后自动醒来继续执行,传递的是毫秒值 public class MyThread extends Thread{ @Override public void run() { for (int i = 0; i < 10; i++) { //线程睡眠 try { Thread.sleep(1000L); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println(Thread.currentThread().getName()+"...执行了"+i); } } } public class Test01 { public static void main(String[] args) throws InterruptedException { //创建线程对象 MyThread t1 = new MyThread(); //给线程设置名字 t1.setName("金莲"); //调用start方法,开启线程,jvm自动调用run方法 t1.start(); for (int i = 0; i < 10; i++) { Thread.sleep(1000L); System.out.println(Thread.currentThread().getName()+"线程..........执行了"+i); } } } 问题:为啥在重写的run方法中有异常只能try,不能throws原因:继承的Thread中的run方法没有抛异常,所以在子类中重写完run方法之后就不能抛,只能try4.Thread中其他的方法void setPriority(int newPriority) -> 设置线程优先级,优先级越高的线程,抢到CPU使用权的几率越大,但是不是每次都先抢到 int getPriority() -> 获取线程优先级 void setDaemon(boolean on) -> 设置为守护线程,当非守护线程执行完毕,守护线程就要结束,但是守护线程也不是立马结束,当非守护线程结束之后,系统会告诉守护线程人家结束了,你也结束吧,在告知的过程中,守护线程会执行,只不过执行到半路就结束了 static void yield() -> 礼让线程,让当前线程让出CPU使用权 void join() -> 插入线程或者叫做插队线程 4.1.线程优先级public class MyThread1 extends Thread{ @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName()+"执行了......"+i); } } }public class Test01 { public static void main(String[] args) { MyThread1 t1 = new MyThread1(); t1.setName("金莲"); MyThread1 t2 = new MyThread1(); t2.setName("阿庆"); //System.out.println(t1.getPriority()); //System.out.println(t2.getPriority()); //设置优先级 t1.setPriority(1); t2.setPriority(10); t1.start(); t2.start(); } } 4.2.守护线程public class Test01 { public static void main(String[] args) { MyThread1 t1 = new MyThread1(); t1.setName("金莲"); MyThread2 t2 = new MyThread2(); t2.setName("阿庆"); //将t2设置成守护线程 t2.setDaemon(true); t1.start(); t2.start(); } } public class MyThread1 extends Thread{ @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName()+"执行了......"+i); } } }public class MyThread2 extends Thread{ @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName()+"执行了..."+i); } } } 4.3.礼让线程场景说明:如果两个线程一起执行,可能会执行一会儿线程A,再执行一会线程B,或者可能线程A执行完毕了,线程B在执行 那么我们能不能让两个线程尽可能的平衡一点 -> 尽量让两个线程交替执行 注意:只是尽可能的平衡,不是绝对的你来我往,有可能线程A线程执行,然后礼让了,但是回头A又抢到CPU使用权了 public class Test01 { public static void main(String[] args) { MyThread1 t1 = new MyThread1(); t1.setName("金莲"); MyThread1 t2 = new MyThread1(); t2.setName("阿庆"); t1.start(); t2.start(); } }public class MyThread1 extends Thread{ @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName()+"执行了......"+i); Thread.yield(); } } } 4.4.插入线程public class MyThread1 extends Thread{ @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName()+"执行了......"+i); } } }public class Test01 { public static void main(String[] args) throws InterruptedException { MyThread1 t1 = new MyThread1(); t1.setName("金莲"); t1.start(); t1.join(); for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName()+"执行了......"+i); } } }5.第二种方式_实现Runnable接口1.创建类,实现Runnable接口 2.重写run方法,设置线程任务 3.利用Thread类的构造方法:Thread(Runnable target),创建Thread对象(线程对象),将自定义的类当参数传递到Thread构造中 -> 这一步是让我们自己定义的类成为一个真正的线程类对象 4.调用Thread中的start方法,开启线程,jvm自动调用run方法 public class Test01 { public static void main(String[] args) { MyRunnable myRunnable = new MyRunnable(); Thread t1 = new Thread(myRunnable); //调用Thread中的start方法,开启线程 t1.start(); for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName()+"...执行了"+i); } } }public class MyRunnable implements Runnable{ @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName()+"...执行了"+i); } } }6.两种实现多线程的方式区别1.继承Thread:继承只支持单继承,有继承的局限性 2.实现Runnable:没有继承的局限性, MyThread extends Fu implements Runnable7.第三种方式_匿名内部类创建多线程严格意义上来说,匿名内部类方式不属于创建多线程方式其中之一,因为匿名内部类形式建立在实现Runnable接口的基础上完成的匿名内部类回顾: 1.new 接口/抽象类(){ 重写方法 }.重写的方法(); 2.接口名/类名 对象名 = new 接口/抽象类(){ 重写方法 } 对象名.重写的方法();public class Test02 { public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName()+"...执行了"+i); } } },"阿庆").start(); new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName()+"...执行了"+i); } } },"金莲").start(); } } 第三章.线程安全1.什么时候发生:当多个线程访问同一个资源时,导致了数据有问题1.线程安全问题-->线程不安全的代码public class MyTicket implements Runnable{ //定义100张票 int ticket = 100; @Override public void run() { while(true){ if (ticket>0){ System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票"); ticket--; } } } } public class Test01 { public static void main(String[] args) { MyTicket myTicket = new MyTicket(); Thread t1 = new Thread(myTicket, "赵四"); Thread t2 = new Thread(myTicket, "刘能"); Thread t3 = new Thread(myTicket, "广坤"); t1.start(); t2.start(); t3.start(); } }原因:CPU在多个线程之间做高速切换导致的2.解决线程安全问题的第一种方式(使用同步代码块)1.格式: synchronized(任意对象){ 线程可能出现不安全的代码 } 2.任意对象:就是我们的锁对象 3.执行: 一个线程拿到锁之后,会进入到同步代码块中执行,在此期间,其他线程拿不到锁,就进不去同步代码块,需要在同步代码块外面等待排队,需要等着执行的线程执行完毕,出了同步代码块,相当于释放锁了,等待的线程才能抢到锁,才能进入到同步代码块中执行public class MyTicket implements Runnable{ //定义100张票 int ticket = 100; //任意new一个对象 Object obj = new Object(); @Override public void run() { while(true){ try { Thread.sleep(100L); } catch (InterruptedException e) { throw new RuntimeException(e); } synchronized (obj){ if (ticket>0){ System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票"); ticket--; } } } } }public class Test01 { public static void main(String[] args) { MyTicket myTicket = new MyTicket(); Thread t1 = new Thread(myTicket, "赵四"); Thread t2 = new Thread(myTicket, "刘能"); Thread t3 = new Thread(myTicket, "广坤"); t1.start(); t2.start(); t3.start(); } }3.解决线程安全问题的第二种方式:同步方法3.1.普通同步方法_非静态1.格式: 修饰符 synchronized 返回值类型 方法名(参数){ 方法体 return 结果 } 2.默认锁:thispublic class MyTicket implements Runnable{ //定义100张票 int ticket = 100; @Override public void run() { while(true){ try { Thread.sleep(100L); } catch (InterruptedException e) { throw new RuntimeException(e); } //method01(); method02(); } } public void method02(){ synchronized(this){ System.out.println(this+".........."); if (ticket>0){ System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票"); ticket--; } } } } public class Test01 { public static void main(String[] args) { MyTicket myTicket = new MyTicket(); System.out.println(myTicket); Thread t1 = new Thread(myTicket, "赵四"); Thread t2 = new Thread(myTicket, "刘能"); Thread t3 = new Thread(myTicket, "广坤"); t1.start(); t2.start(); t3.start(); } } 3.2.静态同步方法1.格式: 修饰符 static synchronized 返回值类型 方法名(参数){ 方法体 return 结果 } 2.默认锁:class对象public class MyTicket implements Runnable{ //定义100张票 static int ticket = 100; @Override public void run() { while(true){ try { Thread.sleep(100L); } catch (InterruptedException e) { throw new RuntimeException(e); } //method01(); method02(); } } public static void method02(){ synchronized(MyTicket.class){ if (ticket>0){ System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票"); ticket--; } } } }public class Test01 { public static void main(String[] args) { MyTicket myTicket = new MyTicket(); Thread t1 = new Thread(myTicket, "赵四"); Thread t2 = new Thread(myTicket, "刘能"); Thread t3 = new Thread(myTicket, "广坤"); t1.start(); t2.start(); t3.start(); } } 第四章.死锁(了解)1.死锁介绍(锁嵌套就有可能产生死锁)指的是两个或者两个以上的线程在执行的过程中由于竞争同步锁而产生的一种阻塞现象;如果没有外力的作用,他们将无法继续执行下去,这种情况称之为死锁根据上图所示:线程1正在持有锁1,但是线程1必须再拿到锁2,才能继续执行 而线程2正在持有锁2,但是线程2需要再拿到锁1,才能继续执行 此时两个线程处于互相等待的状态,就是死锁,在程序中的死锁将出现在同步代码块的嵌套中2.死锁的分析3.代码实现public class LockA { public static LockA lockA = new LockA(); } public class LockB { public static LockB lockB = new LockB(); }public class DieLock implements Runnable{ private boolean flag; public DieLock(boolean flag) { this.flag = flag; } @Override public void run() { if (flag){ synchronized (LockA.lockA){ System.out.println("if...lockA"); synchronized (LockB.lockB){ System.out.println("if...lockB"); } } }else{ synchronized (LockB.lockB){ System.out.println("else...lockB"); synchronized (LockA.lockA){ System.out.println("else...lockA"); } } } } } public class Test01 { public static void main(String[] args) { DieLock dieLock1 = new DieLock(true); DieLock dieLock2 = new DieLock(false); new Thread(dieLock1).start(); new Thread(dieLock2).start(); } } 只需要知道死锁出现的原因即可(锁嵌套),以后尽量避免锁嵌套第五章.线程状态1.线程状态介绍 当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。在线程的生命周期中,有几种状态呢?在API中java.lang.Thread.State这个枚举中给出了六种线程状态: 这里先列出各个线程状态发生的条件,下面将会对每种状态进行详细解析。线程状态导致状态发生条件NEW(新建)线程刚被创建,但是并未启动。还没调用start方法。Runnable(可运行)线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操作系统处理器。Blocked(锁阻塞)当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。Waiting(无限等待)一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。Timed Waiting(计时等待)同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep 、Object.wait。Terminated(被终止)因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。或者调用过时方法stop()
1年前
406
1
0
JAVA 的多线程编程
java的多线程编程下图展示了 java 线程的生命周期当我们只调用 run()方法时,线程并不会被启动,只会执行 run();只有当调用 start()方法时,线程才会被启动,并且调用 run()方法;我们常用的 public static void main(String[] args) main 其实是 java 中的主线程。java创建线程的三种方式通过实现 Runnable 接口 创建一个线程,最简单的方法是创建一个实现 Runnable 接口的类。为了实现 Runnable,一个类只需要执行一个方法调用 run(),声明如下:public void run()你可以重写该方法,重要的是理解的 run() 可以调用其他方法,使用其他类,并声明变量,就像主线程一样。在创建一个实现 Runnable 接口的类之后,你可以在类中实例化一个线程对象。Thread 定义了几个构造方法,下面的这个是我们经常使用的: 教程参考菜鸟教程 Thread(Runnable threadOb,String threadName);这里,threadOb 是一个实现 Runnable 接口的类的实例,并且 threadName 指定新线程的名字。新线程创建之后,你调用它的 start() 方法它才会运行。 通过继承 Thread 类本身创建一个线程的第二种方法是创建一个新的类,该类继承 Thread 类,然后创建一个该类的实例。继承类必须重写 run() 方法,该方法是新线程的入口点。它也必须调用 start() 方法才能执行。该方法尽管被列为一种多线程实现方式,但是本质上也是实现了 Runnable 接口的一个实例。class ThreadDemo extends Thread { private Thread t; private String threadName; ThreadDemo( String name) { threadName = name; System.out.println("Creating " + threadName ); } public void run() { System.out.println("Running " + threadName ); try { for(int i = 4; i > 0; i--) { System.out.println("Thread: " + threadName + ", " + i); // 让线程睡眠一会 Thread.sleep(50); } }catch (InterruptedException e) { System.out.println("Thread " + threadName + " interrupted."); } System.out.println("Thread " + threadName + " exiting."); } public void start () { System.out.println("Starting " + threadName ); if (t == null) { t = new Thread (this, threadName); t.start (); } } } public class TestThread { public static void main(String args[]) { ThreadDemo T1 = new ThreadDemo( "Thread-1"); T1.start(); ThreadDemo T2 = new ThreadDemo( "Thread-2"); T2.start(); } }下表列出了Thread类的一些重要方法:序号方法描述1.使该线程开始执行;Java 虚拟机调用该线程的 run 方法public void start();2.如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回public void run();3.改变线程名称,使之与参数 name 相同。public final void setName(String name);4.更改线程的优先级。public final void setPriority(int priority);5.将该线程标记为守护线程或用户线程。public final void setDaemon(boolean on);6.等待该线程终止的时间最长为 millis 毫秒。public final void join(long millisec);7.中断线程。public void interrupt();8.测试线程是否处于活动状态。public final boolean isAlive();上述方法是被 Thread 对象调用的,下面表格的方法是 Thread 类的静态方法。序号方法描述1.暂停当前正在执行的线程对象,并执行其他线程。public static void yield()2.在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。public static void sleep(long millisec)3.当且仅当当前线程在指定的对象上保持监视器锁时,才返回 true。public static boolean holdsLock(Object x)4.返回对当前正在执行的线程对象的引用。public static Thread currentThread()5.将当前线程的堆栈跟踪打印至标准错误流。public static void dumpStack()方法演示// 文件名 : DisplayMessage.java // 通过实现 Runnable 接口创建线程 public class DisplayMessage implements Runnable { private String message; public DisplayMessage(String message) { this.message = message; } public void run() { while(true) { System.out.println(message); } } }// 文件名 : GuessANumber.java // 通过继承 Thread 类创建线程 public class GuessANumber extends Thread { private int number; public GuessANumber(int number) { this.number = number; } public void run() { int counter = 0; int guess = 0; do { guess = (int) (Math.random() * 100 + 1); System.out.println(this.getName() + " guesses " + guess); counter++; } while(guess != number); System.out.println("** Correct!" + this.getName() + "in" + counter + "guesses.**"); } }// 文件名 : ThreadClassDemo.java public class ThreadClassDemo { public static void main(String [] args) { Runnable hello = new DisplayMessage("Hello"); Thread thread1 = new Thread(hello); thread1.setDaemon(true); thread1.setName("hello"); System.out.println("Starting hello thread..."); thread1.start(); Runnable bye = new DisplayMessage("Goodbye"); Thread thread2 = new Thread(bye); thread2.setPriority(Thread.MIN_PRIORITY); thread2.setDaemon(true); System.out.println("Starting goodbye thread..."); thread2.start(); System.out.println("Starting thread3..."); Thread thread3 = new GuessANumber(27); thread3.start(); try { thread3.join(); }catch(InterruptedException e) { System.out.println("Thread interrupted."); } System.out.println("Starting thread4..."); Thread thread4 = new GuessANumber(75); thread4.start(); System.out.println("main() is ending..."); } }通过 Callable 和 Future 创建线程创建 Callable 接口的实现类,并实现 call() 方法,该 call() 方法将作为线程执行体,并且有返回值。创建 Callable 实现类的实例,使用 FutureTask 类来包装 Callable 对象,该 FutureTask 对象封装了该 Callable 对象的 call() 方法的返回值。使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程。调用 FutureTask 对象的 get() 方法来获得子线程执行结束后的返回值。public class CallableThreadTest implements Callable<Integer> { public static void main(String[] args) { CallableThreadTest ctt = new CallableThreadTest(); FutureTask<Integer> ft = new FutureTask<>(ctt); for(int i = 0;i < 100;i++) { System.out.println(Thread.currentThread().getName()+" 的循环变量i的值"+i); if(i==20) { new Thread(ft,"有返回值的线程").start(); } } try { System.out.println("子线程的返回值:"+ft.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } @Override public Integer call() throws Exception { int i = 0; for(;i<100;i++) { System.out.println(Thread.currentThread().getName()+" "+i); } return i; } }
1年前
276
0
1
Charlotte
54
文章数
17
标签数
50
评论量
动漫分享---《想要成为影之实力者》
使用Spring MVC进行RESTful开发
Java抽象类、抽象方法与接口深度探讨
人生倒计时
热门文章
1
动漫分享---《想要成为影之实力者》
1417 阅读 - 10/11
2
在unity中使用c#语言实现人物的转身
1396 阅读 - 10/13
3
可恶的日本军国主义!我们一定不能遗忘这段历史,遗忘历史就意味着背叛!!!!
1330 阅读 - 10/17
标签云
java
追番~~
计网
c#
unity
脆皮大学生
spring AOP
博客更新
selenium
web测试
springboot
ES6
vue3
elementPlus
idea
舔狗日记