-
Java 虚拟线程(Virtual Threads)是 Java 19 引入的一项重大特性(预览版,Java 21 正式发布),旨在简化高并发编程。它与普通线程(也称为平台线程,Platform Threads)在实现机制、资源占用和使用场景上有显著区别。以下是两者的详细对比:一、核心区别维度虚拟线程(Virtual Threads)普通线程(Platform Threads)实现机制由 JVM 管理,用户态线程(User-Mode Threads),依赖操作系统线程池调度。直接映射到操作系统线程(OS Threads),由内核调度。资源占用每个虚拟线程仅占用少量内存(KB 级别),可创建数百万甚至更多。每个普通线程占用大量内存(MB 级别,默认栈大小通常为 1MB),数量受限(通常数千)。调度方式协作式调度(Cooperative Scheduling),由 JVM 调度器在阻塞操作时挂起/恢复。抢占式调度(Preemptive Scheduling),由操作系统内核调度。阻塞行为阻塞时释放底层操作系统线程,避免线程闲置。阻塞时操作系统线程被占用,无法执行其他任务。使用场景高并发 I/O 密集型任务(如 Web 服务器、微服务)。CPU 密集型任务或需要直接操作系统资源的场景。二、深入对比1. 资源占用与并发能力普通线程:每个线程需要独立的操作系统线程,占用大量内存(如栈空间、线程局部存储等)。线程数量受限于操作系统和硬件(通常数千个线程会导致性能下降)。虚拟线程:每个虚拟线程仅占用少量内存(栈大小可通过 -XX:VirtualThreadStackSize 配置,默认 1MB,但可动态压缩)。可轻松创建数百万个虚拟线程,适合高并发场景。2. 调度与阻塞行为普通线程:阻塞操作(如 I/O、网络请求)会导致操作系统线程被占用,无法执行其他任务。线程池(如 ThreadPoolExecutor)通过复用线程缓解此问题,但需手动管理线程池大小。虚拟线程:阻塞时自动释放底层操作系统线程,允许其他虚拟线程复用。阻塞操作完成后,JVM 调度器恢复虚拟线程的执行。3. 性能与吞吐量普通线程:高并发时,线程切换开销大,可能导致 CPU 资源浪费(如线程等待 I/O 时仍占用 CPU)。虚拟线程:减少线程切换开销,提高吞吐量(尤其在 I/O 密集型任务中)。示例:一个普通线程池可能因线程阻塞导致吞吐量下降,而虚拟线程可充分利用 CPU。4. 编程模型普通线程:需显式管理线程生命周期(如 start()、join())。异步编程(如 CompletableFuture)需回调或协程,代码复杂。虚拟线程:保持同步编程模型(如 try (var executor = Executors.newVirtualThreadPerTaskExecutor()))。无需异步回调,代码更简洁。三、代码示例1. 普通线程示例// 使用线程池管理普通线程 ExecutorService executor = Executors.newFixedThreadPool(10); // 线程数量受限 for (int i = 0; i < 1000; i++) { executor.submit(() -> { try { Thread.sleep(1000); // 阻塞操作 } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }); } executor.shutdown(); 问题:线程池大小固定,高并发时需排队或拒绝任务。2. 虚拟线程示例// 使用虚拟线程执行器(Java 21+) try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { for (int i = 0; i < 1_000_000; i++) { // 轻松创建百万级线程 executor.submit(() -> { try { Thread.sleep(1000); // 阻塞时释放底层线程 } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }); } } // 自动关闭执行器 优势:无需管理线程池大小,阻塞时自动释放资源。四、适用场景虚拟线程:高并发 I/O 密集型任务(如 Web 服务器、微服务、爬虫)。需要简化异步编程的场景。普通线程:CPU 密集型任务(如科学计算、图像处理)。需要直接操作系统资源(如文件锁、硬件设备)。五、总结特性虚拟线程普通线程资源占用低(KB 级别)高(MB 级别)并发能力高(数百万级)低(数千级)调度方式协作式(JVM 调度)抢占式(内核调度)阻塞行为释放底层线程占用线程编程模型同步(类似单线程)显式线程管理或异步回调适用场景I/O 密集型CPU 密集型或直接操作系统资源六、为什么选择虚拟线程?简化高并发编程:无需异步回调,代码更简洁。资源高效利用:减少线程切换开销,提高吞吐量。弹性扩展:轻松应对突发流量(如 Web 请求)。虚拟线程是 Java 对高并发编程的重大改进,尤其适合现代云原生和微服务架构。然而,对于 CPU 密集型任务,普通线程仍是更优选择。
-
显示文件catalina.sh 必须可执行
-
我使用vue直接前端调用华为云obs上传文件服务传参,在文件管理里面我们需要用到版本控制的功能,需要拿到当前文件的versionId参数,但是我看到了BrowserJS语法没有返回这个参数,其他的都有,这是怎么回事,什么时候能更新一下返回值,或者有什么其他解决办法?
-
smartassist.additionalSettings这个有什么属性可以自己设置的。
-
推送镜像到SWR失败cid:link_0构建任务的日志链接可以对外提供吗?cid:link_1我想问下华为云的这个CodeArts支持CICD吗cid:link_2 CodeArts Snap插件支持哪些客户端和语言cid:link_3CodeArts看板项目如何按照自定义字段进行检索cid:link_4代码托管中组的访问级别如何修改cid:link_5麒麟V10X86架构安装ambari-2.7.5后,利用ambari构建大数据平台报错RuntimeError: Failed to execute command '/usr/bin/yum -y install hadoo cid:link_6麒麟V10安装ambari-server-2.7.5执行ambari-server setup报错Unexpected error Ambari repo file path not set for current OScid:link_7
-
在项目配置中,有个别场景需要通过nacos配置中心来维护一些项目中非spring上下文中的配置,比如:第三方特殊配置、一些非标准化的配置,想通过nacos来实现灵活管理与实时更新。这种需求场景下,我们可以通过Nacos中提供的两个注解来非常简单的实现我们需求。@NacosConfig:需要声明在,由spring管理的bean中,比如:bean的属性上,或者bean的类上。当应用启动时,会将声明了该注解的属性或类,进行赋值。@NacosConfigListener:需要声明在,由spring管理的bean中。作用于Bean的方法上,当Nacos中的配置发生变化时,会以方法入参形式将最新配置内容传入,且支持基本数据类型、对象、泛型类。版本要求:2023.x 系列需升级版本至2023.0.3.22022.x 系列需升级版本至2022.0.0.22021.x 系列需升级版本至2021.0.6.22.2.x 系列需升级至2.2.11@NacosConfig注解用法介绍此注解可作用于bean属性上和类上,前提是需要声明由spring进行管理。支持多种数据类型:基本类型、List、Map等List集合,接收JSON格式配置@Componentpublic class NacosConfigData { @NacosConfig(dataId = "list_demo.json",group = "default_group") private List<MyDemo> myDemoList;}Map泛型,接收JSON格式配置@Componentpublic class NacosConfigData { @NacosConfig(dataId = "map_demo.json",group = "default_group") private Map<Long,MyDemo> myDemoMap;}@NacosConfigListener 注解用法介绍此注解主要作用于方法上,在方法上进行声明,当配置发生变化时,会触发声明了此注解的方法,将最新的配置内容以入参方式传入。自定义bean 接收最新配置@Componentpublic class NacosConfigData { @NacosConfig(dataId = "list_demo.json",group = "default_group") private List<MyDemo> myDemoList; @NacosConfigListener(dataId = "list_demo.json",groupId = "default_group") private void myDemoListChanged(List<MyDemo> myDemoList){ this.myDemoList = myDemoList; } @NacosConfig(dataId = "map_demo.json",group = "default_group") private Map<Long,MyDemo> myDemoMap; @NacosConfigListener(dataId = "map_demo.json",groupId = "default_group") private void myDemoMapChanged(List<MyDemo> myDemoList){ this.myDemoList = myDemoList; }}更为详实介绍,可以查看,参考来源:https://sca.aliyun.com/blog/sca-gvr7dx_awbbpb_xr9f0v45pxz9ubnu/?spm=5176.29160081.0.0.74805c721Hvyc4&source=blog/
-
时间复杂度O是表示算法运行时间与输入数据规模(通常用 n 表示)之间的关系。算法执行时间随输入数据规模增长的变化趋势。1、O(1) — 常数时间无论输入数据多大,执行时间固定不变。典型场景:数组按索引访问、哈希表查询。2、O(log n) — 对数时间执行时间随数据量增长,但增速远慢于线性增长。典型场景:二分查找、平衡二叉搜索树操作。3、O(n) — 线性时间执行时间与数据量成正比。典型场景:遍历数组/链表、线性搜索。4、O(n log n) — 线性对数时间比线性慢,但比平方快,常见于高效排序算法。典型场景:快速排序、归并排序、堆排序。5、O(n²) — 平方时间执行时间与数据量的平方成正比,数据量大时性能急剧下降。典型场景:冒泡排序、选择排序、暴力搜索(如两数之和的暴力解法)。6、O(2ⁿ) — 指数时间执行时间呈指数级增长,仅适用于极小规模数据典型场景:暴力穷举、未优化的递归(如斐波那契数列原始递归)。7、O(n!) — 阶乘时间最慢的时间复杂度,通常用于全排列问题。典型场景:全排列生成。排序从最优到最差:O(1) < O(log n) < O(n) < O(n log n) < O(n^2) < O(2^n) < O(n!)。空间复杂度的表示与时间复杂度的表示基本一致。时间复杂度关注的是运行时间,空间复杂度关注的是内存消耗。现在内存比以前便宜,大家更追求时间的优化了。转载自https://www.cnblogs.com/yanshajiuzhou/p/18829902
-
@ModelAttribute@PostMapping("/register")public String register(@ModelAttribute User user) { // 表单数据将自动绑定到 User 对象中 return "userInfo";}@RequestBody@PostMapping("/api/user")public ResponseEntity<?> saveUser(@RequestBody User user) { // JSON 请求体 {"name":"Tom","age":20} return ResponseEntity.ok(user);}@RequestParam@GetMapping("/search")public String search(@RequestParam String keyword) { // /search?keyword=java return keyword;}@PathVariable@GetMapping("/user/{id}")public String getUser(@PathVariable Long id) { return "ID: " + id;}注意,@RequestBody 需要使用 HttpMessageConverter(如 Jackson、FastJson)支持 JSON 解析。转载自https://www.cnblogs.com/yanshajiuzhou/p/18903319
-
一、创建线程池四种方式使用 Executors 类,Executors 类是 Java 中用于创建线程池的工厂类,它提供了多种静态方法来创建不同类型的线程池使用 ThreadPoolExecutor 类,ThreadPoolExecutor 是 Java 中线程池的一个核心类,它提供了更细粒度的控制来创建和管理线程池使用 Future 和 Callable,Future 和 Callable 是并发编程中非常重要的两个接口,它们通常与 ExecutorService 一起使用来执行异步任务。使用 Spring 的 ThreadPooltaskExecutor,ThreadPoolTaskExecutor 是一个基于 java.util.concurrent.ThreadPoolExecutor 的扩展,提供了更丰富的配置选项和与Spring集成的特性二、线程池重要参数corePoolSize (int): 线程池的基本大小,即在没有任务执行时线程池的大小。当新任务提交时,线程池会优先使用已有的空闲线程。maximumPoolSize (int): 线程池能够容纳同时执行的最大线程数。这个参数用于控制线程池的最大规模,防止因任务过多而导致资源耗尽。keepAliveTime (long): 当线程池中的线程数量超过 corePoolSize 时,多余的空闲线程能等待新任务的最长时间。超过这个时间后,多余的线程将被终止。unit (TimeUnit): keepAliveTime 参数的时间单位,常见的时间单位有 TimeUnit.SECONDS、TimeUnit.MINUTES 等。workQueue (BlockingQueue): 一个阻塞队列,用于存储等待执行的任务。常用的阻塞队列有 LinkedBlockingQueue、ArrayBlockingQueue 和 SynchronousQueue 等。threadFactory (ThreadFactory): 用于创建新线程的工厂。可以通过实现 ThreadFactory 接口来自定义线程的创建过程。handler (RejectedExecutionHandler): 当任务太多而线程池无法处理时,用于定义拒绝任务的策略。常见的拒绝策略有 ThreadPoolExecutor.AbortPolicy、ThreadPoolExecutor.CallerRunsPolicy 和 ThreadPoolExecutor.DiscardPolicy 等。package com.demo.threadPool;import java.util.concurrent.*;public class MainDemo1 { public static void main(String[] args) { int corePoolSize = 5; // 核心线程数 int maximumPoolSize = 10; // 最大线程数 long keepAliveTime = 1; // 非核心线程空闲存活时间 /** * 存活时间单位 * TimeUnit.DAYS:天 * TimeUnit.HOURS:小时 * TimeUnit.MINUTES:分 * TimeUnit.SECONDS:秒 * TimeUnit.MILLISECONDS:毫秒 * TimeUnit.MICROSECONDS:微妙 * TimeUnit.NANOSECONDS:纳秒 */ TimeUnit unit = TimeUnit.MINUTES; BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<Runnable>(); // 工作队列 ThreadFactory threadFactory = Executors.defaultThreadFactory(); // 线程工厂 RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy(); // 拒绝策略 ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize,maximumPoolSize,keepAliveTime,unit,workQueue,threadFactory,handler); }}三、线程池5种状态RUNNING:正常运行状态,可接收新任务,可处理阻塞队列中的任务SHUTDOWN:不会接收新任务,但会处理阻塞队列剩余任务STOP:会中断正在执行的任务,并抛弃阻塞队列任务TIDYING:任务全执行完毕,活动线程为 0,即将进入终结TERMINATED:终结状态四、Executors 类创建线程池new newCachedThreadPool():创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。线程池的规模不存在限制。(数量不固定的线程池)new newFixedThreadPool():创建一个固定长度线程池,可控制线程最大并发数,超出的线程会在队列中等待。(固定数量的线程池)new newScheduledThreadPool():创建一个固定长度线程池,支持定时及周期性任务执行。(定时线程池)new newSingleThreadExecutor():创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。(单线程的线程池)固定线程池创建 ( Executors.newFixedThreadPool(5) ):创建一个固定大小的线程池。线程池中的线程数量是固定的,即使有些线程处于空闲状态,它们也不会被回收。package com.demo.threadPool;import java.util.List;import java.util.concurrent.*;public class MainThreadPool { public static void main(String[] args) throws ExecutionException, InterruptedException { //初始化固定大小线程池 ExecutorService executor1 = Executors.newFixedThreadPool(5); //使用 execute(Runnable command) 方法提交一个不需要返回结果的任务, // 或者使用submit(Callable<T> task) 方法提交一个需要返回结果的任务。 for (int i = 0; i < 10; i++) { executor1.execute(new TaskR(i)); } //使用 submit(Callable<T> task) 任务并获取 Future //使用 Future.get() 方法等待任务完成并获取结果。这个方法会阻塞调用线程直到任务完成。 for (int i = 0; i < 10; i++) { Future<String> future = executor1.submit(new TaskC(i)); System.out.println("线程返回结果 "+future.get()); } // 当所有任务都执行完毕,或者需要关闭线程池时,调用 shutdown() 方法。 // 这将等待正在执行的任务完成,但不接收新任务。 executor1.shutdown(); //使用 shutdownNow() 方法尝试立即停止所有正在执行的任务,并返回等待执行的任务列表 List<Runnable> notExecutedTasks = executor1.shutdownNow(); for(Runnable ls : notExecutedTasks){ System.out.println(ls); } //使用 awaitTermination() 方法等待线程池关闭,直到所有任务完成或超时。 boolean res = executor1.awaitTermination(60, TimeUnit.SECONDS); System.out.println("执行结果:"+res); }}/** * 实现 Runnable 接口 */class TaskR implements Runnable { private int id; public TaskR(int id) { this.id = id; } public void run() { System.out.println("TaskR " + id + " is running..."); }}/** * 实现 Callable 接口 * 有返回值 */class TaskC implements Callable { private int id; public TaskC(int id) { this.id = id; } @Override public Object call(){ System.out.println("TaskC " + id + " is running..."); return id+"--TaskC"; }}单线程池 (newSingleThreadExecutor):创建一个只有一个线程的线程池。即使有多个任务提交,它们也会被排队,逐个由单个线程执行package com.demo.threadPool;import java.util.concurrent.ExecutionException;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.Future;/** * 单线程池 (newSingleThreadExecutor): * 创建一个只有一个线程的线程池。即使有多个任务提交,它们也会被排队,逐个由单个线程执行。 */public class MainOne { public static void main(String[] args) throws ExecutionException, InterruptedException { /** * 单线程:创建的执行服务内部有一个线程。所有提交给它的任务将会序列化执行,也就是说,它会在单个线程上依次执行任务,不会有并发执行的情况发生 * 任务队列:如果有多个任务提交给这个执行器,除了当前正在执行的任务外,其他任务将会在一个无界队列中等待,直到线程可用 * 处理任务失败:如果执行中的线程由于任务抛出异常而终止,执行服务会安排一个新的线程来替换它,以继续执行后续的任务 * 使用场景: newSingleThreadExecutor 非常适合需要顺序执行的任务,并且要求任务之间不受并发问题影响的场景 */ ExecutorService executor = Executors.newSingleThreadExecutor(); for (int i = 0; i < 10; i++) { executor.execute(new TaskR(i)); } //使用 submit(Callable<T> task) 任务并获取 Future //使用 Future.get() 方法等待任务完成并获取结果。这个方法会阻塞调用线程直到任务完成。 for (int i = 0; i < 10; i++) { Future<String> future = executor.submit(new TaskC(i)); System.out.println("线程返回结果 "+future.get()); } // 当所有任务都执行完毕,或者需要关闭线程池时,调用 shutdown() 方法。 // 这将等待正在执行的任务完成,但不接收新任务。 executor.shutdown(); }}缓存线程池 (newCachedThreadPool):创建一个可根据需要创建新线程的线程池。如果线程空闲超过60秒,它们将被终止并从池中移除package com.demo.threadPool;import java.util.Date;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;/** * 缓存线程池 (newCachedThreadPool): * 创建一个可根据需要创建新线程的线程池。如果线程空闲超过60秒,它们将被终止并从池中移除 */public class MainCacheThreadPool { public static void main(String[] args) throws InterruptedException { System.out.println(Thread.currentThread().getName() + "线程: Start at: " + new Date()); //初始化缓存线程池 ExecutorService exec = Executors.newCachedThreadPool(); for (int i = 1; i < 10; i++) { System.out.println("添加了第" + i + "个任务类"); Thread.sleep(2000); exec.execute(new TaskR(i)); } //所有任务结束后关闭线程池 exec.shutdown(); System.out.println(Thread.currentThread().getName() + " 线程: Finished all threads at:" + new Date()); }}调度线程池 (newScheduledThreadPool):创建一个支持定时任务和周期性任务的线程池package com.demo.threadPool;import java.util.Date;import java.util.concurrent.Executors;import java.util.concurrent.ScheduledExecutorService;import java.util.concurrent.TimeUnit;/** * 固定频率执行 * 调度线程池 (newScheduledThreadPool): * 创建一个支持定时任务和周期性任务的线程池 */public class MainScheduledThreadPool { public static void main(String[] args) { /** * 场景描述 * 假设你需要一个应用程序,该程序能够每10秒执行一次任务,并在启动后1分钟开始执行。此外, * 你还需要能够安排一次性任务在未来的某个时间点执行 */ ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(10); // 安排定期任务 // 初始延迟1分钟,之后每10秒执行一次 threadPool.scheduleAtFixedRate(new TaskR(2), 60, 10, TimeUnit.SECONDS); // 安排一次性任务 // 使用 schedule 方法安排一个任务,在指定的延迟后执行一次 // 延迟5分钟后执行 threadPool.schedule(new TaskR(3), 5, TimeUnit.MINUTES); // 关闭线程池 // 当不再需要线程池时,调用 shutdown 方法来关闭线程池。这将等待正在执行的任务完成,但不接收新任务 threadPool.shutdown(); // 等待线程池关闭 // 使用 awaitTermination 方法等待线程池关闭,直到所有任务完成或超时。 try { threadPool.awaitTermination(1, TimeUnit.HOURS); } catch (InterruptedException e) { e.printStackTrace(); } }}使用给定的线程工厂创建线程池:可以提供一个自定义的 ThreadFactory 来创建线程池中的线程package com.demo.threadPool;import java.util.concurrent.*;/** * 使用给定的线程工厂创建线程池 */public class MainFactory { public static void main(String[] args) { //自定义线程工厂创建 ThreadFactory threadFactory = new ThreadFactory() { @Override public Thread newThread(Runnable r) { return new Thread(r); } }; //使用给定的线程工厂创建线程池 ExecutorService executor = Executors.newFixedThreadPool(5, threadFactory); executor.execute(new TaskR(2)); }}自定义线程工厂创建:自定义线程工厂可以设置自己的线程名,设置守护线程,设置线程优先级,处理未捕获的异常等package com.demo.threadPool;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.ThreadFactory;import java.util.concurrent.atomic.AtomicInteger;/** * 自定义线程工厂:设置线程名,守护线程,优先级以及UncaughtExceptionHandler */public class MainFactory implements ThreadFactory { private final ThreadGroup group; private final AtomicInteger threadNumber = new AtomicInteger(1); private final String namePrefix; public MainFactory(String namePrefix) { SecurityManager s = System.getSecurityManager(); group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup(); this.namePrefix = namePrefix + "-thread-"; } public MainFactory(ThreadGroup group, String namePrefix) { this.group = group; this.namePrefix = namePrefix; } @Override public Thread newThread(Runnable r) { Thread t = new Thread(group, r,namePrefix + threadNumber.getAndIncrement(),0); //守护线程 if (t.isDaemon()) t.setDaemon(true); //线程优先级 if (t.getPriority() != Thread.NORM_PRIORITY) t.setPriority(Thread.NORM_PRIORITY); /** * 处理未捕捉的异常 */ t.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { @Override public void uncaughtException(Thread t, Throwable e) { System.out.println("处理未捕获的异常"); } }); return t; } //测试方法 public static void main(String[] args) { ExecutorService pool = Executors.newFixedThreadPool(5, new MainFactory("测试线程")); for (int i = 0; i < 10; i++) { pool.execute(new Runnable() { @Override public void run() { System.out.println("线程处理"); //未捕获的异常,走自定义的UncaughtExceptionHandler逻辑 int i = 1 / 0; } }); } pool.shutdown(); }}五、ThreadPoolExecutor 类创建线程池ThreadPoolExecutor 是 java.util.concurrent 包中用来创建线程池的一个类。它提供了一种灵活的方式来管理线程池,允许你控制线程的创建和销毁。1ThreadPoolExecutor 类中的几个重要方法execute():向线程池提交一个任务,交由线程池去执行submit():也是向线程池提交任务,但是和execute()方法不同,它能够返回任务执行的结果它实际上还是调用的 execute() 方法,只不过它利用了 Future 来获取任务执行结果invokeAll():提交一个任务集合invokeAny(): 提交一个任务集合,哪个任务先成功执行完毕,返回此任务执行结果,其它任务取消shutdown():关闭线程池,再也不会接受新的任务不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止shutdownNow():关闭线程池,再也不会接受新的任务立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务isShutdown():不在 RUNNING 状态的线程池,此方法就返回 trueisTerminated():线程池状态是否是 TERMINATEDpackage com.demo.threadPool;import java.util.Random;import java.util.concurrent.*;/** * ThreadPoolExecutor 是 java.util.concurrent 包中用来创建线程池的一个类 * 它提供了一种灵活的方式来管理线程池,允许你控制线程的创建和销毁。 * 以下是几种常见的创建 ThreadPoolExecutor 线程池的方式 * 实际上 Executors 类也是调用 ThreadPoolExecutor 类创建的线程 */public class MainThreadPoolExecutor { //测试方法 public static void main(String[] args) { /** * 核心线程数,核心线程就是一直存在的线程 */ int corePoolSize = 5; /** * 最大线程数,表示线程池中最多能创建多少个线程 * 非核心线程数 = 最大线程数 - 核心线程数 */ int maximumPoolSize = 10; /** * 默认情况下,只有当线程池中的线程数大于corePoolSize时, * keepAliveTime才会起作用,则会终止,直到线程池中的线程数不超过corePoolSize * 则会终止,直到线程池中的线程数不超过corePoolSize * 但是如果调用了 allowCoreThreadTimeOut(boolean) 方法 * 在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为 0 * 针对非核心线程而言,表示线程没有任务执行时最多保持多久时间会终止 */ long keepAliveTime = 60; /** * 时间单位 * 与 keepAliveTime 配合使用,针对非核心线程 */ TimeUnit unit = TimeUnit.SECONDS; /** * 存放任务的阻塞队列 */ BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(5); /** * 创建线程的工厂,可以为线程创建时起个好名字 */ ThreadFactory threadFactory = new ThreadFactory() { @Override public Thread newThread(Runnable r) { return new Thread(r); } }; /** * 拒绝策略 * 任务太多的时候会进行拒绝操作 * 核心线程,非核心线程,任务队列都放不下时 */ // 自定义拒绝策略 RejectedExecutionHandler defaultHandler1 = new MyRejectedExecutionHandler(); // 默认策略,在需要拒绝任务时抛出RejectedExecutionException RejectedExecutionHandler defaultHandler3 = new ThreadPoolExecutor.AbortPolicy(); // 直接在 execute 方法的调用线程中运行被拒绝的任务,如果线程池已经关闭,任务将被丢弃; RejectedExecutionHandler defaultHandler2 = new ThreadPoolExecutor.CallerRunsPolicy(); // 直接丢弃任务 RejectedExecutionHandler defaultHandler4 = new ThreadPoolExecutor.DiscardPolicy(); // 丢弃队列中等待时间最长的任务,并执行当前提交的任务,如果线程池已经关闭,任务将被丢弃 RejectedExecutionHandler defaultHandler5 = new ThreadPoolExecutor.DiscardOldestPolicy(); /** * 创建线程池 */ ExecutorService service1 = new ThreadPoolExecutor( corePoolSize, maximumPoolSize,keepAliveTime, unit,workQueue,threadFactory,defaultHandler1); for (int i = 0; i < 10; i++) { System.out.println("添加第"+i+"个任务"); service1.execute(new MyThread("线程"+i)); } service1.shutdown(); }}/** * 自定义拒绝策略 */class MyRejectedExecutionHandler implements RejectedExecutionHandler { @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { new Thread(r,"新线程"+new Random().nextInt(10)).start(); }}/** * 线程类 */class MyThread implements Runnable { String name; public MyThread(String name) { this.name = name; } @Override public void run() { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("线程:"+Thread.currentThread().getName() +" 执行:"+name +" run"); }}六、Future 和 Callable 类使用创建线程池Callable 是一个函数式接口,它允许你定义一个任务,该任务可以返回一个结果并抛出异常。它是 Runnable 接口的扩展,增加了返回值和抛出异常的能力。返回值:与 Runnable 接口不同,Callable 任务可以返回一个值,返回值通过 Future 对象获取。异常:Callable 任务可以抛出异常,这些异常可以通过 Future 对象处理。Future 接口代表异步计算的结果。它提供了检查计算是否完成的方法,以及获取计算结果的方法。get():获取计算结果。如果计算尚未完成,此方法会阻塞,直到计算完成或抛出异常。isDone():检查计算是否完成。cancel():尝试取消任务。isCancelled():检查任务是否被取消package com.demo.threadPool;import java.util.concurrent.*;/** * Future 使用 */public class MainFuture { public static void main(String[] args) throws ExecutionException, InterruptedException { ExecutorService executorService = Executors.newFixedThreadPool(1); System.out.println("开始时间戳为:" + System.currentTimeMillis()); Future<String> future = executorService.submit(new Test01()); String result = future.get(); //获取计算结果。如果计算尚未完成,此方法会阻塞,直到计算完成或抛出异常 boolean isdone = future.isDone(); //检查计算是否完成 boolean cancel = future.cancel(true); //尝试取消任务 boolean iscancelled = future.isCancelled(); //检查任务是否被取消 System.out.println("result:"+result); System.out.println("isdone:"+isdone); System.out.println("cancel:"+cancel); System.out.println("iscancelled:"+iscancelled); System.out.println("结束时间戳为:" + System.currentTimeMillis()); executorService.shutdown(); }}class Test01 implements Callable { @Override public Object call() throws Exception { return "你好"; }}七、Spring 的 ThreadPoolTaskExecutor 类创建线程池ThreadPoolTaskExecutor 是 Spring 框架提供的一个线程池实现,它扩展了 Java 的 ThreadPoolExecutor 并提供了一些额外的配置和功能添加依赖: 如果你的项目是一个 Maven 项目,确保你的 pom.xml 文件中包含了 Spring Boot 的依赖配置线程池: 在 Spring Boot 应用程序中,你可以通过 Java 配置类来配置 ThreadPoolTaskExecutorpackage com.cnpc.epai.assetcatalog.dmp.controller;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;import java.util.concurrent.ThreadPoolExecutor;/** * 线程池配置类 */@Configurationpublic class ConfigPoolConfiguration { @Bean("TaskExecutorDemo") public ThreadPoolTaskExecutor taskExecutorDemo(){ ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor(); threadPoolTaskExecutor.setCorePoolSize(10); // 核心线程数 threadPoolTaskExecutor.setMaxPoolSize(20);// 最大线程数 threadPoolTaskExecutor.setQueueCapacity(100); //工作队列 threadPoolTaskExecutor.setKeepAliveSeconds(60); // 非核心线程的空闲存活时间 threadPoolTaskExecutor.setAllowCoreThreadTimeOut(true);//指定是否允许核心线程超时。这允许动态增长和收缩,即使与非零队列结合使用也是如此(因为最大池大小只有在队列已满时才会增长) threadPoolTaskExecutor.setThreadNamePrefix("monitor-thread-pool-");// 设置线程名前缀 threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());// 拒绝策略 threadPoolTaskExecutor.setWaitForTasksToCompleteOnShutdown(true);// 设置线程池关闭时需要等待子任务执行完毕,才销毁对应的bean threadPoolTaskExecutor.initialize();//初始化线程池 return threadPoolTaskExecutor; }}测试类package com.cnpc.epai.assetcatalog.dmp.controller;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.scheduling.annotation.Async;import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;import org.springframework.stereotype.Service;@Servicepublic class TestService { @Autowired private ThreadPoolTaskExecutor taskExecutor; @Async("taskExecutor") public void executeTask() { taskExecutor.execute(() -> { System.out.println("Executing task in thread: " + Thread.currentThread().getName()); }); }}———————————————— 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 原文链接:https://blog.csdn.net/weixin_39865508/article/details/140520236
-
一、 数组到底是个啥?简单来说,数组就是一个固定大小的容器 ,里面存放的元素必须是相同的数据类型。几个关键特性要记住:类型统一:一个 int 数组里只能放 int,一个 String 数组里只能放 String。不能混装!长度固定 : 数组一旦创建,它的长度(能装多少个元素)就不能再改变了!这是数组最核心的限制之一 。连续存储 (通常): 在内存中,数组的元素通常是连续存放的,这使得通过索引访问元素非常快 。索引访问 : 每个元素都有一个唯一的索引(编号),从 0 开始!通过索引可以快速定位和访问元素。二、 创建和使用数组 怎么在代码里用数组呢?主要分两步:声明和初始化。2.1 声明数组引用告诉编译器:“我要一个能指向某种类型数组的变量”。// 推荐的声明方式int[] scores;String[] names;double[] prices;// C/C++ 风格的声明方式 (也能用,但不推荐)// int scores[];// String names[];注意:这只是声明了一个引用变量,它现在还是 null,并没有指向任何实际的数组内存空间。2.2 初始化数组(分配空间/赋值)初始化才是真正创建数组对象、分配内存空间的时候。有两种主要方式:方式一:使用 new 指定长度这是最常用的方式,你知道需要多大的数组,但还不确定里面的具体值。// 创建一个能存放 5 个 int 的数组scores = new int[5]; // 分配了 5 个 int 的空间// 创建一个能存放 10 个 String 的数组names = new String[10]; // 分配了 10 个 String 引用的空间// 声明和初始化可以合并double[] salaries = new double[50];重点: 使用 new 创建数组后,里面的元素会被自动赋予默认值:数值类型 (int, double etc.): 0 或 0.0boolean: falsechar: \u0000 (空字符)引用类型 (String, Object etc.): null方式二:静态初始化 (直接提供元素值)如果你在创建数组时就已经知道里面要放哪些元素了,可以用这种更简洁的方式。编译器会根据你提供的值自动确定数组的长度。// 直接提供初始值,长度由编译器确定 (这里是 4)int[] initialScores = {90, 85, 92, 78};// 声明和静态初始化合并String[] weekdays = {"Monday", "Tuesday", "Wednesday", "Thursday", "Friday"};// 不能分开写!下面这样是错误的 // int[] numbers;// numbers = {1, 2, 3}; // 编译错误// 必须这样写:int[] numbers;numbers = new int[]{1, 2, 3}; // 或者声明时就初始化2.3 访问数组元素:靠索引 [] 数组的核心操作就是通过索引来存取元素。记住:索引从 0 开始! 非常非常重要! public class ArrayAccess { public static void main(String[] args) { String[] fruits = {"Apple <>", "Banana <>", "Orange <>"}; // 长度为 3 // 访问元素 (索引 0, 1, 2) System.out.println("First fruit: " + fruits[0]); // Apple <> (索引 0) System.out.println("Second fruit: " + fruits[1]); // Banana <> (索引 1) System.out.println("Third fruit: " + fruits[2]); // Orange <> (索引 2) // 修改元素 fruits[1] = "Grape <>"; // 把第 2 个元素 (索引 1) 改成 Grape System.out.println("Second fruit now: " + fruits[1]); // Grape <> // 尝试访问不存在的索引?后果很严重! // System.out.println(fruits[3]); // 运行时错误: ArrayIndexOutOfBoundsException }}2.4 获取数组长度:.length 属性 想知道数组能装多少东西?用 .length 属性(注意:是属性,不是方法,后面没有括号 (),这点和 String 的 length() 方法不同,容易混淆!)。int[] data = new int[10];System.out.println("Length of data array: " + data.length); // Output: 10String[] colors = {"Red", "Green", "Blue"};System.out.println("Number of colors: " + colors.length); // Output: 3.length 在循环遍历数组时特别有用。三、 遍历数组:挨个“点名” 遍历数组就是按顺序访问数组中的每一个元素,通常用循环来实现。3.1 使用传统 for 循环最灵活的方式,因为你可以拿到当前的索引 i。public class ForLoopArray { public static void main(String[] args) { double[] readings = {12.5, 13.1, 11.8, 12.9}; System.out.println("Sensor Readings:"); // 循环条件通常是 i < array.length for (int i = 0; i < readings.length; i++) { System.out.println("Reading at index " + i + ": " + readings[i]); } }}3.2 使用增强型 for 循环 (for-each)如果不需要关心索引,只是想依次处理每个元素,这种方式更简洁、易读。import java.util.ArrayList; // 只是为了演示集合遍历import java.util.List;public class ForEachArray { public static void main(String[] args) { char[] vowels = {'a', 'e', 'i', 'o', 'u'}; System.out.print("Vowels: "); // 依次取出 vowels 数组中的每个 char 赋给变量 vowel for (char vowel : vowels) { System.out.print(vowel + " "); } System.out.println(); // 输出: a e i o u // 同样适用于集合 (比如 ArrayList) // List<String> names = new ArrayList<>(); ... // for (String name : names) { ... } }}如何选择? 如果需要索引(比如要根据索引修改元素,或者需要知道当前是第几个元素),用传统 for。如果只是读取每个元素的值,增强型 for 通常更好。四、 数组的“常见烦恼”使用数组时,有几个经典错误你很可能会遇到:ArrayIndexOutOfBoundsException <>: 这是最常见的数组错误!当你试图访问一个不存在的索引时(比如索引 < 0,或者索引 >= array.length),就会抛出这个运行时异常。写循环时要特别小心边界条件!NullPointerException : 如果你的数组引用变量本身是 null(即它没有指向任何数组对象),而你试图访问它的 .length 属性或者通过索引访问元素(如 nullArray[0]),就会得到这个运行时异常。使用数组前,确保它已经被正确初始化了!长度固定不变 <>: 前面强调过,数组长度一旦确定就不能改。如果你的程序需要一个可以动态增删元素的容器,那么 Java 集合框架中的 ArrayList 等类通常是更好的选择。五、 超出基础:多维数组与 Arrays 工具类 ▦5.1 多维数组你可以创建“数组的数组”,最常见的是二维数组,可以把它想象成一个表格或矩阵 。// 创建一个 3行 4列 的 int 二维数组int[][] matrix = new int[3][4];// 静态初始化String[][] board = { {"X", "O", "X"}, {"O", "X", "O"}, {"X", "O", "X"}};// 访问元素需要两个索引matrix[0][1] = 5; // 设置第 1 行第 2 列 (索引都是从 0 开始)System.out.println("Board[1][1]: " + board[1][1]); // Output: X遍历二维数组通常需要嵌套循环。5.2 java.util.Arrays 工具类 <>Java 提供了一个非常有用的 Arrays 类(在 java.util 包下),里面包含了很多操作数组的static方法,能省不少事:Arrays.toString(array): 把数组转换成易于阅读的字符串形式,方便打印调试,强烈推荐!Arrays.sort(array): 对数组进行排序(原地排序)。Arrays.fill(array, value): 用指定值填充整个数组。Arrays.copyOf(originalArray, newLength): 复制原数组的一部分或全部到一个新的数组(可以用来变相“扩展”数组)。Arrays.equals(array1, array2): 比较两个数组的内容是否相等(注意,不是用 == 比地址)。import java.util.Arrays; // <--- 别忘了导入!public class ArraysUtilDemo { public static void main(String[] args) { int[] nums = {5, 2, 8, 1, 9}; // 好看的打印方式 System.out.println("Original array: " + Arrays.toString(nums)); // 排序 Arrays.sort(nums); System.out.println("Sorted array: " + Arrays.toString(nums)); // 填充 int[] filled = new int[5]; Arrays.fill(filled, -1); System.out.println("Filled array: " + Arrays.toString(filled)); // 复制 (取前 3 个元素) int[] copied = Arrays.copyOf(nums, 3); // nums 已经是排序后的 {1, 2, 5, 8, 9} System.out.println("Copied first 3: " + Arrays.toString(copied)); }}六、总结 数组是 Java 中存储固定数量、相同类型元素的基础数据结构。核心特性:类型统一、长度固定、索引访问 (从 0 开始)。创建方式:new Type[size] 或静态初始化 {...}。访问与长度:使用 array[index] 访问,.length 获取长度。遍历:常用 for 或增强 for 循环。常见陷阱 :ArrayIndexOutOfBoundsException, NullPointerException, 长度固定限制。好帮手 <>:java.util.Arrays 类提供了很多实用方法。虽然 ArrayList 等集合类在灵活性上更胜一筹,但数组在性能(尤其是访问速度)和表示固定大小数据集时仍然有其优势,并且是理解集合类的基础。所以,扎实掌握数组非常重要!七、练练手,检验成果!来,动手写写代码,巩固一下!10 个 double 类型数据的数组 prices,并将其所有元素初始化为 9.99 (使用循环或 Arrays.fill)。给定一个 int 数组 scores = {88, 92, 75, 98, 85},计算并打印数组中所有分数的平均值 (注意结果可能是小数)。查找数组 int[] data = {5, -2, 9, 15, -8, 1} 中的最大值,并打印出来。尝试解释为什么数组的索引是从 0 开始而不是从 1 开始?(提示:可以从内存地址和偏移量角度思考,或说明其历史渊源和编程习惯)比较数组 (int[]) 和 ArrayList<Integer> 的主要区别,尤其是在大小和类型方面。1.初始化 prices 数组:import java.util.Arrays;public class InitPrices { public static void main(String[] args) { double[] prices = new double[10]; // 方法一:使用循环 // for (int i = 0; i < prices.length; i++) { // prices[i] = 9.99; // } // 方法二:使用 Arrays.fill (更推荐) Arrays.fill(prices, 9.99); System.out.println("Initialized prices: " + Arrays.toString(prices)); }}2.修改 colors 数组:import java.util.Arrays;public class ModifyColors { public static void main(String[] args) { String[] colors = {"Red", "Green", "Blue", "Yellow"}; System.out.println("Original colors: " + Arrays.toString(colors)); // 第三个元素索引是 2 if (colors.length > 2) { // 做个简单检查防止越界 colors[2] = "Purple <>"; } System.out.println("Modified colors: " + Arrays.toString(colors)); // 输出: Modified colors: [Red, Green, Purple <>, Yellow] }}循环与计算答案 3.计算平均分:import java.util.Arrays;public class AverageScore { public static void main(String[] args) { int[] scores = {88, 92, 75, 98, 85}; if (scores.length == 0) { System.out.println("Array is empty, cannot calculate average."); return; } int sum = 0; for (int score : scores) { sum += score; } // 注意:为了得到精确的小数结果,需要将 sum 或 length 转为 double double average = (double) sum / scores.length; // 或者 double average = sum * 1.0 / scores.length; System.out.println("Scores: " + Arrays.toString(scores)); System.out.println("Average score: " + average); // 输出: Average score: 87.6 }}4.查找最大值:import java.util.Arrays;public class FindMaxValue { public static void main(String[] args) { int[] data = {5, -2, 9, 15, -8, 1}; if (data.length == 0) { System.out.println("Array is empty."); return; } int max = data[0]; // 假设第一个元素是最大值 for (int i = 1; i < data.length; i++) { if (data[i] > max) { max = data[i]; // 如果找到更大的,更新 max } } System.out.println("Data: " + Arrays.toString(data)); System.out.println("Maximum value: " + max); // 输出: Maximum value: 15 }}概念辨析答案 5.为什么索引从 0 开始? 这主要是历史原因和底层实现效率的考虑。在 C 语言(Java 的重要祖先)及更早的语言中,数组名通常代表数组第一个元素在内存中的起始地址。访问数组元素 array[i],实际上是计算 起始地址 + i * 每个元素的大小 来找到第 i+1 个元素的地址。如果索引从 0 开始,那么访问第一个元素 array[0] 就是 起始地址 + 0 * size,即起始地址本身,计算最简单、最高效。如果从 1 开始,访问第一个元素 array[1] 就需要计算 起始地址 + (1-1) * size,多了一步减法。这种从 0 开始的索引(称为 zero-based indexing)已经成为 C 家族语言(包括 C++, Java, C#, JavaScript 等)的编程惯例。6.int[] vs ArrayList<Integer> 区别:大小:int[]: 大小固定。一旦创建,长度不能改变。ArrayList<Integer>: 大小可变。可以动态添加或删除元素,其内部容量会自动调整(虽然可能涉及性能开销)。类型:int[]: 存储的是基本数据类型 int 的值。ArrayList<Integer>: 存储的是包装类 <font color="purple">Integer</font> 的对象引用。它不能直接存储基本类型 int(但 Java 的自动装箱/拆箱机制使得使用起来很像在存取 int)。功能: ArrayList 提供了更多现成的方法(如 add, remove, contains, size 等),而数组功能相对基础(主要靠索引和 Arrays 工具类)。性能: 对于固定大小且频繁访问的场景,数组通常比 ArrayList 性能稍好(特别是对于基本类型数组,避免了装箱/拆箱开销和对象引用的开销)。————————————————原文链接:https://blog.csdn.net/2401_83830408/article/details/147653824
-
Java中的四种本地缓存技术深度解析与实践在Java开发中,缓存技术是提高应用性能的关键手段之一。今天,我们来聊聊Java中的四种主流本地缓存技术,并通过实例代码帮助大家更好地理解和应用这些技术。一、基础缓存实现首先,我们从最基础的缓存实现讲起。一个简单的缓存系统通常包括缓存实体类、添加、删除、查询和清除缓存的功能。1. 缓存实体类缓存实体类用于存储缓存的键值对以及过期时间。代码如下:。public class CacheEntity { private String cacheKey; private Object cacheValue; private long expireTime; // 过期时间戳 // 构造方法、getter和setter省略}2. 缓存工具类接下来,我们实现一个缓存工具类,使用ConcurrentHashMap作为存储结构,并通过定时任务清除过期数据。import java.util.concurrent.*;import java.util.Map; publicclassCacheUtil { privatefinalstatic Map<String, CacheEntity> CACHE_MAP = newConcurrentHashMap<>(); privatestaticScheduledExecutorServiceexecutorService= Executors.newSingleThreadScheduledExecutor(); static { executorService.scheduleAtFixedRate(() -> { longcurrentTime= System.currentTimeMillis(); CACHE_MAP.values().removeIf(entity -> entity.getExpireTime() < currentTime); }, 0, 500, TimeUnit.MILLISECONDS); } publicstaticvoidput(String key, Object value, long expireTimeInSeconds) { longexpireTime= System.currentTimeMillis() + expireTimeInSeconds * 1000; CACHE_MAP.put(key, newCacheEntity(key, value, expireTime)); } publicstatic Object get(String key) { CacheEntityentity= CACHE_MAP.get(key); if (entity == null || entity.getExpireTime() < System.currentTimeMillis()) { CACHE_MAP.remove(key); returnnull; } return entity.getCacheValue(); } publicstaticvoiddelete(String key) { CACHE_MAP.remove(key); } publicstaticvoidclear() { CACHE_MAP.clear(); }}测试代码:public class Test { public static void main(String[] args) throws InterruptedException { CacheUtil.put("name", "zzc", 10L); System.out.println("第一次查询结果:" + CacheUtil.get("name")); Thread.sleep(2000L); System.out.println("第二次查询结果:" + CacheUtil.get("name")); }}二、Guava LoadingCacheGuava是Google提供的一个Java基础库,其中的LoadingCache是一个强大的缓存工具。1. 使用示例import com.google.common.cache.*; import java.util.concurrent.ExecutionException;import java.util.concurrent.TimeUnit; publicclassTest { publicstaticvoidmain(String[] args)throws ExecutionException { LoadingCache<String, String> cache = CacheBuilder.newBuilder() .concurrencyLevel(8) .expireAfterWrite(10, TimeUnit.SECONDS) .build(newCacheLoader<String, String>() { @Override public String load(String key)throws Exception { return"default_value"; } }); cache.put("name", "zzc"); StringnameValue= cache.get("name"); StringageValue= cache.get("age", () -> "default_age"); StringsexValue= cache.get("sex", () -> "key 不存在"); System.out.println("nameValue: " + nameValue); System.out.println("ageValue: " + ageValue); System.out.println("sexValue: " + sexValue); }}在上面的代码中,当调用cache.get(key)方法时,如果缓存中不存在对应的key,则会通过CacheLoader的load方法加载默认值。三、SpringBoot整合CaffeineCaffeine是一个高性能的Java缓存库,SpringBoot提供了与Caffeine的无缝整合。1. 开启缓存功能在启动类上添加@EnableCaching注解。import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cache.annotation.EnableCaching; @EnableCaching@SpringBootApplicationpublic class TestApplication { public static void main(String[] args) { SpringApplication.run(TestApplication.class, args); }}2. 配置缓存管理器import com.github.benmanes.caffeine.cache.Caffeine;import org.springframework.cache.CacheManager;import org.springframework.cache.annotation.EnableCaching;import org.springframework.cache.caffeine.CaffeineCacheManager;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration; import java.util.concurrent.TimeUnit; @Configuration@EnableCachingpublicclassCacheConfig { @Bean("caffeineCacheManager") public CacheManager cacheManager() { CaffeineCacheManagercacheManager=newCaffeineCacheManager("userCache"); cacheManager.getCache("userCache").getConfig().setCaffeine(caffeineCacheBuilder()); return cacheManager; } Caffeine<Object, Object> caffeineCacheBuilder() { return Caffeine.newBuilder() .expireAfterWrite(10, TimeUnit.SECONDS) .maximumSize(100); }}3. 使用缓存注解import org.springframework.cache.annotation.CacheEvict;import org.springframework.cache.annotation.Cacheable;import org.springframework.stereotype.Service; @ServicepublicclassUserService { // 模拟数据库操作 private Map<Integer, User> userMap = newHashMap<>(); @Cacheable(value = "userCache", key = "#id") public User getUserById(Integer id) { // 假设从数据库获取用户数据 Useruser=newUser(); user.setId(id); user.setName("User" + id); userMap.put(id, user); return user; } @CacheEvict(value = "userCache", key = "#id") publicvoiddeleteUserById(Integer id) { userMap.remove(id); }}四、JetCache——阿里巴巴的分布式缓存框架JetCache是阿里巴巴开源的一款基于Spring和Redis的分布式缓存框架,提供了强大的缓存抽象和注解支持。1. 引入依赖在pom.xml中添加JetCache依赖。<dependency> <groupId>com.alicp.jetcache</groupId> <artifactId>jetcache-starter-redis</artifactId> <version>最新版本号</version></dependency>2. 配置JetCache在application.yml中配置JetCache。jetcache: stat:enable# 开启统计remote: default: type:redis keyConvertor:fastjson# 序列化方式 valueEncoder:java valueDecoder:java poolConfig: minIdle:5 maxIdle:20 maxTotal:50 host:localhost port: 63793. 使用JetCache注解import com.alicp.jetcache.anno.CacheType;import com.alicp.jetcache.anno.Cached;import com.alicp.jetcache.anno.CacheUpdate;import com.alicp.jetcache.anno.CacheInvalidate;import org.springframework.stereotype.Service; @ServicepublicclassUserService { @Cached(name = "userCache", key = "#id", cacheType = CacheType.BOTH) public String getUser(int id) { return"用户:" + id; } @CacheUpdate(name = "userCache", key = "#id", value = "#user") publicvoidupdateUser(int id, String user) { System.out.println("更新用户:" + user); } @CacheInvalidate(name = "userCache", key = "#id") publicvoiddeleteUser(int id) { System.out.println("删除用户:" + id); }}JetCache支持本地缓存和远程缓存的组合,非常适合分布式系统。总结今天我们一起探索了Java本地缓存的多种实现方式,从手写缓存到Guava Cache、Caffeine、Ehcache和JetCache。每种方式都有自己的特点和适用场景。————————————————原文链接:https://blog.csdn.net/jackeydengjun/article/details/146579570
-
死锁资源可抢占资源与不可抢占资源的对比特性 可抢占资源 不可抢占资源定义 资源可以被操作系统或其他线程/进程强制抢占 资源不能被操作系统或其他线程/进程抢占例子 CPU时间、内存、网络带宽等 锁、文件描述符、硬件设备控制机制 操作系统的调度器管理 需要显式的同步机制,如锁和信号量死锁风险 较低,因资源可能随时被抢占 较高,特别是在锁竞争时容易死锁死锁死锁(Deadlock) 是一种多线程或多进程程序中的问题,指的是两个或多个线程或进程在执行过程中,由于争夺共享资源而导致的相互等待的状态,从而导致这些线程或进程永远无法继续执行。换句话说,死锁是指系统中各个资源的请求无法得到满足,导致进程停滞不前。死锁的四个必要条件(Coffman等人提出)互斥条件:每个资源要么分配给一个进程(线程),要么就是可用的。占有和等待条件:已经得到某个资源的进程(线程)可以再次请求新的资源。不可抢占条件:已经分配给一个进程(线程)的资源不能强制性地被抢占。环路条件:死锁发生的时候一定由两个或两个以上的进程(线程)组成的一条环路,该环路中的每个进程(线程)都在等待着下一个进程(线程)所占有的资源。死锁建模死锁的具体表现:A请求R,B请求S,C请求T(图a),这是初始的资源请求阶段。资源被分配给进程A、B、C(图b和图c),此时每个进程持有了一个资源。进程A请求S,进程B请求T(图d和图e)。此时,进程A已持有资源R,正在请求资源S;进程B已持有资源S,正在请求资源T;进程C已持有资源T,正在请求资源R。此时发生死锁,因为A、B、C各自等待其他进程释放资源,形成循环等待(图g)四种处理死锁的策略以下是关于四种处理死锁策略的表格形式:策略 描述 优点 缺点死锁预防(Deadlock Prevention) 通过破坏死锁的四个条件之一来避免死锁,例如避免占有并等待、循环等待等条件。 - 可以完全避免死锁的发生。 - 系统始终保持安全状态。 - 可能导致系统效率下降,资源利用率低。 - 限制并发性。死锁避免(Deadlock Avoidance) 通过算法(如银行家算法)动态地判断资源请求是否会导致死锁,只有在安全情况下才会分配资源。 - 比死锁预防更加灵活。 - 可以允许更多的并发。 - 实现复杂,系统开销大,尤其在有大量资源和进程时。死锁检测与恢复(Deadlock Detection and Recovery) 系统允许死锁发生,但会定期检查是否有死锁并采取恢复措施(如回滚进程或终止进程)。 - 实现简单,不需要在资源分配时额外考虑死锁问题。 - 死锁检测和恢复会带来额外的开销。 - 恢复过程中可能需要终止进程,影响系统性能。死锁忽略(Deadlock Ignorance) 假设死锁不会发生,或者即使发生死锁也不去检测和处理。 - 简单实现,系统开销小。 - 死锁发生时可能导致系统停滞或无法继续工作。 - 不适用于高可靠性和高可用性要求的系统。死锁预防破坏互斥条件:在某些情况下,资源可以共享(如读操作)。通过允许多个进程共享资源,可以避免互斥条件。例如,多进程同时读取一个文件,而不进行写操作。破坏占有并等待条件:要求进程一次性请求所有资源:进程在执行前,必须先一次性请求所有所需的资源。如果请求资源的数量大于当前可用资源,进程就会阻塞,直到所有资源都可用。这种方式确保了进程不会在占有资源时再请求新的资源,避免了占有并等待条件。请求资源时不持有其他资源:进程请求一个资源时,必须先释放当前持有的所有资源,直到获得请求的资源。这意味着进程在请求资源时不能持有任何资源。破坏非抢占条件:允许资源抢占:当一个进程请求资源时,如果该资源已被其他进程占用,操作系统可以中断当前持有该资源的进程,强制将资源抢占并分配给请求进程。抢占的资源可以在适当的时机返回给原进程。资源可以被强制回收:如果进程无法获得所需资源,系统可以强制回收当前占用的资源,并重新安排资源分配。破坏循环等待条件:资源请求顺序:规定资源的请求顺序,要求进程按固定顺序请求资源。例如,进程必须先请求资源R,然后请求资源S,依此类推。这样,可以确保不存在形成环路的等待关系,从而避免循环等待条件的发生。每个进程在获取资源时按一个固定的顺序请求资源,这样即使多个进程请求不同的资源,也不会形成等待环路死锁避免(银行家算法)Dijkstra(1965年)提出了一种能够修复死锁的调度算法,称为银行家算法(Banker’s Algorithm)。该模型基于一个小城镇的银行家,他向一群客户分别承诺了一定的贷款额度。算法要做的是判断对请求的满足是否会导致进人不安全状态。如果是,就拒绝请求;如果满足请求后系统仍然是安全的,就予以分配。在图中我们看到4个客户A、B、C、D,每个客户都被授予一定数量的贷款单位(比如1单位是1千美元),银行家知道不可能所有客户同时都需要最大贷款额,所以他只保留10个单位而不是22个单位的资金来为客户服务。这里将客户比作进程,贷款单位比作资源,银行家比作操作系统。客户们各自做自己的生意,在某些时刻需要贷款(相当于请求资源)。在某一时刻,具体情况如图b所示。这个状态是安全的,由于保留着2个单位,银行家能够拖延除了c以外的其他请求。因而可以让C先完成,然后释放c所占的4个单位资源。有了这4个单位资源,银行家就可以给D或B分配所需的贷款单位,以此类推。 考虑假如向B提供了另一个他所请求的贷款单位,如图b所示,那么我们就有如图c所示的状态,该状态是不安全的。如果忽然所有的客户都请求最大的限额,而银行家无法满足其中任何一个的要求,那么就会产生死锁。不安全状态并不一定引起死锁,由于客户不一定需要其最大贷款额度,但银行家不敢抱这种侥幸心理。 银行家算法就是对每一个请求进行检查,检查如果满足这一请求是否会达到安全状态。若是,那么就满足该请求;否则,就推迟对这一请求的满足。为了检查状态是否安全,银行家需要考虑他是否有足够的资源满足某一个客户。如果可以,那么这笔贷款就是能够收回的,并且接着检查最接近最大限额的一个客户,以此类推。如果所有投资最终都能被收回,那么该状态是安全的,最初的请求可以批准。死锁忽略死锁忽略(Deadlock Ignorance)是一种处理死锁问题的策略,在这种策略下,系统选择完全不考虑死锁的发生。换句话说,系统不采取任何措施来检测或预防死锁,而是依赖于系统自然的恢复机制或用户干预来解决死锁。鸵鸟算法鸵鸟算法的核心概念:算法的名字来源于“鸵鸟把头埋进沙子里”这一比喻,意味着系统在遇到问题时选择忽略它,而不是进行积极的处理。该算法的基本思想是每个人在面对问题时都有自己的解决方式,有些人可能选择回避,认为这些问题会自动解决或不值得关注。优缺点:优点 缺点简单实现:不需要复杂的错误处理或检测机制,系统设计简洁。 潜在风险:忽略问题可能会导致系统出现严重故障,难以恢复。提高性能:通过不进行错误检测,减少计算和资源消耗,提升系统效率。 缺乏问题反馈:用户或管理员可能无法及时发现系统问题,延误修复。降低复杂性:简化系统设计,减少错误检测和恢复机制的复杂度。 不适合复杂系统:对于复杂系统,忽略问题可能导致不可预见的风险。适应低风险环境:适用于错误发生概率低、系统能够自行恢复的场景。 用户体验差:系统忽视问题可能影响用户的使用体验,无法及时响应错误。死锁检测和死锁恢复死锁检测方法该方法利用资源的当前分配情况进行死锁检测。资源的分配情况通过矩阵来表示,其中:C表示已分配矩阵(Allocation Matrix):表示每个进程已分配的资源数。B表示请求矩阵(Request Matrix):表示每个进程还需要的资源数。A可用资源向量(Available Resource Vector):表示系统中未被分配的资源数。E表示现有资源向量(Existing resource vector):代表每种已经存在的资源总数。关键恒等式:公式表达为:这条公式的含义是:Cij:表示第i个进程对资源类型j的请求数量。Aj:表示资源类型j已分配给所有进程的数量。Ej:表示资源类型j在系统中的总数。总的来说就是已经分配的资源j的总数加起来的再和所有可供使用的资源总数相加,结果就是该类资源总数。死锁恢复终止进程:强制终止一个或多个进程来释放资源,从而解除死锁。资源回收:强制某些进程释放资源,其他进程可以使用这些资源来继续执行。进程回滚:把某些进程的状态恢复到之前的安全状态,让它们重新开始执行。其他问题两阶段加锁两阶段加锁(Two-Phase Locking, 2PL) 是一种常见的数据库事务控制协议,它在保证事务隔离性的同时避免了死锁的发生。两阶段加锁的基本原理是:一个事务必须首先完成对所需资源的加锁,直到它释放资源前不可以请求更多的锁。两阶段加锁分为两个阶段:扩展阶段(Growing Phase):事务可以不断地请求和获取锁。收缩阶段(Shrinking Phase):一旦事务释放了一个锁,它就不能再申请任何新的锁。例子:银行转账事务假设有两个进程(事务)在银行系统中执行转账操作,涉及两个账户的资源:账户A和账户B。事务1(T1):T1的操作是从账户A转账到账户B,具体过程是:请求锁定账户A,进行扣款。请求锁定账户B,进行存款。提交事务,释放锁。事务2(T2):T2的操作是从账户B转账到账户A,具体过程是:请求锁定账户B,进行扣款。请求锁定账户A,进行存款。提交事务,释放锁。两阶段加锁的过程事务T1:扩展阶段:T1首先请求锁定账户A,成功获取锁。T1继续请求锁定账户B,也成功获得锁。T1没有再申请其他锁,进入收缩阶段,开始释放锁。事务T2:扩展阶段:T2首先请求锁定账户B,成功获取锁。然后,T2尝试请求账户A的锁,但由于T1已经锁定了账户A,T2会被阻塞,等待T1释放锁。T2无法再请求任何新的锁(因为进入了收缩阶段),并且等待T1释放账户A的锁。通信死锁通信死锁(Communication Deadlock) 是指在分布式系统或多进程/多线程系统中,由于进程之间的通信(如消息传递、信号量等)引发的死锁问题。具体来说,当多个进程或线程相互依赖对方的消息或响应,而这些消息或响应无法被发送或接收时,系统可能进入死锁状态。举例解释:生产者-消费者模型中的通信死锁假设我们有一个简单的生产者-消费者问题,其中有两个进程:生产者和消费者。生产者将商品生产到缓冲区中,而消费者从缓冲区中取走商品。生产者和消费者通过共享一个缓冲区进行通信。如果在某些情况下,生产者和消费者由于等待彼此的消息而导致死锁,就会发生通信死锁。步骤1:正常通信生产者(P)生产一个商品,并将其放入缓冲区。消费者(C)从缓冲区中取走一个商品。P 和 C 通过缓冲区的状态来进行通信,P 等待缓冲区有空位置,C 等待缓冲区中有商品。步骤2:发生通信死锁假设存在如下场景:生产者 P 正在等待消费者 C 取走一个商品,以便腾出空间存放新商品(即 P 等待 C 消费)。消费者 C 正在等待生产者 P 放入商品,以便它能够消费(即 C 等待 P 生产)。这种情况下,生产者和消费者彼此等待对方的动作,形成了相互依赖的通信链条,导致死锁发生。步骤3:死锁的发生生产者 P 和消费者 C 永远互相等待对方的动作。生产者没有商品可生产,因为缓冲区已满,消费者无法取走商品。消费者没有商品可消费,因为生产者没有生产新商品。结果,系统进入死锁状态,生产者和消费者都无法继续执行。死锁的原因消息依赖性:生产者和消费者相互依赖对方的消息才能继续执行。例如,生产者必须等待消费者消费商品才能继续生产,而消费者必须等待生产者生产商品才能继续消费。资源共享:当多个进程或线程共享资源(如缓冲区、队列等),并且相互依赖对方的动作时,容易发生死锁。活锁(Livelock)活锁(Livelock) 是一种并发编程中的问题,类似于死锁,但有所不同。与死锁不同,活锁中的进程或线程并没有完全停顿,它们仍然在不断地改变自己的状态,但始终无法达到预期的目标或完成任务。换句话说,活锁是一种进程不停地做出反应,但没有实质性进展的状态。活锁的特点进程不断改变状态:尽管进程没有被完全阻塞,它仍然在进行某些操作(如尝试重新获取资源),但这些操作并没有实质性进展。没有前进:进程虽然不停地变化和响应,但始终没有最终完成任务或获得所需资源。饥饿(Starvation)饥饿(Starvation) 是指在并发系统中,某些进程因为系统资源的分配不公或调度策略不合理,长时间得不到执行,导致它们无法完成任务。饥饿问题通常发生在多个进程争用资源时,其中某些进程总是得不到必要的资源,而一直处于等待状态。饥饿的特点长期等待:某些进程长时间无法获得执行机会,甚至无法获得资源来继续执行。不公平资源分配:由于资源分配策略的不公,某些进程被反复忽略,导致它们得不到满足。进程被“饿死”:虽然进程并没有完全被阻塞或终止,它们的执行被永久推迟,无法正常执行下去。#include<iostream>#include<unistd.h>#include<pthread.h>#include"lock.hpp" // 这里的lock.hpp是自定义的头文件,假设它包含一些锁的相关定义using namespace std;// 初始化互斥锁pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;#define NUM 10 // 定义线程数量int ticket = 1000; // 初始票数// 线程执行的函数void* mythread(void* args){ pthread_detach(pthread_self()); // 将当前线程设置为分离线程,线程结束后自动释放资源 uint64_t number = (uint64_t)args; // 获取传递给线程的参数,转化为线程编号 // 无限循环,直到票数耗尽 while(true) { { pthread_mutex_lock(&lock); // 上锁,确保只有一个线程在修改票数时能访问临界区 if(ticket > 0) // 如果票数大于0,则继续执行 { usleep(1000); // 模拟处理过程,暂停1毫秒 cout <<"thread: " << number << " ticket: " << ticket << endl; // 打印当前线程号和票数 ticket--; // 减少票数 } else { pthread_mutex_unlock(&lock); // 解锁,允许其他线程访问临界区 break; // 如果票数小于或等于0,退出循环,结束线程执行 } pthread_mutex_unlock(&lock); // 解锁,允许其他线程访问临界区 } } return nullptr; // 线程结束}int main(){ // 创建NUM个线程 for(int i = 0; i < NUM; i++) { pthread_t tid; pthread_create(&tid, nullptr, mythread, (void*)i); // 创建线程并传递线程编号 } sleep(5); // 主线程休眠5秒钟,等待子线程执行完成 cout <<"process quit ..." <<endl; // 主线程结束时输出消息 return 0;}都是9号线程抢占资源。产生了竞争的关系。其他线程解饿。(需要同步解决)NUM; i++){pthread_t tid;pthread_create(&tid, nullptr, mythread, (void*)i); // 创建线程并传递线程编号}————————————————原文链接:https://blog.csdn.net/Cayyyy/article/details/148286969
-
枚举(Enum)是Java 5引入的一种特殊数据类型,用于定义一组固定的常量。下面我将全面介绍Java SE中枚举的使用方法、特性和最佳实践。一、枚举基础1. 基本定义public enum Day { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY}2. 特点类型安全:编译时检查不可实例化:构造函数私有不可继承:final类实现了Comparable和Serializable接口二、枚举的高级用法1. 带属性的枚举public enum Planet { MERCURY(3.303e+23, 2.4397e6), VENUS(4.869e+24, 6.0518e6), EARTH(5.976e+24, 6.37814e6); private final double mass; // in kilograms private final double radius; // in meters Planet(double mass, double radius) { this.mass = mass; this.radius = radius; } public double surfaceGravity() { return G * mass / (radius * radius); }}2. 枚举实现接口public interface Operation { double apply(double x, double y);}public enum BasicOperation implements Operation { PLUS("+") { public double apply(double x, double y) { return x + y; } }, MINUS("-") { public double apply(double x, double y) { return x - y; } }; private final String symbol; BasicOperation(String symbol) { this.symbol = symbol; }}3. 枚举集合Java提供了专门用于枚举的集合类:EnumSet<Day> weekend = EnumSet.of(Day.SATURDAY, Day.SUNDAY);EnumMap<Day, String> activityMap = new EnumMap<>(Day.class);三、枚举的常用方法1. 内置方法values(): 返回枚举数组valueOf(String): 根据名称返回枚举常量name(): 返回枚举常量名称ordinal(): 返回枚举常量序数2. 示例Day[] days = Day.values();Day monday = Day.valueOf("MONDAY");String name = Day.MONDAY.name();int ordinal = Day.MONDAY.ordinal();四、枚举设计模式1. 单例模式public enum Singleton { INSTANCE; public void doSomething() { // ... }}2. 策略模式public enum Calculator { ADD { public int calculate(int a, int b) { return a + b; } }, SUBTRACT { public int calculate(int a, int b) { return a - b; } }; public abstract int calculate(int a, int b);}五、枚举与switch语句枚举非常适合与switch语句配合使用:Day day = Day.MONDAY;switch(day) { case MONDAY: System.out.println("星期一"); break; case FRIDAY: System.out.println("星期五"); break; default: System.out.println("其他日子");}六、枚举的最佳实践优先使用枚举而非int常量更安全,更易读编译时类型检查避免使用ordinal()依赖序数会使代码脆弱考虑使用EnumSet代替位域更类型安全,性能相当大型枚举考虑策略枚举将行为与枚举常量关联七、枚举的性能考虑枚举实例是静态final的,在类加载时创建EnumSet和EnumMap针对枚举做了优化values()方法每次返回新数组(浅拷贝)八、Java 8+中的枚举增强1. lambda表达式public enum Operation { ADD((x, y) -> x + y), SUBTRACT((x, y) -> x - y); private final DoubleBinaryOperator op; Operation(DoubleBinaryOperator op) { this.op = op; } public double apply(double x, double y) { return op.applyAsDouble(x, y); }}2. Stream APIArrays.stream(Day.values()) .filter(d -> d.toString().endsWith("DAY")) .forEach(System.out::println);枚举是Java中强大且灵活的特性,合理使用可以大大提高代码的可读性和健壮性。
-
一、引言在 Java 编程领域,内存管理看似由虚拟机自动操持,开发者无需过度介入。然而,当内存泄漏或溢出问题悄然浮现,若对虚拟机内存运作机制缺乏深入认知,排查与修复工作将举步维艰。本文将深入剖析 Java 内存区域,并对常见的内存溢出异常进行详细探讨。二、Java 运行时数据区域(一)程序计数器功能:程序计数器是一块较小的内存空间,其作用是指示当前线程执行字节码的行号,是程序控制流的关键指示器,负责分支、循环、跳转等流程控制。特性:为线程私有,当线程执行 Java 方法时,记录字节码指令地址;执行本地方法时,计数器值为空。该区域不会出现 OutOfMemoryError。(二)Java 虚拟机栈线程私有性:Java 虚拟机栈同样是线程私有的,其生命周期与线程紧密相连。栈帧结构:以栈帧为单位存储方法执行时的局部变量表、操作数栈、动态连接及方法出口等信息。局部变量表存放基本数据类型和对象引用,编译期确定其大小。异常情况:可能抛出 StackOverflowError(当线程请求的栈深度超过虚拟机允许的深度)和 OutOfMemoryError(栈动态扩展时无法申请到足够内存)。(三)本地方法栈功能与虚拟机栈的关系:功能类似虚拟机栈,主要为本地方法执行提供支持。实现方式:实现方式由虚拟机自行决定。异常抛出:在栈深度溢出或扩展失败时,会抛出 StackOverflowError 和 OutOfMemoryError。(四)Java 堆地位与作用:是虚拟机管理的最大内存区域,被所有线程共享,用于存放对象实例,是垃圾收集的主要区域。分代收集理论:基于分代收集理论进行区域划分,可设置为固定或扩展大小。内存溢出情况:当内存不足且无法扩展时,抛出 OutOfMemoryError。(五)方法区线程共享性:线程共享区域,用于存储类型信息、常量、静态变量等。历史变迁:JDK 8 前常被称为 “永久代”,之后采用元空间实现。异常情况:内存不足时抛出 OutOfMemoryError。(六)运行时常量池所属区域:属于方法区,存放编译期生成的字面量与符号引用。动态性:具备动态性,运行时可添加常量。内存异常:内存申请失败时抛出 OutOfMemoryError。(七)直接内存特殊性质:并非虚拟机规范定义区域,NIO 可利用其分配堆外内存以提升性能。内存限制:不受 Java 堆大小限制,但受本机内存制约。溢出异常:超出限制时抛出 OutOfMemoryError。三、内存溢出异常实战(一)Java 堆溢出示例代码:通过不断创建对象耗尽堆内存。import java.util.ArrayList;import java.util.List;class HeapOOM { static class OOMObject {} public static void main(String[] args) { List<OOMObject> list = new ArrayList<>(); while (true) { list.add(new OOMObject()); } }}解决思路:可通过 -Xmx 参数设置堆大小,溢出时抛出 OutOfMemoryError: Java heap space,可调整堆大小或优化对象创建逻辑来解决。(二)虚拟机栈溢出示例代码:利用无限递归使栈深度超限。class StackSOF { private static void stackLeak() { stackLeak(); } public static void main(String[] args) { try { stackLeak(); } catch (Throwable e) { System.out.println("Stack depth: " + e.getMessage()); e.printStackTrace(); } }}解决方法:抛出 StackOverflowError,需检查递归算法并设置终止条件。(三)方法区和运行时常量池溢出示例代码:持续向常量池添加字符串。import java.util.ArrayList;import java.util.List;class MethodAreaOOM { public static void main(String[] args) { List<String> list = new ArrayList<>(); int i = 0; while (true) { list.add(String.valueOf(i++).intern()); } }}应对策略:抛出 OutOfMemoryError,需关注常量池使用情况,避免无节制创建常量。(四)本机直接内存溢出示例代码:借助Unsafe类不断分配直接内存。import sun.misc.Unsafe;import java.lang.reflect.Field;class DirectMemoryOOM { private static final int _1MB = 1024 * 1024; public static void main(String[] args) throws IllegalAccessException { Field unsafeField = Unsafe.class.getDeclaredFields()[0]; unsafeField.setAccessible(true); Unsafe unsafe = (Unsafe) unsafeField.get(null); while (true) { unsafe.allocateMemory(_1MB); } }}解决方案:抛出 OutOfMemoryError,需合理配置虚拟机参数并监控直接内存使用。四、结语{unsafe.allocateMemory(_1MB);}}}**解决方案**:抛出 `OutOfMemoryError`,需合理配置虚拟机参数并监控直接内存使用。## 四、结语深入掌握 Java 内存区域划分及内存溢出异常原理,是 `Java` 开发者进阶路上的关键。在日常开发中,应养成良好的内存管理习惯,借助工具———————————————— 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 原文链接:https://blog.csdn.net/2401_87533975/article/details/147261259
-
📕1. 反射JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。✏️1.1 反射相关的类类名 用途Class类 代表类的实体,在运行的Java应用程序中表示类和接口Field类 代表类的成员变量(类的属性)Method类 代表类的方法Constructor 代表类的构造方法✏️1.2 Class类中的相关方法方法 用途getClassLoader() 获得类的加载器getDeclaredClasses() 返回一个数组,数组中包含该类中所有的类和接口的对象(包含私有的)forName(String className) 根据类名返回类的对象newInstance() 创建类的实例getName() 获得类的完整路径名字✏️1.3 Field类中的相关方法方法 用途getField(String name) 获得某个共有的属性对象getFileds() 获得所有共有的属性对象getDeclaredField(String name) 获得某个属性对象getDeclaredFields() 获得所有属性对象✏️1.4 Method类中的相关方法方法 用途getDeclaredMethod((String name, Class<?>… parameterTypes) 获得该类某个方法getDeclaredMethods() 获得该类的所有方法getMethod((String name, Class<?>… parameterTypes) 获得该类某个公有方法getMethods() 获得该类所有公有方法invoke(对象名,要修改的参数) 触发方法执行的方法✏️1.5 Constructor类中的相关方法方法 用途getConstructor( Class<?>… parameterTypes) 获得该类中与参类型匹配的某个公有构造方法getConstructors() 获得该类中所有的公有构造方法geDeclaredtConstructor(Class<?>… parameterTypes) 获得该类中与参类型匹配的某个构造方法geDeclaredtConstructors() 获得该类中所有的构造方法✏️1.6 获取Class对象的三种方式在反射之前,我们需要做的第一步就是先拿到当前需要反射的类的Class对象,然后通过Class对象的核心方法,达到反射的目的我们先构造出一个学生类作为演示示例class Student { //私有属性name private String name = "xiaozhuxiaozhu"; //公有属性age public int age = 18; //不带参数的构造⽅法 public Student(){ System.out.println("Student()"); } private Student(String name,int age) { this.name = name; this.age = age; System.out.println("Student(String,name)"); } private void eat(){ System.out.println("i am eat"); } public void sleep(){ System.out.println("i am pig"); } private void function(String str) { System.out.println(str); } @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", age=" + age + '}'; }}1.使用 Class.forName(“类的全路径名”); 静态方法(前提:已明确类的全路径名。 )。public class test1 { public static void main(String[] args) throws ClassNotFoundException { //通过 Class 对象的 forName() 静态⽅法来获取,⽤的最多,但可能抛出 ClassNotFoundException 异常 //注意这⾥是类的全路径,如果有包需要加包的路径 Class c1 = Class.forName("reflection.Student"); }}使用 .class 方法(仅适合在编译前就已经明确要操作的 Class)。public class test2 { public static void main(String[] args) throws ClassNotFoundException { //直接通过 类名.class 的⽅式得到,该⽅法最为安全可靠,程序性能更⾼ Class c2 = Student.class; }}使用类对象的 getClass() 方法public class test3 { public static void main(String[] args) throws ClassNotFoundException { Student s1 = new Student(); Class c3 = s1.getClass(); }}✏️1.7 反射的使用接下来我们开始使用反射,我们依旧反射上面的Student类注意:所有和反射相关的包都在 import java.lang.reflect 包下面。package reflection;import java.lang.reflect.Method;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.lang.reflect.InvocationTargetException;public class demo_test { public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException, NoSuchFieldException { // 获取Class对象 Class<?> c1 = Class.forName("reflection.Student"); Object newInstance = c1.newInstance();; Student student = (Student)newInstance; System.out.println(student); // 获取构造方法 Constructor<?> constructors = c1.getDeclaredConstructor(String.class, int.class); //设置为true后可修改访问权限 constructors.setAccessible(true); Object newInstance1 = constructors.newInstance("zhangsan", 20); Student student1 = (Student)newInstance1; System.out.println(student1); // 反射私有⽅法 Method method = c1.getDeclaredMethod("function", String.class); //设置为true后可修改访问权限 method.setAccessible(true); method.invoke(student1, "hello"); // 反射私有属性 Field field = c1.getDeclaredField("name"); //设置为true后可修改访问权限 field.setAccessible(true); field.set(student1, "lisi"); System.out.println(student1); }}📕2. 枚举枚举是在JDK1.5以后引⼊的。本质是一个特殊的类,是 java.lang.Enum 的⼦类,⾃⼰写的枚举类,就算没有显示的继承 Enum ,但是其默认继承了这个类。枚举的关键字为enum,一般表示一组常量,比如一年的 4 个季节,一个年的 12 个月份,一个星期的 7 天,方向有东南西北等。2.1 枚举的定义限定符 enum 枚举名称{常量1,常量2,常量3....;}✏️2.2 枚举的使用switch语句public enum testenum { RED,BLACK,WHITE; public static void main(String[] args) { testenum t1 = testenum.BLACK; switch (t1) { case RED: System.out.println("红色"); break; case BLACK: System.out.println("黑色"); break; case WHITE: System.out.println("白色"); break; default: break; } } }常⽤⽅法方法名称 描述values() 以数组形式返回枚举类型的所有成员ordinal() 获取枚举成员的索引位置valueOf() 将普通字符串转换为枚举类型compareTo() 比较两个枚举成员在定义时的顺序//将枚举类型的所有成员以数组形式返回并打印testenum[] values = testenum.values();for (int i = 0; i < values.length; i++) {System.out.println(values[i]); } ✏️2.3 枚举的构造方法💡💡💡💡💡注意:当枚举对象有参数后,需要提供相应的构造函数💡💡💡💡💡注意:枚举的构造函数默认是私有的 这个⼀定要记住public enum testenum { RED("红色",1),BLACK("黑色",2),WHITE("白色",3); private String color; private int key; private testenum(String color,int key){ this.color=color; this.key=key; }}✏️2.4 枚举与反射关于枚举与反射,请记住一个结论:你不能通过反射获取枚举类的实例!这也是<为什么枚举实现单例模式是安全的?>问题的答案.至于为什么呢,等我学完单例模式之后在来补充…📕3. Lambda表达式Lambda 表达式(Lambda expression),基于数学中的λ演算得名,也可称为闭包(Closure) ,是Java SE 8中⼀个重要的新特性. Lambda表达式允许你通过表达式来代替功能接口。 lambda表达式就和方法⼀样,它提供了⼀个正常的参数列表和⼀个使用这些参数的主体(主体可以是⼀个表达式或⼀个代码块)。✏️3.1 Lambda表达式语法基本语法: (parameters) -> expression 或(parameters) -> {statement;}Lambda表达式由三部分组成:1. paramaters:类似方法中的形参列表,这⾥的参数是函数式接口里的参数。2. ->:可理解为“被用于”的意思3. 方法体:可以是表达式也可以代码块,是函数式接口里方法的实现。代码块可返回一个值或者什么都不反回,这里的代码块块等同于方法的方法体。// 1. 不需要参数,返回值为 2() -> 2// 2. 接收⼀个参数(数字类型),返回其2倍的值x -> 2 * x// 3. 接受2个参数(数字),并返回他们的和(x, y) -> x + y// 4. 接收2个int型整数,返回他们的乘积(int x, int y) -> x * y// 5. 接受⼀个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)(String s) -> System.out.print(s)✏️3.2 函数式接口函数式接口定义:⼀个接口有且只有⼀个抽象方法 。💡💡注意:如果一个接口只有一个抽象方法,那么该接口就是⼀个函数式接口如果我们在某个接口上声明了 @FunctionalInterface 注解,那么编译器就会按照函数式接口的定义来要求该接口,这样如果有两个抽象方法,程序编译就会报错的。所以,从某种意义上来说,只要你保证你的接口中只有一个抽象方法,你可以不加这个注解。加上就会自动进行检测的。@FunctionalInterfaceinterface NoParameterNoReturn {//注意:只能有⼀个⽅法void test();}✏️3.3 变量捕获@FunctionalInterfaceinterface NoParameterNoReturn {void test();}public static void main(String[] args) {int a = 10;NoParameterNoReturn noParameterNoReturn = ()->{// a = 99; error———————————————— 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 原文链接:https://blog.csdn.net/2401_82690001/article/details/147606498
上滑加载中
推荐直播
-
HDC深度解读系列 - Serverless与MCP融合创新,构建AI应用全新智能中枢2025/08/20 周三 16:30-18:00
张昆鹏 HCDG北京核心组代表
HDC2025期间,华为云展示了Serverless与MCP融合创新的解决方案,本期访谈直播,由华为云开发者专家(HCDE)兼华为云开发者社区组织HCDG北京核心组代表张鹏先生主持,华为云PaaS服务产品部 Serverless总监Ewen为大家深度解读华为云Serverless与MCP如何融合构建AI应用全新智能中枢
回顾中 -
关于RISC-V生态发展的思考2025/09/02 周二 17:00-18:00
中国科学院计算技术研究所副所长包云岗教授
中科院包云岗老师将在本次直播中,探讨处理器生态的关键要素及其联系,分享过去几年推动RISC-V生态建设实践过程中的经验与教训。
回顾中 -
一键搞定华为云万级资源,3步轻松管理企业成本2025/09/09 周二 15:00-16:00
阿言 华为云交易产品经理
本直播重点介绍如何一键续费万级资源,3步轻松管理成本,帮助提升日常管理效率!
回顾中
热门标签