One Trick Per Day

本文涉及的产品
RDS AI 助手,专业版
PolarDB Agent Express,2核4GB
PolarDB Agent Flow,2核4GB
简介: Map初始化应避免直接指定大小,推荐Guava或手动计算;禁用Executors创建线程池,防止OOM,建议自定义ThreadPoolExecutor;Arrays.asList返回不可变列表,禁止修改操作;遍历Map优先使用entrySet或forEach;SimpleDateFormat非线程安全,建议用ThreadLocal或Java8新时间API;并发更新记录需加锁,推荐乐观锁配合version字段。

1.初始化Map大小并非用多少指定多少
初始化Map并非用多少初始化Size是多少,建议使用Guava,避免扩容引起的动荡()
说明
如:Map map = new HashMap<>(1); 在具体使用时,并非size=1,而是最近的2的幂等,如1实际是2,3实际是4,9实际是16
使用方法
依赖gvaua:Map map = Maps.newHashMapWithExpectedSize(7);
手动声明:Map map = new HashMap<>(实际存储个数 / 0.75 + 1);
2.线程池初始化严禁使用Executors
使用线程池时候,我们可能会使用下面四个场景,这在alibaba代码规范中都是明令禁止的
我们先来一个简单的例子,模拟一下使用 Executors 导致 OOM 的情况。
通过指定 JVM 参数:-Xmx8m -Xms8m 运行以上代码,会抛出 OOM:
以上代码指出,ExecutorsDemo.java 的第 16 行,就是代码中的 executor.execute(new SubThread());。
通过上面的例子,我们知道了 Executors 创建的线程池存在 OOM 的风险,那么到底是什么原因导致的呢?我们需要深入 Executors 的源码来分析一下。其实,在上面的报错信息中,我们是可以看出蛛丝马迹的,在以上的代码中其实已经说了,真正的导致 OOM 的其实是 LinkedBlockingQueue.offer 方法。
如果读者翻看代码的话,也可以发现,其实底层确实是通过 LinkedBlockingQueue 实现的:
如果读者对 Java 中的阻塞队列有所了解的话,看到这里或许就能够明白原因了。Java 中 的 BlockingQueue 主 要 有 两 种 实 现, 分 别 是 ArrayBlockingQueue 和 LinkedBlockingQueue。ArrayBlockingQueue 是一个用数组实现的有界阻塞队列,必须设置容量。LinkedBlockingQueue 是一个用链表实现的有界阻塞队列,容量可以选择进行设置,不设置的话,将是一个无边界的阻塞队列,最大长度为 Integer.MAX_VALUE。这里的问题就出在:不设置的话,将是一个无边界的阻塞队列,最大长度为Integer.MAX_VALUE。也就是说,如果我们不设置 LinkedBlockingQueue 的容量的话,其默认容量将会是 Integer.MAX_VALUE。 而 newFixedThreadPool 中创建 LinkedBlockingQueue 时,并未指定容量。此时,LinkedBlockingQueue 就是一个无边界队列,对于一个无边界队列来说,是可以不断的向队列中加入任务的,这种情况下就有可能因为任务过多而导致内存溢出问题。上面提到的问题主要体现在 newFixedThreadPool 和 newSingleThreadExecutor 两个工厂方法上,并不是说newCachedThreadPool 和 newScheduledThreadPool 这两个方法就安全了,这两种方式创建的最大线程数可能是Integer.MAX_VALUE,而创建这么多线程,必然就有可能导致 OOM
正确使用:
这种情况下,一旦提交的线程数超过当前可用线程数时,就会抛出java.util.concurrent.RejectedExecutionException,这是因为当前线程池使用的队列是有边界队列,队列已经满了便无法继续处理新的请求。但是异常(Exception)总比发生错误(Error)要好。
但是部分alibaba作者更推荐使用guava创建对应的线程池,示例如下:
通过上述方式创建线程时,不仅可以避免 OOM 的问题,还可以自定义线程名称,更加方便的出错的时候溯源。
3.Arrays.asList之后不要调用修改操作
因为asList返回的实际是一个Arrays内部类,并没有实现集合的修改方法(add/remove/clear)// 当操作修改方法时,会报UnsupportedOperationException。
第一种情况:list.add("yangguanbao"); 运行时异常。
第二种情况:str[0] = "gujin"; 那么 list.get(0)也会随之修改。[涉及栈堆指针操作,修改数组的数据,导致同样引用该数据的list值被改变]
4.使用 entrySet 遍历 Map 类集合 KV
说明:keySet 其实是遍历了 2 次,一次是转为 Iterator 对象,另一次是从 hashMap 中取出key 所对应的 value。而 entrySet 只是遍历了一次就把 key 和 value 都放到了 entry 中,效率更高。
如果是 JDK8,使用 Map.foreach 方法。
正例:values()返回的是 V 值集合,是一个 list 集合对象;keySet()返回的是 K 值集合,是一个 Set 集合对象;entrySet()返回的是 K-V 值组合集合。
5.SimpleDateFormat不要定义为static
SimpleDateFormat 是线程不安全的类,一般不要定义为 static 变量,如果定义为static,必须加锁,或者使用 DateUtils 工具类。
正例:注意线程安全,使用 DateUtils。亦推荐如下处理:
Java
运行代码
复制代码
1
2
3
4
5
6
private static final ThreadLocal df = new ThreadLocal() {
@Override
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd");
}
};
说明:如果是 JDK8 的应用,可以使用 Instant 代替 Date,LocalDateTime 代替 Calendar,DateTimeFormatter 代替 SimpleDateFormat,官方给出的解释:simple beautiful strong immutable thread-safe。
6.并发修改同一记录时需要加锁
要么在应用层加锁,要么在缓存加锁,要么在数据库层使用乐观锁,使用 version 作为更新依据。
说明:如果每次访问冲突概率小于 20%,推荐使用乐观锁,否则使用悲观锁。乐观锁的重试次数不得小于 3 次

相关文章
|
6月前
|
数据库
各种锁的专题
数据库锁机制简介:包括写锁(排他)、共享锁(读)、排他锁、间隙锁、行锁、表锁及分布式锁,用于控制并发访问,保证数据一致性与完整性。
|
6月前
|
存储 Java 编译器
Java泛型类型擦除以及类型擦除带来的问题
Java泛型在编译时会进行类型擦除,所有泛型信息被移除,替换为原始类型(如Object或限定类型)。例如,List&lt;String&gt;和List&lt;Integer&gt;在运行时均为List。类型检查在编译期完成,基于引用而非实际对象。擦除导致反射可绕过泛型限制、无法使用基本类型、instanceof失效等问题,并影响多态实现,需通过桥方法等机制解决。静态成员不能使用类的泛型参数,因其实例化依赖对象创建,而静态上下文无此支持。
|
6月前
|
Java 调度
线程池
线程池通过复用线程提升性能,避免频繁创建销毁的开销。Java中Executor框架提供Executors工厂类创建线程池,核心实现为ThreadPoolExecutor和ScheduledThreadPoolExecutor,分别支持普通任务和定时调度,助力高效并发编程。
|
6月前
|
Java 大数据
ArrayList扩容机制
ArrayList的add方法通过ensureCapacityInternal确保容量,首次添加时默认扩容至10。添加第11个元素时触发grow(),容量扩为原容量1.5倍。grow()通过位运算高效计算新容量,实现动态扩容。length为数组属性,length()为字符串方法,size()用于集合。
|
6月前
|
Java 调度
线程池
本文深入解析Java线程池核心类ThreadPoolExecutor的实现原理,通过分析其类图结构、关键成员变量及任务执行流程,揭示线程池如何管理线程创建、任务调度与拒绝策略。重点剖析Worker类作为执行单元的核心作用,以及corePoolSize、maximumPoolSize、workQueue和keepAliveTime等参数在运行机制中的协同关系,帮助理解线程池的动态扩容、任务排队与线程复用逻辑。
|
6月前
|
存储 NoSQL Linux
2-MongoDB单机部署
本文介绍MongoDB在Windows和Linux系统的安装启动方法,包括下载、解压、配置数据目录及启动方式(命令行或配置文件)。支持设置环境变量、修改端口、日志与存储路径,并通过mongo shell或Compass图形化工具连接。Linux下还需配置防火墙与关闭服务,确保数据库稳定运行。
|
6月前
|
存储 JSON NoSQL
3-MongoDB常用命令
本文介绍如何使用MongoDB存储文章评论数据,涵盖数据库与集合的创建、删除,文档的增删改查、批量操作、投影查询、分页排序等功能,结合实际示例说明CRUD操作及注意事项,帮助快速掌握MongoDB基本用法。
|
6月前
|
存储 NoSQL 关系型数据库
4-MongoDB索引知识
MongoDB索引通过B树结构提升查询效率,避免全表扫描。支持单字段、复合、地理空间、文本及哈希索引,优化等值、范围、排序及全文检索,显著提升大数据量下的查询性能。
|
6月前
|
缓存 Java 数据库连接
1.常见配置
MyBatis配置优先级:方法参数 &gt; resource/url &gt; properties体内。核心属性包括缓存、延迟加载、主键生成等。支持多环境配置,通过environments指定,默认使用default环境。事务管理支持JDBC和MANAGED,与Spring集成时由Spring接管。详细配置参考官方Java API文档。
|
6月前
|
SQL 关系型数据库 Java
3.分页
本文介绍了五种分页实现方式:MyBatis自带RowBounds内存分页、PageHelper插件分页、SQL原生分页、数组分页及拦截器分页。前两者属逻辑分页,查全量数据后处理,小数据量高效但内存开销大;后三者为物理分页,直接在数据库层分页,适合大数据量场景。推荐优先使用物理分页以提升性能与系统稳定性。(238字)

热门文章

最新文章