Java 线程池有限大小工作队列 - 不丢弃任务的实现

我们在创建 Java 线程池,无论是用 Executors, ThreadPoolExecutor, 还是 Spring 的 ThreadPoolTaskExecutor, 如果不指定工作队列的大小的话,默认为 Integer.MAX_VALUE(2147483647), 基本不会把它爆满,但是在许多的任务要执行时大量 Runnable 对象的创建却足以把内存撑爆掉。所以才有必要使用一个有限大小的工作队列,如 5000, 再配上 RejectedExecutionHandler(DiscardOldestPolicy, DiscardPolicy, 或 CallerRunsPolicy)。前两种策略会主动放弃最旧最新的任务,一般不是我们想要的,CallerRunsPolicy 还能主动发挥任务提交者的计算能力,是一种不错的选择(只可能会发生工作队列太小且提交者执行的任务太忙时产生线程池一时的空闲。

所以总结起来我们可以有以下几种实现

直接使用 CallerRunsPolicy

在工作队列满时有效利用提交任务的线程,不让它闲着,这种实现最简单, 像下面那样声明线程池

阅读全文 >>

JDBC 设置 PostgreSQL 查询中 any(?) 的参数

这段时间都纠缠于 Java 如何操作 PostgreSQL 数据库上,千方百计的为求得更好的性能。为此我们用上了 Batch, 或用 id = any(?) 这种更 PostgreSQL 化的数组参数操作。其实它还有更多数组方面的花样可以玩,毕竟 PostgreSQL 数据库有一种广纳百川的胸怀,总有好的新特性能在 PostgreSQL 中首先体验到。

回到之前的一篇 postgres in (?,?) 和 =any(?) 用法/性能对比,其中关于如何向查询语句中 id = any(?) 占位符传入数组参数的代码是

在 PreparedStatement(PgPreparedStatement) 中设置数组参数的函数是用 阅读全文 >>

JDBC 批量调用数据库 SQL, 函数与存储过程

继续上一篇数据库相关操作的话题,在有大量的数据操作时(如增删改,甚至调用函数或存储过程),我们应该尽可能的采用批量化操作(先摆下结论,后面我们会看到原由)。想像一下我们要向数据库插入 10 万条记录,如果逐条插入的话,客户端与数据库之间将会有 10 万网络请求响应来回; 而假如以 1000 条记录为一个 batch, 客户端与数据库之间的网络请求响应次数将缩小到 100。 业务数据的内容总量未变,但 Batch 操作除了可重用预编译的 Statement 外还, 可避免每次请求中重复的元数据,所以从 100,000 到 100 的缩减在时效上的表现是非常可观的,有时就是 60 分钟与 1 分钟的区别(在最后面测试结果显示这一差异更为恐怖)。

当然, JDBC 的批处理功能具体还要相应驱动的支持,通过数据库连接的 conn.getMetaData().supportsBatchUpdates() 可探知是否支持批量操作。

API 方面, 在  Statement 接口中定义了如下 batch 相关的操作方法

  1. void addBatch(String sql): 将显式的 SQL 语句编入到当前 Batch 中
  2. void clearBatch(): 清除当前 Batch 列表,以便于建立新的 Batch
  3. int[] executeBatch(): 执行当前 Batch 列表中的语句,返回每条语句受影响行数组成的数组。0 可能表示执行语句无法确知受影响的行
  4. long[] executeLargeBatch(): 当 Batch 中语句受影响行数可能会超过整数最大值时用这个

阅读全文 >>

PostgreSQL 函数与存储过程及调用

PostgreSQL 随着云服务的盛行,越发被广泛的应用,免费开源且有丰富的特性支持,加上性能也很不错,因而备受青睐。PostgreSQL 的函数与存储过程区别并不太大,不像某些数据库的函数与存储过程必须是无副作用或有副作用,在 PostgreSQL 的函数和存储过程中可以进行任何的 SQL 操作。简单列举下 PostgreSQL 的函数与存储过程的区别主要如下:

函数

  1. return 或 out 参数返回值,return 可返回单个值或一系列值(return setof 或 return table), 或返回光标(cursor). 函数 return void 就和存储过程差不多了
  2. 函数因其有返回值,所以可通过 select, insert, updata 或 delete 语句来调用,如 select fn1(), delete * from test1 where fn2(c1)=0
  3. 可以用 execute 执行动态 sql, 如 execute 'delete * from ' || 't1'

存储过程

  1. IN, OUT 或 INOUT 参数,但不直接返回值
  2. 不能用 select, insert 等语句使用
  3. 不能用 execute 执行动态 sql

接下来我们来体验一下 PostgreSQL 的函数与存储过程 阅读全文 >>

Java 9 前/后使用 JAXB (包括支持 javax.* 或 jakarta.*)

使用 Java 处理 Object 与 XML 之间的转换时 JAXB(Java Architecture for XML Binding) 仍然被广泛使用。但随着 Java 9 模块化后把 JAXB 从标准 JDK 中移除后,和 Java EE 8(Jakarta EE 8) 到 Jakarta EE 9 的变迁时命名空间由 javax.* 变成了 jakarta.*,我们在使用 JAXB 时需作出相应的适配。

本文分别使用 Java 1.8, 17,通过 Maven 插件 jaxb2-maven-plugin 的 xjc (从 xsd 文件生成 Java 类), 和如何切换 jakarta.* 命名空间,由此可给我们对使用了 JAXB 的项目升级 JDK 时指明方向。从而不致于因不了解每部分组件的具体功效而在 pom.xml 中胡乱配置,比如之前对 jaxb2-maven-plugin 插件本身配置了多余的 org.glassfish.jaxb:jaxb-xjc 和 org.glassfish.jaxb:jaxb-runtime 依赖,也未能理解 jaxb2-maven-plugin 与 org.glassfish.jaxb:jaxb-runtime 之间的版本对应关系。

 实验准备,创建简单的 Maven 项目,并在路径 src/main/xsd/ 下新建 Schema 文件 sample.xsd,内容为 阅读全文 >>

Windows 安装使用 GCC(mingw-w64)

本人近十来年来本地用 Mac OS 开发, 服务器为 Linux, 为什么又要涉及到 Windows 的 GCC 呢?因为有个跨平台的东西用的是 C++, 需要分别编译出目标平台为 Linux 和 Windows 的二进制文件. 然而 C++ 并没有像 Rust 那样一出生就含着 Cargo 那样的工具链,完美的支持跨平台开发,构建。对于 C++ 代码不得不在 Linux 下用 GCC 编译器(Makefile), 而 Windows 下使用的 Visual Studio 的 MSBuild, 为了能统一用 Makefile 文件 + GCC 的方式编译 C++ 项目, 可选择 Windows 平台下也安装 GCC。

GCC 又是什么呢?它是 GNU 的编译工具集,包括对 C, C++, Objective-C, Foratran, Ada, Go 和 D 等一众语言的支持, 和它类似的工具集有 LLVM。GCC 支持多操作系统平台,怎么找到它的各种二进制安装包呢?我们循着官网去找, 打开 GCC 首页 https://gcc.gnu.org/,从页面的右边栏可找到 阅读全文 >>

Rust 语言学习笔记(五)

终于来到了 Rust 的精髓所在了,那就是使之不依赖于垃圾回收又能保障内存安全且高效运行的所有权系统(Ownership System)。想要用 Rust 做一个稍显规模项目必定绕不过它,所有权系统包括所有权(Ownership), 借用(Borrowing), 生命周期(Lifetimes)。

以下概念的复述基本是从 《Rust编程: 入门, 实战与进阶》一书中而来,那里面有些内容是来自于官方的 The Rust Programming Languge - Understanding Ownership

所有权系统的基本概念

Rust 的编程语法很快就能上手,让学习 Rust 曲线陡然大增的也就是这个所有权系统。所有权检测在编译期完成,Rust 能编译出来的代码就是安全高效的。要理解 Rust 的所有权系统必须首先明白以下两组概念:

  1. 栈内存(Stack),值语义(Value Semantic),按位复制(浅复制)(Shallow Copy),复制语义(Copy Semantic)
  2. 堆内存(Heap), 引用语义(Reference Semantic), 深复制(Deep Copy),移动语义(Move Semantic), 借助(Borrowing)

和其他语言一样,大小固定的所有基本类型都可以存储在栈上,栈上存取数据总是在栈顶操作,很快,而访问堆内存需要搜索内存地址。所有权系统的主要任务是用来跟踪堆上的数据,即引用语义的数据。 阅读全文 >>

从 Rust 官方文档理解 Ownership

Rust 的 Ownership 感觉仍然很复杂,但 Rust 官方文档 The Rust Programming Language - Understanding Ownership 所费篇幅似乎并不多。下面就阅读该文档并记录下来对 Rust Ownership 的理解,相信官方的文档会表述的比准确而清晰。

本文中对 Ownership, Move, Reference, Dereference, Mutable, Immutable, Borrow, Owner, Stack, Heap, Scope 等词不进行翻译,以免走样。同时在阅读过程中不进行过度的联想,不与 C/C++ 的引用, 指针, 指针的指针进行关联,力求做一个完全不会 C/C++ 的 Rust 初学者。

Ownership 是 Rust 独一无二的特性。内存管理一般是两种,显式分配与释放和 GC, 这两种的弊端无需多说。Rust 另辟溪径,用 Owership 的一系列的规则来指导怎么管理内存,编译期保证程序运行期的内存安全性,不影响运行时性能。学习 Rust 的过程中需要很长时间去适应 Ownership, 从 Rust 开发者(Rustacean) 的经验来说是:随着对 Ownership 的掌握,越来轻松自然的写出安全高效的代码(希望如此)。 阅读全文 >>

Rust 语言学习笔记(四)

试手了一下 Rust, 发现止今所学知识尚浅,不少情况处理起来很是茫然,比如经常得到的值是 Result 或 Option, 有时候要 .unwrap(), 有时得后面加个问号,或 .await。还有从自定义函数里返回一个引用都不容易,到处是 move, borrow, owner, lifetime 之类的错误信息。所以说,快充五分钟还真不敢上路,继续学习 Rust 的 Result, 说起它又必须与 Rust 的错误处理联系起来。

编程语言在处理错误无外乎就两种哲学

  1. 得到正确的结果或触发异常,异常不就地处理则向外传播
  2. 总是能得到一个结果,于结果中获知是否有异常,如 Option<T>, Result<T, E>

其实本人更钟爱前一种方式,因为正常逻辑可以更流畅的表达,不至于被众多的 if 语句所打断; 而异常的话可以就地解决,或者延迟到某处去集中处理。Java 多是用第一种方式,所以某些框架尽量使用 Unchecked 异常,不强制插入 try/catch 块。

第二种方式,在 shell 或 C++ 很常见,比如 shell 下每调用一个命令都有返回数值, 即 $?, 0 为正常,非 0 为有错误; C++ 的 GetLastError() 也是一样的意思。

现在所学的 Rust 在处理结果和错误时,采用了第二种方式,不过使用上 Rust 的模式匹配处理起 Option<T>, Result<T, E> 倒也不难,而且 Option<T> 和 Result<T, E> 这样的返回结果还用来向上传播异常,出错时还能追踪到异常栈。 阅读全文 >>

SpringBoot 应用出错 Comparison method violates its general contract!

出现此错误的大致环境如下

  1. SpringBoot 2.7.17, SpringWeb 项目,所引用入的 spring-webmvc-5.3.30, spring 6 已解决
  2. JDK 1.8 或 JDK 17
  3. 依赖了 jackson-dataformat-xml:2.12.6 和 jackson-dataformat-cbor:2.12.6, 它会在 RestTemplate 加上 application/xml, application/cbor 等 Accept 类型
  4. 代码中用 RestTemplate 调用此应用的 Endpoint, 未设置任何头

后面会详细列出能重现此问题的 pom.xml 配置及 Java 代码

在执行

restTemplate.getForEntity("http://localhost:8080/test2", String.class)

时出现如下错误 阅读全文 >>