Leon Ding Blog - Java编程
type: channel
link: https://blog.leonding.me/category/java.html
description: 收录 Java 平台技术栈
items: Go 语言设计丑陋之处
items: Java 原生化的未来 Graal AOT 编译器
items: 使用 WSL + VS Code 现代 Java 开发
items: 函数式编程的思想和优势
items: Java 虚拟机 JVM 内部设计
items: 结构化并发程序设计
items: 在 RHEL9 搭建上 Java 环境
items: 配置高效的 intellij IDEA 环境

Go 语言设计丑陋之处
type: item
date: 2024-11-03T02:24:00+08:00
description: 在互联网上经常可以看到程序员因为编程语言的选择而争论不休，常常出现类似 “XXX vs XXXX” 的评测文章，同时也不排除一些语言布道师或商业公司在背后炒作某种语言的热度，来达到某种商业目的。大多数文章的评测重点通常是程序的执行效率和内存占用，很少从语言特性、语法或语义的角度进行深入分析和讲解，我这篇文章将从这个角度来讨论 Go 语言设计丑陋之处。在我编程生涯中使用过很多门编程语言，这其中就包括所谓的大道至简的 Go 语言，总体来说使用 Go 语言来编写逻辑是非常容易的，天生支持协程，采用 CSP 和 GMP 模型来设计，极度克制的语法和运行时并发模型，显著降低了高并发程序的工程门槛，使得开发高并发应用也非常简单。它的语法简单和 25 关键字非常容易掌握，能使程序员快速掌握并且编写代码逻辑，并不意味着 Go 的设计有多么好。反而在我使用 Go 编程过程中出现很多感觉很多丑陋的设计，而不像国内某些 Go 语言布道师吹崇它多么多么好，我个人感觉哪些很多布道师从 PHP 转换过来的，就没有写过其他高级的编程语言，例如 Kotlin 和 C# 、Rust 或者新版本的 Java 等... 当然作为某个编程语言的布道师可能说这门编程语言有多么多么好，这样才能忽悠到没有任何经验新人去学习这门编程语言。如果读者是 Go 语言的粉丝，这篇文章的内容会让你感到非常不适，乃至会让你感到作呕，但能让读者明白好的设计和相关问题在不同编程语言是如何解决的。空值 nil 和错误处理在计算机科学史上，很少有设计决策像 null 这样，既普遍存在又饱受诟病，它几乎出现在所有主流编程语言中，却也是无数程序崩溃、漏洞与安全事故的根源。这一现象并非偶然，与 C 语言的历史地位密切相关，由于 C 语言诞生于基础软件快速发展期，其设计选择在早期被广泛接受并固化下来，null 作为 “无对象” 未分配的内存的默认表示方式也随之成为事实标准。随后伴随着大量基础软件、操作系统内核、运行时库、编译器以及工具链都以 C 语言或其内存模型为基础构建，导致 null 这一设计被层层继承和放大，深度嵌入整个软件生态之中。众多编程语言实现自身编译器、虚拟机和运行时系统都是以 C 语言为依赖基础，null 不再只是某一门语言的设计细节，而演变为跨语言、跨系统的结构性问题，持续影响着后续数十年的语言设计与软件工程实践。Go 程序的错误处理，在编写逻辑复杂并且存在多种复杂异常的情况时会出现写出满屏 if err != nil，这个和 C 语言的一脉相承的，C 语言使用是错误码，Go 的错误处理只是添加一些上下文描述，并没有附加堆栈信息，例如下面的代码： if len(files) <= 0 { // 它这个错误设计要不停写 if err != nil err := lfs.createActiveRegion() if err != nil { return err } return nil }但一般程序员会自己优化掉，写成下面的这种方式：// return lfs.createActiveRegion()可以直接这么写，但是会丢弃错误堆栈处理的上下文，特别是在处理一些比较细节的错误分类的时候。Go 的错误处理机制中，错误值通过返回值 return 在调用栈中逐层向上冒泡传递，每个函数在检测到错误时，通常会将错误返回给上层调用者，由上层决定如何处理。这对于喜欢函数编程的程序员是一个很糟糕的设计，例如下面代码： sql2 := gsql.SelectAs([]string{"name", gsql.As("age", "年龄"), "id"}).From(UserInfo{}).ById(2) // SELECT name, age AS '年龄' FROM UserInfo WHERE id = 2 t.Log(sql2) sql3 := gsql.SelectAs(gsql.Alias(UserInfo{}, map[string]string{ "name": "名字", })).From(UserInfo{}).ById(1) // SELECT id, name AS '名字', age FROM UserInfo WHERE id = 1 t.Log(sql3)这段代码核心功能是我编写的一个用于 Go 的结构化查询语言代码生成器，类似于一个 SQL 在 GO 语言中 DSL 抽象实现，可以生成出 SQL 查询语句的字符串。但在调用这个 gsql 包提供的各种函数和 API 过程中会出现一个问题就是每个函数逻辑复杂有其他逻辑分支，可能会出现 error 的情况，因此按照 Go 的错误处理设计，每个函数必须返回其函数内部的 error 作为上下文，传递出去方便在出错时定位到问题。[s…

Java 原生化的未来 Graal AOT 编译器
type: item
date: 2024-07-17T22:35:00+08:00
description: Java 这门语言与生俱来的显著特性就是“一次编译，到处运行”，这种功能得益于 JVM 平台的支持，Java 程序通常通过将其打包为 JAR 或 WAR 包，并依赖 JVM 和 Servlet 容器来运行。其底层运行时 JVM 采用 JIT（即时编译）模式来执行程序代码，JVM 会在运行时进行编译优化和动态执行代码，这通常会导致较高的内存占用。这样的好处是采用 JIT 可以热更新和热部署程序，并且 JVM 可以在运行期间对程序进行动态分析，来实时优化程序以达到最好的性能状态。Cloud Native 面临的问题随着云计算的发展，服务端程序的运行和部署方式发生了巨大变化，特别是在云原生（Cloud Native）场景下，服务器程序被打包成一个个容器运行。在这种环境下，传统方式容器化的 Java 程序面临着打包后的镜像文件过大的问题。镜像文件中需要包含 JRE 和各种第三方库，导致镜像体积庞大，占用更多的磁盘空间。目前云计算的计费方式也发生了改变，采用按需分配的实时计费方式，根据程序的内存占用和运行时间计费。这与传统 IDC 自托管式机房中长时间运行服务器程序形成鲜明对比，按需分配的计费方式显然更具性价比，因此优化 Java 程序在容器环境中的运行方式变得尤为重要。Serverless 面临的问题传统的 JVM 工作原理要求程序员先将编写的 Java 程序打包为 JAR 或 WAR 包，这是普通 Java 开发者所能接触到的程序编译和构建阶段。然而当程序运行起来之后，底层的 JVM 会进行许多复杂的操作，包括内存管理、垃圾回收、即时编译等。需要先启动 JRE 中的 JVM 来加载 Tomcat 服务器所需的字节码，再加载 WAR 包中的字节码文件到 Servlet 容器中运行服务，这个过程非常耗时。下图为整个传统 Java 程序从源代码到程序运行各个阶段的工作流程：下图为传统的 Java 运行时执行程序的生命周期，首先应用的开发者将 *.java 源代码编译为 bytecode 和 jar 依赖包通过 maven 构建打包为一个单独的 jar 包。当执行 java -jar xxx.jar 命令时，首先要启动 JRE 中的 JVM 程序，因为 JVM 本身也是一款单独程序对应着 $JAVA_HOME\bin 目录中的 java ，当 JVM 启动之后会在系统中创建一个进程，将要运行的 jar 程序包和 JRE 标准库中 class 文件加载入 JVM 内存中，并且找到 main 方法开始解释执行。当程序运行一段时间之后 JVM 充分获取了 Java 程序的一些执行数据信息，开始对程序进行 JIT 及时编译优化程序，当程序被 JIT 优化之后的程序才能达到和 C/C++ 这种直接编译为 Native Code 程序的性能。在云计算推崇的 Serverless 架构场景下，Java 的 JIT 模式并不适合，Serverless 应用程序需要能够快速启动以响应事件驱动的请求，并且在空闲时自动关机销毁，释放在服务请求期间所使用的内存。然而，这种需求与传统的 Java 程序运行模式背道而驰，使得 Java 在 Serverless 场景中显得不适用，这就是被业界称为的 冷启动 问题。为了解决这一痛点，Oracle Labs 推出一款新的 JDK 产品 GraalVM JDK ，相比传统的 JDK 该产品内置 Java 静态编译工具和一款新的虚拟机，和传统的 JVM 不一样 GraalVM 是支持多语言进行混合编程的虚拟机，这也是它一大亮点。GraalVM 中的静态编译工具不仅是支持将 Java 编译为二进制可执行文件，还支持 JavaScript 和 Python 等其他语言的编译优化功能，本文只关注于 GraalVM 对 Java 程序进行 AOT（Ahead-Of-Time）编译的解决方案。使用 GraalVM 对 Java 程序进行 AOT 编译的解决方案，可以显著缩短启动时间并降低内存占用，使 Java 程序更适合在 Serverless 环境中运行，通过 AOT 编译，编译器生成一个 Native Code 二进制文件，即对应平台架构的机器代码程序。这种方式无需像 JIT 那样依赖于 JVM 运行，在内存和磁盘占用上有显著改善。下图为 AOT 编译工作流程，在编译阶段会程序进行静态分析，通过它内置工具分析 Java 源代码中依赖关系，将其所有依赖和代码执行逻辑都进行提前编译为机器代码，缺点也很明显可能会失去在 JIT 模式下的动态反射功能。如上图，在 AOT 模式下将在运行时的过程放到在程序构建阶段，构建阶段会对 Java 程序执行静态代码分析和依赖可达性分析，将其所有的依赖软件包都编译生产所属平台对应的可执行二进制文件。AOT…

使用 WSL + VS Code 现代 Java 开发
type: item
date: 2024-06-17T16:55:00+08:00
description: 在我的 Java 编程生涯中使用过很多 Java 开发环境，从编写第一个 Java 程序使用的 Notepad++ 到 Eclipse 再到 IntelliJ IDEA。能编写 Java 的 IDE 产品有很多，如何选择一个好的 Java 开发环境？这对于从事 Java 程序设计的程序员来说是一个重要的问题。选择一个合适的 IDE 不仅能提高开发效率，还能减少调试和排错的时间。本篇文章将分享一些选择 Java 开发环境的关键考虑因素，并介绍如何通过 WSL + VSCode 打造一个混合开发基础 IDE 环境来开发 Java 程序。从事 Java 程序开发的程序员主流都是做服务器端程序开发。在编写程序项目源代码阶段，通常使用 Windows 系统的 PC 进行开发工作，而当程序部署上线之后，往往运行在 Linux 系统环境中。这就要求我们不仅要熟悉 Windows 系统，还要熟悉 Linux 系统，才能更好地编写和调试 Java 程序。这也是选择开发环境时需要考虑的一个重要因素，因为一个好的 IDE 应该能够方便地在不同操作系统之间切换，并提供良好的跨平台兼容性。目前主流的 Eclipse 和 IntelliJ IDEA 都是重量级的 Java 集成开发环境，并且针对不同系统架构开发了对应的版本。程序员在 IntelliJ IDEA for Windows 版本下开发，就必须使用 Windows 下的一些开发和调试技巧，而在 IntelliJ IDEA for Linux 中又需要掌握一些 Linux 下特有的功能和设置。那么有没有一种折中的解决方案，可以在 Windows 开发 Java 程序并且能获得 Linux 系统中的一些开发体验和特有功能呢？答案是有的。[scode type="green"]使用 Microsoft 推出的轻量级 IDE 产品 VSCode 和最新版本 Windows 系统所提供的 WSL（Windows Subsystem for Linux）功能就可以实现这种开发模式。我个人认为此种开发方式是更符合现在 Java 程序开发的方式，虽然 JVM 为 Java 程序提供强大跨平台能力，能让 Java 程序一次编写到处运行，但在开发阶段会不可避免的使用所在的开发机上特有命令和功能。对于需要跨平台开发和调试的需求，使用 VSCode 与 WSL 的组合可以在 Windows 环境中提供类似 Linux 的开发体验，能在够让 Windows 和 Linux 系统之间文件和命令互相操作，这种方式不仅简化了环境切换还能提高开发效率，目前最新版本的 IntelliJ IDEA 的 UI/UX 设计越来越像 VSCode 了，所以个人还是更推荐 VSCode 作为基础的 IDE 产品。[/scode]基础 WSL 配置使用 WSL 和 VSCode 作为基础开发环境，最低要求操作系统为 Windows 10。如果使用 Windows 11 则更为理想。在 Windows 11 系统中，WSL 功能已默认开启，但要正常使用还是需要一点额外的操作。WSL 本身不提供完整的 Linux 系统功能，只是提供了一个与 Linux 兼容的内核接口层和虚拟化技术在轻量级虚拟机 VM 中运行 Linux 内核，再配合其兼容层的功能使用户能够无缝结合使用 Windows 和 Linux 的功能。[scode type="yellow"]默认情况下，WSL 并未内置 Linux 系统镜像，需要开发者自行选择和安装相应的 Linux 发行版。[/scode]可以通过在终端中输入以下命令来查看 WSL 官方提供的 Linux 发行版：# 查看能安装的 WSL Linux 版本 wsl --list --online通过列出的版本信息，建议使用默认的 Ubuntu 即可，按照指定的 Linux 发行版本到 Windows 11 系统中：# 指定对应的 Linux 版本进行安装 wsl --install -d Ubuntu通过以上命令就可以安装 Linux 发行版本到 Windows 系统中，命令执行流程如下图：当有多 Linux 版本环境需求时，可以进行多个版本的选择性安装，同样使用 wsl --install -d <发行版本名称> 来获取相应的版本安装，查看已经安装的 Linux 发行版本可以使用 wsl --list --verbose 命令查看，下面是一组常用的命令：功能命令安装指定的版本wsl --install -d <发行版本名称>查看已经安装的版本wsl --list --verbose选择启动指定的 Linux 发行版wsl -d Ubuntu更新 WSL 程序wsl --update查看 WSL 状态wsl --st…

函数式编程的思想和优势
type: item
date: 2024-04-15T01:37:00+08:00
description: 自从高中开始学编程已经使用过好几门（PL）编程语言了，目前主流最多使用的编程语言 C/C++ 、Java 、JavaScript 这些都属于结构式和命令式编程语言。但在 Java 在 8 版本发布之后引入了 Lambda 表达式方式对函数式编程的支持，从现代的 Java 设计角度上来说并不是一个纯粹的结构和命令式编程语言了，个人将其归属为一个综合性很强的多编程范式的语言。在过去一年的重新学习了基于 ES6 版本的 JavaScript 之后，让我对函数式编程语言更为感兴趣了。在此之前使用过 Go 语言的实现过一些类似于函数式编程的代码，但 Go 在语言设计的时候并没有添加三目运算符和 Lambda 表达式，使其在编程体验上逊色于 JavaScript 和 Java8 这类原生支持三目运算符和 Lambda 表达式的函数式语言，目前我已经从完全放弃了 Go 语言传统命令式进行编程了，除了几个极少历史遗留开源项目正在使用它进行维护之外。如果读者正在考虑深入学习一门 PL 编程语言的话，我建议学习并且使用现代 Java21 之后的版本，现代 Java 已经添加很多新特性，这篇文章我会介绍一下函数式编程的优势。面向过程程序员日常开发中使用的最多编程方式是面向过程和面向对象，面向对象 OOP 是从 C 语言的面向过程演变发展设计出来的；面向过程取决于程序员如何设计一个好的数据结构和用编写好的函数去操作数据完成逻辑计算，在面向过程编程代码逻辑过程中，函数是主要的组织单元，程序的执行是一系列的函数调用，使用结构体来组织数据，但是能操作这些结构体仍然是通过函数来进行的。[scode type="yellow"]编程范式最典型的例子，生活中一件事情抽象到计算机程序如何做？例如一个抽象的问题：如何把一头大象放到冰箱里面该如何做？换到人类的思维第一反应是怎么可能会有冰箱能装得下一头大象？在计算机里你只要想将具体逻辑告诉它，它能可以按照你逻辑去做某件事情，它很笨不会考虑这么多其他因素，不能去考虑大象是不是比冰箱大，除非在装填函数中编写了一些逻辑来检查是否合规。[/scode]下面就是使用 C 语言就编写一个 3 个函数把大象结构放到冰箱结构里，每个步骤对应着一个操作函数，面向过程的窜行代码：#include <stdio.h> #include <stdbool.h> // 定义大象的结构体 struct Elephant { char name[20]; int weight; }; // 定义冰箱的结构体 struct Refrigerator { bool isOpen; bool hasElephant; }; // 函数原型 void openRefrigerator(struct Refrigerator *fridge); void closeRefrigerator(struct Refrigerator *fridge); void placeElephantInRefrigerator(struct Elephant *elephant, struct Refrigerator *fridge); int main() { // 创建大象对象 struct Elephant elephant = {"大象", 5000}; // 创建冰箱对象 struct Refrigerator refrigerator = {false, false}; printf("将大象放到冰箱里面：\n"); // 打开冰箱 openRefrigerator(&refrigerator); // 将大象放到冰箱里 placeElephantInRefrigerator(&elephant, &refrigerator); // 关上冰箱 closeRefrigerator(&refrigerator); return 0; } // 打开冰箱的函数 void openRefrigerator(struct Refrigerator *fridge) { printf("打开冰箱门\n"); fridge->isOpen = true; } // 关闭冰箱的函数 void closeRefrigerator(struct Refrigerator *fridge) { printf("关闭冰箱门\n"); fridge->isOpen = false; } // 将大象放到冰箱里的函数 void placeElephantInRefrigerator(struct Elephant *elephant, struct Refrigerator *fridge) { if (fridge->isOpen) { printf("将 %s 放进冰箱\n", …

Java 虚拟机 JVM 内部设计
type: item
date: 2023-03-09T23:06:00+08:00
description: 大多数开发者或多或少都使用过 Java 这门编程语言，Java 这门编程语言最大的特性就是一次编写到处运行，这就使得开发者在编写了 Java 源代码之后通过 Java Compiler 编译出来的程序就可以在不同平台架构上运行。不管是 x86 还是 ARM 的 CPU 都运行正常无误，熟悉计算机 CPU 架构都知道不同 CPU 核心使用的不同架构和指令集，如果想要编写一次程序到处运行那就得需要使用交叉编译来完成，但是 Java 打破这一点，这得益于 JVM 的存在，Java 源代码不是将源代码直接编译成为对应平台机器码，而是自己定义一套指令集规则也就是对应的 Java Bytecode 标准，再通过将其字节码文件放到 JVM 上运行，最终 JVM 充当一个翻译员将其通过 JIT 的方式运行程序。[scode type="green"]JVM 有自己完善的硬体架构，如处理器、堆栈、寄存器等，还具有相应的指令系统。JVM 屏蔽了与具体操作系统平台相关的信息；本质上就是对现有的物理硬件的软件抽象的实现，可以作为一种编程语言的虚拟机，实际上不只是专用于 Java 语言，只要生成的编译文件符合 JVM 对加载编译文件格式要求，任何语言都可以由 JVM 编译运行。目前实现这些标准虚拟机有很多，例如 HotSpot 、 JRockit 、 IBM OpenJ9 、 GraalVM 这些都是相关的虚拟机实现产品。[/scode]Java 跨平台原理编写 Java 程序的源文件都是以 .java 为后缀名的文件，下面则为一个 Java 源代码文件要想运行它必须通过 Java 编译器来生成对应的字节码 .class 文件，从而让不同平台的 JVM 加载 .class 文件来运行程序：这里值得注意的是 Java 现在每 6 个月就有一次更新，每次都会有新的语言特性添加，不同版本的 Java 编译器可能生成的代码文件在不同 JVM 有不同效果，例如低版本的 JVM 不能运行高版本的 Java 编译器生成的 .class 文件，反过来则是可以的，不过这些都是小问题可以通过 AOT 编译成对应平台机器码运行，也可以使用一些第三方提供程序版本代码迁移工具 EMT4J 来完成。在 JDK 整个开发工具包中其实有两套编译器，大多数程序员所使用的 javac 编译器，此编译器只是将编写的 java 源文件编译生成为 JVM 能认识的字节码；而在 JVM 中又有一套 JIT 编译器，针对的是更高性能程序场景使用的，会把字节码再次编译成为机器码。JVM 主体架构当 Java 程序需要运行的时，此时就得需要 JVM 了，JVM 内部大致会和下图接近，首先前面通过 Java Compiler 生成的 .class 文件会把加载到虚拟机内存中，首先头一次执行是通过 JVM 内置 Interpreter 解析执行代码，这个过程 JVM 的解析器会把字节码里面指令翻译成对应平台的指令进行运行。另外 JVM 中内置另外 2 个 JIT Compiler 分别为 C1 和 C2，这两个即时编译器是针对长时间运行的热点代码进行优化的编译器，JVM 的设计者在设计的就考虑到一些程序代码运行效率的问题，前面提到的通过解析器执行代码性能肯定不能和 C 这种原生编译成机器码的程序性能相比，所以设计者针对热点代码会采用 C1 和 C2 编译器进行编译较为底层次的机器码来执行。[scode type="yellow"]使用解释器执行时，当某一行代码被执行了几次，这行代码会被打上 warm 的标签，当某一行代码被执行了很多次，这行代码会被打上 Hot🔥 的标签成为热点代码，这和 JVM GC 分代设计方式类似，通过分代来解决不频繁 GC 放到老年代。[/scode]在运行时将热点函数编译为汇编代码，当程序再次运行到经过实时编译的函数时，就可以执行经过编译和优化的汇编代码，而不再需要解释执行了。由于编译是在运行时进行的，因此 JIT 编译器可以获得代码实际运行的路径、热点和变量值等信息，基于此可以做出非常激进的编译优化，从而获得执行效率更高的代码。何为热点代码？例如下面有一段代码就为热点代码，当然现实中程序热点代码可能更为复杂：public class HotSpotExample { public static void main(String[] args) { long startTime = System.currentTimeMillis(); int iterations = 100000000; for (int i = 0; i < iterations; i++) { // 热点代码块 int result = compute(i); if (result % 2 == 0) {…

结构化并发程序设计
type: item
date: 2023-03-08T01:21:00+08:00
description: 什么是结构化并发？日常开发中我们编写的最多就是多线程程序，服务器端应用更是如此，传统的方式都是依靠着操作系统提供的 1:1 线程方式进行请求处理这对于管理和复用线程有很多挑战，如果一个普通线程大小 8MB 那么开启 1000 个线程，几乎是无法完成的，并且管理这些线程的状态也是很复杂的。今天这篇文章要介绍的是结构化并发，就是为解决并发编程中线程并发任务管理，传统的方式非常容易造成管理混乱。结构化并发解决的问题就是对统一的任务和统一作用域下的任务进行管理，可以统一启动和统一关闭，如果读过我之前的 Linux 进程组那篇文章的话，就完全可以理解是什么意思了，文章地址：Linux 进程树。在了解结构并发编程范式之前得先讲讲编程语言流程控制发展史，了解一件事的全部应该是去了解完整的历史，并且要找到正确的资料和原版资料去了解，而不是已经修改几个版本的资料，让我们回顾编程语言的一些历史：早期如果想在计算机上写程序必须使用很低级的编程语言去写程序，例如汇编语言，通过一条一条硬件指令去操作计算机，并且顺序执行的，这种编写程序的方式真是令人头疼的。这就使一些计算机界大佬想去重新设计一些编程语言，当时一些美籍计算机科学家们 John Warner Backus 和 Grace Hopper 开发了 Fortran 和 FLOW-MATIC初代的编译命令式编程语言，最后在这些基础之上开发了商业通用编程 COBOL 语言。有趣的事情是世界上的第一个 Bug 也是 Grace Hopper 所发现的，当时的计算机(Harvard Mark II)体积还很大。当时这台计算机在运算的时候老是出现问题，但是经过排查编写的程序指令是没有问题的，最后发现原来是一只飞蛾意外飞入电脑内部的继电器而造成短路如下图所示，他们把这只飞蛾移除后便成功让电脑正常运作，这就是世界上第一个计算机程序BUG。早期的 FLOW-MATIC 是第一种使用类似英语的语句来表达操作的编程语言，会预先定义输入和输出文件和打印输出，分为输入文件、输出文件和高速打印机输出，下面是一段程序代码的例子：看完上面的实例，会发现和现在开发者所使用的更高级的 Java 或者 C 语言还是有一些差距的，例如没有函数代码块，没有条件控制语句，在 FLOW-MATIC 被推出的时候这些现在高级语言的特性还没有被发明出来，在当时看来 FLOW-MATIC 应该是能满足编写程序需求。设想一下如果和输入指令一条一条执行程序是不是很麻烦，如果不能复用一些以有编写逻辑那就要重新编写一些代码逻辑会很费时费力，所以 FLOW-MATIC 的设计者在语言加入了 GOTO 语句块， goto 可以让程序在执行的时候执行到了 goto 然后去执行指定位置的代码块，本质上还是非结构化编程，不过可以做到程序的代码复用和重执行， goto 的加入 FLOW-MATIC 之后如下程序执行流程图：FLOW-MATIC 执行语句通常都是顺序执行的，但是下面这种情况就会发生跳转操作，它可以直接将控制权转移到其他地方，例如下面从 8 行跳转到第 4 行。极少量的 goto 语句是很清晰的，但是令人头疼的问题是程序代码逻辑量变多了之后就会产生很多无法通过正常人类思维所理解的代码跳转逻辑，并且跟踪代码的逻辑很困难。这种基于跳转的编程风格是 FLOW-MATIC 几乎直接从汇编语言继承而来的。它功能强大，非常适合计算机硬件的实际工作方式，但直接使用它会非常混乱。像上面图片中的箭头箭头太多了，就发明 Spaghetti Code 一词的原因，代码逻辑存在各种飞线关系，揉成一坨的代码逻辑。显然我们开发者需要更好的流程控制设计，而不是让代码逻辑写出来像意大利面条一样。当然目前讨论的话题是编程语言的结构化编程设计问题，这个不是本篇文章的重点，本篇文章更偏向的是一些编程语言在线程并发状态转播和控制管理上的一些问题，下面正式开始正文内容。非结构化并发介绍了早期编程语言中的 goto 关键字，可以在当前的执行控制流中开一个分支去执行另外的操作，和我们现在在高级编程语言中使用的 Thread 差不多，例如下面代码：package main import ( "fmt" "time" ) func f(from string) { for i := 0; i < 3; i++ { go fmt.Println(from, ":", i) } } func main() { f("direct") go f("goroutine") go func(msg string) { fmt.Println(msg) }("going") time.Sleep(2 * time.Second) fmt.Println("done") }在线运行代码地址： https://go.d…

在 RHEL9 搭建上 Java 环境
type: item
date: 2023-02-01T12:44:00+08:00
description: 最近由于 Java 整个社区正在朝着 Cloud Native 方向发展，本人已经好几年没有编写 Java 应用程序。最近准备捡起来玩玩，准备在一台 RHEL9 服务器配置一些 Java 的运行环境，这里运行环境可以是 JRE 但是我选择的整个 JDK 工具包的方式安装，另外 jar 依赖管理采用的是 Maven 所以还需要配置一些 Maven 开发环境。JDK 配置这里由于 Oracle 官方的 JDK 是收费的（如果是个人开发者使用是免费的），这里我采用的 Amazon Corretto JDK 发型版本，基于 OpenJDK 的发行版本有很多。下载 Corretto JDK 的话去 Amazon Corretto 官方网站下载，Linux 下使用 wget 命令下载即可：wget https://corretto.aws/downloads/latest/amazon-corretto-17-aarch64-linux-jdk.rpm这里使用的 RedHat Package Manager 软件包的方式，如果人 tar 包需要自己解压到指定位置，RPM 包直接使用命令安装即可：# 安装软件显示信息和进度条 rpm -ivh amazon-corretto-17-aarch64-linux-jdk.rpm # 支持直接从http和ftp协议拉取下载并且安装 rpm -ivh https://corretto.aws/downloads/latest/amazon-corretto-17-aarch64-linux-jdk.rpm使用的 RPM 默认其实可以不用配置 Java 环境变量信息的，但是部分比较老使用 Java 编写的软件可能会需要查找环境中变量信息，推荐还是配置一下即可：# 配置 Java 环境信息 vim /etc/profile在末尾最后一行添加下列信息，如下：# JAVA_HOME Java程序安装目录 export JAVA_HOME="/usr/lib/jvm/java-17-amazon-corretto" # 运行时需要CLASS信息 export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar # 运行时 export JRE_HOME=/usr/lib/jvm/jre # 导出到环境变量中 export PATH=$JAVA_HOME/bin:$PATH添加完成之后使用 source /etc/profile 刷新系统环境配置信息。Maven 配置光有 JVM 运行环境是不行的，如果要从源代码做自动化构建的话还需要 Maven 环境支持，直接去 Apache Maven 官网下载安装包，命令如下：# 通过 wget 下载安装包 wget https://dlcdn.apache.org/maven/maven-3/3.8.7/binaries/apache-maven-3.8.7-bin.zip # 解压安装到指定目录 unzip apache-maven-3.8.7-bin.zip -d /usr/local/解压完成之后鉴于某些地区网络问题，必须配置加速 Maven 地址，在 /usr/local/apache-maven-3.8.7/conf/settings.xml 添加阿里云地址：<mirror> <id>alimaven</id> <name>aliyun maven</name> <url>http://maven.aliyun.com/nexus/content/groups/public/</url> <mirrorOf>central</mirrorOf> </mirror>当配置完成之后，还需要将 Maven 的安装目录配置到环境变量中，方便其他程序查找使用：export MAVEN_HOME="/usr/local/apache-maven-3.8.7" export PATH=$MAVEN_HOME/bin:$PATH配置完成之后要刷新系统变量，使用命令 source /etc/profile ，最后查看是否全局生效，使用下面命令查看：[root@rhel9 ~]# mvn -v Apache Maven 3.8.7 (b89d5959fcde851dcb1c8946a785a163f14e1e29) Maven home: /usr/local/apache-maven-3.8.7 Java version: 17.0.6, vendor: Amazon.com Inc., runtime: /usr/lib/jvm/java-17-amazon-corretto Default locale: e…

配置高效的 intellij IDEA 环境
type: item
date: 2022-11-24T15:02:00+08:00
description: 作为一名开发者最重要的工具就是编辑器和集成开发环境，我自从接触编程到现在也用过很多编辑器，从 Sublime、Atom、VSCode、IBM Eclipse、MyEclipse 到 Intellij IDEA 系列不下 5 种。用的最多就是 IBM Eclipse 这是最为古老的集成开发环境，作为 Java 程序员如果年龄老一点都会使用到，以前成熟的集成开发环境也就 Eclipse 和 Microsoft Visual Studio 的天下。现在已经有很多开发者工具可供选择了，可能某些比较 Geek 大佬还在使用 VIM 或者 Emacs 这些上古神器。我是被 Jetbrains 2018 年在国内开发日活动种草，目前已经转移到 JetBrains 全家桶旗下，Intellij IDEA 作为 Java 程序员应该是使用最广使用人数最多的，其他语言在此基础之上安装插件进行二次开发，本篇将介绍 IDEA 最常用的一些设置，本人已经使用 IDEA 快 5 年了，常用一些设置也就那么几个，工欲善其事必先利其器！基础配置日常开发过程中最常用的就是代码提示功能，IDEA 在这方面已经很智能了，但是日常还有一些自定义需求 IDEA 官方也通过允许开发者自己自定义的选项，可以允许用户自定义代码模板和注释模板，配置路径如下：Perferences/Editor/Live Template ，设置如下：如果想配置一个函数签名的注释模板可以使用如下配置，其中的 $params$ 为变量，相关的参数说明可以查看官方的文档:/** * @param $params$ * @return $return$ */这些使用 $ 引入的变量，可以设置通过何种方式获取值替换到具体的位置上，如下图:另外的方式就是通过注释模板来生成每个 Class 文件头的作者说明信息，模板如下：/** * @author LeonDing * @date ${DATE}-${TIME} * @since 0.1.0 * Comments text. */ public class ${NAME} { }设置方式如下图，位置在 Editor\File Code Eemplate：上面这种方式为实时代码补全，另外一种方式就是后缀补全，通过 . 分割符号激活的补全，这和 Eclipse 默认的补全方式类似，相关配置如下：配置完成之后每次新建一个 Class 文件时就会默认给你生成相关的注释信息。在编写代码的时候可能需要导入一些第三方包的 Class 文件，手动导入很麻烦可以让 IDEA 自动优化导入包，配置如下图：有时候导入的 Class 文件较多，程序会自动根据同一个包下的 Class 文件数量来整理导入的包，设置如下图：有时候我们的 Project 栏下面很多不需要展示的文件，可以使用下面的配置来过滤掉一些无用的文件：如果对默认灰色的代码注释快颜色不够满意，可以通过下面的方式进行设置：在编写代码的时候时不时会切换大小写，如果不想使用通过严格的大小写来激活代码提示功能，可以使用下面的方式忽略大小写：如果编写的函数和方法过多，可以设置每个函数之间的分割符号，如下图：如果项目中部分文件的原始编码和 IDEA 默认使用的编码不同可能会出现无法解析的情况，此时可以对某个特定的文件或者目录进行设置专属编码，如下图：环境配置作为 Java 项目开发可能难免会使用到多版本的 JDK 和不同 Java 版本的语言新特性，所以有必要熟悉这些配置的，第一个就是我们使用的 Java Compiler 的相关的设置，如下图：可以设置项目的字节码版本号和单个 Class 文件版本号，目前 Java 还通过 JVM 进行运行的，未来如果 Java 全生态拥抱 GraalVM 可能就不需要这些设置了采用原生机器码运行。另外就是一个项目导入多个版本 JDK ，我电脑已经安装了多个版本的 JDK ，直接导入如下图：另外就是 Jar 依赖导入，Apache Maven 项目依赖管理工具这里也要配置，提前下载好 Maven 并且解压到自己熟悉的目录下：如果在国内要使用阿里云的中央仓库源，内容如下：<mirror> <id>aliyunmaven</id> <mirrorOf>*</mirrorOf> <name>阿里云公共仓库</name> <url>https://maven.aliyun.com/repository/public</url> </mirror>如果需要指定项目模块使用的某个 Java 语义化版本的支持，例如设置为 11 版本的，可以设置如下：大部分情况下都是我们运行项目的时候要会编译才能正常运行，可以设置使用什么 JDK 进行编译，如下图：如果是做基于 Servlet 容器企业级应用开发，就需要 …