JavaSE-线程池(5)- 建议使用的方式
JavaSE-线程池(5)- 建议使用的方式
虽然JDK Executors 工具类提供了默认的创建线程池的方法,但一般建议自定义线程池参数,下面是阿里巴巴开发手册给出的理由:
另外Spring也提供了线程池的实现,比如 ThreadPoolExecutor
ThreadPoolExecutor
如下,初始化一个核心线程数为 2 ,最大线程数为 4,队列长度为 2 的线程池,其中 initialize 为线程池具体初始化方法
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;import java.util.concurrent.ThreadPoolExecutor;public class ThreadPoolTaskExecutorTest {static class MyTask implements Runnable {private int i;public MyTask(int i) {this.i = i;}@Overridepublic void run() {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread() + " " + i);}}public static void main(String[] args) {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();//设置核心线程数executor.setCorePoolSize(2);//设置最大线程数executor.setMaxPoolSize(4);//设置线程被回收的空闲时长executor.setKeepAliveSeconds(6);//设置队列容量executor.setQueueCapacity(2);//设置线程前缀executor.setThreadNamePrefix("t-");//设置拒绝策略executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());//初始化线程池executor.initialize();for (int i = 1; i <= 8; i++) {try {executor.execute(new MyTask(i));} catch (Exception e) {e.printStackTrace();}}}
}
由打印结果可以看出7,8任务由于线程数达到 maxPoolSize,且队列也填充满,线程池执行了拒绝策略
18:47:12.134 [main] INFO org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor - Initializing ExecutorService
org.springframework.core.task.TaskRejectedException: Executor [java.util.concurrent.ThreadPoolExecutor@63e2203c[Running, pool size = 4, active threads = 4, queued tasks = 2, completed tasks = 0]] did not accept task: com.hs.example.base.multithread.threadpool.ThreadPoolTaskExecutorTest$MyTask@3224f60bat org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor.execute(ThreadPoolTaskExecutor.java:317)at com.hs.example.base.multithread.threadpool.ThreadPoolTaskExecutorTest.main(ThreadPoolTaskExecutorTest.java:39)
Caused by: java.util.concurrent.RejectedExecutionException: Task com.hs.example.base.multithread.threadpool.ThreadPoolTaskExecutorTest$MyTask@3224f60b rejected from java.util.concurrent.ThreadPoolExecutor@63e2203c[Running, pool size = 4, active threads = 4, queued tasks = 2, completed tasks = 0]at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2047)at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:823)at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1369)at org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor.execute(ThreadPoolTaskExecutor.java:314)... 1 more
org.springframework.core.task.TaskRejectedException: Executor [java.util.concurrent.ThreadPoolExecutor@63e2203c[Running, pool size = 4, active threads = 4, queued tasks = 2, completed tasks = 0]] did not accept task: com.hs.example.base.multithread.threadpool.ThreadPoolTaskExecutorTest$MyTask@4bbfb90aat org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor.execute(ThreadPoolTaskExecutor.java:317)at com.hs.example.base.multithread.threadpool.ThreadPoolTaskExecutorTest.main(ThreadPoolTaskExecutorTest.java:39)
Caused by: java.util.concurrent.RejectedExecutionException: Task com.hs.example.base.multithread.threadpool.ThreadPoolTaskExecutorTest$MyTask@4bbfb90a rejected from java.util.concurrent.ThreadPoolExecutor@63e2203c[Running, pool size = 4, active threads = 4, queued tasks = 2, completed tasks = 0]at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2047)at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:823)at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1369)at org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor.execute(ThreadPoolTaskExecutor.java:314)... 1 more
Thread[t-4,5,main] 6
Thread[t-1,5,main] 1
Thread[t-3,5,main] 5
Thread[t-2,5,main] 2
Thread[t-3,5,main] 3
Thread[t-4,5,main] 4
ThreadPoolTaskExecutor initialize 方法
在上述例子中,initialize 方法是线程池初始化的具体实现,源码如下:
public void initialize() {if (this.logger.isInfoEnabled()) {this.logger.info("Initializing ExecutorService" + (this.beanName != null ? " '" + this.beanName + "'" : ""));}if (!this.threadNamePrefixSet && this.beanName != null) {this.setThreadNamePrefix(this.beanName + "-");}。;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;this.executor = this.initializeExecutor(this.threadFactory, this.rejectedExecutionHandler);
}
由以下代码可以看出 ThreadPoolExecutor 是对 ThreadPoolExecutor 的封装,其中 this.threadFactory即为 ThreadPoolExecutor 本身,由下面的类结构图可以看出 ThreadPoolExecutor 继承自 ExecutorConfigurationSupport,而ExecutorConfigurationSupport实现了 ThreadFactory 接口
protected ExecutorService initializeExecutor(ThreadFactory threadFactory, RejectedExecutionHandler rejectedExecutionHandler) {BlockingQueue<Runnable> queue = this.createQueue(this.queueCapacity);ThreadPoolExecutor executor;if (this.taskDecorator != null) {executor = new ThreadPoolExecutor(this.corePoolSize, this.maxPoolSize, (long)this.keepAliveSeconds, TimeUnit.SECONDS, queue, threadFactory, rejectedExecutionHandler) {public void execute(Runnable command) {Runnable decorated = ThreadPoolTaskExecutor.this.taskDecorator.decorate(command);if (decorated != command) {ThreadPoolTaskExecutor.this.decoratedTaskMap.put(decorated, command);}super.execute(decorated);}};} else {executor = new ThreadPoolExecutor(this.corePoolSize, this.maxPoolSize, (long)this.keepAliveSeconds, TimeUnit.SECONDS, queue, threadFactory, rejectedExecutionHandler);}if (this.allowCoreThreadTimeOut) {executor.allowCoreThreadTimeOut(true);}this.threadPoolExecutor = executor;return executor;
}
ThreadPoolTaskExecutor 类结构图:

直接注入ThreadPoolTaskExecutor
除了手动实例化 ThreadPoolTaskExecutor 外,也可以直接注入 ThreadPoolTaskExecutor ,如下例:
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.test.context.junit4.SpringRunner;import java.util.concurrent.CountDownLatch;@RunWith(SpringRunner.class)
@SpringBootTest
public class ThreadPoolTaskExecutorTests {static class MyTask implements Runnable {private int i;private CountDownLatch countDownLatch;public MyTask(int i, CountDownLatch countDownLatch) {this.i = i;this.countDownLatch = countDownLatch;}@Overridepublic void run() {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread() + " " + i);countDownLatch.countDown();}}@Autowiredprivate ThreadPoolTaskExecutor taskExecutor;@Testpublic void contextLoads() {CountDownLatch countDownLatch = new CountDownLatch(8);long start = System.currentTimeMillis();for (int i = 1; i <= 8; i++) {try {taskExecutor.execute(new MyTask(i, countDownLatch));} catch (Exception e) {e.printStackTrace();}}try {countDownLatch.await();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("耗时:" + (System.currentTimeMillis() - start));}}
2023-02-19 21:25:15.219 INFO 123964 --- [ main] c.h.e.aop.ThreadPoolTaskExecutorTests : Starting ThreadPoolTaskExecutorTests on 0IZV69K0AKR0ELX with PID 123964 (started by Administrator in D:\workspace\idea_workspace\idea-test\example\2-aop)
2023-02-19 21:25:15.220 INFO 123964 --- [ main] c.h.e.aop.ThreadPoolTaskExecutorTests : No active profile set, falling back to default profiles: default
2023-02-19 21:25:16.349 INFO 123964 --- [ main] c.h.e.aop.ThreadPoolTaskExecutorTests : Started ThreadPoolTaskExecutorTests in 1.571 seconds (JVM running for 3.269)
2023-02-19 21:25:16.382 INFO 123964 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
Thread[task-8,5,main] 8
Thread[task-6,5,main] 6
Thread[task-4,5,main] 4
Thread[task-2,5,main] 2
Thread[task-3,5,main] 3
Thread[task-5,5,main] 5
Thread[task-7,5,main] 7
Thread[task-1,5,main] 1
耗时:1005
2023-02-19 21:25:17.637 INFO 123964 --- [ Thread-2] o.s.s.concurrent.ThreadPoolTaskExecutor : Shutting down ExecutorService 'applicationTaskExecutor'
之所以可以直接使用 ThreadPoolTaskExecutor ,是因为SpringBoot自动注入了此类,具体看
org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration:
package org.springframework.boot.autoconfigure.task;import java.util.concurrent.Executor;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.task.TaskExecutionProperties.Pool;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.task.TaskExecutorBuilder;
import org.springframework.boot.task.TaskExecutorCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.task.TaskDecorator;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;@ConditionalOnClass({ThreadPoolTaskExecutor.class})
@Configuration
@EnableConfigurationProperties({TaskExecutionProperties.class})
public class TaskExecutionAutoConfiguration {public static final String APPLICATION_TASK_EXECUTOR_BEAN_NAME = "applicationTaskExecutor";private final TaskExecutionProperties properties;private final ObjectProvider<TaskExecutorCustomizer> taskExecutorCustomizers;private final ObjectProvider<TaskDecorator> taskDecorator;public TaskExecutionAutoConfiguration(TaskExecutionProperties properties, ObjectProvider<TaskExecutorCustomizer> taskExecutorCustomizers, ObjectProvider<TaskDecorator> taskDecorator) {this.properties = properties;this.taskExecutorCustomizers = taskExecutorCustomizers;this.taskDecorator = taskDecorator;}@Bean@ConditionalOnMissingBeanpublic TaskExecutorBuilder taskExecutorBuilder() {Pool pool = this.properties.getPool();TaskExecutorBuilder builder = new TaskExecutorBuilder();builder = builder.queueCapacity(pool.getQueueCapacity());builder = builder.corePoolSize(pool.getCoreSize());builder = builder.maxPoolSize(pool.getMaxSize());builder = builder.allowCoreThreadTimeOut(pool.isAllowCoreThreadTimeout());builder = builder.keepAlive(pool.getKeepAlive());builder = builder.threadNamePrefix(this.properties.getThreadNamePrefix());builder = builder.customizers(this.taskExecutorCustomizers);builder = builder.taskDecorator((TaskDecorator)this.taskDecorator.getIfUnique());return builder;}//实例化 ThreadPoolTaskExecutor @Lazy@Bean(name = {"applicationTaskExecutor", "taskExecutor"})@ConditionalOnMissingBean({Executor.class})public ThreadPoolTaskExecutor applicationTaskExecutor(TaskExecutorBuilder builder) {return builder.build();}
}
默认配置 TaskExecutionProperties :
@ConfigurationProperties("spring.task.execution")
public class TaskExecutionProperties {private final TaskExecutionProperties.Pool pool = new TaskExecutionProperties.Pool();private String threadNamePrefix = "task-";public TaskExecutionProperties() {}public TaskExecutionProperties.Pool getPool() {return this.pool;}public String getThreadNamePrefix() {return this.threadNamePrefix;}public void setThreadNamePrefix(String threadNamePrefix) {this.threadNamePrefix = threadNamePrefix;}public static class Pool {private int queueCapacity = 2147483647;private int coreSize = 8;private int maxSize = 2147483647;private boolean allowCoreThreadTimeout = true;private Duration keepAlive = Duration.ofSeconds(60L);public Pool() {}public int getQueueCapacity() {return this.queueCapacity;}public void setQueueCapacity(int queueCapacity) {this.queueCapacity = queueCapacity;}public int getCoreSize() {return this.coreSize;}public void setCoreSize(int coreSize) {this.coreSize = coreSize;}public int getMaxSize() {return this.maxSize;}public void setMaxSize(int maxSize) {this.maxSize = maxSize;}public boolean isAllowCoreThreadTimeout() {return this.allowCoreThreadTimeout;}public void setAllowCoreThreadTimeout(boolean allowCoreThreadTimeout) {this.allowCoreThreadTimeout = allowCoreThreadTimeout;}public Duration getKeepAlive() {return this.keepAlive;}public void setKeepAlive(Duration keepAlive) {this.keepAlive = keepAlive;}}
}
相关文章:
JavaSE-线程池(5)- 建议使用的方式
JavaSE-线程池(5)- 建议使用的方式 虽然JDK Executors 工具类提供了默认的创建线程池的方法,但一般建议自定义线程池参数,下面是阿里巴巴开发手册给出的理由: 另外Spring也提供了线程池的实现,比如 Thread…...
城市轨道交通供电系统研究(Matlab代码实现)
👨🎓个人主页:研学社的博客💥💥💞💞欢迎来到本博客❤️❤️💥💥🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密…...
什么是 RESTful 风格?
一、什么是 REST ? REST即表述性状态传递(英文:Representational State Transfer,简称REST)是Roy Thomas Fielding博士在2000年他的博士论文中提出来的一种软件架构风格。它是一种针对网络应用的设计和开发方式&#…...
从业6年,对敏捷和自动化测试的一点心得
不久前,参加Thoughtworks组织的一场自动化测试的分享,同事由于出差国外不能参加,特意嘱托我提问两个问题: 在互联网这个将“敏捷”与“持续集成”进行积极实践的环境里,“敏捷测试”与“自动化测试”成了一个大家经常…...
ThreeJS 之界面控制
文章目录参考描述界面自适应问题resize 事件修改画布大小修改视锥体的宽高比全屏显示dblclick 事件检测全屏显示状态进入全屏显示状态退出全屏显示状态尾声参考 项目描述ThreeJS官方文档哔哩哔哩老陈打码搜索引擎BingMDN 文档document.mozFullScreenElementMDN 文档Element.re…...
【查找算法】解析学习四大常用的计算机查找算法 | C++
第二十二章 四大查找算法 目录 第二十二章 四大查找算法 ●前言 ●查找算法 ●一、顺序查找法 1.什么是顺序查找法? 2.案例实现 ●二、二分查找法 1.什么是二分查找法? 2.案例实现 ●三、插值查找法 1.什么是插值查找法? 2…...
Android实例仿真之一
目录 零 开局三问 第一问:为什么要有这一章? 第二问:Android算不算是一个嵌入式系统? 第三问:用什么方法来分析Android这个大系统? 一 讨论Android的流行 二 深入浅出Android 零 开局三问 在正式开始…...
软考高级-信息系统管理师之重要工具和技术的口语化表示(最新版)
重要工具和技术的口语化表示 本文主要介绍重要工具和技术的口语化解释 1、 模板、表格和标准:就是用之前的项目的模版、表格、标准,结合本项目进行了修改,在编制一些计划、方案的时候就可以采用这个工具和技术。可以拿来就用的,节约时间、提高质量的。 2、 产品分析:通过一…...
基于springboot+vue的个人健康信息服务平台
基于springbootvue的个人健康信息服务平台 ✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 🍅文末获取项目下载方式🍅 一、项目背…...
SpringBoot2.x实战专题——SpringBoot2 多配置文件【开发环境、测试环境、生产环境】
SpringBoot2.x实战专题——SpringBoot2 多配置文件【开发环境、测试环境、生产环境】 目录SpringBoot2.x实战专题——SpringBoot2 多配置文件【开发环境、测试环境、生产环境】一、创建一个SpringBoot项目二、修改pom.xml中SpringBoot的版本三、配置文件3.1 application-dev.ym…...
测试2:编写测试用例的方法
2.编写测试用例的方法 7种 测试常用的方法:code review 代码静态分析、CI/CD CI–持续集成–开发成员经常集成它们的工作,尽快发现集成错误 CD–持续部署–将集成后的代码部署到更贴近真实运行的环境 2.1 测试用例的描述: 用例编号 用例…...
docker安装配置镜像加速器-拉取创建Mysql容器示例
List item docker 常见命令大全docker安装docker拉取创建Mysql容器docker 安装 1、安装链接:https://blog.csdn.net/BThinker/article/details/123358697 ; 2、安装完成需要配置docker镜像加速器 3、docker 镜像加速器推荐使用阿里云的: 编…...
WSL1和WSL2相互转换以及安装路径迁移相关问题
目录 1.从WSL 1如何切换到WSL 2? 2.从WSL 2如何切换回WSL 1? 3.WSL1转换为WSL2后,WSL1里面安装的程序和库需要重装吗? 4.WSL2转换为WSL1后,WSL2里面安装的程序和库需要重装吗? 5.如何备份WSL2…...
系统分析*
文章目录系统分析分析的任务结构化方法OO的方法的任务常用的详细调查方法有哪些?系统分析的建模TFD业务流程图DFDDD数据流图用例模型(重点用例图)用例图的内容:用例之间的关系:对象模型(类图)时…...
【redis】持久化:RDB和AOF
redis的持久化指将数据写入可靠内存中,如ssd。Redis提供了4种持久化策略 RDB:Redis Database,周期性的将某个时间点的数据集快照持久化AOF:Append Only File,每次redis服务接收到写操作(修改内存的操作),都…...
2023Python接口自动化测试实战教程,附视频实战讲解
这两天一直在找直接用python做接口自动化的方法,在网上也搜了一些博客参考,今天自己动手试了一下。 一、整体结构 上图是项目的目录结构,下面主要介绍下每个目录的作用。 Common:公共方法:主要放置公共的操作的类,比如数据库sql…...
【原创】java+swing+sqlserver药品管理系统设计与实现
之前数据库都是用的mysql,今天我们使用sqlserver在配合swing来开发一个药品管理系统。方便医院工作人员进行药品的管理,基础功能基本都是一些增删改查操作。 功能分析: 药品管理系统主要提供给管理员和员工使用,功能如下&#x…...
软考高级信息系统项目管理师系列之二十七:信息文档管理与配置管理
软考高级信息系统项目管理师系列之二十七:信息文档管理与配置管理 一、信息文档管理与配置管理内容整理二、信息系统文档管理1.信息系统文档概念2.软件文档分类与质量等级三、配置管理1.配置管理2.典型配置项3.配置项4.配置项操作权限5.配置项状态6.配置项版本号7.配置项版本管…...
软考高级-信息系统管理师之项目管理基础(最新版)
项目管理基础 项目管理特点战略管理三个过程IT项目特点项目管理概念项目管理特点软技能PRINCE2的四个要素组织结构职能型组织优缺点职能型组织优点同时,职能型组织也存在着如下缺点:项目型组织优缺点项目型组织优点项目型组织也存在着如下缺点:矩阵型组织优缺点矩阵型组织的优…...
leetcode240+Search a 2D Matrix II+从右上角开始
链接 class Solution { public:bool searchMatrix(vector<vector<int>>& matrix, int target) {if(matrix.size()0 || matrix[0].size()0) return false;int i0, jmatrix[0].size()-1; //从右上角开始while (i<matrix.size()&&j>0) {int x mat…...
Linux相关概念和易错知识点(42)(TCP的连接管理、可靠性、面临复杂网络的处理)
目录 1.TCP的连接管理机制(1)三次握手①握手过程②对握手过程的理解 (2)四次挥手(3)握手和挥手的触发(4)状态切换①挥手过程中状态的切换②握手过程中状态的切换 2.TCP的可靠性&…...
Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility
Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility 1. 实验室环境1.1 实验室环境1.2 小测试 2. The Endor System2.1 部署应用2.2 检查现有策略 3. Cilium 策略实体3.1 创建 allow-all 网络策略3.2 在 Hubble CLI 中验证网络策略源3.3 …...
【机器视觉】单目测距——运动结构恢复
ps:图是随便找的,为了凑个封面 前言 在前面对光流法进行进一步改进,希望将2D光流推广至3D场景流时,发现2D转3D过程中存在尺度歧义问题,需要补全摄像头拍摄图像中缺失的深度信息,否则解空间不收敛…...
智能在线客服平台:数字化时代企业连接用户的 AI 中枢
随着互联网技术的飞速发展,消费者期望能够随时随地与企业进行交流。在线客服平台作为连接企业与客户的重要桥梁,不仅优化了客户体验,还提升了企业的服务效率和市场竞争力。本文将探讨在线客服平台的重要性、技术进展、实际应用,并…...
工程地质软件市场:发展现状、趋势与策略建议
一、引言 在工程建设领域,准确把握地质条件是确保项目顺利推进和安全运营的关键。工程地质软件作为处理、分析、模拟和展示工程地质数据的重要工具,正发挥着日益重要的作用。它凭借强大的数据处理能力、三维建模功能、空间分析工具和可视化展示手段&…...
Rapidio门铃消息FIFO溢出机制
关于RapidIO门铃消息FIFO的溢出机制及其与中断抖动的关系,以下是深入解析: 门铃FIFO溢出的本质 在RapidIO系统中,门铃消息FIFO是硬件控制器内部的缓冲区,用于临时存储接收到的门铃消息(Doorbell Message)。…...
2025季度云服务器排行榜
在全球云服务器市场,各厂商的排名和地位并非一成不变,而是由其独特的优势、战略布局和市场适应性共同决定的。以下是根据2025年市场趋势,对主要云服务器厂商在排行榜中占据重要位置的原因和优势进行深度分析: 一、全球“三巨头”…...
保姆级教程:在无网络无显卡的Windows电脑的vscode本地部署deepseek
文章目录 1 前言2 部署流程2.1 准备工作2.2 Ollama2.2.1 使用有网络的电脑下载Ollama2.2.2 安装Ollama(有网络的电脑)2.2.3 安装Ollama(无网络的电脑)2.2.4 安装验证2.2.5 修改大模型安装位置2.2.6 下载Deepseek模型 2.3 将deepse…...
vulnyx Blogger writeup
信息收集 arp-scan nmap 获取userFlag 上web看看 一个默认的页面,gobuster扫一下目录 可以看到扫出的目录中得到了一个有价值的目录/wordpress,说明目标所使用的cms是wordpress,访问http://192.168.43.213/wordpress/然后查看源码能看到 这…...
MacOS下Homebrew国内镜像加速指南(2025最新国内镜像加速)
macos brew国内镜像加速方法 brew install 加速formula.jws.json下载慢加速 🍺 最新版brew安装慢到怀疑人生?别怕,教你轻松起飞! 最近Homebrew更新至最新版,每次执行 brew 命令时都会自动从官方地址 https://formulae.…...
