Java

将虚拟线程与 ScheduledExecutorService 结合使用

1、简介 虚拟线程是 JDK 21 中正式引入的一项杀手锏级别的功能,是提高应用程序性能、吞吐量的一种解决方案。 但是,JDK 没有内置的使用虚拟线程的任务调度器。因此,我们得自己编写使用虚拟线程运行的任务调度器。 本文将带你了解如何使用 Thread.sleep() 方法和 ScheduledExecutorService 类为虚拟线程创建自定义调度器。 2、虚拟线程是什么? JEP-444 中引入了虚拟线程,作为线程类的轻量级版本,提高了应用程序的并发性和吞吐量。 虚拟线程占用的空间比通常的操作系统线程(或平台线程)要少得多。因此,我们可以在应用程序中同时产生比平台线程更多的虚拟线程。毫无疑问,这增加了并发单元的最大数量,也提高了应用程序的吞吐量。 关键的一点是,虚拟线程并不比平台线程更快(任务的耗时不会有改变)。在我们的应用中,虚拟线程的数量只比平台线程多,这样它们就能执行更多的并行工作。 虚拟线程的成本很低,因此我们不需要使用资源池等技术来为数量有限的线程安排任务。相反,我们可以在现代计算机中几乎无限地生成虚拟线程,而不会出现内存问题。 最后,虚拟线程是动态的,而平台线程的大小是固定的。因此,虚拟线程比平台线程更适合小型任务,如简单的 HTTP 或数据库调用。 3、虚拟线程调度 如上所述,虚拟线程的一大优势是体积小、成本低。我们可以在一个简单的机器中有效地生成数百万个虚拟线程,而不会出现内存不足的错误。因此,像使用平台线程、网络或数据库连接等更昂贵的资源那样池化虚拟线程并没有太大意义。 如果使用线程池,就会产生另一种开销,即为池中可用的线程调度任务,这将更加复杂,速度也可能更慢。此外,Java 中的大多数线程池都受到平台线程数的限制,而平台线程数总是小于程序中可能存在的虚拟线程数。 因此,我们必须避免使用带有线程池 API(如 ForkJoinPool 或 ThreadPoolExecutor)的虚拟线程。相反,我们应该始终为每个任务创建一个新的虚拟线程。 目前,Java 并没有提供使用虚拟线程进行调度的标准 API,就像我们使用其他并发 API(如 ScheduledExecutorService 的 schedule() 方法)一样。因此,为了有效地让虚拟线程运行计划任务,我们需要编写自己的 Scheduler(调度器)。 3.1、使用 Thread.sleep() 调度虚拟线程 创建自定义 Scheduler 的最直接方法是使用 Thread.sleep() 方法,让程序在当前线程执行时等待: static Future<?> schedule(Runnable task, int delay, TemporalUnit unit, ExecutorService executorService) { return executorService.submit(() -> { try { Thread.sleep(Duration.of(delay, unit)); } catch (InterruptedException e) { Thread.

Java 中的日期和时间处理类:从传统到现代

1、概览 处理 Date(日期)和 Time(时间)是许多 Java 应用程序的基本组成部分。多年来,Java 在处理日期方面不断发展,引入了更好的解决方案来简化开发者的工作。 2、传统的日期和时间处理类 在 java.time 包出现之前,Java 主要使用 Date 和 Calendar 类来处理日期。尽管它们现在也可以使用,但是有一些缺陷。 2.1、java.util.Date 类 java.util.Date 类是 Java 最初处理日期的解决方案,但它有一些缺点: 它是可变的,这意味着可能会遇到 线程安全 问题。 不支持时区。 它使用了令人困惑的方法名称和返回值,比如 getYear(),它返回的是自 1900 年以来的年数。 许多方法已废弃。 使用无参数构造函数创建 Date 对象,表示当前日期和时间(对象创建时)。 如下,实例化一个 Date 对象并打印其值: Date now = new Date(); logger.info("Current date and time: {}", now); 这将输出当前日期和时间,如 Wed Sep 24 10:30:45 PDT 2024。虽然该构造函数仍然有效,但由于上述原因,这里不再建议新项目使用该构造函数。 2.2、java.util.Calendar 类 由于 Date 的局限性,Java 引入了 Calendar 类,对其进行了改进: 支持各种日历系统。 时区管理。 更加直观的日期操作方法。 我们可以使用 Calendar 操作日期。 Calendar cal = Calendar.

使用 Java 通过 SSH 连接远程 MySQL 数据库

1、概览 Secure Shell(SSH)允许我们安全地访问和管理远程系统,包括执行命令、传输文件和隧道服务。 我们可以通过 SSH 会话建立与远程 MySQL 数据库的连接。Java 有多个 SSH 客户端,其中最常见的是 Java Secure Channel(JSch)。 本文将带你了解如何通过 SSH 会话连接到运行在远程服务器上的 MySQL 数据库。 2、了解 SSH 端口转发 端口转发允许通过在 SSH 连接上将流量从本地端口重定向到远程服务器上的端口,从而实现客户系统和远程服务器之间的数据传输。 当防火墙或其他限制阻止直接连接远程服务器的 IP 和端口时,这一点尤其有用。 在本例中,MySQL 服务器运行在远程服务器的 localhost 上,通常使用 3306 端口。虽然从技术上讲,可以直接连接到远程服务器的 IP 和 MySQL 端口,但出于安全考虑,这通常会受到限制(3306 端口未开放)。相反,我们可以通过 SSH 使用本地端口转发来建立与数据库的安全连接。 在本地端口转发中,我们在本地机器上分配一个可用端口,并将其与远程运行的 MySQL 服务器端口绑定,以允许我们的程序与远程服务器之间进行数据通信。 3、Maven 依赖 首先,在 pom.xml 中添加 JSch 和 MySQL 驱动依赖: <dependency> <groupId>com.github.mwiede</groupId> <artifactId>jsch</artifactId> <version>0.2.20</version> </dependency> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <version>9.0.0</version> </dependency> JSch 提供了 Session 等类,这些类对于建立与远程服务器的 SSH 连接至关重要。此外,MySQL 驱动允许我们与运行中的 MySQL 服务器建立连接。

处理 MySQL 异常:“MysqlDataTruncation: Data truncation: Data too long for column”

1、概览 Java 数据库连接(JDBC)应用编程接口(API)提供了一系列类和接口。我们可以使用它们连接关系数据库等数据源并运行 SQL 语句。 以流行的 MYSQL 为例,当我们需要连接到 MySQL 时,就需要使用 MySQL 数据库的专用 JDBC 驱动程序: com.mysql.cj.jdbc.Driver,该驱动实现了 JDBC API。 在运行 SQL 语句时,我们可能会遇到异常:“com.mysql.cj.jdbc.exceptions.MysqlDataTruncation: Data truncation: Data too long for column”。 本文将带你了解出现此异常的原因,以及解决办法。 2、Schema 设置 首先,创建如下关系表 department: DESC department; +-------+-------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +-------+-------------+------+-----+---------+-------+ | id | int | NO | PRI | NULL | | | name | varchar(50) | YES | | NULL | | | code | varchar(4) | YES | | NULL | | +-------+-------------+------+-----+---------+-------+ 注意,code 列定义只允许使用大小为 4 或更小的 varchar。

使用 Bouncy Castle 签署 CSR

1、概览 签署(也叫做签发)证书签名请求(CSR)是密码学中的一项常见操作。本文将带你了解如何使用 Bouncy Castle 签署 CSR。 2、签署 CSR 签署 CSR 是证书颁发机构(CA)验证 CSR 中的信息并颁发证书的过程。CA 使用其私钥签署证书。签名后的证书可在客户端和服务器之间建立安全连接。 要使用 Bouncy Castle 签署 CSR,需要执行几个基本步骤: 生成可信实体 CA 证书和私钥。 生成证书签名请求(CSR)。 使用 CA 证书和私钥签署 CSR。 3、设置 首先,需要在 pom.xml 中添加 Bouncy Castle 依赖: <dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcpkix-jdk18on</artifactId> <version>1.76</version> </dependency> 接下来,需要创建一个 SecurityProvider 类来注册 Bouncy Castle Provider: static { Security.addProvider(new BouncyCastleProvider()); } 4、使用 Bouncy Castle 签署 CSR 使用 Bouncy Castle 签署 CSR 需要几个步骤。 4.1、生成可信实体 CA 证书和私钥 CA 是向客户签发证书的可信实体。我们必须生成 CA 证书和私钥来签署 CSR。 先生成一个密钥对:

Java 中 interface 和 @interface

1、概览 本文将带你了解 Java 中 interface(接口)和 @interface(注解接口)的区别以及它们的应用。 interface是一个类实现的规范。在最常见的形式中,它是一组相关方法,这些方法没有具体的实现。 而 @interface 则允许你在代码中添加元数据。编译器、工具或框架使用这些元数据来影响类的行为或处理过程。 2、interface interface 是一种规范。它规定了实现类必须实现的行为,但没有规定如何实现。它表明,任何实现接口的类都必须为接口的所有方法提供具体的实现。 public interface Animal { String eat(); String sleep(); } public class Dog implements Animal { @Override public String eat() { return "Dog is eating"; } @Override public String sleep() { return "Dog is sleeping"; } } 接口的所有方法都是默认 public 和 abstract 的(default 方法和 static 方法除外),所有字段都是 public、static 和 final 的。在 Java 中,我们可以使用接口实现抽象、多重继承和松散耦合。 抽象:接口只定义了调用方法所需的基本信息,而实现方法的复杂性则被隐藏起来。 多重继承:一个类可以实现多个接口,从而避免了在允许类多重继承的语言中可能出现的 “菱形继承” 问题。 松耦合:接口在功能和实现细节之间提供了明显的分离。由于我们对方法和签名进行了单独定义,它使一个类可以改变其内部流程,而不影响其用户。 3、@interface 在Java中,我们使用 @interface 来声明注解类型。注解提供了一种向 Java 代码元素(如类、方法和字段)添加元数据的方式。因此,工具和库可以利用这些元数据在编译过程或运行时收集信息,以进行代码处理。

使用 Stream API 处理 JDBC ResultSet

1、概览 通常,我们使用遍历从 JDBC ResultSet 中迭代检索到的数据,不过在某些情况下,我更喜欢用 record Stream。 本文将带你了解使用 Stream API 处理 JDBC 结果集的几种方法。 2、使用 Spliterators 首先是纯 JDK 方法,使用 Spliterators 创建流。 首先,为实体定义一个 Model: public record CityRecord(String city, String country) { } 在 CityRecord 中,我们存储了有关 city(城市)及其 country(国家)的信息。 接下来,创建一个能与数据库交互并返回 Stream<CityRecord> 的 Repository: public class JDBCStreamAPIRepository { private static final String QUERY = "SELECT name, country FROM cities"; private final Logger logger = LoggerFactory.getLogger(JDBCStreamAPIRepository.class); public Stream<CityRecord> getCitiesStreamUsingSpliterator(Connection connection) throws SQLException { PreparedStatement preparedStatement = connection.

Java JMS 读写 IBM MQ 队列

1、简介 本文将会带你了解如何使用 Java JMS(Java Message Service)从 IBM MQ 队列读写消息。 2、设置环境 我们可以在 Docker 容器中运行 IBM MQ,以避免手动安装和配置的复杂性。 使用以下命令以基本配置运行容器: docker run -d --name my-mq -e LICENSE=accept -e MQ_QMGR_NAME=QM1 MQ_QUEUE_NAME=QUEUE1 -p 1414:1414 -p 9443:9443 ibmcom/mq 接下来,需要在 pom.xml 文件中添加 IBM MQ 客户端: <dependency> <groupId>com.ibm.mq</groupId> <artifactId>com.ibm.mq.allclient</artifactId> <version>9.4.0.0</version> </dependency> 3、配置 JMS Connection 首先,我们需要用 QueueConnectionFactory 建立 JMS Connection(连接),用于创建与队列管理器(Queue Manager)的连接: public class JMSSetup { public QueueConnectionFactory createConnectionFactory() throws JMSException { MQQueueConnectionFactory factory = new MQQueueConnectionFactory(); factory.setHostName("localhost"); factory.setPort(1414); factory.setQueueManager("QM1"); factory.setChannel("SYSTEM.DEF.SVRCONN"); return factory; } } 首先创建一个 MQQueueConnectionFactory 实例,用于配置和创建与 IBM MQ 服务器的连接。我们将主机名设置为 localhost,因为 MQ 服务器是在本地 Docker 容器内运行的。暴露的映射端口为 1414。

NetBeans Profiler 的编程式用法

1、概览 对应用程序进行分析可以深入了解其运行时的行为。Java 生态系统中有多种流行的分析器(Profiler),如用于通用分析的 NetBeans Profiler、JProfiler 和 VisualVM。 本文将带你了解如何以编程方式使用 NetBeans profiler API。 2、NetBeans Profiler NetBeans IDE 提供免费的分析器来分析 Java 应用。它通过 IDE 中直观的嵌入式用户界面,提供了评估 CPU 性能和内存使用情况的功能。 然而,NetBeans Profiler 还提供了可用于编程式的分析 API。这可用于 Heap Dump 的自动化分析,而不需要依赖于 UI 界面。 Heap Dump(堆转储)是一段时间内应用的内存快照。它是深入了解内存使用情况的良好指标,因为它包括内存中的实时对象、对象的类和字段以及对象之间的引用。 3、示例项目 要使用 NetBeans Profiler API,首先在 pom.xml 中添加 依赖: <dependency> <groupId>org.netbeans.modules</groupId> <artifactId>org-netbeans-lib-profiler</artifactId> <version>RELEASE220</version> </dependency> 该依赖提供了 JavaClasses 和 Instances 等各种工具类,以帮助我们分析类、创建的实例数量和使用的内存。 接着,创建一个简单的项目并分析它的 Heap Dump: class SolarSystem { private static final Logger LOGGER = Logger.getLogger(SolarSystem.class.getName()); private int id; private String name; private List<String> planet = new ArrayList<>(); // 构造函数 public void logSolarSystem() { LOGGER.

Java 反射中的 AccessFlag(访问标志)

1、概览 Java 中的反射是一个强大的功能,它允许我们操纵不同的成员,如类、接口、字段和方法。此外,使用反射,我们可以在编译时实例化类、调用方法和访问字段,而无需知道类型。 本文将带你了解如何使用 JVM AccessFlag(访问标志),以及 Modifier 和 AccessFlag 之间的区别。 2、JVM AccessFlag Java 虚拟机规范 定义了 JVM 中已编译类的结构,它由一个 ClassFile 组成: ClassFile { u4 magic; u2 minor_version; u2 major_version; u2 constant_pool_count; cp_info constant_pool[constant_pool_count-1]; u2 access_flags; u2 this_class; u2 super_class; u2 interfaces_count; u2 interfaces[interfaces_count]; u2 fields_count; field_info fields[fields_count]; u2 methods_count; method_info methods[methods_count]; u2 attributes_count; attribute_info attributes[attributes_count]; } 除其他项目外,ClassFile 还包含 access_flags 项。简而言之,access_flags 是一个掩码,由定义类的访问权限和其他属性的各种标志组成。 此外,ClassFile 还包括 field_info 和 method_info 项,每个项都包含其 access_flags 项。 Javassist 和 ASM 等库使用 JVM AccessFlag 来操作 Java 字节码。