<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>精灵王 Blog (EN)</title>
        <link>https://jingling.im/blog</link>
        <description>个人博客，什么都写一点</description>
        <lastBuildDate>Tue, 03 Mar 2026 00:00:00 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <image>
            <title>精灵王 Blog (EN)</title>
            <url>https://jingling.im/imgs/logo.png</url>
            <link>https://jingling.im/blog</link>
        </image>
        <copyright>All rights reserved 2026, 精灵王</copyright>
        <item>
            <title><![CDATA[Markdown 语法全面测试指南]]></title>
            <link>https://jingling.im/blog/markdown-syntax-guide</link>
            <guid isPermaLink="false">https://jingling.im/blog/markdown-syntax-guide</guid>
            <pubDate>Tue, 03 Mar 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[这份综合指南涵盖了全新 MDX 静态博客系统支持的所有 Markdown 特性，并提供基础排版测试。]]></description>
            <content:encoded><![CDATA[
欢迎来到这份终极 **Markdown 语法指南**！本文旨在测试并展示由 `next-mdx-remote` 配合 Shadcn UI 与 Tailwind CSS 渲染出的各个基础排版组件。

## 强大的 MDX

MDX 允许我们在 Markdown 中直接嵌入 JSX 组件。但在享受这项强大特性之前，我们需要确保系统的底层 HTML 标签已经具备现代、优雅的排版样式。

### 基础排版测试

这里展示了一段包含 **加粗**、*斜体* 以及 ~~删除线~~ 的排版效果。你还可以将它们组合使用，比如 ***加粗且倾斜***。

在技术博客中，我们经常会用到行内代码块：`console.log('Hello, World!')`。系统会自动为其赋予高亮背景与专属的等宽字体。

> **引用块 (Blockquotes)** 非常适合高亮关键信息或引用外部文献。
> 系统会自动为其添加左侧高亮边框，并使用柔和颜色的文字加以区分。

### 列表展示

#### 无序列表
- 列表项一
- 列表项二
- 列表项三
  - 嵌套项 A
  - 嵌套项 B

#### 有序列表
1....]]></content:encoded>
            <author>AI 助手</author>
            <enclosure url="https://jingling.im/imgs/logo.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Spring AI 1.0 正式发布，属于 Java 开发者的 AI 框架]]></title>
            <link>https://jingling.im/blog/spring-ai-1-0-release-java-ai-framework</link>
            <guid isPermaLink="false">https://jingling.im/blog/spring-ai-1-0-release-java-ai-framework</guid>
            <pubDate>Wed, 21 May 2025 22:38:28 GMT</pubDate>
            <description><![CDATA[Spring AI 1.0 正式发布，为Java开发者带来AI工程化解决方案，支持多种模型提供商和向量数据库，基于Spring生态系统设计原则，让AI应用开发更简单]]></description>
            <content:encoded><![CDATA[
## 前言

最近，Spring 家族迎来了新成员——Spring AI 1.0 正式发布了！作为一个常年和 Spring 打交道的 Java 开发者，这个消息让我兴奋不已。终于，我们 Java 开发者也有了自己的 AI 开发框架，不用羡慕 Python 生态的 LangChain 了！

其实从去年就开始关注 Spring AI 项目了，一直处于快速迭代中。现在 1.0 版本正式发布，意味着 API 稳定，功能完善，可以放心在生产环境使用了。这篇文章就来聊聊 Spring AI 到底是什么，它能帮我们做什么，以及怎么上手使用。

## Spring AI 是什么？

简单来说，Spring AI 是一个 AI 工程应用框架，它的目标是将 Spring 生态系统的设计原则（可移植性和模块化设计）应用到 AI 领域，并推广使用 POJO 作为应用程序的构建块。

![Spring AI 集成图](/public/assets/upload/2025/spring-ai-integration-diagram.png)

Spring AI 的核心价值在于解决 AI 集成的基本挑战：**...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[Java使用iText 7将HTML转PDF示例详解]]></title>
            <link>https://jingling.im/blog/java-itext7-html-to-pdf-guide</link>
            <guid isPermaLink="false">https://jingling.im/blog/java-itext7-html-to-pdf-guide</guid>
            <pubDate>Fri, 02 May 2025 17:38:28 GMT</pubDate>
            <description><![CDATA[使用Java和iText 7库实现HTML到PDF的转换，详细介绍核心组件、转换流程、高级功能与常见问题解决方案]]></description>
            <content:encoded><![CDATA[

## 一、简介

在Java开发中，有时我们需要将HTML内容转换为PDF文件。iText 7是这方面的一个不错选择，它功能强大且灵活。这篇文章我会分享如何使用iText 7来实现HTML转PDF的过程，包括一些关键概念和实现细节。

## 二、核心组件

使用iText 7转换HTML到PDF，主要涉及这几个核心组件：

1. **ConverterProperties**：用于配置转换过程中的各种参数
2. **FontProvider**：负责处理字体渲染，特别是对中文等非ASCII字符的支持
3. **PdfWriter**：将内容写入到输出流
4. **PdfDocument**：代表整个PDF文档的结构
5. **HtmlConverter**：真正执行HTML到PDF转换的类

## 三、转换流程详解

### 0. 引入依赖

首先需要在pom.xml中添加相关依赖：

```java
<!-- iText 7 Core -->
<dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>kern...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[Supabase 本地直连（Docker部署）]]></title>
            <link>https://jingling.im/blog/supabase-local-deployment-docker</link>
            <guid isPermaLink="false">https://jingling.im/blog/supabase-local-deployment-docker</guid>
            <pubDate>Sat, 17 Aug 2024 17:33:39 GMT</pubDate>
            <description><![CDATA[详细介绍如何在本地使用 Docker 部署 Supabase 服务。]]></description>
            <content:encoded><![CDATA[
## Supabase 本地部署

最近在做研究学习 Supabase 时，本地连接 Supabase 的云服务非常的慢，经常超时，忍无可忍，决定在本地用Docker部署一个，本地连接本地的 Supabase 服务。这里记录一下过程，以备后用。

### 准备工作

首先，确保电脑上已经安装了这两个东西：

- [Git](https://git-scm.com/downloads)
- Docker（根据自己的系统选择：[Windows](https://docs.docker.com/desktop/install/windows-install/)、[MacOS](https://docs.docker.com/desktop/install/mac-install/) 或 [Linux](https://docs.docker.com/desktop/install/linux-install/)）

安装完后记得检查一下 Docker 是否正常运行，我之前就遇到过安装完忘记启动 Docker 服务的情况。

### 下载 Supabase 源码

打开终端，运行下面的命令：...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[设计模式系列目录]]></title>
            <link>https://jingling.im/blog/design-pattern-learning-directory</link>
            <guid isPermaLink="false">https://jingling.im/blog/design-pattern-learning-directory</guid>
            <pubDate>Sat, 03 Aug 2024 11:27:33 GMT</pubDate>
            <description><![CDATA[本文提供了一个关于设计模式的系列目录，涵盖了创建型、结构型和行为型三种主要的设计模式类别。帮助大家快速理解并定位这些模式的基本概念和应用场景，以提升代码的灵活性、可维护性和降低系统耦合度。]]></description>
            <content:encoded><![CDATA[
本文是关于设计模式的系列目录，列举了包括创建型、结构型和行为型在内的多种设计模式，旨在帮助读者快速定位和理解各种设计模式的基本概念和应用场景。

## 设计模式系列目录

### 创建型

创建型设计模式: 包括单例模式、原型模式、工厂模式和建造者模式，这些模式主要用于对象的创建，以提高代码的灵活性和可维护性。

阅读创建型设计模式相关文章：

- [设计模式：创建型—单例模式](/posts/singleton-design-pattern)
- [设计模式：创建型—原型模式](/posts/prototype-design-pattern)
- [设计模式：创建型—工厂模式](/posts/factory-design-pattern)
- [设计模式：创建型—建造者模式](/posts/builder-design-pattern)

### 结构型

“结构型”设计模式是软件设计中用来描述如何将类或对象组合成更大的结构的一组模式。每种模式都解决特定的设计问题，并在软件架构中扮演不同的角色。

阅读结构型设计模式相关文章：

- [设计模式：结构型—享元模式](/posts/f...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[操作系统系列目录]]></title>
            <link>https://jingling.im/blog/summary-of-operating-system-learning-catalog</link>
            <guid isPermaLink="false">https://jingling.im/blog/summary-of-operating-system-learning-catalog</guid>
            <pubDate>Sat, 03 Aug 2024 09:58:26 GMT</pubDate>
            <description><![CDATA[本文是一篇操作系统系列的概述，涵盖了操作系统的基本概念、运行环境、进程管理、多线程、进程间交互、处理器调度与死锁、存储器管理、虚拟存储管理、设备管理和文件系统等关键领域。]]></description>
            <content:encoded><![CDATA[
这篇文章是一系列关于操作系统的总结，涵盖了操作系统的概述、运行环境、进程管理、多线程、进程间交互、处理器调度与死锁、主存储器管理、虚拟存储管理、设备管理和文件系统等关键主题。

下面是学习笔记的一些记录文章目录：

[（一）操作系统-概述](/posts/system01)

[（二）操作系统-运行环境](/posts/system02)

[（三）操作系统-进程管理](/posts/system03)

[（四）操作系统-多线程](/posts/system04)

[（五）操作系统-进程间的交互](/posts/system05)

[（六）操作系统-处理器调度及死锁](/posts/system06)

[（七）操作系统-主存储器管理](/posts/system07)

[（八）操作系统-虚拟存储管理](/posts/system08)

[（九）操作系统-设备管理](/posts/system09)

[（十）操作系统-文件系统](/posts/system10)
...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[系统设计：设计 Google Drive（15）]]></title>
            <link>https://jingling.im/blog/design-google-drive</link>
            <guid isPermaLink="false">https://jingling.im/blog/design-google-drive</guid>
            <pubDate>Fri, 26 May 2023 21:35:41 GMT</pubDate>
            <description><![CDATA[系统设计面试指南，设计 Google Drive（design google drive）]]></description>
            <content:encoded><![CDATA[
 

- [第1步：了解问题并确定设计范围](#第1步了解问题并确定设计范围)
- [第2步：提出高层次的设计方案并获得认同](#第2步提出高层次的设计方案并获得认同)
  - [API](#api)
  - [摆脱单一服务器](#摆脱单一服务器)
  - [同步冲突](#同步冲突)
  - [高层设计](#高层设计)
- [第3步：深入设计](#第3步深入设计)
  - [块服务器](#块服务器)
  - [元数据数据库](#元数据数据库)
  - [上传流程](#上传流程)
  - [下载流程](#下载流程)
  - [通知服务](#通知服务)
  - [节省储存空间](#节省储存空间)
  - [故障处理](#故障处理)
- [第4步：总结](#第4步总结)
- [参考资料](#参考资料)
 

近年来，Google Drive、Dropbox、Microsoft OneDrive、Apple iCloud 等云存储服务非常流行。在本章中，你需要设计 Google Drive。

在开始设计之前，让我们花点时间了解一下 Google Drive。 Google Drive 是一...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[系统设计：设计YouTube（14）]]></title>
            <link>https://jingling.im/blog/design-youtube</link>
            <guid isPermaLink="false">https://jingling.im/blog/design-youtube</guid>
            <pubDate>Mon, 22 May 2023 23:32:21 GMT</pubDate>
            <description><![CDATA[系统设计面试指南，设计YouTube（design youtube）]]></description>
            <content:encoded><![CDATA[
 

- [第1步：了解问题并确定设计范围](#第1步了解问题并确定设计范围)
  - [粗略估计](#粗略估计)
- [第2步：提出高层次的设计方案并获得认同](#第2步提出高层次的设计方案并获得认同)
  - [视频上传流程](#视频上传流程)
  - [视频流](#视频流)
- [第3步：深入设计](#第3步深入设计)
  - [视频转码](#视频转码)
  - [有向无环图(DAG)模型](#有向无环图dag模型)
  - [视频转码架构](#视频转码架构)
    - [预处理器](#预处理器)
    - [DAG调度器](#dag调度器)
    - [资源管理器](#资源管理器)
    - [任务工作者](#任务工作者)
    - [临时存储](#临时存储)
    - [编码后的视频](#编码后的视频)
  - [系统优化](#系统优化)
    - [速度优化：并行化视频上传](#速度优化并行化视频上传)
    - [速度优化：将上传中心放在靠近用户的地方](#速度优化将上传中心放在靠近用户的地方)
    - [速度优化：无处不在的并行性](#速度优化无处...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[系统设计：设计一个搜索自动完成系统（13）]]></title>
            <link>https://jingling.im/blog/design-a-search-autocomplete-system</link>
            <guid isPermaLink="false">https://jingling.im/blog/design-a-search-autocomplete-system</guid>
            <pubDate>Thu, 11 May 2023 22:01:14 GMT</pubDate>
            <description><![CDATA[系统设计面试指南，设计一个搜索自动完成系统（design a search autocomplete system）]]></description>
            <content:encoded><![CDATA[
 

- [第1步：了解问题并确定设计范围](#第1步了解问题并确定设计范围)
  - [需求](#需求)
  - [粗略估算](#粗略估算)
- [第2步：提出高层次的设计方案并获得认同](#第2步提出高层次的设计方案并获得认同)
  - [数据收集服务](#数据收集服务)
  - [查询服务](#查询服务)
- [第3步：深入设计](#第3步深入设计)
  - [Trie 数据结构](#trie-数据结构)
  - [数据收集服务](#数据收集服务)
  - [查询服务](#查询服务)
  - [Trie 操作](#trie-操作)
  - [扩展存储](#扩展存储)
- [第4步：总结](#第4步总结)
- [参考资料](#参考资料)
 

当你在谷歌上搜索或在亚马逊购物时，在搜索框中输入，会有一个或多个与搜索词相匹配的内容呈现给你。这一功能被称为自动完成、提前输入、边输入边搜索或增量搜索。图13-1是谷歌搜索的一个例子，当在搜索框中输入"dinner"时，显示了一个自动完成的结果列表。搜索自动完成是许多产品的一个重要功能。这就把我们引向了面试问题：设计一个搜索自动完成系统，也...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[系统设计：设计一个聊天系统（12）]]></title>
            <link>https://jingling.im/blog/design-a-chat-system</link>
            <guid isPermaLink="false">https://jingling.im/blog/design-a-chat-system</guid>
            <pubDate>Fri, 05 May 2023 16:11:24 GMT</pubDate>
            <description><![CDATA[系统设计面试指南：设计一个高性能分布式聊天系统 (design a chat system) - 从明确需求开始，逐步构建高层次架构，并深入解析关键技术细节，最终总结设计要点，深度解析聊天系统架构、消息流、在线状态管理和性能优化]]></description>
            <content:encoded><![CDATA[
 

- [第1步：了解问题并确定设计范围](#第1步了解问题并确定设计范围)
- [第2步：提出高层次的设计方案并获得认同](#第2步提出高层次的设计方案并获得认同)
  - [轮询](#轮询)
  - [长轮询](#长轮询)
  - [WebSocket](#websocket)
  - [高层次设计](#高层次设计)
  - [可扩展性](#可扩展性)
  - [储存](#储存)
  - [数据模型](#数据模型)
- [第3步：深入设计](#第3步深入设计)
  - [服务发现](#服务发现)
  - [消息流](#消息流)
    - [1对1聊天](#1对1聊天)
    - [多个设备间的信息同步](#多个设备间的信息同步)
    - [群组聊天流程](#群组聊天流程)
  - [在线状态](#在线状态)
    - [用户登入](#用户登入)
    - [用户登出](#用户登出)
    - [用户断开连接](#用户断开连接)
    - [在线状态输出](#在线状态输出)
- [第4步：总结](#第4步总结)
- [参考资料](#参考资料)
 

在本章中，我们...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[系统设计：设计一个新闻订阅系统（11）]]></title>
            <link>https://jingling.im/blog/design-a-news-feed-system</link>
            <guid isPermaLink="false">https://jingling.im/blog/design-a-news-feed-system</guid>
            <pubDate>Fri, 21 Apr 2023 21:12:30 GMT</pubDate>
            <description><![CDATA[系统设计面试指南：设计一个高性能新闻订阅系统 (design a news feed system) - 从明确需求开始，深入探讨架构设计、消息流、缓存策略等关键技术细节，并总结设计要点，掌握新闻订阅系统的核心概念。]]></description>
            <content:encoded><![CDATA[
 

- [第1步：了解问题并确定设计范围](#第1步了解问题并确定设计范围)
- [第2步：提出高层次的设计方案并获得认同](#第2步提出高层次的设计方案并获得认同)
  - [新闻源API](#新闻源api)
  - [新闻发布（Feed publishing）](#新闻发布feed-publishing)
  - [新闻源构建（Newsfeed building）](#新闻源构建newsfeed-building)
- [第3步：深入设计](#第3步深入设计)
  - [新闻发布深入研究](#新闻发布深入研究)
    - [web 服务](#web-服务)
    - [扇出服务](#扇出服务)
  - [新闻源检索深入研究](#新闻源检索深入研究)
- [第4步：总结](#第4步总结)
- [参考资料](#参考资料)
 

在本章中，您需要设计一个新闻推送系统。 什么是新闻推送？ 根据 Facebook 帮助页面，“动态是位于首页中间不断更新的动态列表。动态包括您在 Facebook 上关注的用户、公共主页和小组发布的状态更新、照片、视频、链接、应用事件和点赞。”[1]。 ...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[系统设计：设计一个通知系统（10）]]></title>
            <link>https://jingling.im/blog/design-a-notification-system</link>
            <guid isPermaLink="false">https://jingling.im/blog/design-a-notification-system</guid>
            <pubDate>Tue, 11 Apr 2023 13:08:10 GMT</pubDate>
            <description><![CDATA[系统设计面试指南，设计一个通知系统（design a notification system）]]></description>
            <content:encoded><![CDATA[
 

- [第1步：了解问题并确定设计范围](#第1步了解问题并确定设计范围)
- [第2步：提出高层次的设计方案并获得认同](#第2步提出高层次的设计方案并获得认同)
  - [不同的通知类型](#不同的通知类型)
    - [iOS推送通知](#ios推送通知)
    - [Android推送通知](#android推送通知)
    - [短信](#短信)
    - [邮件](#邮件)
  - [联系人信息收集流程](#联系人信息收集流程)
  - [通知发送/接收流程](#通知发送接收流程)
    - [高层设计](#高层设计)
    - [高层设计（改进后的）](#高层设计改进后的)
- [第3步：深入设计](#第3步深入设计)
  - [可靠性](#可靠性)
    - [如何防止数据丢失？](#如何防止数据丢失)
    - [接受者只会收到一次通知吗？](#接受者只会收到一次通知吗)
  - [其他组件和考虑因素](#其他组件和考虑因素)
  - [更新后的设计](#更新后的设计)
- [第4步：总结](#第3步总结)
- [参考资料](#参考资料)
 

...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[系统设计：网络爬虫设计（09）]]></title>
            <link>https://jingling.im/blog/design-a-web-crawler</link>
            <guid isPermaLink="false">https://jingling.im/blog/design-a-web-crawler</guid>
            <pubDate>Sat, 08 Apr 2023 13:18:50 GMT</pubDate>
            <description><![CDATA[系统设计面试指南，网络爬虫设计（design a web crawler）]]></description>
            <content:encoded><![CDATA[
 

- [第1步：了解问题并确定设计范围](#第1步了解问题并确定设计范围)
  - [粗略估算](粗略估算#)
- [第2步：提出高层次的设计方案并获得认同](#第2步提出高层次的设计方案并获得认同)
  - [Seed URLs](#seed-urls)
  - [URL Frontier](#url-frontier)
  - [HTML Downloader](#html-downloader)
  - [DNS Resolver](#dns-resolver)
  - [Content Parser](#content-parser)
  - [Content Seen?](#content-seen)
  - [Content Storage](#content-storage)
  - [URL Extractor](#url-extractor)
  - [URL Filter](#url-filter)
  - [URL Seen？](#url-seen)
  - [URL Storage](#url-storage)
  - [网络爬虫工作流程](#网络爬虫工作...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[系统设计：短网址设计（08）]]></title>
            <link>https://jingling.im/blog/design-a-url-shortener</link>
            <guid isPermaLink="false">https://jingling.im/blog/design-a-url-shortener</guid>
            <pubDate>Thu, 06 Apr 2023 13:18:50 GMT</pubDate>
            <description><![CDATA[系统设计面试指南：高性能短网址设计（design a url shortener），本指南涵盖了需求分析、系统量级估算、REST API设计、哈希函数策略、以及实现URL缩短和重定向的详细步骤。]]></description>
            <content:encoded><![CDATA[
 

- [第1步 ：了解问题并确定设计范围](#第1步了解问题并确定设计范围)
  - [粗略估计系统量级](#粗略估计系统量级)
- [第2步：提出高层次的设计方案并获得认同](#第2步提出高层次的设计方案并获得认同)
  - [API 端点](#api-端点)
  - [URL 重定向](#url-重定向)
  - [缩短网址](#缩短网址)
- [第3步：深入设计](#第3步深入设计)
  - [数据模型](#数据模型)
  - [哈希函数](#哈希函数)
    - [哈希值的长度](#哈希值的长度)
    - [哈希+碰撞解决](#哈希碰撞解决)
    - [base 62 转换](#base-62-转换)
    - [两种方法的比较](#两种方法的比较)
  - [URL 缩短的深入研究](#url-缩短的深入研究)
  - [URL重定向的深入研究](#url重定向的深入研究)
- [第4步：总结](#第4步总结)
- [参考资料](#参考资料)
 

在这一章中，我们将解决一个有趣而经典的系统设计面试问题：设计一个像tinyurl一样的URL缩短服务。

##...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[系统设计：在分布式系统中设计唯一 ID 生成器（07）]]></title>
            <link>https://jingling.im/blog/design-a-unique-id-generator</link>
            <guid isPermaLink="false">https://jingling.im/blog/design-a-unique-id-generator</guid>
            <pubDate>Sun, 02 Apr 2023 11:35:23 GMT</pubDate>
            <description><![CDATA[系统设计面试指南：在分布式系统中设计唯一 ID 生成器（design a unique id generator），详细讲解多主复制、UUID、票据服务器和推特雪花算法，全面理解ID生成的挑战和解决方案]]></description>
            <content:encoded><![CDATA[
 

- [第1步：了解问题并确定设计范围](#第1步了解问题并确定设计范围)
- [第2步：提出高层次的设计方案并获得认同](#第2步提出高层次的设计方案并获得认同)
- [第3步：深入设计](#第3步深入设计)
- [第4步：总结](#第4步总结)
- [参考资料](#参考资料)
 

在本章中，要求在分布式系统中设计一个唯一 ID 生成器。 你的第一个想法可能是在传统数据库中使用具有 auto_increment 属性的主键。 但是，auto_increment 在分布式环境中不起作用，因为
单个数据库服务器不够大，跨多个数据库以最小延迟生成唯一 ID 具有挑战性。
以下是唯一 ID 的一些示例：

![](https://github.com/Admol/SystemDesign/raw/main/images/chapter7/figure7-1.jpg)

## 第1步：了解问题并确定设计范围

了解清楚问题是解决任何系统设计面试问题的第一步。下面是一个候选人与面试官互动的例子：

候选人：唯一ID有什么特点？

面试官：ID 必须是唯一的，并且是可排序的。

候选人：每...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[系统设计：设计一个key-value存储（06）]]></title>
            <link>https://jingling.im/blog/design-a-key-value-store</link>
            <guid isPermaLink="false">https://jingling.im/blog/design-a-key-value-store</guid>
            <pubDate>Fri, 31 Mar 2023 19:45:44 GMT</pubDate>
            <description><![CDATA[系统设计面试指南：设计一个高性能分布式 Key-Value 存储 (design a key-value store) - 从明确需求出发，深入探讨架构设计、数据分区、复制、一致性、故障处理等关键技术细节，并总结设计要点，掌握 Key-Value 存储的核心概念。]]></description>
            <content:encoded><![CDATA[
 

- [理解问题并确定设计范围](#理解问题并确定设计范围)
- [单一服务器的键值存储](#单一服务器的键值存储)
- [分布式键值存储](#分布式键值存储)
- [系统组件](#系统组件)
- [数据分区](#数据分区)
- [数据复制](#数据复制)
- [一致性](#一致性)
- [一致性模型](#一致性模型)
- [不一致的解决方法：版本控制](#不一致的解决方法版本控制)
- [故障处理](#故障处理)
- [系统构架图](#系统构架图)
- [写入路径](#写入路径)
- [读取路径](#读取路径)
- [总结](#总结)
- [参考资料](#参考资料)
 

键值存储，也称为键值数据库，是一种非关系数据库。每个唯一标识符都存储为一个键及其关联值，这种数据配对称为“键值”对。

在键值对中，键必须是唯一的，通过键可以访问与键关联的值。键可以是纯文本或散列值。出于性能原因，短键效果更好。键是什么样子的？

- 纯文本键：“last_logged_in_at”
- 哈希键：253DDEC4

键值对中的值可以是字符串、列表、对象等。在键值存储中，值通常被视为不透明的对象...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[系统设计：一致性hash设计（05）]]></title>
            <link>https://jingling.im/blog/design-consistent-hashing</link>
            <guid isPermaLink="false">https://jingling.im/blog/design-consistent-hashing</guid>
            <pubDate>Sun, 26 Mar 2023 11:55:03 GMT</pubDate>
            <description><![CDATA[系统设计面试指南，一致性hash设计（design consistent hashing）]]></description>
            <content:encoded><![CDATA[
 

- [重新哈希问题](#重新哈希问题)
- [一致性哈希](#一致性哈希)
- [哈希空间和哈希环](#哈希空间和哈希环)
- [哈希服务器](#哈希服务器)
- [哈希键](#哈希键)
- [服务器查找](#服务器查找)
- [添加一台服务器](#添加一台服务器)
- [移除一台服务器](#移除一台服务器)
- [基本方法中的两个问题](#基本方法中的两个问题)
- [虚拟节点](#虚拟节点)
- [找出受影响的键](#找出受影响的键)
- [总结](#总结)
- [参考资料](#参考资料)
 

## 一致性hash设计

为了实现水平扩展，在服务器之间高效、均匀地分配请求/数据非常重要。一致哈希是实现这一目标的常用技术。但首先，让我们深入研究一下这个问题。

### 再哈希问题

如果你有n个缓存服务器，平衡负载的一个常用方法是使用以下哈希方法：

`serverIndex = hash(key) % N`，其中N是服务器池的大小

让我们用一个例子来说明它是如何工作的。如表5-1所示，我们有4个服务器和8个字符串键及其哈希值。

![](https://github.c...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[系统设计：设计一个分布式限流器（04）]]></title>
            <link>https://jingling.im/blog/design-a-rate-limiter</link>
            <guid isPermaLink="false">https://jingling.im/blog/design-a-rate-limiter</guid>
            <pubDate>Wed, 22 Mar 2023 11:50:44 GMT</pubDate>
            <description><![CDATA[系统设计面试指南：本文讲解了分布式限流器中的常见算法、架构、性能优化和监控，深入浅出详细介绍了常见分布式限流器实现原理。]]></description>
            <content:encoded><![CDATA[
 

- [第1步：了解问题并确定设计范围](#第1步了解问题并确定设计范围)
- [第2步：提出高层次的设计方案并获得认同](#第2步提出高层次的设计方案并获得认同)
  - [限流器放在哪里](#限流器放在哪里)
  - [限流器算法](#限流器算法)
    - [令牌桶算法](#令牌桶算法)
    - [漏桶算法](#漏桶算法)
    - [固定窗口计数算法](#固定窗口计数器算法)
    - [滑动窗口日志算法](#滑动窗口日志算法)
    - [滑动窗口计数算法](#滑动窗口计数器算法)
  - [高层次的架构](#高层次的架构)
- [第3步：深入设计](#第3步深入设计)
  - [限流规则](#限流规则)
  - [超过速率限制](#超过速率限制)
  - [限流器请求头](#限流器请求头)
  - [详细设计](#详细设计)
  - [分布式环境下的限流器](#分布式环境下的限流器)
    - [竞争条件](#竞争条件)
    - [同步问题](#同步问题)
  - [性能优化](#性能优化)
  - [监控](#监控)
- [第4步：总结](#第4步...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[系统设计：系统设计面试框架（03）]]></title>
            <link>https://jingling.im/blog/a-framework-for-system-design-interviews</link>
            <guid isPermaLink="false">https://jingling.im/blog/a-framework-for-system-design-interviews</guid>
            <pubDate>Fri, 25 Nov 2022 20:38:28 GMT</pubDate>
            <description><![CDATA[系统设计面试指南，系统设计面试框架（a framework for system design interviews）]]></description>
            <content:encoded><![CDATA[
 

- [有效系统设计面试的 4 步流程](#有效系统设计面试的-4-步流程)
  - [第1步 ：了解问题并确定设计范围](#第1步-了解问题并确定设计范围)
  - [第2步：提出高层次的设计方案并获得认同](#第2步提出高层次的设计方案并获得认同)
  - [第3步：深入设计](#第3步深入设计)
  - [第4步：总结](#第4步总结)
   

你刚刚获得了梦寐以求的现场面试机会。招聘协调员给你发送了当天的日程安排。看完整个日程安排后，你觉得一切都挺好，直到你看到了这个面试环节 - 系统设计面试。

系统设计面试往往令人生畏，它可能像“设计一个知名的产品X？”那样的模糊不清。这些问题模棱两可，似乎过于宽泛。 你的担心是可以理解的。
毕竟，在一个小时内设计一个已经由数百甚至数千名工程师构建的流行产品，这似乎是不可能的？

好消息是，没有人期望你这样做。现实世界的系统设计是非常复杂的。例如，谷歌搜索看起来简单；然而，支持这种简单性背后的技术数量真的令人惊讶。
如果没有人期望你在一小时内设计出一个真实世界的系统，那么系统设计面试的好处是什么？

系统设计面试模拟了现实生活中的问...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[系统设计：粗略估算（02）]]></title>
            <link>https://jingling.im/blog/estimation</link>
            <guid isPermaLink="false">https://jingling.im/blog/estimation</guid>
            <pubDate>Fri, 25 Nov 2022 19:38:28 GMT</pubDate>
            <description><![CDATA[系统设计面试指南，粗略估算（back-of-the-envelope estimation）]]></description>
            <content:encoded><![CDATA[
 

- [2的幂次方](#2的幂次方)
- [每个程序员都应该了解的延迟数据](#每个程序员都应该了解的延迟数据)
- [可用性数据](#可用性数据)
- [示例：估算 Twitter 的查询量和存储需求](#示例估算Twitter的查询量和存储需求)
- [小贴士](#小贴士)
- [参考资料](#参考资料)
 

在系统设计面试中，有时会要求你使用粗略估算来估计系统的容量或性能需求。根据谷歌高级研究员杰夫·迪恩（Jeff Dean）的说法，“粗略估算是使用一系列思维实验和常见性能数据的组合进行估算，以便对哪种设计能够满足你的要求有一个良好的了解” [1]。

要有效地进行粗略估算，你需要对可扩展性基础知识有很好的了解。以下概念应该被深入理解：二的幂 [2]、每个程序员都应该知道的延迟数字和可用性数字。

## 2的幂次方

尽管在处理分布式系统时数据量可能变得非常庞大，但所有计算归结为基础知识。为了获得正确的计算结果，了解使用二的幂的数据量单位至关重要。一个字节是8个位的序列。一个 ASCII 字符使用一个字节的内存（8位）。下面是解释数据量单位的表格（表2-1）。

![](...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[系统设计：从0到百万用户（01）]]></title>
            <link>https://jingling.im/blog/scale-from-zero-to-millions-of-users</link>
            <guid isPermaLink="false">https://jingling.im/blog/scale-from-zero-to-millions-of-users</guid>
            <pubDate>Fri, 25 Nov 2022 17:38:28 GMT</pubDate>
            <description><![CDATA[系统设计面试指南，从0到百万用户（scale from zero to millions of users）]]></description>
            <content:encoded><![CDATA[
 

- [单服务器设置](#单服务器设置)
- [数据库](#数据库)
  - [垂直扩展 vs 水平扩展](#垂直扩展vs水平扩展)
- [负载均衡](#负载均衡)
- [数据库复制](#数据库复制)
- [缓存](#缓存)
  - [缓存层](#缓存层)
  - [使用缓存的注意事项](#使用缓存的注意事项)
- [CDN](#cdn)
  - [使用CDN的注意事项](#使用CDN的注意事项)
- [无状态web层](#无状态web层)
  - [有状态的架构](#有状态的架构)
  - [无状态的架构](#无状态的架构)
- [数据中心](#数据中心)
- [消息队列](#消息队列)
- [日志记录、指标、自动化](#日志记录、指标、自动化)
  - [添加消息队列和不同的工具](#添加消息队列和不同的工具)
- [数据库扩展](#数据库扩展)
  - [垂直扩展](#垂直扩展)
  - [水平扩展](#水平扩展)
- [百万用户及以上](#百万用户及以上)
- [参考资料](#参考资料)
 

设计一个支持数百万用户的系统具有挑战性，是一个需要持续完善和不断改进的过程。在...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[设计模式：行为型-中介模式]]></title>
            <link>https://jingling.im/blog/mediator-design-pattern</link>
            <guid isPermaLink="false">https://jingling.im/blog/mediator-design-pattern</guid>
            <pubDate>Fri, 21 Oct 2022 17:58:23 GMT</pubDate>
            <description><![CDATA[中介模式是一种行为型设计模式，中介模式定义了一个单独的（中介）对象，来封装一组对象之间的交互。将这组对象之间的交互委派给与中介对象交互，来避免对象之间的直接交互。]]></description>
            <content:encoded><![CDATA[
### 原理和实现

中介模式的英文翻译是 Mediator Design Pattern。在 GoF 中的《设计模式》一书中，它是这样定义的：

> Mediator pattern defines a separate (mediator) object that encapsulates the interaction between a set of objects and the objects delegate their interaction to a mediator object instead of interacting with each other directly.

翻译成中文就是：**中介模式定义了一个单独的（中介）对象，来封装一组对象之间的交互。将这组对象之间的交互委派给与中介对象交互，来避免对象之间的直接交互。**

还记得我们在第 30 节课中讲的“如何给代码解耦”吗？**其中一个方法就是引入中间层。**

实际上，中介模式的设计思想跟中间层很像，通过引入中介这个中间层，将一组对象之间的交互关系（或者说依赖关系）从多对多（网状关系）转换为一对多...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[设计模式：行为型-解释器模式]]></title>
            <link>https://jingling.im/blog/interpreter-design-pattern</link>
            <guid isPermaLink="false">https://jingling.im/blog/interpreter-design-pattern</guid>
            <pubDate>Wed, 19 Oct 2022 19:09:45 GMT</pubDate>
            <description><![CDATA[解释器模式（Interpreter Design Pattern）是一种行为型设计模式，解释器模式为某个语言定义它的语法（或者叫文法）表示，并定义一个解释器用来处理这个语法。]]></description>
            <content:encoded><![CDATA[
比起命令模式，解释器模式更加小众，只在一些特定的领域会被用到，比如编译器、规则引擎、正则表达式。

### 原理和实现

解释器模式的英文翻译是 Interpreter Design Pattern。在 GoF 的《设计模式》一书中，它是这样定义的：

> Interpreter pattern is used to defines a grammatical representation for a language and provides an interpreter to deal with this grammar.

翻译成中文就是：**解释器模式为某个语言定义它的语法（或者叫文法）表示，并定义一个解释器用来处理这个语法。**

看了定义，你估计会一头雾水，因为这里面有很多我们平时开发中很少接触的概念，比如“语言”“语法”“解释器”。实际上，这里的“语言”不仅仅指我们平时说的中、英、日、法等各种语言。从广义上来讲，只要是能承载信息的载体，我们都可以称之为“语言”，比如，古代的结绳记事、盲文、哑语、摩斯密码等。

要想了解“语言”表达的信息，我们就必须定义相应的语法规则。这...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[设计模式：行为型-命令模式]]></title>
            <link>https://jingling.im/blog/command-design-pattern</link>
            <guid isPermaLink="false">https://jingling.im/blog/command-design-pattern</guid>
            <pubDate>Mon, 17 Oct 2022 18:25:54 GMT</pubDate>
            <description><![CDATA[命令模式（Command Design Pattern）是一种行为型设计模式，命令模式将请求（命令）封装为一个对象，这样可以使用不同的请求参数化其他对象（将不同请求依赖注入到其他对象），并且能够支持请求（命令）的排队执行、记录日志、撤销等（附加控制）功能。]]></description>
            <content:encoded><![CDATA[
命令模式的英文翻译是 Command Design Pattern。在 GoF 的《设计模式》一书中，它是这么定义的：

> The command pattern encapsulates a request as an object, thereby letting us parameterize other objects with different requests, queue or log requests, and support undoable operations.

翻译成中文就是下面这样。为了帮助你理解，我对这个翻译稍微做了补充和解释，也一起放在了下面的括号中。

命令模式将请求（命令）封装为一个对象，这样可以使用不同的请求参数化其他对象（将不同请求依赖注入到其他对象），并且能够支持请求（命令）的排队执行、记录日志、撤销等（附加控制）功能。

对于 GoF 给出的定义，我这里再进一步解读一下。

落实到编码实现，命令模式用的最核心的实现手段，是将函数封装成对象。

我们知道，C 语言支持函数指针，我们可以把函数当作变量传递来传递去。但是，在大部分编程语言中，...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[设计模式：行为型-备忘录模式]]></title>
            <link>https://jingling.im/blog/memento-design-pattern</link>
            <guid isPermaLink="false">https://jingling.im/blog/memento-design-pattern</guid>
            <pubDate>Sat, 15 Oct 2022 15:20:50 GMT</pubDate>
            <description><![CDATA[备忘录模式是一种行为型设计模式，也叫快照（Snapshot）模式，英文翻译是 Memento Design Pattern。在不违背封装原则的前提下，捕获一个对象的内部状态，并在该对象之外保存这个状态，以便之后恢复对象为先前的状态。]]></description>
            <content:encoded><![CDATA[
备忘录模式应用场景也比较明确和有限，主要是用来防丢失、撤销、恢复等。

### 原理与实现

备忘录模式，也叫快照（Snapshot）模式，英文翻译是 Memento Design Pattern。在 GoF 的《设计模式》一书中，备忘录模式是这么定义的：

> Captures and externalizes an object’s internal state so that it can be restored later, all without violating encapsulation.

翻译成中文就是：**在不违背封装原则的前提下，捕获一个对象的内部状态，并在该对象之外保存这个状态，以便之后恢复对象为先前的状态。**

在我看来，这个模式的定义主要表达了两部分内容。

一部分是，存储副本以便后期恢复。这一部分很好理解。

另一部分是，要在不违背封装原则的前提下，进行对象的备份和恢复。这部分不太好理解。

接下来，我就结合一个例子来解释一下，特别带你搞清楚这两个问题：

1. 为什么存储和恢复副本会违背封装原则？
2. 备忘录模式是如何做到不违背封装原则的？

假...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[设计模式：行为型-访问者模式]]></title>
            <link>https://jingling.im/blog/visitor-design-pattern</link>
            <guid isPermaLink="false">https://jingling.im/blog/visitor-design-pattern</guid>
            <pubDate>Thu, 13 Oct 2022 18:20:15 GMT</pubDate>
            <description><![CDATA[访问者模式（Visitor Design Pattern）是一种行为型设计模式，它允许一个或者多个操作应用到一组对象上，解耦操作和对象本身。]]></description>
            <content:encoded><![CDATA[
假设我们从网站上爬取了很多资源文件，它们的格式有三种：PDF、PPT、Word。我们现在要开发一个工具来处理这批资源文件。这个工具的其中一个功能是，把这些资源文件中的文本内容抽取出来放到 txt 文件中。如果让你来实现，你会怎么来做呢？

实现这个功能并不难，不同的人有不同的写法，我将其中一种代码实现方式贴在这里。其中，ResourceFile 是一个抽象类，包含一个抽象函数 extract2txt()。

PdfFile、PPTFile、WordFile 都继承 ResourceFile 类，并且重写了 extract2txt() 函数。在 ToolApplication 中，我们可以利用多态特性，根据对象的实际类型，来决定执行哪个方法。

```java
public abstract class ResourceFile {
  protected String filePath;

  public ResourceFile(String filePath) {
    this.filePath = filePath;
  }

  public abstract void...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[设计模式：行为型-迭代器模式]]></title>
            <link>https://jingling.im/blog/iterator-design-pattern</link>
            <guid isPermaLink="false">https://jingling.im/blog/iterator-design-pattern</guid>
            <pubDate>Sun, 09 Oct 2022 18:13:45 GMT</pubDate>
            <description><![CDATA[迭代器模式（Iterator Design Pattern）是一种行为型设计模式，也叫作游标模式（Cursor Design Pattern），迭代器模式将集合对象的遍历操作从集合类中拆分出来，放到迭代器类中，让两者的职责更加单一。]]></description>
            <content:encoded><![CDATA[
### 迭代器模式的原理和实现

迭代器模式（Iterator Design Pattern），也叫作游标模式（Cursor Design Pattern）。

迭代器模式将集合对象的遍历操作从集合类中拆分出来，放到迭代器类中，让两者的职责更加单一。

迭代器是用来遍历容器的，所以，一个完整的迭代器模式一般会涉及容器和容器迭代器两部分内容。

为了达到基于接口而非实现编程的目的，容器又包含容器接口、容器实现类，迭代器又包含迭代器接口、迭代器实现类。

对于迭代器模式，我画了一张简单的类图，你可以看一看，先有个大致的印象。

![image](/assets/upload/2022/11/image-8ca896c41b7044dfa7b342f27945fe8b.png)

通过一个例子来具体讲，如何实现一个迭代器。

大部分编程语言都提供了遍历容器的迭代器类，我们在平时开发中，直接拿来用即可，几乎不大可能从零编写一个迭代器。

不过，这里为了讲解迭代器的实现原理，我们假设某个新的编程语言的基础类库中，还没有提供线性容器对应的迭代器，需要我们从零开始开发。

现在，我们一块来看具体该...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[设计模式：行为型-状态模式]]></title>
            <link>https://jingling.im/blog/state-pattern</link>
            <guid isPermaLink="false">https://jingling.im/blog/state-pattern</guid>
            <pubDate>Wed, 28 Sep 2022 17:32:43 GMT</pubDate>
            <description><![CDATA[状态模式一般用来实现状态机，而状态机常用在游戏、工作流引擎等系统开发中。不过，状态机的实现方式有多种，除了状态模式，比较常用的还有分支逻辑法和查表法。]]></description>
            <content:encoded><![CDATA[
状态模式一般用来实现状态机，而状态机常用在游戏、工作流引擎等系统开发中。不过，状态机的实现方式有多种，除了状态模式，比较常用的还有分支逻辑法和查表法。今天，我们就详细讲讲这几种实现方式，并且对比一下它们的优劣和应用场景。

### 什么是有限状态机？

有限状态机，英文翻译是 Finite State Machine，缩写为 FSM，简称为状态机。

状态机有 3 个组成部分：`状态（State）`、`事件（Event）`、`动作（Action）`。

其中，事件也称为转移条件（Transition Condition）。事件触发状态的转移及动作的执行。不过，动作不是必须的，也可能只转移状态，不执行任何动作。

对于刚刚给出的状态机的定义，我结合一个具体的例子，来进一步解释一下。

“超级马里奥”游戏不知道你玩过没有？在游戏中，马里奥可以变身为多种形态，比如小马里奥（Small Mario）、超级马里奥（Super Mario）、火焰马里奥（Fire Mario）、斗篷马里奥（Cape Mario）等等。在不同的游戏情节下，各个形态会互相转化，并相应的增减积分。比如，初始形态是小马里...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[设计模式：行为型-职责链模式]]></title>
            <link>https://jingling.im/blog/chain-of-responsibility-design-pattern</link>
            <guid isPermaLink="false">https://jingling.im/blog/chain-of-responsibility-design-pattern</guid>
            <pubDate>Thu, 15 Sep 2022 17:07:46 GMT</pubDate>
            <description><![CDATA[职责链模式（Chain Of Responsibility Design Pattern）是一种行为型设计模式，它将请求的发送和接收解耦，让多个接收对象都有机会处理这个请求。将这些接收对象串成一条链，并沿着这条链传递这个请求，直到链上的某个接收对象能够处理它为止。]]></description>
            <content:encoded><![CDATA[
### 职责链模式的原理和实现

职责链模式的英文翻译是 Chain Of Responsibility Design Pattern。在 GoF 的《设计模式》中，它是这么定义的：

> Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it.

翻译成中文就是：**将请求的发送和接收解耦，让多个接收对象都有机会处理这个请求。将这些接收对象串成一条链，并沿着这条链传递这个请求，直到链上的某个接收对象能够处理它为止。**

这么说比较抽象，我用更加容易理解的话来进一步解读一下。

在职责链模式中，多个处理器（也就是刚刚定义中说的“接收对象”）依次处理同一个请求。一个请求先经过 A 处理器处理，然后再把请求传递给 B 处理器，B...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[设计模式：行为型-策略模式]]></title>
            <link>https://jingling.im/blog/strategy-design-pattern</link>
            <guid isPermaLink="false">https://jingling.im/blog/strategy-design-pattern</guid>
            <pubDate>Tue, 13 Sep 2022 17:08:55 GMT</pubDate>
            <description><![CDATA[策略模式（Strategy Design Pattern）是一种行为型设计模式，定义一族算法类，将每个算法分别封装起来，让它们可以互相替换。策略模式可以使算法的变化独立于使用它们的客户端（这里的客户端代指使用算法的代码）]]></description>
            <content:encoded><![CDATA[
### 策略模式的原理与实现

策略模式，英文全称是 Strategy Design Pattern。在 GoF 的《设计模式》一书中，它是这样定义的：

> Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.

翻译成中文就是：定义一族算法类，将每个算法分别封装起来，让它们可以互相替换。策略模式可以使算法的变化独立于使用它们的客户端（这里的客户端代指使用算法的代码）。

我们知道，工厂模式是解耦对象的创建和使用，观察者模式是解耦观察者和被观察者。

策略模式跟两者类似，也能起到解耦的作用，不过，它解耦的是策略的定义、创建、使用这三部分。接下来，我就详细讲讲一个完整的策略模式应该包含的这三个部分。

### 1. 策略的定义

策略类的定义比较简单，包含一个策略接口和一组实现这个接口的策略类。

因为所有的策略类都实现相同的接...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[设计模式：行为型-模板模式]]></title>
            <link>https://jingling.im/blog/template-method-design-pattern</link>
            <guid isPermaLink="false">https://jingling.im/blog/template-method-design-pattern</guid>
            <pubDate>Sat, 10 Sep 2022 15:00:32 GMT</pubDate>
            <description><![CDATA[模板模式是一种行为型设计模式，模板模式，全称是模板方法设计模式，英文是 `Template Method Design Pattern`。模板方法模式在一个方法中定义一个算法骨架，并将某些步骤推迟到子类中实现。模板方法模式可以让子类在不改变算法整体结构的情况下，重新定义算法中的某些步骤。]]></description>
            <content:encoded><![CDATA[
模板模式主要是用来解决复用和扩展两个问题。

### 模板模式的原理与实现

模板模式，全称是模板方法设计模式，英文是 `Template Method Design Pattern`。在 GoF 的《设计模式》一书中，它是这么定义的：

> Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm’s structure.

翻译成中文就是：**模板方法模式在一个方法中定义一个算法骨架，并将某些步骤推迟到子类中实现。模板方法模式可以让子类在不改变算法整体结构的情况下，重新定义算法中的某些步骤。**

这里的“算法”，我们可以理解为广义上的“业务逻辑”，并不特指数据结构和算法中的“算法”。这里的算法骨架就是“模板”，包含算法骨架的方法就是“模板方法”，这也是模板方法...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[设计模式：行为型-观察者模式]]></title>
            <link>https://jingling.im/blog/observer-design-pattern</link>
            <guid isPermaLink="false">https://jingling.im/blog/observer-design-pattern</guid>
            <pubDate>Thu, 08 Sep 2022 15:57:08 GMT</pubDate>
            <description><![CDATA[观察者模式（Observer Design Pattern）是一种行为型设计模式，它也被称为发布订阅模式，它在对象之间定义一个一对多的依赖，当一个对象状态改变的时候，所有依赖的对象都会自动收到通知。]]></description>
            <content:encoded><![CDATA[
根据应用场景的不同，观察者模式会对应不同的代码实现方式：有同步阻塞的实现方式，也有异步非阻塞的实现方式；有进程内的实现方式，也有跨进程的实现方式。今天我会重点讲解原理、实现、应用场景。

下一节课，我会带你一块实现一个基于观察者模式的异步非阻塞的 EventBus，加深你对这个模式的理解。

### 原理及应用场景剖析

**观察者模式**（Observer Design Pattern）也被称为**发布订阅模式**（Publish-Subscribe Design Pattern）。在 GoF 的《设计模式》一书中，它的定义是这样的：

> Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.

翻译成中文就是：在对象之间定义一个一对多的依赖，当一个对象状态改变的时候，所有依赖的对象都会自动收到通知。

一般情况下，被依赖的对象叫作**被观察者（O...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[设计模式：结构型-享元模式]]></title>
            <link>https://jingling.im/blog/flyweight-design-pattern</link>
            <guid isPermaLink="false">https://jingling.im/blog/flyweight-design-pattern</guid>
            <pubDate>Fri, 02 Sep 2022 16:52:35 GMT</pubDate>
            <description><![CDATA[享元模式是属于结构型模式，所谓“享元”，顾名思义就是被共享的单元。享元模式的意图是复用对象，节省内存，前提是享元对象是不可变对象。]]></description>
            <content:encoded><![CDATA[
跟其他所有的设计模式类似，享元模式的原理和实现也非常简单。今天，我会通过棋牌游戏和文本编辑器两个实际的例子来讲解。除此之外，我还会讲到它跟单例、缓存、对象池的区别和联系。

### 享元模式原理与实现

所谓“享元”，顾名思义就是**被共享的单元。**

享元模式的意图是**复用对象，节省内存**，前提是享元对象是不可变对象。

具体来讲，当一个系统中存在大量重复对象的时候，如果这些重复的对象是不可变对象，我们就可以利用享元模式将对象设计成享元，在内存中只保留一份实例，供多处代码引用。

这样可以减少内存中对象的数量，起到节省内存的目的。

实际上，不仅仅相同对象可以设计成享元，对于相似对象，我们也可以将这些对象中相同的部分（字段）提取出来，设计成享元，让这些大量相似对象引用这些享元。

这里我稍微解释一下，定义中的“不可变对象”指的是，一旦通过构造函数初始化完成之后，它的状态（对象的成员变量或者属性）就不会再被修改了。

所以，不可变对象不能暴露任何 set() 等修改内部状态的方法。之所以要求享元是不可变对象，那是因为它会被多处代码共享使用，避免一处代码对享元进行了修改，影响到其...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[设计模式：结构型-组合模式]]></title>
            <link>https://jingling.im/blog/composite-design-pattern</link>
            <guid isPermaLink="false">https://jingling.im/blog/composite-design-pattern</guid>
            <pubDate>Tue, 30 Aug 2022 11:41:30 GMT</pubDate>
            <description><![CDATA[组合模式是属于结构型模式，将一组对象组织（Compose）成树形结构，以表示一种“部分 - 整体”的层次结构。组合让客户端（在很多设计模式书籍中，“客户端”代指代码的使用者。）可以统一处理单个对象和组合对象的处理逻辑。]]></description>
            <content:encoded><![CDATA[
组合模式跟我们之前讲的面向对象设计中的“组合关系（通过组合来组装两个类）”，完全是两码事。

这里讲的“组合模式”，主要是用来处理树形结构数据。这里的“数据”，你可以简单理解为一组对象集合，待会我们会详细讲解。

正因为其应用场景的特殊性，数据必须能表示成树形结构，这也导致了这种模式在实际的项目开发中并不那么常用。但是，一旦数据满足树形结构，应用这种模式就能发挥很大的作用，能让代码变得非常简洁。

### 组合模式的原理与实现

在 GoF 的《设计模式》一书中，组合模式是这样定义的：

> Compose objects into tree structure to represent part-whole hierarchies.Composite lets client treat individual objects and compositions of objects uniformly.

翻译成中文就是：**将一组对象组织（Compose）成树形结构，以表示一种“部分 - 整体”的层次结构。组合让客户端（在很多设计模式书籍中，“客户端”代指代码的使用者。）可以统一处理...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[设计模式：结构型—门面模式]]></title>
            <link>https://jingling.im/blog/facade-design-pattern</link>
            <guid isPermaLink="false">https://jingling.im/blog/facade-design-pattern</guid>
            <pubDate>Sun, 28 Aug 2022 10:56:17 GMT</pubDate>
            <description><![CDATA[门面模式是一种结构型设计模式，门面模式，也叫外观模式，英文全称是 `Facade Design Pattern`。门面模式为子系统提供一组统一的接口，定义一组高层接口让子系统更易用。]]></description>
            <content:encoded><![CDATA[
## 定义

门面模式，也叫外观模式，英文全称是 `Facade Design Pattern`。

在 GoF 的《设计模式》一书中，门面模式是这样定义的：

> Provide a unified interface to a set of interfaces in a subsystem. Facade Pattern defines a higher-level interface that makes the subsystem easier to use.

翻译成中文就是：**门面模式为子系统提供一组统一的接口，定义一组高层接口让子系统更易用。**

### 解释

假设有一个系统 A，提供了 a、b、c、d 四个接口。

系统 B 完成某个业务功能，需要调用 A 系统的 a、b、d 接口。

利用门面模式，我们提供一个包裹 a、b、d 接口调用的门面接口 x，给系统 B 直接使用。

不知道你会不会有这样的疑问，让系统 B 直接调用 a、b、d 感觉没有太大问题呀，为什么还要提供一个包裹 a、b、d 的接口 x 呢？

通过一个具体的例子来解释一下。

假设我们刚刚...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[设计模式：结构型—适配器模式]]></title>
            <link>https://jingling.im/blog/adapter-design-pattern</link>
            <guid isPermaLink="false">https://jingling.im/blog/adapter-design-pattern</guid>
            <pubDate>Fri, 10 Jun 2022 10:10:17 GMT</pubDate>
            <description><![CDATA[适配器模式是一种结构型设计模式，适配器模式（Adapter Design Pattern）将一个类的接口转换成客户希望的另外一个接口，使得原本由于接口不兼容而不能一起工作的那些类能一起工作。]]></description>
            <content:encoded><![CDATA[
## 定义

适配器模式（Adapter Design Pattern）将一个类的接口转换成客户希望的另外一个接口，使得原本由于接口不兼容而不能一起工作的那些类能一起工作。

适配器模式分为**类结构型模式**和**对象结构型模式**两种，其中，类适配器一般使用**继承关系**来实现，对象适配器使用**组合关系**来实现。

## 实现原理

对于适配器模式，我们可以用生活中常见的一个使用场景来解释，就是 **USB 转接头**充当**适配器**，把两种不兼容的接口，通过转接头变得可以一起工作。

适配器模式（Adapter）包含以下主要角色：

1. 目标（Target）接口：当前系统业务所期待的接口，它可以是抽象类或接口。
2. 适配者（Adaptee）类：它是被访问和适配的现存组件库中的组件接口。
3. 适配器（Adapter）类：它是一个转换器，通过继承或引用适配者的对象，把适配者接口转换成目标接口，让客户按目标接口的格式访问适配者。

下面是两种不同的适配器代码实现方式，其中，`ITarget`表示要转化成的目标接口定义。`Adaptee`是一组不兼容 `ITarget` ...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[设计模式：结构型—桥接模式]]></title>
            <link>https://jingling.im/blog/bridge-design-pattern</link>
            <guid isPermaLink="false">https://jingling.im/blog/bridge-design-pattern</guid>
            <pubDate>Fri, 15 Apr 2022 09:54:34 GMT</pubDate>
            <description><![CDATA[桥接模式是一种结构型设计模式，也叫作桥梁模式，英文是 `Bridge Design Pattern`。将抽象和实现进行解耦，让它们可以独立变化。]]></description>
            <content:encoded><![CDATA[
## 定义

桥接模式，也叫作桥梁模式，英文是 `Bridge Design Pattern`。

在 GoF 的《设计模式》一书中，桥接模式是这么定义的：“Decouple an abstraction from its implementation so that the two can vary independently。”

翻译成中文就是：“将**抽象**和**实现**进行**解耦**，让它们可以独立变化。”

### 说明

关于桥接模式，很多书籍、资料中，还有另外一种理解方式：“**一个类存在两个（或多个）独立变化的维度，我们通过组合的方式，让这两个（或多个）维度可以独立进行扩展。**” 通过组合关系来替代继承关系，避免继承层次的指数级爆炸。这种理解方式非常类似于，我们之前讲过的“组合优于继承”设计原则。

桥接模式的代码实现非常简单，但是理解起来稍微有点难度，并且应用场景也比较局限，所以，相当于代理模式来说，桥接模式在实际的项目中并没有那么常用，你只需要简单了解，见到能认识就可以，并不是我们学习的重点。

## 经典应用

### JDBC驱动

JDBC 驱动是桥...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[设计模式：结构型—装饰器模式]]></title>
            <link>https://jingling.im/blog/decorator-design-pattern</link>
            <guid isPermaLink="false">https://jingling.im/blog/decorator-design-pattern</guid>
            <pubDate>Tue, 22 Mar 2022 10:00:59 GMT</pubDate>
            <description><![CDATA[装饰器模式（Decorator Pattern）是一种结构型设计模式， 允许你通过将对象放入包含行为的特殊封装对象中来为原对象绑定新的行为，在保持类方法签名完整性的前提下，提供了额外的功能。]]></description>
            <content:encoded><![CDATA[
# 设计模式：结构型—装饰器模式

## 定义

装饰器模式（Decorator Pattern）是一种结构型设计模式， 允许你通过将对象放入包含行为的特殊封装对象中来为原对象绑定新的行为，在保持类方法签名完整性的前提下，提供了额外的功能。

## JDK中的实现

### Java IO 类

Java IO 类库非常庞大和复杂，有几十个类，负责 IO 数据的读取和写入。如果对 Java IO 类做一下分类，我们可以从下面两个维度将它划分为四类。

1. 输入流：InputStream、Reader
2. 输出流：OutputStream、Writer
3. 字节流：InputStream、OutputStream
4. 字符流：Reader、Writer

针对不同的读取和写入场景，Java IO 又在这四个父类基础之上，扩展出了很多子类。

![Java-IO](https://s2.loli.net/2022/07/22/dtnEyVRLSozFxpq.png)

### JavaIO的设计思考

如下面这样一段代码，我们打开文件 test.txt，从中读取数据。其中，Inp...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[设计模式：结构型—代理模式]]></title>
            <link>https://jingling.im/blog/proxy-design-pattern</link>
            <guid isPermaLink="false">https://jingling.im/blog/proxy-design-pattern</guid>
            <pubDate>Mon, 07 Feb 2022 17:26:19 GMT</pubDate>
            <description><![CDATA[代理模式（Proxy Design Pattern）是一种结构型设计模式， 它在不改变原始类（或叫被代理类）代码的情况下，通过引入代理类来给原始类附加功能。]]></description>
            <content:encoded><![CDATA[
## 代理模式定义

**代理模式（Proxy Design Pattern）**是一种结构型设计模式， 它**在不改变原始类（或叫被代理类）代码的情况下，通过引入代理类来给原始类附加功能。**

### 解决什么问题

假设我们开发了一个 `MetricsCollector`类，用来收集接口请求的原始数据，比如访问时间、处理时长等。

在业务系统中，我们采用如下方式来使用这个 MetricsCollector 类：

```java
public class UserController {
  //...省略其他属性和方法...
  private MetricsCollector metricsCollector; // 依赖注入

  public UserVo login(String telephone, String password) {
    long startTimestamp = System.currentTimeMillis();

    // ... 省略login逻辑...

    long endTimeStamp = System.curre...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[庖丁解InnoDB之UNDO LOG]]></title>
            <link>https://jingling.im/blog/mysqlundolog</link>
            <guid isPermaLink="false">https://jingling.im/blog/mysqlundolog</guid>
            <pubDate>Tue, 09 Nov 2021 10:49:12 GMT</pubDate>
            <content:encoded><![CDATA[
> 本文系转载至公众号：[庖丁解InnoDB之UNDO LOG](https://mp.weixin.qq.com/s/SQe5TTB42thRqAyfcz5mHA)
> 转载系列的文章都是本人觉得写得非常好的，值得认真品读学习的文章。

---

Undo Log是InnoDB十分重要的组成部分，它的作用横贯InnoDB中两个最主要的部分，并发控制（Concurrency Control）和故障恢复（Crash Recovery），InnoDB中Undo Log的实现亦日志亦数据。本文将从其作用、设计思路、记录内容、组织结构，以及各种功能实现等方面，整体介绍InnoDB中的Undo Log，文章会深入一定的代码实现，但在细节上还是希望用抽象的实现思路代替具体的代码。本文基于MySQL 8.0，但在大多数的设计思路上MySQL的各个版本都是一致的。考虑到篇幅有限，以及避免过多信息的干扰，从而能够聚焦Undo Log本身的内容，本文中一笔带过或有意省略了一些内容，包括索引、事务系统、临时表、XA事务、Virtual Column、外部记录、Blob等。

## **一 Undo Log...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[庖丁解InnoDB之REDO LOG]]></title>
            <link>https://jingling.im/blog/mysql-redolog</link>
            <guid isPermaLink="false">https://jingling.im/blog/mysql-redolog</guid>
            <pubDate>Mon, 08 Nov 2021 11:08:53 GMT</pubDate>
            <content:encoded><![CDATA[
> 本文系转载至公众号：[庖丁解InnoDB之REDO LOG](https://mp.weixin.qq.com/s/2G_2ZYAbQIblVJY7pmrhKg)
> 转载系列的文章都是本人觉得写得非常好的，值得认真品读学习的文章。

---

数据库故障恢复机制的前世今生一文中提到，今生磁盘数据库为了在保证数据库的原子性(A, Atomic) 和持久性(D, Durability)的同时，还能以灵活的刷盘策略来充分利用磁盘顺序写的性能，会记录REDO和UNDO日志，即ARIES方法。本文将重点介绍REDO LOG的作用，记录的内容，组织结构，写入方式等内容，希望读者能够更全面准确的理解REDO LOG在InnoDB中的位置。本文基于MySQL 8.0代码。

## **一 为什么需要记录REDO**

为了取得更好的读写性能，InnoDB会将数据缓存在内存中（InnoDB Buffer Pool），对磁盘数据的修改也会落后于内存，这时如果进程或机器崩溃，会导致内存数据丢失，为了保证数据库本身的一致性和持久性，InnoDB维护了REDO LOG。修改Page之前需要先将修改的内容...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[设计模式：创建型—原型模式]]></title>
            <link>https://jingling.im/blog/prototype-design-pattern</link>
            <guid isPermaLink="false">https://jingling.im/blog/prototype-design-pattern</guid>
            <pubDate>Wed, 03 Nov 2021 16:22:56 GMT</pubDate>
            <description><![CDATA[原型模式是属于创建型模式，基于原型来创建对象的方式就叫作原型设计模式（Prototype Design Pattern），简称原型模式。]]></description>
            <content:encoded><![CDATA[
### 原型模式的定义

如果对象的创建成本比较大，而同一个类的不同对象之间差别不大（大部分字段都相同），在这种情况下，我们可以利用对已有对象（原型）进行复制（或者叫拷贝）的方式来创建新对象，以达到节省创建时间的目的。

这种基于原型来创建对象的方式就叫作**原型设计模式（Prototype Design Pattern）**，简称**原型模式。**

### 理解**对象的创建成本**

实际上，创建对象包含的申请内存、给成员变量赋值这一过程，本身并不会花费太多时间，或者说对于大部分业务系统来说，这点时间完全是可以忽略的。

但是，如果对象中的数据**需要经过复杂的计算**才能得到（比如排序、计算哈希值），或者需要从 RPC、网络、数据库、文件系统等非常慢速的 IO 中读取，这种情况下，我们就可以利用原型模式，从其他已有对象中直接拷贝得到，而不用每次在创建新对象的时候，都重复执行这些耗时的操作。

### 原型模式的实现方式：深拷贝和浅拷贝

在 Java 语言中，Object 类的 clone() 方法执行的就是我们常说的浅拷贝。它只会拷贝对象中的基本数据类型的数据（比如，int...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[设计模式：创建型—建造者模式]]></title>
            <link>https://jingling.im/blog/builder-design-pattern</link>
            <guid isPermaLink="false">https://jingling.im/blog/builder-design-pattern</guid>
            <pubDate>Tue, 12 Oct 2021 10:54:14 GMT</pubDate>
            <description><![CDATA[建造者模式是属于创建型模式，建造者模式（Builder Pattern）是一个比较常用的创建型设计模式，又叫Builder 模式，中文翻译为建造者模式或者构建者模式，也有人叫它生成器模式。]]></description>
            <content:encoded><![CDATA[
建造者模式（Builder Pattern）是一个比较常用的创建型设计模式，又叫**Builder 模式**，中文翻译为**建造者模式或者构建者模式**，也有人叫它**生成器模式**。

### 定义

英文定义：

> The intent of the Builder design pattern is to separate the construction of a complex object from its representation. By doing so the same construction process can create different representations.

翻译成中文的大概意思就是：

> Builder 设计模式的目的是将复杂对象的构造与其表示分离。通过这样做，相同的构建过程可以创建不同的表示。

### 为什么需要建造者模式

首先，我们知道，建造者模式也是属于创建型的设计模式，所以它的目的也是为了创建一个类的，那什么时候需要我们用到建造者模式，而不是用我们经常使用的new（Java）方式来创建呢？

当使用new的方式创建...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[设计模式：创建型—工厂模式]]></title>
            <link>https://jingling.im/blog/factory-design-pattern</link>
            <guid isPermaLink="false">https://jingling.im/blog/factory-design-pattern</guid>
            <pubDate>Sat, 25 Sep 2021 21:30:15 GMT</pubDate>
            <description><![CDATA[工厂模式是属于创建型模式，一般情况下，工厂模式被分为三种更加细分的模式：简单工厂、工厂方法和抽象工厂]]></description>
            <content:encoded><![CDATA[
一般情况下，工厂模式被分为三种更加细分的模式：**简单工厂、工厂方法和抽象工厂**。

## 简单工厂（Simple Factory）

### 定义

简单工厂模式(Simple Factory Pattern)：又称为静态工厂方法(Static Factory Method)模式，它属于类创建型模式。在简单工厂模式中，可以根据参数的不同返回不同类的实例。简单工厂模式专门定义一个类来负责创建其他类的实例，被创建的实例通常都具有共同的父类。

### 代码示例

```jsx
public class RuleConfigSource {
  public RuleConfig load(String ruleConfigFilePath) {
    String ruleConfigFileExtension = getFileExtension(ruleConfigFilePath);
    IRuleConfigParser parser = RuleConfigParserFactory.createParser(ruleConfigFileExtension);
   ...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[设计模式：创建型—单例模式]]></title>
            <link>https://jingling.im/blog/singleton-design-pattern</link>
            <guid isPermaLink="false">https://jingling.im/blog/singleton-design-pattern</guid>
            <pubDate>Sun, 05 Sep 2021 17:27:33 GMT</pubDate>
            <description><![CDATA[单例模式是属于创建型模式，一个类只允许创建一个对象（或者实例），那这个类就是一个单例类]]></description>
            <content:encoded><![CDATA[
## 定义

单例模式是属于创建型模式；

其英文定义的原话是：`Ensure a class has only one instance,and provide a global point of access to it.`

我们通俗点理解就是：**一个类只允许创建一个对象（或者实例），那这个类就是一个单例类**。

## 如何实现

单例类的实现也非常的简单，实现一个简单的单例类，一般需要注意以下几点：

1. 构造函数需要是 private 访问权限，这样才能避免外部通过 new 创建实例；
2. 考虑对象创建时的线程安全问题；
3. 考虑是否支持延迟加载；
4. 考虑 getInstance() 性能是否高（是否加锁）。

下面Java语言实现单例的几种常见写法

### 1.饿汉式

饿汉式的实现方式比较简单。一个“饿”字的精髓就体现于在类加载的时候，instance 静态实例就已经创建并初始化好了。不过，这种方式的缺点也很明显，那就是不支持延迟加载，但是instance 实例的创建过程是线程安全的。

下面是一个简单的单例ID生成器具体的Java实现

```jsx
...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[面向对象九大设计原则]]></title>
            <link>https://jingling.im/blog/design-principles</link>
            <guid isPermaLink="false">https://jingling.im/blog/design-principles</guid>
            <pubDate>Mon, 09 Aug 2021 14:57:39 GMT</pubDate>
            <description><![CDATA[面向对象九大设计原则，其中包括有SOLID、KISS、YAGNI、DRY、LOD 等。其中SOLID 原则并非单纯的 1 个原则，而是由 5 个设计原则组成的，它们分别是：单一职责原则、开闭原则、里式替换原则、接口隔离原则和依赖反转原则，依次对应 SOLID 中的 S、O、L、I、D 这 5 个英文字母。]]></description>
            <content:encoded><![CDATA[
面向对象经典的设计原则，其中包括有`SOLID`、`KISS`、`YAGNI`、`DRY`、`LOD` 等。其中SOLID 原则并非单纯的 1 个原则，而是由 5 个设计原则组成的，它们分别是：单一职责原则、开闭原则、里式替换原则、接口隔离原则和依赖反转原则，依次对应 SOLID 中的 S、O、L、I、D 这 5 个英文字母。

### 1.单一职责原则（SRP）

单一职责原则的英文是 `Single Responsibility Principle`，缩写为 `SRP`。

这个原则的英文描述是这样的：`A class or module should have a single responsibility`。如果我们把它翻译成中文，那就是：一个类或者模块只负责完成一个职责（或者功能）。

通俗点说就是**不要设计大而全的类，要设计粒度小、功能单一的类**。再换个角度来讲就是，一个类包含了两个或者两个以上业务不相干的功能，那我们就说它职责不够单一，应该将它拆分成多个功能更加单一、粒度更细的类。

如何判断类的职责是否足够单一？

在真实的软件开发中，对于一个类是否职责单一的判定...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[面向对象设计7大理论]]></title>
            <link>https://jingling.im/blog/ood-theory</link>
            <guid isPermaLink="false">https://jingling.im/blog/ood-theory</guid>
            <pubDate>Fri, 06 Aug 2021 14:30:51 GMT</pubDate>
            <description><![CDATA[深入理解面向对象设计的7大理论，什么是OOP，如何通过封装、抽象、继承、多态解决编程问题，面向对象的优势，及接口与抽象类的使用，提高代码的可维护性和扩展性。]]></description>
            <content:encoded><![CDATA[
### 1. 当谈论面向对象的时候，我们到底在谈论什么?

**什么是面向对象编程？**

面向对象编程的英文缩写是 `OOP`，全称是 `Object Oriented Programming`。

面向对象编程语言的英文缩写是 `OOPL`，全称是 `Object Oriented Programming Language`。

面向对象编程是一种**编程范式或编程风格**。它以**类**或**对象**作为组织代码的基本单元，并将`封装`、`抽象`、`继承`、`多态`四个特性，作为代码设计和实现的基石 。

**什么是面向对象编程语言？**

面向对象编程语言是支持类或对象的语法机制，并有现成的语法机制，能方便地实现面向对象编程四大特性（封装、抽象、继承、多态）的编程语言。

**什么是面向对象分析和面向对象设计？**

面向对象分析英文缩写是 `OOA`，全称是 `Object Oriented Analysis`；

面向对象设计的英文缩写是 `OOD`，全称是 `Object Oriented Design`。

`OOA`、`OOD`、`OOP` 三个连在一起就是面向对象...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[Java IO总结]]></title>
            <link>https://jingling.im/blog/javaio</link>
            <guid isPermaLink="false">https://jingling.im/blog/javaio</guid>
            <pubDate>Wed, 21 Jul 2021 11:52:04 GMT</pubDate>
            <description><![CDATA[Java IO总结]]></description>
            <content:encoded><![CDATA[
## 什么是I/O

> I/O（英语：Input/Output），即输入／输出，通常指数据在存储器（内部和外部）或其他周边设备之间的输入和输出，是信息处理系统（例如计算机）与外部世界（可能是人类或另一信息处理系统）之间的通信。输入是系统接收的信号或数据，输出则是从其发送的信号或数据。
> ——维基百科[I/O](https://zh.wikipedia.org/wiki/I/O)

## Java IO分类

### 按**传输方式**

从**传输方式**上，一般可以分为字符流和字节流；字节流一般读取单个字节，字符流读取单个字符；

可以理解字节是给计算机看的，字符是给人看的。

常见的字节流有：

- `InputStream`
- `OutputStream`

常见的字符流有：

- `Reader`
- `Writer`

### 按数据来源

从**数据来源**的角度看IO， 可以有以下几类：

1. 文件(file)

   `FileInputStream`、`FileOutputStream`、`FileReader`、`FileWriter`

2. 数组([]...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[用Redis实现一个相对可靠的分布式锁]]></title>
            <link>https://jingling.im/blog/distributed-lock</link>
            <guid isPermaLink="false">https://jingling.im/blog/distributed-lock</guid>
            <pubDate>Thu, 24 Jun 2021 11:29:37 GMT</pubDate>
            <description><![CDATA[用Redis实现一个相对可靠的分布式锁]]></description>
            <content:encoded><![CDATA[
将 Redis 作为分布式锁的实现，本质上就是让我们的目标线程到Redis里面去占据一个“萝卜坑”，萝卜就是目标线程要对共享资源访问变更的整个操作动作，当别的线程也想来占坑时，发现已经有萝卜在坑里面了，就需要放弃或者等待坑空余出来。

### 使用 SETNX 命令

只在键 `key` 不存在的情况下， 将键 `key` 的值设置为 `value` 。

若键 `key` 已经存在， 则 `SETNX` 命令不做任何动作。

```java
// 加锁命令：
SETNX  key  value
do something
// 解锁命令
del key
```

其中`key`是萝卜坑的唯一标识，根据业务需求组合而来，`value`值随意。

一个完整的正常业务逻辑就是，先通过SETNX 获得锁，然后做业务处理，最后释放锁。

上面这种实现方式看着非常简单，实现起来也很简单，但是存在很多的问题，比如：

1. 持有锁的线程因为某些异常（进程退出，网络异常等），导致没有成功的执行解锁命令（`del key`）,会导致其他线程永远也拿不到锁
2. 当前线程持有锁的情况下，其他线程可以调用...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[CopyOnWriteArraySet 源码分析]]></title>
            <link>https://jingling.im/blog/copyonwritearrayset-source-code-analysis</link>
            <guid isPermaLink="false">https://jingling.im/blog/copyonwritearrayset-source-code-analysis</guid>
            <pubDate>Fri, 11 Jun 2021 11:20:15 GMT</pubDate>
            <description><![CDATA[JDK源码分析 CopyOnWriteArraySet，CopyOnWriteArraySet是一个基于CopyOnWriteArrayList实现的线程安全的Set集合，所以该Set和CopyOnWriteArrayList拥有完全相似的特性。]]></description>
            <content:encoded><![CDATA[
## 简介

CopyOnWriteArraySet是一个基于CopyOnWriteArrayList实现的线程安全的Set集合，所以该Set和CopyOnWriteArrayList拥有完全相似的特性。

- 线程安全
- 可变操作（add、remove、set等）代价高，都是通过复制整个数组实现
- 迭代器不支持可变操作（add、remove、set），要防止遍历期间线程之间的干扰。
- 最适合集合大小保持较小，读操作大大超过可变操作的场景

## 结构分析

CopyOnWriteArraySet继承了AbstractSet，所以具有Set集合的功能，内部持有一个CopyOnWriteArrayList实例，作为数据底层的存储。

```java
public class CopyOnWriteArraySet<E> extends AbstractSet<E>
        implements java.io.Serializable {
    private static final long serialVersionUID = 545774765134403426...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[源码分析：线程安全的列表—CopyOnWriteArrayList]]></title>
            <link>https://jingling.im/blog/copyonwritearraylist</link>
            <guid isPermaLink="false">https://jingling.im/blog/copyonwritearraylist</guid>
            <pubDate>Wed, 09 Jun 2021 17:13:42 GMT</pubDate>
            <description><![CDATA[JDK源码分析 CopyOnWriteArrayList，CopyOnWriteArrayList 是一个线程安全的ArrayList，但是它的每次操作（add ，set，remove等）都是通过复制一个底层的数组副本来实现的，在写操作的时候都会加上锁，有读写分离的意思。]]></description>
            <content:encoded><![CDATA[
## 简介

CopyOnWriteArrayList 是一个线程安全的ArrayList，但是它的每次操作（add ，set，remove等）都是通过复制一个底层的数组副本来实现的，在写操作的时候都会加上锁，有读写分离的意思。

### CopyOnWrite

> [写入时复制](https://zh.wikipedia.org/wiki/%E5%AF%AB%E5%85%A5%E6%99%82%E8%A4%87%E8%A3%BD)（英语：Copy-on-write，简称COW）是一种计算机程序设计领域的优化策略。其核心思想是，如果有多个调用者（callers）同时请求相同资源（如内存或磁盘上的数据存储），他们会共同获取相同的指针指向相同的资源，直到某个调用者试图修改资源的内容时，系统才会真正复制一份专用副本（private copy）给该调用者，而其他调用者所见到的最初的资源仍然保持不变。这过程对其他的调用者都是透明的。此作法主要的优点是如果调用者没有修改该资源，就不会有副本（private copy）被创建，因此多个调用者只是读取操作时可以共享同一份资源。——维基百科

## ...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[源码分析：ConcurrentHashMap—JDK1.8版本]]></title>
            <link>https://jingling.im/blog/chm8</link>
            <guid isPermaLink="false">https://jingling.im/blog/chm8</guid>
            <pubDate>Tue, 08 Jun 2021 13:50:17 GMT</pubDate>
            <description><![CDATA[JDK源码分析 ConcurrentHashMap，ConcurrentHashMap—JDK1.8版本，JDK1.8版本的ConcurrentHashMap采用了和HashMap类似的实现机制：数组+链表+红黑树，通过自旋+synchronized锁+CAS+volatile的方式来实现保证数据的一致性。]]></description>
            <content:encoded><![CDATA[
## 简介

JDK1.7版本的`ConcurrentHashMap`是根据分段锁实现的，分段的意思体现在初始化时的Segment数组上，而且是不支持扩容的，所以并发性能还是会有一定的限制。而JDK1.8版本的`ConcurrentHashMap`采用了和HashMap类似的实现机制：数组+链表+红黑树，通过自旋+`synchronized`锁+CAS+`volatile`的方式来实现保证数据的一致性。

## 结构分析

### 重要的内部类

### Node

Node类实现了Map.Entry接口，主要存放key/value键值对，链表结构具有next域。

```java
static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        // volatile 修饰，保证可见性
        volatile V val;
        volatile Node<K,V> next;
...
}
```

### TreeNod...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[源码分析：ConcurrentHashMap—JDK1.7版本]]></title>
            <link>https://jingling.im/blog/chm7</link>
            <guid isPermaLink="false">https://jingling.im/blog/chm7</guid>
            <pubDate>Sun, 06 Jun 2021 16:25:46 GMT</pubDate>
            <description><![CDATA[JDK源码分析 ConcurrentHashMap，ConcurrentHashMap—JDK1.7版本，ConcurrentHashMap是从JDK 1.5开始支持一定并发性的哈希表，其中所有的操作都是线程安全的，所以常常会被应用于高并发的场景中。]]></description>
            <content:encoded><![CDATA[
## 简介

ConcurrentHashMap是从JDK 1.5开始支持一定并发性的哈希表，其中所有的操作都是线程安全的，所以常常会被应用于高并发的场景中。

**为什么会出现ConcurrentHashMap？**

1. HashMap 非线程安全，不适用于并发场景
2. HashTable 线程安全，但是效率低下。

## 结构分析

### Segment

Segment 是CHM中非常重要的一个类，它继承至`ReentrantLock`，作为CHM分段锁实现的重要组成部分 。

```java
static final class Segment<K,V> extends ReentrantLock implements Serializable{
	...
  transient volatile HashEntry<K,V>[] table;
  transient int threshold;
  final float loadFactor;
	...
}
```

Segment 里面还包含了一个`HashEntry`数组，而`HashEntry`就是实际组装...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[如何写出高质量的技术文章？]]></title>
            <link>https://jingling.im/blog/how-to-write</link>
            <guid isPermaLink="false">https://jingling.im/blog/how-to-write</guid>
            <pubDate>Tue, 18 May 2021 10:58:44 GMT</pubDate>
            <content:encoded><![CDATA[
> 本文系转载至公众号：[如何写出高质量的技术文章？](https://mp.weixin.qq.com/s/s27aEhwgwHZZbtd4wn9MVA)
> 转载系列的文章都是本人觉得写得非常好的，值得认真品读学习的文章。

> 对于一个从小不喜欢语文、不喜欢阅读、作文很少及格的理科生来说，做梦也没想到，有一天我会写一篇文章教人如何写文章  :)

## **一 为什么要写文章**

懂了，不一定能说出来，说得出来，不一定能写出来。这就是写文章最大的好处，官方术语叫“费曼教学法”。写文章是一个逼迫自己深入理解问题、把问题想清楚，整理好思路，并能清晰表达出来的过程。其本质是一种自我学习、自我提升、构建知识体系的最佳方法。

除此之外，写文章还有一个副产品——帮助我们扩大影响力。就拿我来说，我大概是在4年前，开始有规划地搭建自己的知识体系，包括阅读、记笔记、写文章、分享。

在这期间，我连续3年获得最佳年度作者，上头条的文章也不少。另外，阿里技术公众号，也发表了我10篇左右的文章，其中有2篇入选了创刊最佳文章，很多篇文章都有3万+阅读的不错表现。

基于这些总结沉淀，我在人民邮电出版...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[如何做规划？分享2种思维和4个方法]]></title>
            <link>https://jingling.im/blog/how-to-plan</link>
            <guid isPermaLink="false">https://jingling.im/blog/how-to-plan</guid>
            <pubDate>Mon, 17 May 2021 10:16:51 GMT</pubDate>
            <content:encoded><![CDATA[
> 本文系转载至公众号：[如何做规划？分享2种思维和4个方法](https://mp.weixin.qq.com/s/LILm--w9vj_K7qjI4EakBw)
> 转载系列的文章都是本人觉得写得非常好的，值得认真品读学习的文章。

每年每个部门都要进行规划，规划能让目标更聚焦，让我们清晰地知道今后我们要做什么、如何去做。并非每个人都会参与规划中去，但需要掌握规划的方法，否则让你来做规划时，你会发现很痛苦，找不到什么头绪，要么规划出来的内容都是散点，要么规划出来的太抽象。

在本篇文章中，提到了规划的2种思维模式，并分享自己在规划中用到的4个规划方法，让开始做规划的你显得不那么迷茫。

## **一  你应该学会规划**

1  规划不只是高层的事

规划不仅仅是高层的事，每个人都要学会规划，只是每个人规划事情的格局不一样，高层做战略规划，下面的人做战术规划。规划也是分层次的，有战略规划、部门规划、团队规划、个人规划，站在每一层中，你看到的内容是不一样的，一般都是高层规划包含底层规划，底层规划支撑高层规划。

有些人认为规划是主管的事，自己只要按照规划去做事就行，这是一种被动接受...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[单体系统时代：应用最广泛的架构风格]]></title>
            <link>https://jingling.im/blog/monolithic-application</link>
            <guid isPermaLink="false">https://jingling.im/blog/monolithic-application</guid>
            <pubDate>Fri, 14 May 2021 11:45:18 GMT</pubDate>
            <content:encoded><![CDATA[
为什么单体架构能够在相当长的时间里成为软件架构的主流风格？

### 大型单体系统

单体架构是绝大部分软件开发者都学习和实践过的一种软件架构，很多介绍微服务的图书和技术资料中，也常常会把这种架构形式的应用称作“[巨石系统](https://en.wikipedia.org/wiki/Monolithic_application)”（Monolithic Application）。

整个软件架构演进的历史进程里，单体架构是出现时间最早、应用范围最广、使用人数最多、统治历史最长的一种架构风格。但“单体”这个名称，却是从微服务开始流行之后，才“事后追认”所形成的概念。在这之前，并没有多少人会把“单体”看成一种架构。

如果你去查找软件架构的开发资料，可以轻轻松松找到很多以微服务为主题的图书和文章，但却很难能找到专门教我们怎么开发单体系统的任何形式的材料。

这一方面体现了单体架构本身的简单性；另一方面也体现出，在相当长的时间里，我们都已经习惯了，软件架构就应该是单体这种样子的。

那在剖析单体架构之前呢，我们有必要先**搞清楚一个思维误区**，那就是单体架构是落后的系统架构风格，最终会...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[收藏！Java编程技巧之单元测试用例编写流程]]></title>
            <link>https://jingling.im/blog/unit-test</link>
            <guid isPermaLink="false">https://jingling.im/blog/unit-test</guid>
            <pubDate>Wed, 12 May 2021 11:21:09 GMT</pubDate>
            <description><![CDATA[精通Java单元测试：从基础到高级模拟技术，学习如何使用Mockito和PowerMock进行有效的单元测试。深入理解测试框架的选择、模拟依赖、注入技术、测试用例编写流程。探索解决复杂测试问题的方法，包括参数捕获、日志模拟、容器兼容性，以及避免类型转换警告的技巧。提升代码质量，确保全面覆盖，本文是Java开发者编写高质量单元测试的指南。]]></description>
            <content:encoded><![CDATA[
> 本文系转载至公众号：[收藏！Java编程技巧之单元测试用例编写流程](https://mp.weixin.qq.com/s/hX_RIYs-nBnqVwdq5B4rhg)
> 转载系列的文章都是本人觉得写得非常好的，值得认真品读学习的文章。

> 温馨提示：本文较长，同学们可收藏后再看 :)

## 前言

清代杰出思想家章学诚有一句名言：“学必求其心得，业必贵其专精。”

意思是：学习上一定要追求心得体会，事业上一定要贵以专注精深。做技术就是这样，一件事如果做到了极致，就必然会有所心得体会。作者最近在一个项目上，追求单元测试覆盖率到极致，所以才有了这篇心得体会。

上一篇文章[《Java单元测试技巧之PowerMock》](http://mp.weixin.qq.com/s?__biz=MzIzOTU0NTQ0MA==&mid=2247502647&idx=1&sn=eb1600c04511243d5a2c4c3e6ea43bfa&chksm=e92af638de5d7f2e2b0078bb4cee5b67a7b6d0f8ee3f7e666db9a753fede8ca53088...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[多线程-按序打印]]></title>
            <link>https://jingling.im/blog/print-in-order</link>
            <guid isPermaLink="false">https://jingling.im/blog/print-in-order</guid>
            <pubDate>Sat, 08 May 2021 17:02:50 GMT</pubDate>
            <content:encoded><![CDATA[
### 描述

[点击查看原题](https://leetcode-cn.com/problems/print-in-order/)

我们提供了一个类：

```java
public class Foo {
  public void first() { print("first"); }
  public void second() { print("second"); }
  public void third() { print("third"); }
}
```

三个不同的线程 A、B、C 将会共用一个 `Foo` 实例。

- 一个将会调用 `first()` 方法
- 一个将会调用 `second()` 方法
- 还有一个将会调用 `third()` 方法

请设计修改程序，以确保 `second()` 方法在 `first()` 方法之后被执行，`third()` 方法在 `second()` 方法之后被执行。

### **示例**

**示例 1:**

```
输入: [1,2,3]
输出: "firstsecondthird"
解释:
有三个线程会被异步启...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[我的开发工具百宝箱]]></title>
            <link>https://jingling.im/blog/tools</link>
            <guid isPermaLink="false">https://jingling.im/blog/tools</guid>
            <pubDate>Thu, 29 Apr 2021 15:28:23 GMT</pubDate>
            <description><![CDATA[Java 后端常用开发工具]]></description>
            <content:encoded><![CDATA[
记录我的开发工具，以后持续更新和完善。

### 编辑器

- IntelliJ IDEA
- Sublime Text
- HBuilderX
- 微信开发者工具

### IDEA 插件

- AlibabaJava Coding Guidelines
  阿里Java代码规约检查
- Maven helper
  pom依赖冲突检查
- Rainbow brackets
  代码中的各种括号用不同的颜色分层
- Lombok
  Lombok支持
- restfultoolkit
  RESTful服务开发的工具包

      1.根据 URL 直接跳转到对应的方法定义 ( Ctrl \ or Ctrl Alt N );
      2.提供了一个 Services tree 的显示窗口;
      3.一个简单的 http 请求工具;
      4.在请求方法上添加了有用功能: 复制生成 URL;,复制方法参数...
      5.其他功能: java 类上添加 Convert to JSON 功能，格式化 json 数据

- CodeGlance
  类似于Sub...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[ConcurrentHashMap 使用示例]]></title>
            <link>https://jingling.im/blog/cumap-example</link>
            <guid isPermaLink="false">https://jingling.im/blog/cumap-example</guid>
            <pubDate>Wed, 28 Apr 2021 10:57:12 GMT</pubDate>
            <description><![CDATA[ConcurrentHashMap 使用示例]]></description>
            <content:encoded><![CDATA[
### 构造方法

```java
// 1.无参数构造方法
new ConcurrentHashMap();

// 2.指定初始容量
new ConcurrentHashMap(initialCapacity)

// 3.指定初始容量和加载因子
new ConcurrentHashMap(initialCapacity,loadFactor)

// 4.指定初始容量和加载因子与并发级别(并发更新线程数)
new ConcurrentHashMap(initialCapacity, loadFactor, concurrencyLevel)

// 5.创建与给定映射具有相同映射的新映射
new ConcurrentHashMap(Map<? extends K, ? extends V> m)
```

## 方法介绍

### 新增元素

```java
// 1.添加元素，不允许null
map.put(1,1);

// 2.添加一个map
map.putAll(map);
// 3.添加元素, 键不存在映射关系才添加成功
map.putIfAbsent(2,1);
`...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[Java 位运算技巧]]></title>
            <link>https://jingling.im/blog/bitoperation</link>
            <guid isPermaLink="false">https://jingling.im/blog/bitoperation</guid>
            <pubDate>Tue, 27 Apr 2021 10:37:54 GMT</pubDate>
            <description><![CDATA[Java 位运算技巧，位运算常用技巧]]></description>
            <content:encoded><![CDATA[
## 二进制

二进制（binary）通常用0和1来进行表示，在数字电子电路中，逻辑门直接采用了二进制，现代计算机和依赖计算机设备里面也都用到了二进制，而位运算就是直接对整数在内存中的二进制位进行操作。

比如二进制：0000 1000

对应的十进制是：8

### Java 中进制的表示

在Java7之前，Java是不支持直接书写二进制的，但在Java7开始就支持直接书写二进制。

- 二进制（前缀：0b/0B）
- 8进制（前缀：0）
- 16进制（前缀：0x/0X）

下面是10进制数32，在Java语言中的4种不同进制表示方式

```java
public static void main(String[] args){
    // 二进制
    int a = 0b00100000;
    // 8进制
    int b = 040;
    // 十进制
    int c = 32;
    // 十六进制
    int d = 0x20;
    System.out.println(a);
    System.out.println(b);
   ...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[（十）操作系统-文件系统]]></title>
            <link>https://jingling.im/blog/system10</link>
            <guid isPermaLink="false">https://jingling.im/blog/system10</guid>
            <pubDate>Mon, 26 Apr 2021 11:31:37 GMT</pubDate>
            <description><![CDATA[本文详细介绍了操作系统中的文件系统，包括文件的定义、类型、目录结构、实现方式以及磁盘空间管理。文章解释了文件系统如何组织、存储和访问文件，以及不同文件分配方法的优缺点。同时，还讨论了UNIX系统目录实现和磁盘空间管理技术。]]></description>
            <content:encoded><![CDATA[
### 什么是文件系统

- 文件是用来保存数据的，而文件系统可让用户组织、操纵和存取文件；
- 文件——具有符号名的数据项的集合；
- 普遍将文件看成是命名的相关记录的集合（数据项——记录——文件）
- 文件系统是操作系统中负责存取和管理辅助存储器上文件信息的模块，它用统一的方式管理用户和系统信息的存储、检索、更新、共享和保护，并为用户提供一整套方便有效的文件使用和操作方法。

### 文件的类型

文件的类型涉及对文件的操作功能、访问方法和访问权利。

**按用途分**

1. 系统文件——与操作系统本身有关的信息（程序或数据）组成的文件；
2. 库文件——由系统提供给用户调用的各种标准过程、函数和应用程序等；
3. 用户文件——由用户的信息（程序或数据）所组成的文件。

**按文件中数据分**

1. 源文件——指源程序和数据，以及作为处理结果的输出数据的文件；
2. 相对地址形式文件——由各种语言编译程序所输出的相对地址形式的程序文件；
3. 可执行的目标文件——由连接装配程序连接后所生成的可执行的目标程序文件。

**按操作保护分**

1. 只读文件——仅允许对其进行读操...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[（九）操作系统-设备管理]]></title>
            <link>https://jingling.im/blog/system09</link>
            <guid isPermaLink="false">https://jingling.im/blog/system09</guid>
            <pubDate>Fri, 23 Apr 2021 17:00:45 GMT</pubDate>
            <description><![CDATA[本文详细介绍了操作系统中的设备管理，包括存储型设备和输入输出型设备的作用，以及I/O功能实现技术的进展。重点讨论了编程I/O、中断驱动I/O、DMA技术，以及I/O子系统的设计目标和设备分类。同时，还涉及了设备命名、磁盘调度算法和虚拟设备的概念，旨在提高系统效率和设备利用率。]]></description>
            <content:encoded><![CDATA[
## 概述

### 计算机外围设备

- 存储型设备：磁带机、磁盘机等，以存储大量的信息和加快检索为目标，作为主存储器的扩充，又称为辅助存储器；
- 输入输出型设备：显示器、打印机等，把外界信息输入计算机，把运算结果从计算机输出。

设备管理，普遍使用I/O中断、缓冲区管理、通道、设备驱动调度等多种技术，较好地克服了外设与主机速度不匹配的问题，使主机与外设并行工作，提高使用效率。

### I/O功能实现技术的进展

- 主存与外部设备之间的信息传递操作称为I/O操作；
- 早期CPU直接控制和管理外部设备；
- 而后在设备上添加了设备控制器或I/O模块，此时处理器开始逐渐与外部设备的有关细节相脱离；
- 随着中断技术的出现，I/O执行模式中使用了中断技术；
- 出现直接存储访问DMA技术，使I/O模块可以直接控制主存，在没有处理器的干预下向或者从主存移动数据块；
- I/O模块的概念进一步发展成为专门的I/O处理器，此时CPU完全从I/O实现的细节中脱离出来。

### I/O子系统的技术模式

**编程I/O**

- 处理器根据用户进程中的指令，向I/O设备块发出I/O命令；...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[（八）操作系统-虚拟存储管理]]></title>
            <link>https://jingling.im/blog/system08</link>
            <guid isPermaLink="false">https://jingling.im/blog/system08</guid>
            <pubDate>Fri, 23 Apr 2021 11:09:42 GMT</pubDate>
            <description><![CDATA[本文详细介绍了虚拟存储管理的概念、技术特点、实现方式以及页面置换算法。重点讨论了实存管理的限制，虚拟存储器的工作原理，包括虚拟地址和实地址的区别、虚拟地址空间和实地址空间的定义，以及动态地址映象机构的作用。同时，探讨了分页技术、分段技术及其组合使用的优势和缺点，并分析了不同页面置换算法的适用场景和效率。]]></description>
            <content:encoded><![CDATA[
## 虚拟存储系统的基本概念

实存管理技术特点

- 在作业运行时，整个作业的逻辑地址空间必须全部装入主存；
- 当作业尺寸大于主存可用空间时，该作业就无法运行。

虚拟存储器

- 一种实际上并不以物理形式存在的虚假的存储器；

把被运行进程访问的地址同主存的物理地址区别开来；

一个程序被编译连接后产生目标程序，该目标程序所限定的地址的集合称为逻辑地址空间，目标程序中指令和数据放置的位置称相对地址或逻辑地址；

而CPU能直接访问的主存称为物理地址空间或实存地址空间；

虚拟地址

- 运行进程访问的地址；

实地址

- 处理器可直接访问的主存地址；

虚拟地址空间

- 运行进程可以访问的虚地址的集合；

实地址空间

- 计算机的主存；

由动态地址映象机构来完成虚地址到实地址的转换；

虚实地址的区分，为使进程的虚拟地址空间大于主存实地址空间创造了条件，也为作业大小可大于主存空间创造了条件。

## 虚存管理技术

在虚拟存储系统中，用户认为机器具有无穷大的存储空间，并且只有这一个用户在使用机器；

用户只使用了物理主存的很少部分；

原因：操作系统内核只把进程当前要用...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[（七）操作系统-主存储器管理]]></title>
            <link>https://jingling.im/blog/system07</link>
            <guid isPermaLink="false">https://jingling.im/blog/system07</guid>
            <pubDate>Thu, 22 Apr 2021 14:22:12 GMT</pubDate>
            <description><![CDATA[本文详细介绍了操作系统中的主存储器管理，包括主存分配、地址转换与重定位、存储保护与共享、存储扩充、地址映射方式、固定与可变分区存储管理、存储分配算法、碎片问题及其解决方法、动态重定位、多重分区存储管理、简单分页与分段存储管理等关键概念和技术。文章强调了提高CPU利用率、存储利用率、缩短程序启动时间、实现虚拟存储技术、以及多任务环境下的存储管理的重要性。]]></description>
            <content:encoded><![CDATA[
## 主存储器的管理功能

主存分配

- 可以使多个程序同时驻留在主存中，以提高CPU利用率；
- 保证系统的高性能，提高存储利用率和主存的分配和释放(回收)速度，以加快任务的执行。

地址转换和重定位

- 程序不必事先约定存放地址，可在执行过程中移动；
- 可以运行只装入了一部分的程序，缩短程序的启动时间；
- 研究和使用各种有效的地址转换技术以及相应的地址转换机构。

存储保护和主存共享

- 如何保护各存储区中信息不被破坏和偷窃；
- 由于许多不同的任务可能要执行同一个程序，进程中多个合作进程要访问相同的数据结构，所以存储保护机制要提供进程对某些主存区共享的灵活性。

存储扩充

- 使用有效的存储管理技术来实现逻辑上的扩充——即虚拟存储技术；
- 运行的程序应不受主存大小的限制，理想情况下应能运行任意大小的程序。

## 地址映射

### 什么是地址映射？

首先，在多用户共享主存时，需要由系统分配主存；

一般情况下，一个作业程序分配到的存储空间和它的地址空间是不一致的；

因此作业的相应进程在处理器上运行时，所要访问的指令和数据的实际地址和地址空间中的地址是不同的；...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[（六）操作系统-处理器调度及死锁]]></title>
            <link>https://jingling.im/blog/system06</link>
            <guid isPermaLink="false">https://jingling.im/blog/system06</guid>
            <pubDate>Thu, 22 Apr 2021 09:59:50 GMT</pubDate>
            <description><![CDATA[本文详细介绍了操作系统中的处理器调度和死锁问题。长期、中期和短期调度的层次被阐述，包括它们的作用和重要性。文章还列举了多种处理器调度算法，如先进先出、优先级调度等，并讨论了它们的优缺点。同时，介绍了死锁的概念、必要条件、预防策略和恢复方法，以及死锁模型的有向图表示。]]></description>
            <content:encoded><![CDATA[
## 调度与作业

### 调度的层次

1. 长期调度
   - 又称作业调度；
   - 主要功能是按照某种原则从磁盘某些盘区的作业队列和交互作业中选取作业进入主存，并为作业做好运行前的准备工作和作业完成后的善后工作。
2. 中期调度
   - 决定哪些进程被允许参与竞争处理器资源；
   - 主要起到短期调整系统负荷的作用，以平顺系统的操作。
3. 短期调度
   - 又称处理器调度；
   - 主要功能是按照某种原则将处理器分配给就绪进程或线程。

![2](https://i.loli.net/2021/04/22/Tkd3SsiwOBGPDyA.png)

### 作业状态

作业在每一阶段中所处的状况称为作业的状态；

系统中的作业通常分为四种状态：

1. 提交状态
2. 后备状态
3. 运行状态
4. 完成状态

   ![3](https://i.loli.net/2021/04/22/9Ln7JBjKh1mEsZI.png)

## 处理器调度

### 常见的调度算法

1. 先进先出调度算法
2. 优先级调度算法——非抢占的、可抢占的
3. 时间片轮转算法
...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[（五）操作系统-进程间的交互]]></title>
            <link>https://jingling.im/blog/system05</link>
            <guid isPermaLink="false">https://jingling.im/blog/system05</guid>
            <pubDate>Wed, 21 Apr 2021 17:29:27 GMT</pubDate>
            <description><![CDATA[本文深入探讨了操作系统中进程间的交互机制，包括竞争关系和协作关系，以及进程同步和互斥的实现方法。详细解释了临界区、互斥硬件方法、信号量、互斥信号量、读者-写者问题以及进程间通信（IPC）的方式，包括信号、共享存储区、管道和消息传递机制。通过具体示例，如哲学家就餐问题和爸爸、儿子、女儿的同步问题，展示了同步机制的应用。]]></description>
            <content:encoded><![CDATA[
在多道程序设计系统中，同一时刻可能有许多进程，这些进程之间存在两种基本关系：竞争关系和协作关系。

### 竞争关系

系统中的多个进程之间彼此无关，它们并不知道其他进程的存在，但由于共享系统资源，就会出现竞争。当多个进程竞争共享硬设备、变量、表格、链表、文件等资源时，可能导致处理出错。

- 死锁、饥饿（资源的竞争出现了这两个控制问题）
- 进程的互斥（临界区管理）

### 协作关系

某些进程为完成同一任务需要分工协作。例如：input, compute, output ，协作进程之间
各自知道对方的存在

- 进程的同步（解决进程间协作关系的手段 ）

进程互斥关系是一种特殊的进程同步关系，即逐次使用共享资源

### 临界区

临界区（critical sections）

- 进程中访问共享变量（临界资源）的代码段（程序段）
- 不同进程关于同一变量的临界段代码可能是完全不同的
- 临界资源—共享变量

进程对临界区的访问必须互斥地进行

![Untitled](https://i.loli.net/2021/04/21/DYXln8UvRbrZQKh.png)

## ...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[（四）操作系统-多线程]]></title>
            <link>https://jingling.im/blog/system04</link>
            <guid isPermaLink="false">https://jingling.im/blog/system04</guid>
            <pubDate>Wed, 21 Apr 2021 14:58:26 GMT</pubDate>
            <description><![CDATA[本文详细介绍了操作系统中多线程的概念、性质、状态、生命周期、描述和管理，以及线程与传统进程相比的优势。多线程机制能显著提升系统并行处理能力，降低创建和切换的开销，简化用户编程，适用于多机和单CPU系统，是现代操作系统中广泛应用的技术。]]></description>
            <content:encoded><![CDATA[
## 线程的引入

- 提高系统并行性
  - 硬件方面：流水线计算机、数据流计算机、并行处理器、流水存储器、多体交叉及多端口存储器等；
  - 操作系统方面：中断、通道、多道程序设计技术、并发程序技术。
- 仅使用进程来解决并行性问题的缺点
  - 经常性的进程开关，系统开销很大；
    - 与进程运行有关的表格要改写；
    - 进程的地址空间要进行转换为新被调度的进程的地址空间；
    - 两次模式开关的开销（用户模式—内核模式—用户模式）
- 传统的进程概念有两个严重的局限性
  1. 许多应用想并发执行彼此间独立的任务，但又必须要共享一个公共的地址空间和其他资源，将这类应用中独立的任务串行化，效率很低；
  2. 传统的进程不能很好地利用多处理器系统。

## 线程（thread）的概念

概念：

- 进程内的一个执行单元
- 进程内的一个可调度实体
- 线程是程序（或进程）中相对独立的一个控制流序列
- 线程是执行的上下文，即执行的现场数据和其他调度所需要的信息

线程是进程内一个相对的、可调度的执行单元 ； 有些系统把线程称为轻质进程（lightweight p...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[（三）操作系统-进程管理]]></title>
            <link>https://jingling.im/blog/system03</link>
            <guid isPermaLink="false">https://jingling.im/blog/system03</guid>
            <pubDate>Tue, 20 Apr 2021 16:14:34 GMT</pubDate>
            <description><![CDATA[本文详细讨论了操作系统中的进程管理，包括进程的定义、基本状态、状态变化、进程控制块（PCB）的结构和功能，以及进程控制原语和调度算法。]]></description>
            <content:encoded><![CDATA[
## 进程的定义

- 程序在处理器上的执行
- 进程是一个可调度的实体
- 进程是逻辑上的一段程序，它在每一瞬间都含有一个程序控制点，指出现在正在执行的指令
- 顺序进程是一个程序及其数据在处理器上顺序地执行时所发生的活动

### 进程与程序之间的区别

进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。

- 进程是程序的执行，属于动态概念，而程序是一组指令的有序集合，是静态概念
- 进程的存在是暂时的，而程序的存在是永久的
- 进程的组成包括程序和数据，还由“进程控制块(PCB)”组成
- 一个程序可能对应多个进程
- 一个进程可以包含多个程序

## 进程的基本状态

- 运行状态(Running)：正在处理器上运行的状态
- 就绪状态(Ready)：一个进程获得了除处理器外的一切所需资源，一旦得到处理器即可运行的状态
- 等待/阻塞状态(Blocked)：一个进程正在等待某一事件发生而暂时停止运行的状态14

  ![1](https://i.loli.net/2021/04/20/Fjzm9cEbsXI7akD.png)

  ### **进程状态的变化*...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[（二）操作系统-运行环境]]></title>
            <link>https://jingling.im/blog/system02</link>
            <guid isPermaLink="false">https://jingling.im/blog/system02</guid>
            <pubDate>Tue, 20 Apr 2021 11:08:29 GMT</pubDate>
            <description><![CDATA[本文详细介绍了操作系统的运行环境，包括处理器的特权指令、状态管理、程序状态字（PSW）、存储保护机制、缓冲技术和中断技术。同时，还探讨了中断响应、中断处理、时钟分类和地址转换等概念，为理解操作系统的硬件基础提供了全面的信息。]]></description>
            <content:encoded><![CDATA[
## 硬件环境

### 处理器（CPU）

特权指令：

- 那些只能由操作系统使用、不允许一般用户使用的指令。
- 如：启动某设备指令、设置时钟指令、控制中断屏蔽的某些指令、清内存指令、建立存储保护指令等。

### 处理器的状态

1. 核心状态
2. 管理状态
   - 管态（管理态）——可以执行全部指令
3. 用户程序状态（目标状态）
   - 目态（问题态）——只能执行非特权指令

### 程序状态字（PSW）

一个专门用来指示处理器状态的寄存器

**程序状态字的内容**

- 程序现在应该执行哪条指令
- 当前指令执行情况
- 机器处于何种程序状态
- 程序在执行时应该屏蔽哪些中断
- 寻址方法、编址、保护键
- 响应中断的内容

### **存储保护**

对主存中的信息严格保护，是操作系统和其他程序正确运行的基本条件之一

常用的存储器保护机制：

1.  界地址寄存器（界限寄存器）
    在CPU中设置一对界限寄存器来存放该作业在主存中的下限和上限地址
2.  存储键
    每个存储块有一个与其相关的由五位二进位组成的存储保护键
    左边四位——存储保...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[（一）操作系统-概述]]></title>
            <link>https://jingling.im/blog/system01</link>
            <guid isPermaLink="false">https://jingling.im/blog/system01</guid>
            <pubDate>Mon, 19 Apr 2021 18:13:33 GMT</pubDate>
            <description><![CDATA[本文全面概述了操作系统的基本概念、组成、发展阶段、功能、特性以及不同类型的操作系统。文章从硬件和软件的层次结构关系入手，介绍了冯·诺伊曼结构和存储程序式计算机的概念，进而详细解释了操作系统的定义、功能、发展阶段和特性。]]></description>
            <content:encoded><![CDATA[
## 操作系统的组成

计算机系统由硬件和软件两部分组成

- 硬件部分：指其物理装置本身，包括各种处理器（如中央处理器、输入输出处理器和该系统中的其他处理器）、存储器、输入输出设备和通信装置；
- 软件部分：指由计算机硬件执行以完成一定任务的所有程序及其数据。

### 层次结构关系

软硬件及软件各部分之间，是一种层次结构的关系，硬件与软件是互相依赖、互相促进的。

![1](https://i.loli.net/2021/04/19/rsVJRTNfv4t6qEQ.png)

### 层次结构说明

- 底层是硬件
  - 最底层是物理设备
  - 其次是直接控制设备并向上一层提供更清晰的接口的很原始的软件——微程序
  - 由微程序解释执行的一套指令集称为机器语言
- 系统软件

  - 操作系统：隐藏复杂性，受硬件保护而免遭用户篡改；

    操作系统是最基本的系统软件，它控制计算机的所有资源并提供应用程序开发的
    基础。

  - 其他系统软件：这些程序本身并不是操作系统的部分

- 应用软件
  - 实现用户所需要的功能

## 计算机的硬件组织

### 冯·...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[MySQL explain 命令分析]]></title>
            <link>https://jingling.im/blog/mysql-explain</link>
            <guid isPermaLink="false">https://jingling.im/blog/mysql-explain</guid>
            <pubDate>Thu, 15 Apr 2021 11:37:23 GMT</pubDate>
            <description><![CDATA[本文详细解释了 MySQL explain 命令的使用方法和每个字段含义，帮助你识别 SQL 性能瓶颈，优化查询效率。]]></description>
            <content:encoded><![CDATA[
explain关键字可以模拟MySQL优化器执行SQL语句，可以很好的分析SQL语句或表结构的性能瓶颈。

### explain 执行效果

```java
mysql> explain SELECT * FROM `db_goods`.`t_shopcart`;
+----+-------------+------------+------------+------+---------------+------+---------+------+-------+----------+-------+
| id | select_type | table      | partitions | type | possible_keys | key  | key_len | ref  | rows  | filtered | Extra |
+----+-------------+------------+------------+------+---------------+------+---------+------+-------+----------+-------+
| ...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[我在架构设计和代码开发中的一些常用原则]]></title>
            <link>https://jingling.im/blog/kaifayuanze</link>
            <guid isPermaLink="false">https://jingling.im/blog/kaifayuanze</guid>
            <pubDate>Wed, 31 Mar 2021 11:45:55 GMT</pubDate>
            <content:encoded><![CDATA[
> 本文系转载至公众号：[我在架构设计和代码开发中的一些常用原则](https://mp.weixin.qq.com/s/XAdSl1zkOeJpcWHrS0IAVw)
> 转载系列的文章都是本人觉得写得非常好的，值得认真品读学习的文章。

---

> 不管我一生中取得了多大的成功，其主要原因都不是我知道多少事情，而是我知道在无知的情况下自己应该怎么做。我一生中学到的最重要的东西是一种以原则为基础的生活方式，是它帮助我发现真相是什么，并据此如何行动。——瑞·达利欧（Ray Dalio）

在日常的开发和设计过程中，大家对技术设计上的一些问题往往会面临很多的选择，不同的人会有不同的选择，每每如此，我都会尝试着问自己：我做出选择和判断背后的原则是什么？

经过这么多年的发展，在软件设计过程，目前沉淀下来的原则有很多，但很多情况下，很多原则为了普适性，总结得会比较抽象，一旦太过抽象，对原则的解释和理解就会因人而异，譬如：高内聚低耦合原则，大家都懂，但是如何落地和执行却是很难说完全达成一致。因此，需要针对一些实际的场景中的问题去总结和补充，在大的原则下具化形成大家容易理解一致的相对明确原则...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[浅谈分库分表那些事儿]]></title>
            <link>https://jingling.im/blog/fenkufenbiao</link>
            <guid isPermaLink="false">https://jingling.im/blog/fenkufenbiao</guid>
            <pubDate>Wed, 31 Mar 2021 10:57:19 GMT</pubDate>
            <content:encoded><![CDATA[
> 本文系转载至公众号：[浅谈分库分表那些事儿](https://mp.weixin.qq.com/s/X6FI9Ci7ZXGDNDCkh2VnNA)
> 转载系列的文章都是本人觉得写得非常好的，值得认真品读学习的文章。

---

> 本文适合：需要从单库单表改造为多库多表的新手。

本文主要阐述在分库分表改造过程中需要考虑的因素以及对应的解法，还有踩过的那些坑。

## **一  前言**

我们既然要做分库分表，那总要有个做事的动机。那么，在动手之前，首先就要弄明白下面两个问题。

1  什么是分库分表？

其实就是字面意思，很好理解：

- 分库：从单个数据库拆分成多个数据库的过程，将数据散落在多个数据库中。
- 分表：从单张表拆分成多张表的过程，将数据散落在多张表内。

2  为什么要分库分表？

关键字：提升性能、增加可用性。

### **从性能上看**

随着单库中的数据量越来越大、数据库的查询QPS越来越高，相应的，对数据库的读写所需要的时间也越来越多。数据库的读写性能可能会成为业务发展的瓶颈。对应的，就需要做数据库性能方面的优化。本文中我们只讨论数据库层面的优化，...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[Java异步非阻塞编程的几种方式]]></title>
            <link>https://jingling.im/blog/java-async-code</link>
            <guid isPermaLink="false">https://jingling.im/blog/java-async-code</guid>
            <pubDate>Wed, 24 Feb 2021 10:09:28 GMT</pubDate>
            <description><![CDATA[深入浅出解读 Java 异步非阻塞编程：从同步阻塞的 Http 调用开始，逐步讲解 Future、Callback、CompletableFuture、Reactive Streams 和 Reactor 等主流技术，帮助你提升 Java 应用程序的性能和响应能力。]]></description>
            <content:encoded><![CDATA[
> 本文系转载至公众号：[Java异步非阻塞编程的几种方式](https://mp.weixin.qq.com/s/AVMN8jyqMXWhE9bRs_e0aQ)
> 转载系列的文章都是本人觉得写得非常好的，值得认真品读学习的文章。

### 一  从一个同步的Http调用说起

一个很简单的业务逻辑，其他后端服务提供了一个接口，我们需要通过接口调用，获取到响应的数据。

逆地理接口：通过经纬度获取这个经纬度所在的省市区县以及响应的code：

```auto
curl-i"http://xxx?latitude=31.08966221524924&channel=amap7a&near=false&longitude=105.13990312814713"
```

```auto
{"adcode":"510722"}
```

服务端执行，最简单的同步调用方式：

![1](https://i.loli.net/2021/02/24/XNxcIbrKq7o3iaf.png)

服务端响应之前，IO会阻塞在：java.net.SocketInputStream#socketRea...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[Java中9种常见的CMS GC问题分析与解决]]></title>
            <link>https://jingling.im/blog/java-9gc</link>
            <guid isPermaLink="false">https://jingling.im/blog/java-9gc</guid>
            <pubDate>Tue, 23 Feb 2021 09:57:15 GMT</pubDate>
            <content:encoded><![CDATA[
> 本文系转载至：[美团技术团队--Java中9种常见的CMS GC问题分析与解决](https://tech.meituan.com/2020/11/12/java-9-cms-gc.html)
> 转载系列的文章都是本人觉得写得非常好的，值得认真品读学习的文章。

## 1\. 写在前面

| 本文主要针对 Hotspot VM 中“CMS + ParNew”组合的一些使用场景进行总结。重点通过部分源码对根因进行分析以及对排查方法进行总结，排查过程会省略较多，另外本文专业术语较多，有一定的阅读门槛，如未介绍清楚，还请自行查阅相关材料。

| 总字数 2 万左右（不包含代码片段），整体阅读时间约 30min ，文章较长，可以选择你感兴趣的场景进行研究。

### 1.1 引言

自 Sun 发布 Java 语言以来，开始使用 GC 技术来进行内存自动管理，避免了手动管理带来的悬挂指针（Dangling Pointer）问题，很大程度上提升了开发效率，从此 GC 技术也一举成名。GC 有着非常悠久的历史，1960 年有着“Lisp 之父”和“人工智能之父”之称的 John McCar...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[高可用的本质]]></title>
            <link>https://jingling.im/blog/the-essence-of-high-availability</link>
            <guid isPermaLink="false">https://jingling.im/blog/the-essence-of-high-availability</guid>
            <pubDate>Mon, 22 Feb 2021 10:41:45 GMT</pubDate>
            <description><![CDATA[架构设计，高可用的本质]]></description>
            <content:encoded><![CDATA[
> 本文系转载至公众号：[阿里技术--高可用的本质](https://mp.weixin.qq.com/s/CkFHTuxqoj1WJ7d0HUEbAg)
> 转载系列的文章都是本人觉得写得非常好的，值得认真品读学习的文章。

我是乐羊，一个热爱风险防控的人，之前参与过蚂蚁Glocal多个站点从0到1的建站和高可用建设，目前正在参与蚂蚁大安全的高可用建设。无论是一个域，一个BG，还是一个站点，虽然范围有大有小，对象有所不同，但其高可用的理念都是相通的，今天将自己对高可用的一点点思考以及总结的【nPRT公式】分享给大家。

本文采用“高可用是什么，为什么要高可用，怎么做高可用，为什么这么做，软件风险又在哪里”的逻辑来介绍。

## 一  高可用是一种控制风险的能力

高可用是一种面向风险设计，使系统具备控制风险，提供更高的可用性的能力。

## 二  为什么要高可用

对于一个公司而言，“为什么要高可用”可以完整理解为“公司为什么要（做系统）高可用”。以公司为对象，从内看包括：人，软件（物），硬件（物）；从外看包括：客户，股东，社会；从自身看包括：公司。

![1](https://i...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[技术方案设计没有深度？试试这套方法论]]></title>
            <link>https://jingling.im/blog/technical-solution-design</link>
            <guid isPermaLink="false">https://jingling.im/blog/technical-solution-design</guid>
            <pubDate>Mon, 22 Feb 2021 09:45:41 GMT</pubDate>
            <content:encoded><![CDATA[
> 本文系转载至公众号：[阿里技术--技术方案设计没有深度？试试这套方法论](https://mp.weixin.qq.com/s/8Uo4Da2vOmIhMcb-b3vy4g)
> 转载系列的文章都是本人觉得写得非常好的，值得认真品读学习的文章。

平时听到一些同学说技术方案没什么深度，好难讲出来，怎么去体现技术方案设计的深度是大家普遍关心的一个问题，这个问题不是个例问题，因此分享下自己的一些观点和看法。主要从三个部分来讲：

- 第一部分主要分析为什么技术方案没有体现出深度，找到问题后就好解决，并提出技术方案的广度和深度特征；

- 第二部分是技术方案设计的方法论，主要包括了本质论、矛盾论、系统论、演进论四个方法论方法，构成一个闭环反馈链路；

- 第三部分是通过具体的案例，反复运用第二部分的方法论阐述在实例的案例中如何去应用，加深对方法论的理解。

## 技术方案体现广度和深度

### 1 方案设计常见的反馈

我们都希望的自己设计的技术方案能够让人眼前一亮、叹为观止、拍案叫绝……，然而在实际情况下，却并不是这样的，经常听到如下的说法：

- 场景简单：业务场景很简单，怎么也...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[Java 线程池总结]]></title>
            <link>https://jingling.im/blog/threadpoolexecutor</link>
            <guid isPermaLink="false">https://jingling.im/blog/threadpoolexecutor</guid>
            <pubDate>Mon, 08 Feb 2021 16:26:19 GMT</pubDate>
            <content:encoded><![CDATA[
### 线程池概念

说的简单明了一点，就是管理线程的一个池子，是一种基于池化思想管理线程的工具。

为解决资源分配的问题，线程池采用了“池化”（Pooling）思想。池化，顾名思义，是为了最大化收益并最小化风险，而将资源统一在一起管理的一种思想。

> Pooling is the grouping together of resources (assets, equipment, personnel, effort, etc.) for the purposes of maximizing advantage or minimizing risk to the users. The term is used in finance, computing and equipment management.——wikipedia

### 为什么需要线程池

Java 线程使用的是一对一线程模型来实现的，线程每次创建和销毁都会有一定的性能消耗，线程池的作用就是把创建好的线程统一管理起来，能避免重复创建和销毁带来的开销，从而达到减低资源损耗，提升程序响应速度的好处。

### 线程池体系...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[Java 多线程总结]]></title>
            <link>https://jingling.im/blog/thread</link>
            <guid isPermaLink="false">https://jingling.im/blog/thread</guid>
            <pubDate>Fri, 05 Feb 2021 16:50:24 GMT</pubDate>
            <description><![CDATA[Java 多线程总结，Java 线程多种实现方式]]></description>
            <content:encoded><![CDATA[
### 什么是进程

**进程是程序运行资源分配的最小单位**

进程是操作系统进行资源分配的最小单位，其中资源包括：CPU资源、内存空间、磁盘IO等。同一进程中的多条线程共享该进程的全部系统资源，而进程和进程之间是相互独立的。

进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动，进程是系统进行资源分配和调度的一个独立单位。

进程可以分为用户进程和系统进程，凡是用于完成操作系统的各种功能的进程就是系统进程，它们就是处于运行状态下的操作系统本身；用户进程就是由用户启动的进程。

### 什么是线程

**线程是CPU调度的最小单位，必须依赖于进程而存在**

线程是进程的一个实体，是CPU调度和分派的基本单位，它是比进程更小的、能独立运行的基本单位。它可以与同属一个进程的其他线程共享进程所拥有的全部资源。

线程是无处不在的，任何一个进程都必须要创建线程。

### 什么是并行运行

> 并行是指“并排行走”或“同时实行或实施”。
> 在操作系统中是指，一组程序按独立异步的速度执行，无论从微观还是宏观，程序都是一起执行的。

举个例子：高速公路有4条车道，同时最多可以有4...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[源码分析：jdk1.8版本的HashMap源码分析]]></title>
            <link>https://jingling.im/blog/hashmap</link>
            <guid isPermaLink="false">https://jingling.im/blog/hashmap</guid>
            <pubDate>Fri, 05 Feb 2021 16:34:24 GMT</pubDate>
            <description><![CDATA[JDK源码分析 HashMap，HashMap 主要是用来存放键值对的，是基于哈希表的实现的Map接口，并允许null的值和null键，是常用的Java集合之一。]]></description>
            <content:encoded><![CDATA[
本文源码主要基于JDK 1.8 分析

## 简介

HashMap 主要是用来存放键值对的，是基于哈希表的实现的Map接口，并允许null的值和null键，是常用的Java集合之一。

### **数据结构**

JDK1.8 之前 HashMap 由数组+链表组成的，数组是 HashMap 的主体，链表则是主要为了解决哈希冲突而存在的（“拉链法”解决冲突）。

JDK1.8 以后在解决哈希冲突时有了较大的变化，当链表长度大于阈值（默认为 8）时，将链表转化为红黑树（将链表转换成红黑树前会判断，如果当前数组的长度小于 64，那么会选择先进行数组扩容，而不是转换为红黑树），以减少搜索时间，具体可以参考 `treeifyBin`方法。

### 继承体系结构图

![Untitled](https://i.loli.net/2021/02/05/yxsXohLQ2IBYHRZ.png)

## 源码解析

### **重要属性**

```java
		/** 默认初始容量-必须为2的幂。*/
    static final int DEFAULT_INITIAL_CAPACITY ...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[源码分析：Phaser 之更灵活的同步屏障]]></title>
            <link>https://jingling.im/blog/phaser</link>
            <guid isPermaLink="false">https://jingling.im/blog/phaser</guid>
            <pubDate>Thu, 04 Feb 2021 18:07:27 GMT</pubDate>
            <description><![CDATA[JDK源码分析 Phaser，Phaser 是 JDK 1.7 开始提供的一个可重复使用的同步屏障，功能类似于 CyclicBarrier 和 CountDownLatch，但使用更灵活，支持对任务的动态调整，并支持分层结构来达到更高的吞吐量。]]></description>
            <content:encoded><![CDATA[
## 简介

`Phaser` 是 JDK 1.7 开始提供的一个可重复使用的同步屏障，功能类似于`CyclicBarrier`和`CountDownLatch`，但使用更灵活，支持对任务的动态调整，并支持分层结构来达到更高的吞吐量。

### Registration（注册）

与其他屏障的情况不同，在 `Phaser` 上注册同步的参与方的数量可能随时间而变化。任务可以在任何时候注册（使用方法`register`、`bulkRegister`或建立初始参与方数量的构造函数），可以在任何到达时取消注册（使用`arriveAndDeregister`），注册和注销只影响内部计数，任务无法查询它们是否已注册。

### Synchronization（同步）

像`CyclicBarrier`，`Phaser`也可以重复`await`。方法`arriveAndAwaitAdvance()`有效果类似于`CyclicBarrier.await` 。phaser的每一代都有一个相关的phase number，初始值为0，当所有注册的任务都到达phaser时phase+1，到达最大值(Int...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[源码分析：Exchanger之数据交换器]]></title>
            <link>https://jingling.im/blog/exchanger</link>
            <guid isPermaLink="false">https://jingling.im/blog/exchanger</guid>
            <pubDate>Mon, 01 Feb 2021 21:46:42 GMT</pubDate>
            <description><![CDATA[JDK源码分析Exchanger，Exchanger是Java5 开始引入的一个类，它允许两个线程之间交换持有的数据。]]></description>
            <content:encoded><![CDATA[
## 简介

[`Exchanger`](https://jingling.im)是Java5 开始引入的一个类，它允许两个线程之间交换持有的数据。当[`Exchanger`](https://jingling.im)在一个线程中调用exchange方法之后，会阻塞等待另一个线程调用同样的exchange方法，然后以线程安全的方式交换数据，之后线程继续执行。

### 官方示例

在JDK的源码注释中，提供了一个简单的示例demo，稍加修改后就可以运行

```java
public class FillAndEmpty {
    Exchanger<Integer> exchanger = new Exchanger<Integer>();
    Integer initialEmptyBuffer = 1;
    Integer initialFullBuffer = 2;

     class FillingLoop implements Runnable {
        public void run() {
            Integer current...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[源码分析：CyclicBarrier 之循环栅栏]]></title>
            <link>https://jingling.im/blog/cyclicbarrier</link>
            <guid isPermaLink="false">https://jingling.im/blog/cyclicbarrier</guid>
            <pubDate>Fri, 29 Jan 2021 13:45:47 GMT</pubDate>
            <description><![CDATA[JDK源码分析 CyclicBarrier，CyclicBarrier 是一个同步辅助工具，允许一组线程全部等待彼此达到共同屏障点，且等待的线程被释放后还可以重新使用，所以叫做Cyclic（循环的）。]]></description>
            <content:encoded><![CDATA[
## 简介

CyclicBarrier 是一个同步辅助工具，允许一组线程全部等待彼此达到共同屏障点，且等待的线程被释放后还可以重新使用，所以叫做Cyclic（循环的）。

### 应用场景

比如出去旅行时，导游需要等待所有的客人到齐后，导游才会给大家讲解注意事项等

### 官方示例

在JDK的源码注释中，提供了一个简单的示例demo，稍加修改后就可以运行

```java
public class Solver {
    AtomicInteger sum = new AtomicInteger(0);
    // 自己新增的一个标识，true代表所有的计算完成了
    volatile boolean done = false;
    final int N;
    final int[][] data;
    final CyclicBarrier barrier;

    class Worker implements Runnable {
        int myRow;
        Worker(int row) {
            m...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[如何优化你的if-else？来试试“责任树模式”]]></title>
            <link>https://jingling.im/blog/ifelse</link>
            <guid isPermaLink="false">https://jingling.im/blog/ifelse</guid>
            <pubDate>Tue, 26 Jan 2021 10:13:03 GMT</pubDate>
            <content:encoded><![CDATA[
本文系转载至公众号：[阿里技术--如何实现Java类隔离加载？](https://mp.weixin.qq.com/s/Wib0Ly45te00HMUnIG-tbg)

> 阿里妹导读：写业务逻辑时，if-else 可能是最容易想到的逻辑方式了。然而大量堆砌的 if-else 毫无疑问将给代码维护带来巨大的困难。如何优化这些 if-else 呢？本文分享一种设计模式——责任树模式，通过将责任链与策略模式融合，成为一种广义的责任链模式，不仅可以完成任务的逐级委托，也可以在任一级选择不同的下游策略进行处理，并将责任树模式抽象出一个通用的框架。

> 扪心自问，你在写业务代码时是不是也习惯狂堆 if-else 呢？

### 一 问题背景

最近开发了一个需求，该接口需要根据 p1、p2、p3、version 多个入参的不同组合按照其对应的业务策略给出结果数据。由于该接口已经开发了三期了，每次开发新一期的需求时为了兼容老的业务逻辑，大家都倾向于不删不改只新增，因此这块代码已经产生了一些“坏味道”，函数入口通过不断添加“卫语句”判断 version 的方式跳转到新一期的业务逻辑方法中，而每一...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[JVM 垃圾回收总结]]></title>
            <link>https://jingling.im/blog/jvm-gc</link>
            <guid isPermaLink="false">https://jingling.im/blog/jvm-gc</guid>
            <pubDate>Thu, 14 Jan 2021 17:33:39 GMT</pubDate>
            <content:encoded><![CDATA[
## **什么是垃圾回收**

垃圾回收（Garbage Collection，GC），顾名思义就是释放垃圾占用的空间，防止内存泄露。

有效的使用可以使用的内存，对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收。

Java 语言出来之前，大家都在拼命的写 C 或者 C++ 的程序，而此时存在一个很大的矛盾，C++ 等语言创建对象要不断的去开辟空间，不用的时候又需要不断的去释放控件，既要写构造函数，又要写析构函数，很多时候都在重复的 allocated，然后不停的析构。于是，有人就提出，能不能写一段程序实现这块功能，每次创建，释放控件的时候复用这段代码，而无需重复的书写呢？

1960年，基于 MIT 的 Lisp 首先提出了垃圾回收的概念，而这时 Java 还没有出世呢！所以实际上 GC 并不是Java的专利，GC 的历史远远大于 Java 的历史！

## **定义垃圾**

要做垃圾回收，必须要知道什么是垃圾，也就是在内存中，哪些是可以被GC线程回收利用的。

如何统计哪些内存可以回收?

### **1.引用计数算法（Reachability Counting）*...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[⑧SpringCloud 实战：引入 Actuator监控+整合Grafana监控页面]]></title>
            <link>https://jingling.im/blog/actuator</link>
            <guid isPermaLink="false">https://jingling.im/blog/actuator</guid>
            <pubDate>Mon, 04 Jan 2021 15:42:51 GMT</pubDate>
            <description><![CDATA[SpringCloud 实战，引入 Actuator监控+整合Grafana监控页面，Spring Boot Actuator 模块提供了生产级别的功能，比如健康检查，审计，指标收集，HTTP 跟踪等，帮助我们监控和管理Spring Boot 应用。]]></description>
            <content:encoded><![CDATA[
## Actuator是什么？

Spring Boot Actuator 模块提供了生产级别的功能，比如健康检查，审计，指标收集，HTTP 跟踪等，帮助我们监控和管理Spring Boot 应用。这个模块是一个采集应用内部信息暴露给外部的模块，上述的功能都可以通过HTTP 和 JMX 访问。

因为暴露内部信息的特性，Actuator 也可以和一些外部的应用监控系统整合（Prometheus, Graphite, DataDog, Influx, Wavefront, New Relic等）。这些监控系统提供了出色的仪表板，图形，分析和警报，可帮助你通过一个统一友好的界面，监视和管理你的应用程序。

Actuator使用Micrometer与这些外部应用程序监视系统集成。这样一来，只需很少的配置即可轻松集成外部的监控系统。

## Actuator 使用

### 引入依赖

我们新建一个项目：`jlw-actuator`

1. 引入 spring-boot-starter-actuator 的Maven依赖

   ```xml
   <dependency>
       <...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[记录一下上海飞三亚4天3晚自由行]]></title>
            <link>https://jingling.im/blog/sanya</link>
            <guid isPermaLink="false">https://jingling.im/blog/sanya</guid>
            <pubDate>Wed, 30 Dec 2020 16:16:34 GMT</pubDate>
            <content:encoded><![CDATA[
### 说走就走？

2020马上就要过去了，年假剩余的还有几天没有用，于是乎在天时地利人和的条件下，决定请两天年假加上一个周末去三亚玩耍一下避避寒暖暖身。

### 订机票

这次决定去三亚也是临时决定，23号晚上计划决定去三亚，然后马上请假，请假通过后就赶紧买了25号到28号的往返机票。可能时间比较近，所以价格也不便宜，两个人2820 💰。

### 酒店

因为上海的天气比较冷，穿的衣服比较多，而三亚基本上温度只需要穿👕就可以了，所以准备了一个行李箱。如果拖着一个行李箱在三亚到处跑的话，会是一件比较麻烦的事情，游玩的体验会大打折扣，所以最后决定3晚都选择同一个酒店。

在地图上面把三亚的景点大致标注了一下了解了大致的分布位置，于是就把酒店地点定在了大东海，离市区很近，去东西两边的景点距离都适中，价格相对于亚龙湾的酒店也基本上要便宜一些，然后就订了一个相对便宜点的湾景房，最后入住发现也能看到部分海。

![1](https://i.loli.net/2020/12/30/372IH1sMYejbBu5.png)

### 租车

我最开始是在飞猪上面订的机票和酒店，所以也在上...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[如何实现Java类隔离加载？]]></title>
            <link>https://jingling.im/blog/class-isolation-loading</link>
            <guid isPermaLink="false">https://jingling.im/blog/class-isolation-loading</guid>
            <pubDate>Wed, 30 Dec 2020 09:43:55 GMT</pubDate>
            <content:encoded><![CDATA[
本文系转载至公众号：[阿里技术--如何实现Java类隔离加载？](https://mp.weixin.qq.com/s/oK6A3viNP3XafEvl7rzjKA)

> 阿里妹导读：Java 开发中，如果不同的 jar 包依赖了某些通用 jar 包的版本不一样，运行时就会因为加载的类跟预期不符合导致报错。如何避免这种情况呢？本文通过分析 jar 包产生冲突的原因及类隔离的实现原理，分享两种实现自定义类加载器的方法。

### **一  什么是类隔离技术**

只要你 Java 代码写的足够多，就一定会出现这种情况：系统新引入了一个中间件的 jar 包，编译的时候一切正常，一运行就报错：java.lang.NoSuchMethodError，然后就哼哧哼哧的开始找解决方法，最后在几百个依赖包里面找的眼睛都快瞎了才找到冲突的 jar，把问题解决之后就开始吐槽中间件为啥搞那么多不同版本的 jar，写代码五分钟，排包排了一整天。

上面这种情况就是 Java 开发过程中常见的情况，原因也很简单，不同 jar 包依赖了某些通用 jar 包（如日志组件）的版本不一样，编译的时候没问题，到了运...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[Java 开发中常用Linux命令总结]]></title>
            <link>https://jingling.im/blog/javainlinux</link>
            <guid isPermaLink="false">https://jingling.im/blog/javainlinux</guid>
            <pubDate>Tue, 22 Dec 2020 17:09:23 GMT</pubDate>
            <content:encoded><![CDATA[
作为一个Java开发人员，我们的服务基本上都是部署在Linux环境，所以常用的Linux命令必须掌握。本文不对所以的命令进行详细的解释，只给出关键的命令，如果当你看了一眼想不起它的详细用法，那就说明是时候再去学习巩固一下了。这只是我对部分知识的一个整理，方便后面学习和查阅。

---

### 查询命令帮助手册

```bash
man  <command>
```

### **进程相关**

1. 查看Java进程

   ```bash
   ps -ef | grep java
   ```

2. 查看端口属于哪个进程

   ```bash
   lsof -i :8080
   ```

3. 杀掉进程

   ```bash
   kill -9 pid #强制
   ```

### **JAVA JVM相关**

1. 查看yum库中的Java安装包

   ```bash
   yum -y list java*
   ```

2. 使用yum安装Java

   ```bash
   # 注:“*”表示将java-1.8.0-openjdk的所有相关Java...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[⑦SpringCloud 实战：引入Sleuth组件，完善服务链路跟踪]]></title>
            <link>https://jingling.im/blog/sleuth</link>
            <guid isPermaLink="false">https://jingling.im/blog/sleuth</guid>
            <pubDate>Mon, 21 Dec 2020 13:54:50 GMT</pubDate>
            <description><![CDATA[SpringCloud 实战，引入Sleuth组件，完善服务链路跟踪，分布式链路跟踪原理在于如何能将请求经过的服务节点都关联起来。]]></description>
            <content:encoded><![CDATA[
这是SpringCloud实战系列中第7篇文章，了解前面第两篇文章更有助于更好理解本文内容：
[①SpringCloud 实战：引入Eureka组件，完善服务治理](/posts/eureka)
[②SpringCloud 实战：引入Feign组件，发起服务间调用](/posts/feign)
[③SpringCloud 实战：使用 Ribbon 客户端负载均衡](/posts/ribbon)
[](/posts/feign)[④SpringCloud 实战：引入Hystrix组件，分布式系统容错](/posts/hystrix)
[⑤SpringCloud 实战：引入Zuul组件，开启网关路由](/posts/zuul)
[⑥SpringCloud 实战：引入gateway组件，开启网关路由功能](/posts/sleuth)

## 背景

近年来，随着微服务架构的流行，很多公司都走上了微服务拆分之路。从而使系统变得越来越复杂，原本单体的系统被拆成很多个服务，每个服务之间通过轻量级的 HTTP 协议进行交互。

单体架构时，一个请求的调用链路非常清晰，一般由负载均衡器，比如 Ng...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[Java 8 新特性详细解析]]></title>
            <link>https://jingling.im/blog/newinjava8</link>
            <guid isPermaLink="false">https://jingling.im/blog/newinjava8</guid>
            <pubDate>Tue, 15 Dec 2020 14:16:10 GMT</pubDate>
            <content:encoded><![CDATA[
JDK 8 是一个拥有丰富特性的主要版本， Oracle 公司于 2014 年 3 月 18 日发布，在编程语言、集合IO、网络、并发性虚拟机等方面都做了升级，下面就详细介绍一下每个特性。

## **JAVA 编程语言**

### Lambda 表达式

Lambda表达式（也称为闭包）是Java 8中最大和最令人期待的语言改变。

它允许我们将函数当成参数传递给某个方法，或者把代码本身当作数据处理：函数式开发者非常熟悉这些概念。很多JVM平台上的语言（Groovy、Scala等）从诞生之日就支持Lambda表达式，但是Java开发者没有选择，只能使用匿名内部类代替Lambda表达式。

最简单的Lambda表达式可由逗号分隔的参数列表、->符号和语句块组成，例如：

```java
Arrays.asList( "a", "b", "d" ).forEach( e -> System.out.println( e ) );
```

在上面这个代码中的参数e的类型是由编译器推理得出的，你也可以显式指定该参数的类型，例如：

```java
Arrays.asList( "a",...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[⑥SpringCloud 实战：引入gateway组件，开启网关路由功能]]></title>
            <link>https://jingling.im/blog/gateway</link>
            <guid isPermaLink="false">https://jingling.im/blog/gateway</guid>
            <pubDate>Sat, 12 Dec 2020 22:25:01 GMT</pubDate>
            <description><![CDATA[SpringCloud 实战，引入gateway组件，开启网关路由功能，Spring Cloud Gateway 是 Spring Cloud 的一个子项目，该项目是基于 Spring 5.0，Spring Boot 2.0 和 Project Reactor 等技术开发的网关，它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。]]></description>
            <content:encoded><![CDATA[
这是SpringCloud实战系列中第4篇文章，了解前面第两篇文章更有助于更好理解本文内容：
[①SpringCloud 实战：引入Eureka组件，完善服务治理](/posts/eureka)
[②SpringCloud 实战：引入Feign组件，发起服务间调用](/posts/feign)
[③SpringCloud 实战：使用 Ribbon 客户端负载均衡](/posts/ribbon)
[](/posts/feign)[④SpringCloud 实战：引入Hystrix组件，分布式系统容错](/posts/hystrix)
[⑤SpringCloud 实战：引入Zuul组件，开启网关路由](/posts/zuul)

## 简介

Spring Cloud Gateway 是 Spring Cloud 的一个子项目，该项目是基于 Spring 5.0，Spring Boot 2.0 和 Project Reactor 等技术开发的网关，它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。

Spring Cloud Gateway 作为 Spring Cloud...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[⑤SpringCloud 实战：引入Zuul组件，开启网关路由]]></title>
            <link>https://jingling.im/blog/zuul</link>
            <guid isPermaLink="false">https://jingling.im/blog/zuul</guid>
            <pubDate>Fri, 04 Dec 2020 17:50:38 GMT</pubDate>
            <description><![CDATA[SpringCloud 实战，引入Zuul组件，开启网关路由，Zuul 也是 Netflix OSS 中的一员，是一个基于 JVM 路由和服务端的负载均衡器，支持动态路由、监控、弹性和安全等特性。]]></description>
            <content:encoded><![CDATA[
这是SpringCloud实战系列中第4篇文章，了解前面第两篇文章更有助于更好理解本文内容：
[①SpringCloud 实战：引入Eureka组件，完善服务治理](/posts/eureka)
[②SpringCloud 实战：引入Feign组件，发起服务间调用](/posts/feign)
[③SpringCloud 实战：使用 Ribbon 客户端负载均衡](/posts/ribbon)
[](/posts/feign)[④SpringCloud 实战：引入Hystrix组件，分布式系统容错](/posts/hystrix)

## 简介

Zuul 也是 Netflix OSS 中的一员，是一个基于 JVM 路由和服务端的负载均衡器，支持动态路由、监控、弹性和安全等特性。Spring Cloud 会创建一个嵌入式 Zuul 代理来简化一个常见用例的开发，比如用户程序可能会对一个或多个后端服务进行调用，引入 Zuul 网关能有效避免为所有后端独立管理CORS和身份验证问题的需求

Zuul的使用了一系列的过滤器，这些过滤器可以完成以下功能：

- 身份验证和安全性
  识别每个...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[④SpringCloud 实战：引入Hystrix组件，分布式系统容错]]></title>
            <link>https://jingling.im/blog/hystrix</link>
            <guid isPermaLink="false">https://jingling.im/blog/hystrix</guid>
            <pubDate>Tue, 01 Dec 2020 16:21:54 GMT</pubDate>
            <description><![CDATA[SpringCloud 实战，引入Hystrix组件，Hystrix 是一个延迟和容错库，旨在隔离对远程系统、服务和第三方库的访问点，停止级联故障，并在故障不可避免的复杂分布式系统中实现恢复能力。]]></description>
            <content:encoded><![CDATA[
## 简介

Hystrix 是一个延迟和容错库，旨在隔离对远程系统、服务和第三方库的访问点，停止级联故障，并在故障不可避免的复杂分布式系统中实现恢复能力。

## 服务雪崩

在分布式微服务的架构体系下，一般都会存在多层级服务服务的调用链，当链路中的某个服务发生异常，最后导致整个系统不可用，这种现象称为服务雪崩效应。
![1](/assets/upload/2020/12/1-38c2919074ab4d4f94bfe20bc379d42c.png)

如上图所示，从最开始的整个系统正常状态，到单个服务出现异常，再到多个服务出现异常，到最后整个系统可不用，整个过程就是服务雪崩效应。如果在单个服务出现异常的时候，我们能及时发现、预防、处理，也就不会出现级联效果导致整个系统不可用。Hystrix 就是来保证上面的情况发生时能停止级联故障，保证系统稳定运行的。

## Hystrix遵循的设计原则

1. 避免线程耗尽
   由于被调用方出现问题，调用方无法及时获取响应结果，而一直在发送请求，最终会耗尽所有线程的资源。
2. 快速失败
   当被调用方出现问题后，调用方发起的请求可以快速...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[③SpringCloud 实战：使用 Ribbon 客户端负载均衡]]></title>
            <link>https://jingling.im/blog/ribbon</link>
            <guid isPermaLink="false">https://jingling.im/blog/ribbon</guid>
            <pubDate>Mon, 30 Nov 2020 14:06:19 GMT</pubDate>
            <description><![CDATA[SpringCloud 实战，使用 Ribbon 客户端负载均衡，Ribbon 是由 Netflix 发布的一个客户端负载均衡器]]></description>
            <content:encoded><![CDATA[
这是SpringCloud实战系列中第三篇文章，了解前面第两篇文章更有助于更好理解本文内容：

[①SpringCloud 实战：引入Eureka组件，完善服务治理](/posts/eureka)

[②SpringCloud 实战：引入Feign组件，发起服务间调用](/posts/feign)

## 简介

Ribbon 是由 Netflix 发布的一个客户端负载均衡器，它提供了对 HTTP 和 TCP 客户端行为的大量控制。Ribbon 可以基于某些负载均衡的算法，自动为客户端选择发起理论最优的网络请求。常见的负载均衡算法有：轮询，随机，哈希，加权轮询，加权随机等。

客户端负载均衡的意思就是发起网络请求的端根据自己的网络请求情况来做相应的负载均衡策略，与之相对的非客户端负载均衡就有比如硬件F5、软件Nginx，它们更多是介于消费者和提供者之间的，并非客户端。

### 改造[eureka-provider](/posts/feign)项目

在使用之前我们先把第二节里面的 eureka-provider 项目改造一下，在HelloController 里面新增一个接口，输出...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[Spring 事件监听机制及原理分析]]></title>
            <link>https://jingling.im/blog/springevent</link>
            <guid isPermaLink="false">https://jingling.im/blog/springevent</guid>
            <pubDate>Wed, 25 Nov 2020 14:48:17 GMT</pubDate>
            <description><![CDATA[深入理解 Spring 事件监听机制原理：从基础到高级，包括观察者模式、Java事件处理、Spring事件类详解、异步事件处理以及Spring容器事件广播原理。学习如何使用 Spring 事件监听机制实现异步操作、解耦设计、提升应用性能和响应能力]]></description>
            <content:encoded><![CDATA[
## 简介

在JAVA体系中，有支持实现事件监听机制，在Spring 中也专门提供了一套事件机制的接口，方便我们实现。比如我们可以实现当用户注册后，给他发送一封邮件告诉他注册成功的一些信息，比如用户订阅的主题更新了，通知用户注意及时查看等。

## 观察者模式

观察者模式还有很多其他的称谓，如发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。观察者模式定义了一种一对多的依赖关系，让多个观察者对象同时监听某一个主题对象。这个主题对象在状态上发生变化时，会通知所有观察者对象，使它们能够自动更新自己。

观察者模式一般包含以下几个对象：

Subject：

被观察的对象。它提供一系列方法来增加和删除观察者对象，同时它定义了通知方法notify()。目标类可以是接口，也可以是抽象类或具体类。

ConcreteSubject：

具体的观察对象。Subject的具体实现类，在这里实现通知事件。

Observer：

观察者。这里是抽象的观察者，观察者有一个...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[②SpringCloud 实战：引入Feign组件，完善服务间调用]]></title>
            <link>https://jingling.im/blog/feign</link>
            <guid isPermaLink="false">https://jingling.im/blog/feign</guid>
            <pubDate>Wed, 25 Nov 2020 13:38:13 GMT</pubDate>
            <description><![CDATA[SpringCloud 实战，引入Feign组件，完善服务间调用，Feign 是一个声明式的 REST 客户端，它的目的就是让 REST 调用更加简单。]]></description>
            <content:encoded><![CDATA[
这是SpringCloud实战系列中第二篇文章，了解前面第一篇文章更有助于更好理解本文内容：

[①SpringCloud 实战：引入Eureka组件，完善服务治理](/posts/eureka)

## 简介

Feign 是一个声明式的 REST 客户端，它的目的就是让 REST 调用更加简单。

Feign 提供了 HTTP 请求的模板，通过编写简单的接口和插入注解，就可以定义好 HTTP 请求的参数、格式、地址等信息。

而且 Feign 会完全代理 HTTP 请求，我们只需要像调用方法一样调用它就可以完成服务请求及相关处理。Spring Cloud 对 Feign 进行了封装，使其支持 SpringMVC 标准注解和 ttpMessageConverters。Feign 可以与 Eureka 和 Ribbon 组合使用以支持负载均衡，与 Hystrix 组合使用，支持熔断回退。

如果你没有使用 Spring Cloud，那么可以直接用原生的 Feign 来调用 API，如果你使用了 Spring Cloud，可以直接用 Spring Cloud OpenFeign 来调用...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[①SpringCloud 实战：引入Eureka组件，完善服务治理]]></title>
            <link>https://jingling.im/blog/eureka</link>
            <guid isPermaLink="false">https://jingling.im/blog/eureka</guid>
            <pubDate>Tue, 24 Nov 2020 14:12:19 GMT</pubDate>
            <description><![CDATA[SpringCloud 实战，引入Eureka组件，完善服务治理，Netflix Eureka 是一款由 Netflix 开源的基于 REST 服务的注册中心，用于提供服务发现功能]]></description>
            <content:encoded><![CDATA[
### 简介

Netflix Eureka 是一款由 Netflix 开源的基于 REST 服务的注册中心，用于提供服务发现功能。Spring Cloud Eureka 是 Spring Cloud Netflix 微服务套件的一部分，基于 Netflix Eureka 进行了二次封装，主要负责完成微服务架构中的服务治理功能。

Spring Cloud Eureka 是一个基于 REST 的服务，并提供了基于 Java 的客户端组件，能够非常方便的将服务注册到 Spring Cloud Eureka 中进行统一管理。

### 部署 Eureka Server

1. 创建一个名为 eureka-server 的 Spring Cloud 的项目（略）
2. 引入 eureka-server 依赖（maven）

   ```xml
   <dependency>
       <groupId>org.springframework.cloud</groupId>
       <artifactId>spring-cloud-starter-netflix-eureka-se...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[AbstractQueuedSynchronizer(AQS) 总结篇]]></title>
            <link>https://jingling.im/blog/aqs</link>
            <guid isPermaLink="false">https://jingling.im/blog/aqs</guid>
            <pubDate>Mon, 23 Nov 2020 11:38:25 GMT</pubDate>
            <description><![CDATA[AQS 全称是AbstractQueuedSynchronizer，在java.util.concurrent.locks包下面，是一个抽象的可以实现阻塞线程、排队控制、唤醒线程等操作的同步器基础框架类，AQS 可以实现排它锁、共享锁、条件锁、计数器等相关功能。]]></description>
            <content:encoded><![CDATA[
### 简介

在之前已经有6篇关于AQS源码分析的文章了，关于源码分析的一些问题可以去看看我之前的文章，文章连接可以在文末查看。这一篇文章主要是对AQS的一些总结，或者说是面经。

### AQS是什么

AQS 全称是AbstractQueuedSynchronizer，在`java.util.concurrent.locks`包下面，是一个抽象的可以实现阻塞线程、排队控制、唤醒线程等操作的同步器基础框架类，AQS 可以实现排它锁、共享锁、条件锁、计数器等相关功能。

### AQS 对资源的共享方式

AQS定义两种资源共享方式

```java
// AQS.NODE
static final Node SHARED = new Node();
static final Node EXCLUSIVE = null;
```

- Exclusive(独占)：只有一个线程能执行，如ReentrantLock。又可分为公平锁和非公平锁：
  - 公平锁：按照线程在队列中的排队顺序，先到者先拿到锁
  - 非公平锁：当线程要获取锁时，无视队列顺序直接去抢锁，谁抢到就是谁的
- Sh...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[源码分析：CountDownLatch 之倒计时门栓]]></title>
            <link>https://jingling.im/blog/countdownlatch</link>
            <guid isPermaLink="false">https://jingling.im/blog/countdownlatch</guid>
            <pubDate>Sun, 22 Nov 2020 19:25:51 GMT</pubDate>
            <description><![CDATA[JDK源码分析 CountDownLatch，CountDownLatch 是JDK1.5 开始提供的一种同步辅助工具，它允许一个或多个线程一直等待，直到其他线程执行的操作完成为止。在初始化的时候给定 CountDownLatch 一个计数，调用await() 方法的线程会一直等待，其他线程执行完操作后调用countDown()，当计数减到0 ，调用await() 方法的线程被唤醒继续执行。]]></description>
            <content:encoded><![CDATA[
## 简介

CountDownLatch 是JDK1.5 开始提供的一种同步辅助工具，它允许一个或多个线程一直等待，直到其他线程执行的操作完成为止。在初始化的时候给定 CountDownLatch 一个计数，调用await() 方法的线程会一直等待，其他线程执行完操作后调用countDown()，当计数减到0 ，调用await() 方法的线程被唤醒继续执行。

### 应用场景

1. 多线程并发下载或上传
   主线程初始化一个为5的CountDownLatch ，然后分发给5个线程去完成下载或上传的动作，主线程等待其他线程完成任务后返回成功呢。
2. 首页，一个复杂的查询包含多个子查询，但是子查询结果互相不依赖，也可以使用 CountDownLatch ，等待多个查询完成后再一起返回给首页。

## 源码分析

CountDownLatch 的源码相对于之前介绍的几个同步类，代码量要少很多很多，在JDK 1.8版本中也就300多行（包含注释），所以分析起来也比较简单。

### 内部类Sync

同样的，该内部类也继承了AQS，代码展示：

```java
private st...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[源码分析：Semaphore之信号量]]></title>
            <link>https://jingling.im/blog/semaphore</link>
            <guid isPermaLink="false">https://jingling.im/blog/semaphore</guid>
            <pubDate>Sat, 21 Nov 2020 16:01:07 GMT</pubDate>
            <description><![CDATA[JDK源码分析 Semaphore，Semaphore 又名计数信号量，从概念上来讲，信号量初始并维护一定数量的许可证，使用之前先要先获得一个许可，用完之后再释放一个许可。信号量通常用于限制线程的数量来控制访问某些资源，从而达到单机限流的目的，比如SpringCloud 中的Zuul 组件用的是 Hystrix 的信号量（semaphore）隔离模式。]]></description>
            <content:encoded><![CDATA[
## 简介

Semaphore 又名计数信号量，从概念上来讲，信号量初始并维护一定数量的许可证，使用之前先要先获得一个许可，用完之后再释放一个许可。信号量通常用于限制线程的数量来控制访问某些资源，从而达到单机限流的目的，比如SpringCloud 中的Zuul 组件用的是 Hystrix 的信号量（semaphore）隔离模式。

## 源码分析

### 重要的内部类

Semaphore 和 ReentrantLock 内部类完全相似， 有3个重要的内部类，分别也是 `Sync`、`NonfairSync`和`FairSync`；

1. Sync 是后面两个的父类，继承至AbstractQueuedSynchronizer（AQS）
2. NonfairSync和FairSync都继承至Sync
3. NonfairSync 主要用于实现非公平锁，FairSync 主要用于实现公平锁

如果你看了前面几天关于锁的源码分析，是不是发现它们的套路都差不多呢？

### **重要的属性**

和 ReentrantLock 也完全一样，只有一个重要的属性，同步器sync：

```...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[源码分析：StampedLock之升级版的读写锁]]></title>
            <link>https://jingling.im/blog/stampedlock</link>
            <guid isPermaLink="false">https://jingling.im/blog/stampedlock</guid>
            <pubDate>Thu, 19 Nov 2020 18:05:59 GMT</pubDate>
            <description><![CDATA[JDK StampedLock 源码分析，StampedLock 是JDK1.8 开始提供的一种锁， 是对之前介绍的读写锁 ReentrantReadWriteLock 的功能增强。StampedLock 有三种模式：Writing（写）、Reading（读）、Optimistic Reading（乐观度），StampedLock 的功能不是基于AQS来实现的，而是完全自己内部实现的功能，不支持重入。在加锁的时候会返回一个戳，解锁的时候需要传入，匹配完成解锁操作。]]></description>
            <content:encoded><![CDATA[
## 简介

StampedLock 是JDK1.8 开始提供的一种锁， 是对之前介绍的读写锁 ReentrantReadWriteLock 的功能增强。StampedLock 有三种模式：Writing（写）、Reading（读）、Optimistic Reading（乐观度），StampedLock 的功能不是基于AQS来实现的，而是完全自己内部实现的功能，不支持重入。在加锁的时候会返回一个戳，解锁的时候需要传入，匹配完成解锁操作。

## 官方使用示例

```java
class Point {
    private double x, y;
    private final StampedLock sl = new StampedLock();

    void move(double deltaX, double deltaY) { // an exclusively locked method
        // 写锁-独占资源
        long stamp = sl.writeLock();
        try {
            x +=...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[源码分析：ReentrantReadWriteLock之读写锁]]></title>
            <link>https://jingling.im/blog/reentrantreadwritelock</link>
            <guid isPermaLink="false">https://jingling.im/blog/reentrantreadwritelock</guid>
            <pubDate>Fri, 13 Nov 2020 17:47:26 GMT</pubDate>
            <description><![CDATA[JDK源码分析 ReentrantReadWriteLock，从字面意思可以看出，是和重入、读写有关系的锁，实际上 ReentrantReadWriteLock 确实也是支持可重入的读写锁，并且支持公平和非公平获取锁两种模式。]]></description>
            <content:encoded><![CDATA[
## 简介

ReentrantReadWriteLock 从字面意思可以看出，是和重入、读写有关系的锁，实际上 ReentrantReadWriteLock 确实也是支持可重入的读写锁，并且支持公平和非公平获取锁两种模式。

**为什么会出现读写锁？**

普通锁可以保证共享数据在同一时刻只被一个线程访问，就算有多个线程都只是读取的操作，也还是要排队等待获取锁，我们知道数据如果只涉及到读操作，是不会出现线程安全方面的问题的，那这部分加锁是不是可以去掉？或者是加锁不互斥？如果在读多写少的情况下，使用普通的锁，在所有读的情况加锁互斥等待会是一个及其影响系统并发量的问题，如果所有的读操作不互斥，只有涉及到写的时候才互斥，这样会不会大大的提高并发量呢？答案是肯定的，ReentrantReadWriteLock 就是这样干的，读读不互斥，读写、写读、写写都是互斥的，可以大大提高系统并发量。

## 源码分析

### 类结构

ReentrantReadWriteLock 仅实现了ReadWriteLock接口

```java
public class ReentrantReadWrite...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[源码分析：②ReentrantLock之条件锁Condition]]></title>
            <link>https://jingling.im/blog/reentrantlock-condition</link>
            <guid isPermaLink="false">https://jingling.im/blog/reentrantlock-condition</guid>
            <pubDate>Tue, 10 Nov 2020 14:47:21 GMT</pubDate>
            <description><![CDATA[JDK源码分析 ReentrantLock Condition，条件锁，指在获得锁之后，还需要达成某些条件后，才能继续执行的锁。且必须配合Lock一起使用，也就是说必须获得锁之后才可以调用condition.await()方法]]></description>
            <content:encoded><![CDATA[
## 简介

条件锁，指在获得锁之后，还需要达成某些条件后，才能继续执行的锁。且必须配合Lock一起使用，也就是说必须获得锁之后才可以调用condition.await()方法

## 源码分析

ReentrantLock 的条件锁使用的 `AbstractQueuedSynchronizer` 中的`ConditionObject` 来实现的，所以其实标题说的ReentrantLock 源码分析，其实应该是AQS源码分析之条件锁`Condition`，但是这里为什么还是要说成ReentrantLock 源码分析呢？主要是AQS是一个抽象类，用户并不能直接使用，而ReentrantLock 提供了使用条件锁的入口，源码如下：：

```java
final ConditionObject newCondition() {
    return new ConditionObject();
}
```

### Condition 接口

Condition 是一个接口，定义了7个方法，分别是：

1. void await() throws InterruptedException...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[源码分析：①ReentrantLock之公平锁和非公平锁]]></title>
            <link>https://jingling.im/blog/reentrantlock-lock</link>
            <guid isPermaLink="false">https://jingling.im/blog/reentrantlock-lock</guid>
            <pubDate>Mon, 09 Nov 2020 17:46:04 GMT</pubDate>
            <description><![CDATA[JDK源码分析 ReentrantLock，ReentrantLock 是JDK 1.5开始提供的一种可重入的互斥锁，并且构造方法支持公平性参数。]]></description>
            <content:encoded><![CDATA[
## 简介

ReentrantLock 是JDK 1.5开始提供的一种可重入的互斥锁，并且构造方法支持公平性参数。

## 源码分析

### 类结构体系

ReentrantLock实现了Lock接口：

```java
public class ReentrantLock implements Lock, java.io.Serializable {
...
}
```

Lock接口中定义了6个方法，需要自己去实现：

```java
public interface Lock {
	// 获得锁
	void lock();
	// 可被中断的获得锁
	void lockInterruptibly() throws InterruptedException;
	// 尝试获取锁（如果可用），并立即返回值true。如果锁不可用，则此方法将立即返回值false
	boolean tryLock();
	// 如果锁可用，此方法将立即返回值true，如果锁不可用，则当前线程将处于休眠状态
	boolean tryLock(long time, TimeUnit unit) thro...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[源码分析：AbstractQueuedSynchronizer(AQS)—强大的同步基础框架]]></title>
            <link>https://jingling.im/blog/abstractqueuedsynchronizer</link>
            <guid isPermaLink="false">https://jingling.im/blog/abstractqueuedsynchronizer</guid>
            <pubDate>Sun, 01 Nov 2020 17:09:22 GMT</pubDate>
            <description><![CDATA[JDK源码分析 AbstractQueuedSynchronizer，AbstractQueuedSynchronizer(AQS) 位于java.util.concurrent.locks包下面，AQS 提供了一个基于FIFO的队列和维护了一个状态state变量赖表示状态，可以作为构建锁或者其他相关同步装置的基础框架。AQS 支持两种模式：共享模式 和 排他模式。]]></description>
            <content:encoded><![CDATA[
## 简介

AQS 全称是 `AbstractQueuedSynchronizer`，位于`java.util.concurrent.locks` 包下面，AQS 提供了一个基于FIFO的队列和维护了一个状态state变量赖表示状态，可以作为构建锁或者其他相关同步装置的基础框架。AQS 支持两种模式：共享模式 和 排他模式，当它被定义为一个排他模式时，其他线程对其的获取就被阻止，而共享模式对于多个线程获取都可以成功。之所以说它是一个同步基础框架是因为很多同步类里面都用到了AQS，比如 ReentrantLock 中的内部类同步器Sync继承至AQS，ReentrantReadWriteLock中的同步器也是继承至AQS，还有 Semaphore 、CountDownLatch等都是基于AQS来实现的。

## 核心源码

### 类结构

AQS 继承了 `AbstractOwnableSynchronizer`， AbstractOwnableSynchronizer 这个类比较简单，就一个属性 `private transient Thread exclusiveOwnerTh...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[Java 虚拟机常用垃圾收集器总结]]></title>
            <link>https://jingling.im/blog/lajishoujiqi</link>
            <guid isPermaLink="false">https://jingling.im/blog/lajishoujiqi</guid>
            <pubDate>Fri, 30 Oct 2020 16:48:08 GMT</pubDate>
            <content:encoded><![CDATA[
通常虚拟机中往往不止一种垃圾收集器，Hotspot一共有7种收集器：

1. Serial 收集器
2. ParNew 收集器
3. Parallel Scavenge收集器
4. Serial Old 收集器
5. Parallel Old 收集器
6. CMS 收集器
7. G1 收集器

### Serial 收集器

`Serial 收集器`是最基本、历史最悠久的收集器，Serial(串行)，从名字就可以看出这个收集器是一个单线程的收集器。它在进行垃圾收集时，必须要暂停其他所有的工作线程，知道它垃圾收集结束。这个暂停其他工作线程的动作一般被成为“Stop The World”，简称`STW`。

采用的是复制算法。
![untitled.jpg](http://118.24.105.59/upload/2020/11/untitled-f5b9e5407e9640a4a1d2363d9b581108.jpg)

优点：

简单，高效，适合运行在client模式下虚拟机

缺点：

会产生STW，整个应用停顿

### ParNew 收集器

ParNew收集器其实就是Seri...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[Java 中类加载的机制总结]]></title>
            <link>https://jingling.im/blog/classloader</link>
            <guid isPermaLink="false">https://jingling.im/blog/classloader</guid>
            <pubDate>Wed, 28 Oct 2020 16:50:42 GMT</pubDate>
            <description><![CDATA[Java 虚拟机把描述类的数据从class文件加载到内存，并对数据进行校验、转换解析、和初始化，最终形成可以被虚拟机直接使用的JAVA类型，这就是虚拟机的类加载机制 。]]></description>
            <content:encoded><![CDATA[
> 虚拟机把描述类的数据从class文件加载到内存，并对数据进行校验、转换解析、和初始化，最终形成可以被虚拟机直接使用的JAVA类型，这就是虚拟机的类加载机制 。

### 类的生命周期

类从被加载到虚拟机到被卸载出内存，它的整个生命周期包括：**加载（Loading）、验证（Verification）、准备（Preparation）、解析（Resolution）、初始化（Initialization）、使用（Using）、卸载（Unloading）** 这7个过程。其中验证、准备、解析这三个阶段被统称为连接（Linking）。

其中加载→验证→准备→初始化→卸载这5个阶段的顺序是确定的。而解析则不一定：某些情况下，解析可以在初始化阶段之后，这是为了支持JAVA语言运行时绑定（也称为动态绑定或晚期绑定）。

### 类加载的时机

什么时候开始类加载，虚拟机规范没有进行强制约束，由虚拟机具体的实现来把握。

但是初始化阶段，虚拟机规范则严格规定了**有且只有**5种情况必须立即对类进行初始化（加载、验证、准备必然在初始化之前）：

1. 使用new关键字进行实例化对象的时候、读取...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[Java 虚拟机内存的各个区域]]></title>
            <link>https://jingling.im/blog/memoryarea</link>
            <guid isPermaLink="false">https://jingling.im/blog/memoryarea</guid>
            <pubDate>Sun, 25 Oct 2020 16:25:22 GMT</pubDate>
            <content:encoded><![CDATA[
JAVA 虚拟机管理的内存会包括以下几个内存区域:

1. 程序计数器
2. JAVA虚拟机栈
3. 本地方法栈
4. JAVA 堆
5. 方法区
6. 运行时常量池
7. 直接内存

内存结构图:

![](/assets/upload/2020/11/untitled-3b6893c82e484a828f9af09a70c20af6.jpg)

## 程序计数器(Program Counter Register)

程序计数寄存器（**Program Counter Register**），Register 的命名源于 CPU 的寄存器，寄存器存储指令相关的线程信息，CPU 只有把数据装载到寄存器才能够运行。

这里，并非是广义上所指的物理寄存器，叫程序计数器（或PC计数器或指令计数器）会更加贴切，并且也不容易引起一些不必要的误会。**JVM 中的 PC 寄存器是对物理 PC 寄存器的一种抽象模拟**。

程序计数器是一块比较小的内存空间，可以被看做是当前线程执行的字节码的**行号指示器**。

为了线程切换后能够恢复到正确的执行位置，每个线程都需要有一个独立的计数器，每条线程之...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[Java 线程池生命周期]]></title>
            <link>https://jingling.im/blog/threadpoollifecycle</link>
            <guid isPermaLink="false">https://jingling.im/blog/threadpoollifecycle</guid>
            <pubDate>Tue, 20 Oct 2020 17:43:41 GMT</pubDate>
            <content:encoded><![CDATA[
### 问题

（1）线程池的状态有哪些？

（2）各种状态下对于任务队列中的任务有何影响？

### 看源码线程池状态

```java
ThreadPoolExecutor.class

// 状态控制  ctl的高三位保存运行状态
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
//用二进制补码形式表示整型值的位数, 值为32-3
private static final int COUNT_BITS = Integer.SIZE - 3;
// 00‭10 0000 0000 0000 0000 0000 0000 0000‬, 一共32位
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

// runState is stored in the high-order bits
// 1110 0000 0000 0000 0000 0000 0000 0000‬
private static final i...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[Java 线程生命周期]]></title>
            <link>https://jingling.im/blog/threadlifecycle</link>
            <guid isPermaLink="false">https://jingling.im/blog/threadlifecycle</guid>
            <pubDate>Tue, 20 Oct 2020 17:34:31 GMT</pubDate>
            <content:encoded><![CDATA[
### 线程的状态有哪些?

源码Thread.State 说明一切

```java
public enum State {
    // 才开始new  还没有调用start()方法
    NEW,

        // 可运行状态, 正在运行或者等待操作系统资源
    RUNNABLE,

        // 阻塞状态，在等待一个监视器锁（也就是我们常说的synchronized）
    //或者在调用了Object.wait()方法且被notify()之后也会进入BLOCKED状态
    BLOCKED,

        //等待状态，在调用了以下方法后进入此状态
    // 1. Object.wait()无超时的方法后且未被notify()前，如果被notify()了会进入BLOCKED状态
    // 2. Thread.join()无超时的方法后
    // 3. LockSupport.park()无超时的方法后
    WAITING,

    //超时等待状态，在调用了以下方法后会进入超时等待状态
    //1. Thread.sleep...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[Java 对象内存分配与回收策略]]></title>
            <link>https://jingling.im/blog/object-memory-allocation</link>
            <guid isPermaLink="false">https://jingling.im/blog/object-memory-allocation</guid>
            <pubDate>Sat, 17 Oct 2020 16:49:24 GMT</pubDate>
            <description><![CDATA[Java 对象内存分配与回收策略]]></description>
            <content:encoded><![CDATA[
### 通常情况下会先在Eden区分配

大多数的情况下，对象都会先在Eden区分配，当Eden区没有足够的空间分配的时候，会触发一次MinorGC。

MinorGC: 指的是新生代GC，也就是发生在新生代的垃圾回收动作
MajorGC: 指的是老年代GC，也就是发生在老年代的垃圾回收动作，出现MinorGC一般会伴随着至少一次MinorGC，但这并非绝对，在Parallel Scavenge收集器的策略里面就有直接使用MinorGC的过程。

触发新生代垃圾回收代码示例：

```java
JVM参数：-Xmn10m -Xmx20m -Xms20m -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:+UseSerialGC
private static final int _1M = 1024*1024;
private static void testMinorGC(){
    byte[] ob1,ob2,ob3,ob4;
    ob1 = new byte[_1M * 2];
    ob2 = new byte[_1M * 2];...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[Java 虚拟机对象的创建、布局和访问]]></title>
            <link>https://jingling.im/blog/object-creation</link>
            <guid isPermaLink="false">https://jingling.im/blog/object-creation</guid>
            <pubDate>Fri, 16 Oct 2020 16:37:41 GMT</pubDate>
            <content:encoded><![CDATA[
主要讨论虚拟机在Java堆中对象的分配、布局和访问全过程

### 对象的创建

虚拟机收到一条new指令时, 首先将会去检查这个指令的参数是否能在常量池中定位到一个类的符号引用, 并检查这个引用代表的类是否已经被加载、解析和初始化过。如果没有, 必须先执行类加载的过程。

类加载通过后, 虚拟机会为新生对象分配内存, 对象所需的内存大小在类加载完成后就可以完全确定下来, 为对象分配空间等同于从JAVA堆中划分一块确认大小的内存, 分配的方式有两种:

1. **指针碰撞(Bump the Pointer)**

   假设JAVA堆中的内存是规整的, 所有用过的内存放在一边, 空闲可用的内存在另一边, 中间放一个指针作为分界线的指示器, 那么分配内存就是把指针往空闲区域挪动一段和新生对象大小相等的距离

   Serial、ParNew采用的指针碰撞

2. **空闲列表(Free List)**

   如果JAVA堆中的内存是不规整的, 虚拟机就必须要维护一个列表, 用来记录哪一块内存是可用的, 在分配内存的时候再从列表中找一块足够大的空间分配给新生对象, 并更新列表上的记录。...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[Java 内存模型（JMM）总结]]></title>
            <link>https://jingling.im/blog/jmm</link>
            <guid isPermaLink="false">https://jingling.im/blog/jmm</guid>
            <pubDate>Sat, 10 Oct 2020 16:54:20 GMT</pubDate>
            <description><![CDATA[Java 内存模型（JMM）总结，JAVA内存模型的主要目的是为了定义程序各个变量的访问规则，即在虚拟机中将变量存储到内存和从内存中读取变量这样的底层细节。]]></description>
            <content:encoded><![CDATA[
### 主内存和工作内存

JAVA内存模型的主要目的是为了定义程序各个变量的访问规则，即在虚拟机中将变量存储到内存和从内存中读取变量这样的底层细节。

JMM规定了所有的变量都存储在了主内存，每条线程还有自己的工作内存，工作内存保存的是线程使用到的主内存变量的副本拷贝，线程对变量的所有操作（读取、赋值）都必须在工作内存中进行，不能直接读写主内存的变量。线程之间也无法访问对方的工作内存，线程之间的变量值传递都需要通过主内存来完成。
![untitled.jpg](/assets/upload/2020/11/untitled-535eababd80e4433ad19f0363f845856.jpg)

### 内存间的交互操作

JAVA内存模型定义了8种操作来完成主内存和工作内存之间变量的交互，虚拟机实现时必须要保证每一种操作都是**原子的**。

1. Lock（锁定）

   🐖作用于主内存的变量

   把一个变量标识为一条线程独占的状态

2. unlock（解锁）

   🐖作用于主内存的变量

   把处于锁定状态的变量释放锁，缩放锁后其他线程才可以锁定

3. r...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[Java 字节码生成技术与JDK动态代理的实现]]></title>
            <link>https://jingling.im/blog/bytecode</link>
            <guid isPermaLink="false">https://jingling.im/blog/bytecode</guid>
            <pubDate>Sat, 10 Oct 2020 16:51:50 GMT</pubDate>
            <description><![CDATA[Java 字节码生成技术与JDK动态代理的实现]]></description>
            <content:encoded><![CDATA[
### 简介

“字节码生成”并不是什么高深的技术，我们能想到的常用字节码类库有Javassist、CGLib、ASM等，其实JDK里面的javac命令才是字节码生成技术的“祖宗”。JDK8版本的javac的源代码在[OpenJDK](http://hg.openjdk.java.net/jdk8/jdk8/langtools/file/30db5e0aaf83/src/share/classes/com/sun/tools/javac)上可以看到。

使用字节码生成技术的例子有很多，比如Web服务器的JSP编译器，编译时植入的AOP框架，还有很多常用的动态代理技术，甚至使用反射时虚拟机也有可能会在运行时生成字节码来提升执行速度。

### 动态代理的实现

在JAVA中使用动态代理有两种常用的方式：一是使用JDK的原生动态代理，二是使用CGLib来实现动态代理。

这里主要说下JDK的动态代理实现。
动态代理所谓的“动态”，是针对使用JAVA代码实际编写了代理类的“静态”代理而言的，它的优势不是在于省去了编写代理类那一点的工作量，而是**实现了可以在原始类和接口还未知的情况下，就能...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[Java 线程模型与线程调度]]></title>
            <link>https://jingling.im/blog/java-thread-model</link>
            <guid isPermaLink="false">https://jingling.im/blog/java-thread-model</guid>
            <pubDate>Thu, 08 Oct 2020 16:55:56 GMT</pubDate>
            <content:encoded><![CDATA[
## JAVA线程模型

线程的实现主要有3种方式：

1. 使用内核线程实现（1:1）
2. 使用用户线程实现（1:N）
3. 使用用户线程加轻量级进程实现（N:M）

### 使用内核线程实现（Kernel-Level Thread, KLT）（1:1）

内核线程就是直接由操作系统内核支持的线程，这种线程由内核来完成线程的切换，内核通过操作调度器对线程进行调度，并负责将线程的任务映射到各个处理器上。

程序一般不会直接去使用内核，而是去使用线程的一种高级接口——轻量级进程（Light Weight Process，LWP），轻量级进程就是我们通常意义上的线程，由于每个轻量级进程都是由一个内核线程支持的，因此只有先支持内核线程，才能有轻量级进程。这种轻量级进程与内核线程直接1：1的关系称为一对一线程模型。
![untitled.jpg](/assets/upload/2020/11/untitled-9e7cc4869d36468cb5bbdfec0c999fe2.jpg)

优点：实现简单

缺点：

- 线程的各种操作都需要系统调度，需要在用户态和内核态中来回切换，系统调用的...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[Java 内存中几种常见的OOM 异常]]></title>
            <link>https://jingling.im/blog/oom</link>
            <guid isPermaLink="false">https://jingling.im/blog/oom</guid>
            <pubDate>Thu, 24 Sep 2020 16:41:33 GMT</pubDate>
            <description><![CDATA[Java 内存中几种常见的 OOM 异常，JAVA堆溢出，栈溢出，方法区溢出，直接堆外内存溢出]]></description>
            <content:encoded><![CDATA[
在JAVA虚拟机规范说明中，除了程序计数器不会出现OutOfMemoryError（简称OOM），其他几个内存区域都有可能发生OOM异常。

### JAVA堆溢出

JAVA堆用于存储对象实例，只要不断的new对象，并且保证GC roots到对象之间有可达路径来避免垃圾回收这些对象，在对象达到一定的数量之后，就会产生OOM异常。

示例：

1. 使用JVM参数限制JAVA堆的大小

   `-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=D:/var/log/gc_dump.dump`

2. JAVA程序代码示例：

   ```java
   public class TestOOM{
       static class OOMObejct{

       }
       public static void main(String[] args){
           List<OOMObejct> list = new ArrayList<>();
           while...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[自己写一个简单的线程池]]></title>
            <link>https://jingling.im/blog/thread-pool-demo</link>
            <guid isPermaLink="false">https://jingling.im/blog/thread-pool-demo</guid>
            <pubDate>Fri, 18 Sep 2020 17:30:54 GMT</pubDate>
            <description><![CDATA[自己写一个简单的 JAVA 线程池]]></description>
            <content:encoded><![CDATA[
### 需求分析

（1）自己动手写一个线程池需要考虑哪些因素？

（2）自己动手写的线程池如何测试？

**思考**:

1. 既然是线程池, 池, 就是需要一个放线程的地方
2. 可以控制池的大小, 并不是无限的, 也就是coreSize
3. coreSzie满了, 任务需要排队, 所以需要一个有限的队列, 且这个队列必须要是线程安全的, 比如阻塞队列BlockingQueue
4. 如果coreSize的线程执行的很快, 那队列里面排队的线程就可以很快被执行完成, 如果队列满了, 可以再增加线程来执行队列里的任务, 也就是maxSize
5. 最后当队列满了, maxSize也已经达到了, 这时候就需要一种拒接策略了, 常用的策略有丢弃当前任务、丢弃最老的任务、调用者自己处理、抛出异常等。

根据上面的思考，我们定义一个线程池一共需要这么四个变量：

核心线程数coreSize、最大线程数maxSize、阻塞队列BlockingQueue、拒绝策略RejectPolicy。

### 定义线程池

定义核心参数以及构造方法

```java
public class MyTh...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[Java 虚拟机垃圾回收算法总结]]></title>
            <link>https://jingling.im/blog/gc-algorithm</link>
            <guid isPermaLink="false">https://jingling.im/blog/gc-algorithm</guid>
            <pubDate>Fri, 18 Sep 2020 16:44:26 GMT</pubDate>
            <content:encoded><![CDATA[
### 标记 — 清除算法(Mark Sweep)

最基本的垃圾收集算法，从它的名字就可以看得出来，算法分为“标记”和“清除”两个阶段：首先标记处所有需要回收的对象，在标记完成后统一回收被标记的对象。

标记清除算法主要不足有两个方面：

1. 效率低

   标记和清除两个阶段的效率都不够高

2. 空间碎片问题

   标记清除之后会产生大量不连续的内存碎片，如果空间碎片太多，会导致以后程序需要分配打对象时无法找到足够的内存空间，而不得不触发一次垃圾回收的动作

### 复制算法（Copying）

为了解决标记清除的效率问题，它将内存分为大小相等的两个区域，但是每次只使用其中一块区域。当其中一块区域用完了，就将还活着的对象复制（移动堆顶指针，按顺序分配内存）到另外一块区域上，然后再把已使用的那一块区域空间一次清理掉。

优点：

实现简单，运行高效，不会产生空间碎片问题

缺点：

浪费内存，将内存缩小为了原来的一般。

在对象存活率高的时候效率会变得低下。

虚拟机一般都才用此方法来回收新生代。虚拟机将内存划分成为一块较大的Eden区和两块较小的Survivor区，每次就使...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[java 同步锁：synchronized 关键字]]></title>
            <link>https://jingling.im/blog/synchronized</link>
            <guid isPermaLink="false">https://jingling.im/blog/synchronized</guid>
            <pubDate>Wed, 16 Sep 2020 17:06:55 GMT</pubDate>
            <description><![CDATA[Java synchronized 关键字是 Java 里面最基本的同步手段]]></description>
            <content:encoded><![CDATA[
## 简介

synchronized关键字是Java里面最基本的同步手段，它经过编译之后，会在同步块的前后分别生成 `monitorenter`和 `monitorexit`字节码指令，这两个字节码指令都需要一个引用类型的参数来指明要锁定和解锁的对象；而直接使用 synchronized 关键字锁定方法时，生成的字节码指令里面并没有 monitorenter 和 monitorexit 这两个指令，而是为方法添加了一个`flags: ACC_SYNCHRONIZED`， 该标识指明了该方法是一个同步方法。

## 两种用法

一、作用于方法上

```java
public synchronized void method1(){
    System.out.println(1);
}
```

查看编译后的字节码，发现会在方法的中加入**`ACC_SYNCHRONIZED`**的标识：

```java
public synchronized void method1();
      descriptor: ()V
      flags: (0x0021) ACC_PUBL...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[Java 魔法类：Unsafe 解析]]></title>
            <link>https://jingling.im/blog/unsafe</link>
            <guid isPermaLink="false">https://jingling.im/blog/unsafe</guid>
            <pubDate>Sat, 12 Sep 2020 17:03:42 GMT</pubDate>
            <content:encoded><![CDATA[
### Unsafe是什么

Unsafe是位于`sun.misc`包下的一个类，主要提供一些用于执行低级别、不安全操作的方法，如直接访问系统内存资源、自主管理内存资源等。不能被普通用户直接使用。

### 基本介绍

如下Unsafe源码所示，Unsafe类为一单例实现，提供静态方法getUnsafe获取Unsafe实例，当且仅当调用getUnsafe方法的类为引导类加载器所加载时才合法，否则抛出SecurityException异常。

```java
public final class Unsafe {
  // 单例对象
  private static final Unsafe theUnsafe;

  private Unsafe() {
  }
  @CallerSensitive
  public static Unsafe getUnsafe() {
    Class var0 = Reflection.getCallerClass();
    // 仅在引导类加载器`BootstrapClassLoader`加载时才合法
    if(!VM.isSyst...]]></content:encoded>
            <author>精灵王</author>
        </item>
        <item>
            <title><![CDATA[Java 中的锁优化]]></title>
            <link>https://jingling.im/blog/lock-up</link>
            <guid isPermaLink="false">https://jingling.im/blog/lock-up</guid>
            <pubDate>Thu, 10 Sep 2020 17:01:33 GMT</pubDate>
            <description><![CDATA[Java 中常见的锁，以及锁优化]]></description>
            <content:encoded><![CDATA[
### 简介

高效并发是 JDK1.5 到JDK1.6 的一个重要改进，虚拟机在这个版本上实现了各种锁优化技术，比如：适应性自旋锁、锁消除、锁粗化、轻量级锁、偏向锁等，这些技术都是为了在线程之间更高效的共享数据，以及解决竞争问题。

### 自旋锁与自适应锁

同步(加锁)对性能最大的影响就是实现阻塞，挂起线程和恢复线程都需要转入到内核态完成，这会给系统的并发性带来很大的压力，然而有时候锁定状态只会维持很短的一段时间，为了这一小段时间去挂起和恢复线程并不值得。如果物理机上有一个以上的处理器，我们就可以让后面请求锁的那个线程多等待一会儿，但是并不放弃 处理器的执行时间，看看持有锁的线程是否会很快就释放锁，为了让线程等待一会儿，我们只需要让线程执行一个忙循环(自旋操作)，这就是所谓的自旋锁。

自旋锁在 JDK1.4.2 中就已经引入，不过默认是关闭的，在 JDK1.6 中默认已经开启。自旋锁默认的自旋次数是 `10` 次，可以使用参数 -XX：PreBlockSpin 来更改。

自适应自旋锁是在 JDK1.6 中引入的。自适应的意思就是自旋的时间不再固定，而是由前一次在同一个锁上的...]]></content:encoded>
            <author>精灵王</author>
        </item>
    </channel>
</rss>