some小窍门

简介: 本文总结了 Java 开发中的几个重要编码规范与优化建议:1. **Map 初始化大小**:避免直接初始化 HashMap 时指定不准确的容量。2. **线程池创建**:禁止使用 `Executors` 工具类创建线程池。3. **Arrays.asList 操作限制**:`Arrays.asLis` 返回的是固定大小的列表,不能调用修改方法。4. **Map 遍历优化**:优先使用 `entrySet` 遍历 Map,效率高于 `keySet`。5. **SimpleDateFormat 线程安全**:`SimpleDateFormat` 是线程不安全的,不应定义为 static。

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);

<dependency>
  <groupId>com.google.guava</groupId>
  <artifactId>guava</artifactId>
  <version>17.0</version>
</dependency>

● 手动声明:Map map = new HashMap<>(实际存储个数 / 0.75 + 1);

2.线程池初始化严禁使用Executors

使用线程池时候,我们可能会使用下面四个场景,这在alibaba代码规范中都是明令禁止的。

// 创建一个单线程化的Executor
//[因为数量固定,可能会堆积大量请求,导致OOM]
private static ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();

// 创建一个固定数目线程的线程池
// [因为数量固定,可能会堆积大量请求,导致OOM]
private static ExecutorService fixedThreadPool = Executors.newFixedThreadPool(10);

// 创建一个可执行命令的单线程Executor
// [可能会创建大量的线程,导致OOM]
private static ExecutorService singleThreadScheduledExecutor = Executors.newSingleThreadScheduledExecutor();

// 创建一个可缓存的线程池(60S存活时间)
// [可能会创建大量的线程,导致OOM]
private static ExecutorService cachedThreadPool = Executors.newCachedThreadPool();

举一个简单的例子,模拟使用 Executors 导致 OOM 的情况。

public class ExecutorsDemo {
   
    private static ExecutorService executor = Executors.newFixedThreadPool(25);
    public static void main(String[] args) {
   
        for (int i = 1; i < Integer.MAX_VALUE; i++) {
   
            executor.execute(new SubThread());
        }
    }
}
class SubThread implements Runnable {
   
    @Override
    public void run() {
   
        try {
   
            Thread.sleep(10000);
        } catch (InterruptedException e) {
   
            //
        }
    }
}

通过指定 JVM 参数:-Xmx8m -Xms8m 运行以上代码,会抛出 OOM:

Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded 
 at java.util.concurrent.LinkedBlockingQueue.offer(LinkedBlockingQueue.java:416)
 at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1371)
 at com.hollis.ExecutorsDemo.main(ExecutorsDemo.java:16)

以上代码指出,ExecutorsDemo.java 的第 16 行,就是代码中的 executor.execute(new SubThread());。
通过上面的例子,我们知道了 Executors 创建的线程池存在 OOM 的风险,那么到底是什么原因导致的呢?我们需要深入 Executors 的源码来分析一下。其实,在上面的报错信息中,我们是可以看出蛛丝马迹的,在以上的代码中其实已经说了,真正的导致 OOM 的其实是 LinkedBlockingQueue.offer 方法。

Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
 at java.util.concurrent.LinkedBlockingQueue.offer(LinkedBlockingQueue.java:416)
 at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1371)
 at com.hollis.ExecutorsDemo.main(ExecutorsDemo.java:16)

可以发现,其实底层确实是通过 LinkedBlockingQueue 实现的:

public static ExecutorService newFixedThreadPool(int nThreads) {
   
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

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

正确使用:

private static ExecutorService executor = new ThreadPoolExecutor(10, 10, 60L, TimeUnit.SECONDS,
                                                                 new ArrayBlockingQueue(10));

这种情况下,一旦提交的线程数超过当前可用线程数时,就会抛出java.util.concurrent.RejectedExecutionException,这是因为当前线程池使用的队列是有边界队列,队列已经满了便无法继续处理新的请求。但是异常(Exception)总比发生错误(Error)要好。
但是部分alibaba作者更推荐使用guava创建对应的线程池,示例如下:

public class ExecutorsDemo {
   
    private static ThreadFactory namedThreadFactory = new
            ThreadFactoryBuilder()
            .setNameFormat("demo-pool-%d").build();
    private static ExecutorService pool = new ThreadPoolExecutor(5, 200,
            0L, TimeUnit.MILLISECONDS,
            new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new
            ThreadPoolExecutor.
                    AbortPolicy());
    public static void main(String[] args) {
   
        for (int i = 0; i < Integer.MAX_VALUE; i++) {
   
            pool.execute(new SubThread());
        }
    }
}

通过上述方式创建线程时,不仅可以避免 OOM 的问题,还可以自定义线程名称,更加方便的出错的时候溯源。

3.Arrays.asList之后不要调用修改操作

String[] str = new String[] {
    "kuokuo", "quaner" };
List list = Arrays.asList(str);

因为asList返回的实际是一个Arrays内部类,并没有实现集合的修改方法(add/remove/clear)// 当操作修改方法时,会报UnsupportedOperationException。
第一种情况:list.add("yangguanbao"); 运行时异常。
第二种情况:str[0] = "gujin"; 那么 list.get(0)也会随之修改。[涉及栈堆指针操作,修改数组的数据,导致同样引用该数据的list值被改变]
image.png

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。亦推荐如下处理:

private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>() {
    
    @Override 
    protected DateFormat initialValue() {
    
        return new SimpleDateFormat("yyyy-MM-dd"); 
    } 
};
相关文章
|
10月前
|
SQL 机器学习/深度学习 人工智能
MaxCompute SQL + AI:重塑企业智能决策的底层逻辑
阿里云MaxCompute SQL融合AI能力,让一行SQL实现数据清洗、特征工程到模型推理的全链路智能处理。无需切换语言,支持时序预测、向量匹配、NLP等200+算子,助力电商、金融、医疗等行业降本增效,数据不出湖即可完成安全高效的AI闭环,开启SQL驱动的生产力革命。
|
10月前
|
SQL 机器学习/深度学习 人工智能
MaxCompute SQL + AI:重塑企业智能决策的底层逻辑
阿里云MaxCompute SQL推动SQL与AI深度融合,通过内建AI函数实现数据清洗、特征工程到模型推理的全链路智能化。无需切换语言,一行SQL即可完成智能分析,助力电商、金融、医疗等六大行业降本增效,释放数据价值,开启智能生产力新时代。(238字)
278 1
|
12月前
|
负载均衡 Java API
《服务治理》RPC详解与实践
RPC是微服务架构的核心技术,实现高效远程调用,具备位置透明、协议统一、高性能及完善的服务治理能力。本文深入讲解Dubbo实践,涵盖架构原理、高级特性、服务治理与生产最佳实践,助力构建稳定可扩展的分布式系统。(238字)
|
12月前
|
存储 边缘计算 人工智能
数据空间:数字经济时代的“新基建”与信任生态
数据空间不仅是技术架构的革新,更是一场关于信任机制、协作模式与价值分配的深刻变革。它既是技术平台,也是信任生态,更是国家战略基础设施。 未来,随着人工智能、物联网、边缘计算等技术的发展,数据的规模与复杂性将持续增长。唯有构建安全、可信、高效的数据流通网络,才能释放数据的“倍增效应”,驱动经济社会的全面智能化转型。
434 1
|
7月前
|
NoSQL Java 测试技术
全链路压测硬核实战:从方案落地、瓶颈根因定位到全链路性能优化
全链路压测是保障微服务系统稳定性的核心手段,通过模拟真实线上流量,在预发或生产环境验证端到端性能与容错能力,精准暴露跨服务调用、资源竞争等隐藏瓶颈,并提供标准化的定位与优化方案。
927 3
|
12月前
|
Prometheus 监控 Kubernetes
自定义通过helm部署的prometheus+Grafana监控框架
本文基于Helm快速部署kube-prometheus-stack,通过自定义`custom-values.yaml`实现Prometheus与Grafana的精细化配置,涵盖资源限制、持久化存储、服务暴露、告警规则及仪表盘导入,支持K8s集群全方位监控,提升可观测性与运维效率。
733 0
|
安全 数据安全/隐私保护 Windows
如何在Windows 10系统中查看已连接WiFi密码-亲测可用-优雅草卓伊凡
如何在Windows 10系统中查看已连接WiFi密码-亲测可用-优雅草卓伊凡
1594 16
如何在Windows 10系统中查看已连接WiFi密码-亲测可用-优雅草卓伊凡
|
SQL Serverless 测试技术
体验 Serverless StarRocks × Paimon (DLF) 查询 TPC-DS 标准库性能
本文介绍如何通过共享TPC-DS样例数据集,创建并绑定DLF Catalog到StarRocks实例,实现高效数据查询与分析。内容涵盖RAM用户授权、外部Catalog连接、性能测试(如TPC-DS Query 3)、数据写入内外表、物化视图构建数仓DWD-ADS层,以及通过Quick BI进行可视化报表展示的完整流程,助力快速搭建湖仓一体架构。
397 0
|
11月前
|
缓存 JavaScript 安全
Vue 3 Props 响应式深度解析:从原理到最佳实践
本文深入解析 Vue 3 中 `props` 的响应式机制,涵盖单向数据流原理、类型安全声明、性能优化策略及高级应用场景。通过实例讲解解构响应性丢失、深层监听开销、不可变数据处理等常见问题,总结最佳实践与调试技巧,助你构建高效、可维护的组件通信体系。
622 6

热门文章

最新文章