开闭原则

开闭原则 开闭原则(Open-Close Principle/OCP)是面向对象设计中"可复用设计"的基石,是面向对象设计中最重要的原则之一,其它很多的设计原则都是实现开闭原则的一种手段。 1988年,Bertrand Meyer在他的著作《Object Oriented Software Construction》中提出了开闭原则,它的原文是这样: “Software entities should be open for extension,but closed for modification”。翻译过来就是: “软件实体应当对扩展开放,对修改关闭”。这句话说得略微有点专业,我们把它讲得更通俗一点,也就是: 软件系统中包含的各种组件,例如模块 (Modules) 、类 (Classes) 以及功能 (Functions) 等等,应该在不修改现有代码的基础上,引入新功能。开闭原则中"开",是指对于组件功能的扩展是开放的,是允许对其进行功能扩展的;开闭原则中"闭",是指对于原有代码的修改是封闭的,即不应该修改原有的代码。实现开闭原则的关键就在于"抽象"。把系统的所有可能的行为抽象成一个抽象底层,这个抽象底层规定出所有的具体实现必须提供的方法的特征。作为系统设计的抽象层,要预见所有可能的扩展,从而使得在任何扩展情况下,系统的抽象底层不需修改;同时,由于可以从抽象底层导出一个或多个新的具体实现,可以改变系统的行为,因此系统设计对扩展是开放的。 我们在软件开发的过程中,一直都是提倡需求导向的。这就要求我们在设计的时候,要非常清楚地了解用户需求,判断需求中包含的可能的变化,从而明确在什么情况下使用开闭原则。 关于系统可变的部分,还有一个更具体的对可变性封装原则 (Principle of Encapsulation of Variation, EVP) ,它从软件工程实现的角度对开闭原则进行了进一步的解释。EVP要求在做系统设计的时候,对系统所有可能发生变化的部分进行评估和分类,每一个可变的因素都单独进行封装。 我们在实际开发过程的设计开始阶段,就要罗列出来系统所有可能的行为,并把这些行为加入到抽象底层,根本就是不可能的,这么去做也是不经济的,费时费力。另外,在设计开始阶段,对所有的可变因素进行预计和封装也不太现实,也是很难做得到。所以,开闭原则描绘的愿景只是一种理想情况或是极端状态,现实世界中是很难被完全实现的。我们只能在某些组件,在某种程度上符合开闭原则的要求。 通过以上的分析,对于开闭原则,我们可以得出这样的结论: 虽然我们不可能做到百分之百的封闭,但是在系统设计的时候,我们还是要尽量做到这一点。 对于软件系统的功能扩展,我们可以通过继承、重载或者委托等手段实现。以接口为例,它对修改就是是封闭的,而对具体的实现是开放的,我们可以根据实际的需要提供不同的实现,所以接口是符合开闭原则的。如果一个软件系统符合开闭原则的,那么从软件工程的角度来看,它至少具有这样的好处: 可复用性好。 我们可以在软件完成以后,仍然可以对软件进行扩展,加入新的功能,非常灵活。因此,这个软件系统就可以通过不断地增加新的组件,来满足不断变化的需求。 可维护性好。 由于对于已有的软件系统的组件,特别是它的抽象底层不去修改,因此,我们不用担心软件系统中原有组件的稳定性,这就使变化中的软件系统有一定的稳定性和延续性。开闭原则具有理想主义的色彩,它是面向对象设计的终极目标。因此,针对开闭原则的实现方法,一直都有面向对象设计的大师费尽心机,研究开闭原则的实现方式。后面要提到的里氏代换原则 (LSP) 、依赖倒转原则 (DIP) 、接口隔离原则 (ISP) 以及抽象类 (Abstract Class) 、接口(Interface)等等,都可以看作是开闭原则的实现方法。 http://baike.baidu.com/view/866233.htm

2026-04-16 · 1 min · 53 words · -

distributed lock 分布式锁

distributed lock 分布式锁 分布式是一种分布式协调技术,控制分布式系统不同进程共同访问共享资源的一种锁的实现。如果不同的系统或同一个系统的不同主机之间共享了某个临界资源,往往需要互斥来防止彼此干扰,以保证一致性。 分布式锁是由于单机锁无法满足分布式系统锁,在多进程/分布式环境下,需要分布式锁来控制共享内容,保证线程的安全。 为何需要分布式锁 Martin Kleppmann 是英国剑桥大学的分布式系统的研究员,之前和 Redis 之父 Antirez 进行过关于 RedLock (红锁,后续有讲到) 是否安全的激烈讨论。 Martin 认为一般我们使用分布式锁有两个场景: 效率: 使用分布式锁可以避免不同节点重复相同的工作,这些工作会浪费资源。比如用户付了钱之后有可能不同节点会发出多封短信。 正确性: 加分布式锁同样可以避免破坏正确性的发生,如果两个节点在同一条数据上面操作, 比如多个节点机器对同一个订单操作不同的流程有可能会导致该笔订单最后状态出现错误,造成损失。 分布式锁的一些特点 当我们确定了在不同节点上需要分布式锁,那么我们需要了解分布式锁到底应该有哪些特点? 分布式锁的特点如下: 互斥性: 和本地锁一样互斥性是锁最基本的特性, 任意时刻,只有一个客户端能持有锁。 可重入性: 同一个节点上的同一个线程如果获取了锁之后那么也可以再次获取这个锁。 超时释放: 锁失效机制, 防止死锁。正常情况下,请求获取锁之后,处理任务,处理完成之后释放锁。 但是如果在处理任务发生服务异常,或者网络异常时,导致锁无法释放。其他请求都无法获取锁,变成死锁。 为了防止锁变成死锁,需要设置锁的超时时间。过了超时时间后,锁自动释放,其他请求能正常获取锁。 自动续期: 锁设置了超时机制后,如果持有锁的节点处理任务的时候过长超过了超时时间,就会发生线程未处理完任务锁就被释放了, 其他线程就能获取到该锁,导致多个节点同时访问共享资源。对此,就需要延长超时时间。 开启一个监听线程,定时监听任务,监听任务线程还存活就延长超时时间。当任务完成、或者任务发生异常就不继续延长超时时间。 高可用, 高性能: 加锁和解锁需要高效,同时也需要保证高可用防止分布式锁失效,可以增加降级。 安全性: 锁只能被持有锁的客户端释放, 不能被其它客户端释放. 支持阻塞和非阻塞: 和 ReentrantLock 一样支持 lock 和 trylock 以及 tryLock(long timeOut)。即没有获取到锁将直接返回获取锁失败 支持公平锁和非公平锁(可选): 公平锁的意思是按照请求加锁的顺序获得锁,非公平锁就相反是无序的。这个一般来说实现的比较少。 分布式锁一般有三种实现方式: 数据库乐观锁 基于 Redis 的分布式锁 基于 ZooKeeper 的分布式锁 可靠性 首先,为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件: 互斥性。在任意时刻,只有一个客户端能持有锁。 不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。 具有容错性。只要大部分的 Redis 节点正常运行,客户端就可以加锁和解锁。 解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。 Redis 实现分布式锁 Redis 实现分布式锁,性能会比关系式数据库高一些. ...

2026-04-14 · 10 min · 2091 words · -

EAFP vs LBYL

两种处理错误/边界条件的编程风格,在 Python 社区尤为常见。 LBYL — Look Before You Leap(先检查再操作) 谚语来源: “Look Before You Leap” 字面意思是"跳跃之前,先看清脚下"。想象要跳过一条沟——不先看清落脚点就跳,可能会摔进坑里。引申为行动之前先确认条件是否安全,对应中文谚语"三思而后行"、“谋定而后动”。 在编程里,“leap”(跳跃)= 执行操作,“look”(看)= if 条件检查。 操作前先检查前提条件是否满足。 # LBYL 风格 if key in my_dict: value = my_dict[key] else: value = default if os.path.exists(filepath): with open(filepath) as f: data = f.read() 特点: 逻辑清晰,防御性强 存在 TOCTOU(Time-Of-Check-Time-Of-Use)竞态条件风险——检查和使用之间状态可能改变 代码中充满 if 判断,容易冗长 EAFP — Easier to Ask Forgiveness than Permission(先操作再处理异常) 短语来源: 这句话来自生活场景的比喻——想做某事时,与其事先去请示许可(可能被拒绝、很麻烦),不如直接去做,事后出了问题再道歉求谅解,因为道歉往往比请示更容易。 英文 字面意思 编程含义 Ask Permission 事先请求许可 用 if 检查条件是否满足 Ask Forgiveness 事后请求原谅 用 except 处理出错的异常 这个说法由 Python 之父 Guido van Rossum 推广,体现了 Python 的哲学:代码应该表达意图,而不是充满防御性检查。 ...

2026-03-23 · 1 min · 161 words · -

Fork 项目同步与提交 PR 的完整流程

概述 在参与开源项目开发时,通常的工作流程是:Fork 主仓库 → 在自己的仓库开发 → 保持与主仓库同步 → 提交 Pull Request。本文记录这个完整的操作流程。 前置准备 添加上游仓库 首次 fork 项目后,需要添加原始仓库(上游仓库)作为远程仓库: # 添加上游仓库 git remote add upstream <原始仓库的 git 地址>igin https://github.com/your-username/project.git (fetch) origin https://github.com/your-username/project.git (push) upstream https://github.com/original-owner/project.git (fetch) upstream https://github.com/original-owner/project.git (push) origin: 你的 fork 仓库 upstream: 原始仓库(上游) 保持本地主分支同步 在开发新功能前,或定期需要将上游仓库的最新变更同步到本地: 1. 获取上游仓库的最新变更 git fetch upstream 这会下载上游仓库的所有分支和提交,但不会合并到本地。 2. 切换到本地主分支 git checkout main # 或者 git switch main 3. 检查当前状态 git status 确保工作区是干净的,没有未提交的更改。 4. 合并上游主分支 git merge upstream/main 为什么 main 分支用 merge 而不是 rebase? ...

2026-02-02 · 3 min · 457 words · -

给开源项目提交 Pull Request 完整指南

概述 给开源项目贡献代码是参与开源社区的重要方式。本文记录了从 Fork 仓库到提交 Pull Request (PR) 的完整流程。 前置准备 Fork 仓库 在 GitHub 上打开你想贡献的开源项目 点击右上角的 Fork 按钮 将仓库 Fork 到你自己的账号下 克隆你的 Fork git clone https://github.com/你的账号/仓库名.git cd 仓库名 配置上游仓库 添加上游仓库(只需做一次) git remote add upstream https://github.com/官方账号/官方仓库.git 验证 remote 配置 git remote -v 输出应该类似: origin https://github.com/你的账号/仓库名.git (fetch) origin https://github.com/你的账号/仓库名.git (push) upstream https://github.com/官方账号/官方仓库.git (fetch) upstream https://github.com/官方账号/官方仓库.git (push) 同步上游代码 在开始新功能开发前,确保你的本地代码与上游保持同步: # 拉取上游最新代码 git fetch upstream # 切换到 main 分支 git checkout main # 合并上游的 main git merge upstream/main # 推送到你的 fork git push origin main 创建功能分支 不要直接在 main 分支上开发,应该创建新的功能分支: ...

2026-02-01 · 2 min · 370 words · -

Taskfile

介绍 Taskfile 是一个现代化的任务运行器和构建工具,使用 YAML 格式定义任务,是 Makefile 的替代方案。 task run vs task start 语义区别 run(运行) 强调"执行"程序 通常用于开发环境,直接运行源代码 示例:go run、python script.py 适合开发调试,不需要编译 用于一次性执行任务或脚本 start(启动) 强调"启动"服务 通常用于启动已编译的二进制文件或服务 示例:systemctl start、service start 暗示这是一个长期运行的服务 用于启动后台服务或守护进程 命名建议 在 Taskfile 中定义任务时: 使用 run 命名:开发环境执行、脚本运行、一次性任务 使用 start 命名:服务启动、后台进程、长期运行的应用 示例: version: '3' tasks: run: desc: 运行应用(开发模式) cmds: - go run main.go start: desc: 启动服务(生产模式) cmds: - ./bin/app 参考 Taskfile 官方文档

2026-01-11 · 1 min · 60 words · -

正则表达式, regex

正则表达式, regex # PCRE # 10.0.0.0/8; 172.0.0.0/12; 192.168.0.0/16 ^[\d|\.|\/|;| ]+$ 元字符 描述 将下一个字符标记为一个特殊字符, 或一个原义字符, 或一个向后引用, 或一个八进制转义符。例如,“n” 匹配字符 “n”。"\n" 匹配一个换行符。 [中括号] []是定义匹配的字符范围 [xyz] 字符集合。匹配所包含的任意一个字符。例如,"[abc]"可以匹配"plain"中的"a"。 [^xyz] 负值字符集合。匹配未包含的任意字符。例如,"[^abc]"可以匹配"plain"中的"p"。 [a-z] 字符范围。匹配指定范围内的任意字符。例如,"[a-z]"可以匹配"a"到"z"范围内的任意小写字母字符。 [^a-z] 负值字符范围。匹配任何不在指定范围内的任意字符。例如, "[^a-z]"可以匹配任何不在"a"到"z"范围内的任意字符。 [A-Za-z0-9] 数字和26个英文字母组成的字符串 {大括号} {} 一般用来表示匹配的长度, 比如 \s{3} 表示匹配三个空格,\s{1,3} 表示匹配一到三个空格。 PCRE PCRE: Perl-compatible regular expressions ^ 匹配字符串的开始位置 $ 匹配字符串的结束位置 \ 将下一个字符标记为一个特殊字符(File Format Escape)、或一个原义字符(Identity Escape,有 ^$()*+?.[{| 共计12个)、或一个向后引用(backreferences)、或一个八进制转义符。例如,“n”匹配字符“n”。“\n”匹配一个换行符。序列“\”匹配“\”而“(”则匹配“(”。 . 匹配除 \r \n 之外的任何单个字符。要匹配包括 \r \n 在内的任何字符,请使用像 (.|\r|\n) 的模式。 * 匹配前面的子表达式零次或多次。例如,zo能匹配“z”、“zo”以及“zoo”。等价于{0,}。 + 匹配前面的子表达式一次或多次。例如,“zo+“能匹配"zo"以及"zoo”,但不能匹配"z”。+等价于{1,}。 (pattern) 匹配pattern并获取这一匹配的子字符串。该子字符串用于向后引用。所获取的匹配可以从产生的Matches集合得到,在VBScript中使用SubMatches集合,在JScript中则使用$0…$9属性。要匹配圆括号字符,请使用“(”或“)”。可带数量后缀。 ? 匹配前面的子表达式零次或一次。例如,“do(es)?“可以匹配"does"或"does"中的"do”。?等价于{0,1}。 {n} n 是一个非负整数。匹配确定的 n 次。例如,“o{2}“不能匹配"Bob"中的"o”,但是能匹配"food"中的两个o。 {n,} n 是一个非负整数。至少匹配n次。例如,“o{2,}“不能匹配"Bob"中的"o”,但能匹配"foooood"中的所有o。“o{1,}“等价于"o+"。“o{0,}“则等价于"o_"。 {1,3} 匹配一次到三次 linux 测试正则表达式 https://anjia0532.github.io/2017/06/29/nginx-regex-test-way/ ...

2022-09-15 · 3 min · 601 words · -

Memory Barrior, 内存屏障

“Memory Barrior, 内存屏障” 屏障技术 内存屏障技术是一种屏障指令,它可以让 CPU 或者编译器在执行内存相关操作时遵循特定的约束,目前多数的现代处理器都会乱序执行指令以最大化性能,但是该技术能够保证内存操作的顺序性,在内存屏障前执行的操作一定会先于内存屏障后执行的操作6。 https://draveness.me/golang/docs/part3-runtime/ch07-memory/golang-garbage-collector/ 内存屏障 Memory Barrior 内存屏障 (Memory barrier) 为什么会有内存屏障 每个CPU都会有自己的缓存 (有的甚至L1,L2,L3) ,缓存的目的就是为了提高性能,避免每次都要向内存取。但是这样的弊端也很明显: 不能实时的和内存发生信息交换,分在不同CPU执行的不同线程对同一个变量的缓存值不同。 用volatile关键字修饰变量可以解决上述问题,那么volatile是如何做到这一点的呢?那就是内存屏障,内存屏障是硬件层的概念,不同的硬件平台实现内存屏障的手段并java通过屏蔽这些差异,统一由jvm来生成内存屏障的指令。 内存屏障是什么 硬件层的内存屏障分为两种: Load Barrier 和 Store Barrier即读屏障和写屏障。 内存屏障有两个作用: 阻止屏障两侧的指令重排序; 强制把写缓冲区/高速缓存中的脏数据等写回主内存,让缓存中相应的数据失效。 对于Load Barrier来说,在指令前插入Load Barrier,可以让高速缓存中的数据失效,强制从新从主内存加载数据; 对于Store Barrier来说,在指令后插入Store Barrier,能让写入缓存中的最新数据更新写入主内存,让其他线程可见。 java内存屏障 java的内存屏障通常所谓的四种即LoadLoad,StoreStore,LoadStore,StoreLoad实际上也是上述两种的组合,完成一系列的屏障和数据同步功能。 LoadLoad屏障: 对于这样的语句Load1; LoadLoad; Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。 StoreStore屏障: 对于这样的语句Store1; StoreStore; Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。 LoadStore屏障: 对于这样的语句Load1; LoadStore; Store2,在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。 StoreLoad屏障: 对于这样的语句Store1; StoreLoad; Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。它的开销是四种屏障中最大的。 volatile语义中的内存屏障 volatile的内存屏障策略非常严格保守,非常悲观且毫无安全感的心态: 在每个volatile写操作前插入StoreStore屏障,在写操作后插入StoreLoad屏障; 在每个volatile读操作前插入LoadLoad屏障,在读操作后插入LoadStore屏障; 由于内存屏障的作用,避免了volatile变量和其它指令重排序、线程之间实现了通信,使得volatile表现出了锁的特性。 final语义中的内存屏障 对于final域,编译器和CPU会遵循两个排序规则: 新建对象过程中,构造体中对final域的初始化写入和这个对象赋值给其他引用变量,这两个操作不能重排序; (废话嘛) 初次读包含final域的对象引用和读取这个final域,这两个操作不能重排序; (晦涩,意思就是先赋值引用,再调用final值) 总之上面规则的意思可以这样理解,必需保证一个对象的所有final域被写入完毕后才能引用和读取。这也是内存屏障的起的作用: 写final域: 在编译器写final域完毕,构造体结束之前,会插入一个StoreStore屏障,保证前面的对final写入对其他线程/CPU可见,并阻止重排序。 读final域: 在上述规则2中,两步操作不能重排序的机理就是在读final域前插入了LoadLoad屏障。 X86处理器中,由于CPU不会对写-写操作进行重排序,所以StoreStore屏障会被省略;而X86也不会对逻辑上有先后依赖关系的操作进行重排序,所以LoadLoad也会变省略。 ...

2021-07-09 · 2 min · 248 words · -

race condition, 竞态条件

race condition 数据争用 (data race) 和竞态条件 (race condition) 在有关多线程编程的话题中,数据争用 (data race) 和竞态条件 (race condition) 是两个经常被提及的名词,它们两个有着相似的名字,也是我们在并行编程中极力避免出现的。但在处理实际问题时,我们应该能明确区分它们两个。 数据争用 (data race) 定义: 多个线程对于同一个变量、同时地、进行读/写操作的现象并且至少有一个线程进行写操作。 (也就是说,如果所有线程都是只进行读操作,那么将不构成数据争用) 后果: 如果发生了数据争用,读取该变量时得到的值将变得不可知,使得该多线程程序的运行结果将完全不可预测,可能直接崩溃。 如何防止: 对于有可能被多个线程同时访问的变量使用排他访问控制,具体方法包括使用mutex (互斥量) 和monitor (监视器) ,或者使用atomic变量。 竞态条件 (race condition) 竞态条件(race conditions),多个线程以非一致性的顺序同时访问数据资源 相对于数据争用,竞态条件(race condition) 指的是更加高层次的更加复杂的现象,一般需要在设计并行程序时进行细致入微的分析,才能确定。 (也就是隐藏得更深) 定义: 受各线程上代码执行的顺序和时机的影响,程序的运行结果产生 (预料之外) 的变化。 后果: 如果存在竞态条件(race condition),多次运行程序对于同一个输入将会有不同的结果,但结果并非完全不可预测,它将由输入数据和各线程的执行顺序共同决定。 如何预防: 竞态条件产生的原因很多是对于同一个资源的一系列连续操作并不是原子性的,也就是说有可能在执行的中途被其他线程抢占,同时这个“其他线程”刚好也要访问这个资源。解决方法通常是: 将这一系列操作作为一个 critical section (临界区) 。 代码示例 下面以C++实现的一个银行存款转账操作为例,说明数据争用(data race) 和竞态条件(race condition)的区别。 该系统的不変性条件: 存款余额≥0,不允许借款。 3.1.数据争用的例子 int my_account = 0; //我的账户余额 int your_account = 100; //你的账户余额 // 转账操作: 存在数据争用(data race)! bool racy_transfer(int& src, int& dst, int m) { if (m <= src) { //操作结果不可预测 src -= m; //操作结果不可预测 dst += m; //操作结果不可预测 return true; } else { return false; } } // 将下面两个函数在两个线程分别运行 racy_transfer(your_account, my_account, 50); racy_transfer(your_account, my_account, 80); 运行上面的的代码后,不光我们双方账号的余额不可预测,甚至整个系统会发生什么事情都无法保证。 ...

2021-07-01 · 2 min · 275 words · -

密码加密存储技术详解 (Password Storage Cheat Sheet)

密码加密存储技术详解 (Password Storage Cheat Sheet) 从最早的明文保存密码,到 md5 sha1 sha256 sha512 加密,到加 salt、加 pepper、多次 hash 计算,再到现代的密码加密算法Bcrypt PBKDF2 Argon2。在保护用户密码的过程中,软件工程师作出了巨大的努力,为网络安全的建设添砖加瓦。 本文详细的描述了密码加密存储技术涉及到的方方面面,并在最后给出了Java语言的实现代码。代码虽然简单,但其中原理却非常值得一读。 介绍 大多数用户在不同的网站或应用中使用相同的密码,因此当网站的数据库被盗取,存储的密码也不应该被攻击者获取。与密码学大多数领域一样,需要考虑很多因素;幸运的是,大多数现代编程语言和框架都提供了内置的功能来帮助存储密码,让问题变得简单很多。 本文章提供了与存储密码有关的各个方面的指导。简而言之: 使用Bcrypt。除非你有足够充分的理由不这么做。 设置合理的计算因子(work factor)。 使用盐Salt (现代算法会自动帮你这么做)。 考虑使用胡椒Pepper来提供额外的防御深度 (尽管单独使用它无法提供额外的安全特性)。 https://www.ujcms.com/knowledge/509.html Argon2 设计目标:Argon2 是目前最先进的密码哈希算法,专门设计用于对抗现代硬件攻击,包括暴力破解和 GPU 破解。它在 2015 年获得了密码哈希竞赛(Password Hashing Competition, PHC)中的冠军。 工作原理:Argon2 的设计包括内存硬化和迭代机制。它提供三种变体: Argon2d:强调防止 GPU 并行化攻击,适用于需要最大化时间复杂度的场景。 Argon2i:优化了对抗侧信道攻击,适用于对内存硬化要求较高的场景。 Argon2id:结合了 Argon2d 和 Argon2i 的优点,是最常用的版本。 优点: 高效的内存使用,适合防止使用大量并行计算能力(如 GPU)的破解。 可调整内存、时间和并行度,灵活性高。 通过设计抵抗 GPU 和 ASIC 攻击,非常适合现代硬件环境。 缺点:相比于 bcrypt 和 PBKDF2,Argon2 的实现较为复杂,可能会涉及更多的计算资源,尤其是内存要求较高。

2021-02-26 · 1 min · 64 words · -

账号, account

账号, account https://36kr.com/p/2342280589940227 最近,话题#账与帐很多人分不清#登上热搜,引发网友热议。笔者注意到,不同互联网平台,“帐号”和“账号”两种表述都存在。 实话实说,如果不是这条热搜,笔者也对“帐”和“账”傻傻分不清楚;到底“账号”和“帐号”哪种表述是正确的呢? 根据第七版《现代汉语词典》,只有“账号”而无“帐号”,“账号”解释为单位或者个人跟银行建立经济关系后,银行在账上给该单位或个人编的号码。而“帐”这个字释为用布或者其他材料做成的遮蔽用的东西,和“账号”所表达的概念完全无关。 值得注意的是,《现代汉语词典》、《新华字典》等对“帐”这个字有解释称其“旧同‘帐’”,但这仅仅是指“帐”和“账”在过去一段时间内一致,现如今已经独立使用。

2019-08-04 · 1 min · 7 words · -

比特, 字节, 字, bit, byte, word

比特, 字节, 字, bit, byte, word 位 (bit) 来自英文bit,音译为"比特",表示二进制位。位是计算机内部数据储存的最小单位,11010100是一个8位二进制数。一个二进制位只可以表示0和1两种状态 (2^1. ;两个二进制位可以表示00、01、10、11四种(2^2) 状态;三位二进制数可表示八种状态 (2^3) … 字节 (byte) 字节来自英文Byte,习惯上用大写的"B"表示。 字节是计算机中数据处理的基本单位。计算机中以字节为单位存储和解释信息,规定一个字节由八个二进制位构成,即1个字节等于8个比特 (1Byte=8bit) 。八位二进制数最小为00000000,最大为11111111;通常1个字节可以存入一个ASCII码 数据存储是以“字节” (Byte)为单位,数据传输是以大多是以“位” (bit,又名“比特”)为单位,一个位就代表一个0或1 (即二进制),每8个位 (bit,简写为b)组成一个字节 (Byte,简写为B),是最小一级的信息单位。 还可以从以下几个方面来理解: 1.字节(Byte)是电脑中表示信息含义的最小单位,因为在通常情况下一个ACSII码就是一个字节的空间来存放。而事实上电脑中还有比字节更小的单位,因为一个字节是由八个二进制位组成的,换一句话说,每个二进制位所占的空间才是电脑中最小的单位,我们把它称为位,也称比特 (bit)。由此可见,一个字节等于八个位。人们之所以把字节称为电脑中表示信息含义的最小单位,表示最基本的字符,是因为一个位并不能表示我们现实生活中的一个相对完整的信息。另外,内存中运算的最小存储单位是字节,位运算也是在一个字节的存储单位的基础上进行的,所以存储的最小单位可以理解为字节。 https://cloud.tencent.com/developer/article/1353743 字长 CPU在单位时间内(同一时间)能一次处理的二进制数的位数 字 word 计算机进行数据处理时,一次存取、加工和传送的数据长度称为字 (word) 。一个字通常由一个或多个 (一般是字节的整数位) 字节构成。例如286微机的字由2个字节组成,它的字长为16;486微机的字由4个字节组成,它的字长为32位机。 计算机的字长决定了其CPU一次操作处理实际位数的多少,由此可见计算机的字长越大,其性能越优越。 8位的CPU字长为8位,一个字等于一个字节,一次只能处理一个字节,而32位的CPU字长为32位,一个字等于4个字节,一次就能处理4个字节,同理字长为64位的CPU一次可以处理8个字节,一个字等于8个字节。 另一种说法 字 在计算机中,一串数码作为一个整体来处理或运算的,称为一个计算机字,简称字。 字通常分为若干个字节(每个字节一般是8位)。在存储器中,通常每个单元存储一个字,因此每个字都是可以寻址的。字的长度用位数来表示。 在计算机的运算器、控制器中,通常都是以字为单位进行传送的。字出现在不问的地址其含义是不相同。例如,送往控制器去的字是指令,而送往运算器去的字就是一个数。 在计算机中作为一个整体被存取、传送、处理的二进制数字符串叫做一个字或单元,每个字中二进制位数的长度,称为字长。一个字由若干个字节组成,不同的计算机系统的字长是不同的,常见的有8位、16位、32位、64位等,字长越长,计算机一次处理的信息位就越多,精度就越高,字长是计算机性能的一个重要指标。目前主流计算机都是64位机。 注意字与字长的区别,字是单位,而字长是指标, 指标需要用单位去衡量。 正象生活中重量与公斤的关系,公斤是单位,重量是指标,重量需要用公斤加以衡量。 字长 计算机的每个字所包含的位数称为字长。 根据计算机的不同,字长有固定的和可变的两种。固定字长,即字长度不论什么情况都是固定不变的;可变字长,则在一定范围内,其长度是可变的。 计算的字长是指它一次可处理的二进制数字的数目。计算机处理数据的速率,自然和它一次能加工的位数以及进行运算的快慢有关。如果一台计算机的字长是另一台计算机的两倍,即使两台计算机的速度相同,在相同的时间内,前者能做的工作是后者的两倍。 字长是衡量计算机性能的一个重要因素。 字节 字节是指一小组相邻的二进制数码。通常是8位作为一个字节。它是构成信息的一个小单位,并作为一个整体来参加操作,比字小,是构成字的单位。 在微型计算机中,通常用多少字节来表示存储器的存储容量。 字块 在信息处理中,一群字作为一个单元来处理的称为"字块".也称"字组"。例如,储存于磁鼓的一个磁道上的字群就称为一个字块。在磁带上通常每120个字符就间隔一个字块际志,也称为一个字块。块与块之间一般留1.27―2.54厘米(1/2一1英寸)的间隔。在大容量存储中,信息都是以字块为单位而存入的,因此只有字块才是可选址的。目前,在高速绥冲技术中也引入了"字块"的概念。 B = 8 bit KB,2^10: 1024 BYTE. MB,2 的 20 次方 : 1048576 BYTE, 或 1024 KB. GB,2 的 30 次方 : 1073741824 BYTE, 或 1024 MB. TB,2 的 40 次方 : 1099511627776 BYTE, 或 1024 GB. PB,2 的 50 次方 : 1125899906842624 BYTE, 或 1024 TB. EB,2 的 60 次方 : 1152921504606846976 BYTE, 或 1024 PB. ZB,2 的 70 次方 : 1024 EB. YB,2 的 80 次方 : 1024 ZB. ...

2019-07-21 · 1 min · 139 words · -

clonezill

clonezill download clonezill iso from https://clonezilla.org/downloads.php install balenaEtcher dd bs=1M conv=fdatasync if=./clonezilla-live-3.2.0-5-amd64.iso of=/dev/sdx start clonezilla device-image nfs_server dhcp nfs4 192.168.50.227 /backup_xxxx/ beginner save parts clonezill https://clonezilla.org/ https://clonezilla.org/liveusb.php#macos-setup Download the Clonezilla Live iso file. Insert a USB flash drive on the Mac machine. Erase it using the standard Mac Disk Utility (exFAT works fine). Download balenaEtcher for macOS, then follow its document to burn the image to the USB flash drive. Eject the USB drive. Thanks to Hans Palm for providing this info. ...

2019-03-15 · 1 min · 141 words · -

base64, base64 URL, base16, base32

Base64 base64是一种将二进制的01序列转化成ASCII字符的编码方法。编码后的文本或者二进制消息, 就可以运用SMTP等只支持ASCII字符的协议传送了. Base64 一般被认为会平均增加 33% 的报文长度, 而且经过编码的消息对于人类来说是不可读的。 Base64是一种基于 64 个可打印字符来表示二进制数据的表示方法。由于 2 的 6 次方等于 64, 所以每 6 个比特为一个单元, 对应某个可打印字符。三个字节有 24 个比特, 对应于 4 个 base64 单元, 即 3 个字节需要用 4 个可打印字符来表示。它可用来作为电子邮件的传输编码。在Base64中的可打印字符包括字母A-Z、a-z、数字0-9, 这样共有62个字符,此外两个可打印符号在不同的系统中而不同。一些如uuencode的其他编码方法, 和之后binhex的版本使用不同的64字符集来代表6个二进制数字, 但是它们不叫Base64. Base64常用于在通常处理文本数据的场合,表示、传输、存储一些二进制数据。包括 MIME 的 email, email via MIME, 在XML中存储复杂数据. linux 命令行 base64 编码/解码, base64 encode/decode # encode base64 sample.txt > encodedData.txt cat encodedData.txt # decode echo eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9 |base64 -d 标准 base64, StdEncoding A-Z, a-z, 0-9, /, +, = (pad/填充) Base64 URL A-Z, a-z, 0-9, _, - Base64URL 采用了和 Base64 一样的算法作为主要标准,在以下几个方面做了稍许调整: ...

2018-11-12 · 4 min · 758 words · -

ForkJoin

ForkJoin ForkJoinPool 适合执行计算密集型且可进行拆分任务并汇总结果(类似MapReduce)的任务,执行这种任务可以充分利用多核处理器优势提高任务处理速度,实际上 ForkJoinPool 内部的工作窃取队列的高性能 (远高于普通线程池的BlockingQueue) 也决定其适用于执行大量的简短的小任务。 ———————————————— 版权声明:本文为CSDN博主「heng_zou」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/heng_zou/article/details/118193846 什么是 Fork/Join 框架 Fork/Join 框架是 Java7 提供的一个用于并行执行任务的框架(Fork/Join Framework), 是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架。 我们再通过 Fork 和 Join 这两个单词来理解下 Fork/Join 框架,Fork 就是把一个大任务切分为若干子任务并行的执行,Join 就是合并这些子任务的执行结果,最后得到这个大任务的结果。比如计算 1+ 2+… + 10000,可以分割成 10 个子任务,每个子任务分别对 1000 个数进行求和,最终汇总这 10 个子任务的结果。 ForkJoinPool http://blog.dyngr.com/blog/2016/09/15/java-forkjoinpool-internals/ ForkJoinPool 不是为了替代 ExecutorService, 而是它的补充, 在某些应用场景下性能比 ExecutorService 更好。 (见 Java Tip: When to use ForkJoinPool vs ExecutorService ) https://www.infoworld.com/article/2078440/java-tip-when-to-use-forkjoinpool-vs-executorservice.html ForkJoinPool 主要用于实现"分而治之"的算法, 特别是分治之后递归调用的函数, 例如 quick sort 等。 [[quick-sort]] ForkJoinPool 最适合的是计算密集型的任务, 如果存在 I/O, 线程间同步, sleep() 等会造成线程长时间阻塞的情况时, 最好配合使用 ManagedBlocker ...

2017-06-22 · 4 min · 721 words · -

程序,进程, 线程, 内核线程, 轻量级进程, 用户线程

程序,进程, 线程, 内核线程, 轻量级进程, 用户线程 程序 程序用于描述进程要完成的功能,是控制进程执行的指令集; 数据集合是程序在执行时所需要的数据和工作区; 程序控制块(Program Control Block,简称PCB),包含进程的描述信息和控制信息,是进程存在的唯一标志。 进程 动态性:进程是程序的一次执行过程,是临时的,有生命期的,是动态产生,动态消亡的; 并发性:任何进程都可以同其他进程一起并发执行; 独立性:进程是系统进行资源分配和调度的一个独立单位; 结构性:进程由程序、数据和进程控制块三部分组成。 Linux进程类别 虽然我们在区分Linux进程类别, 但是我还是想说Linux下只有一种类型的进程,那就是task_struct,当然我也想说linux其实也没有线程的概念, 只是将那些与其他进程共享资源的进程称之为线程。 一个进程由于其运行空间的不同, 从而有内核线程和用户进程的区分, 内核线程运行在内核空间, 之所以称之为线程是因为它没有虚拟地址空间, 只能访问内核的代码和数据, 而用户进程则运行在用户空间, 但是可以通过中断, 系统调用等方式从用户态陷入内核态。 用户进程运行在用户空间上, 而一些通过共享资源实现的一组进程我们称之为线程组, Linux下内核其实本质上没有线程的概念, Linux下线程其实上是与其他进程共享某些资源的进程而已。但是我们习惯上还是称他们为线程或者轻量级进程 因此, Linux上进程分3种,内核线程 (或者叫核心进程)、用户进程、用户线程, 当然如果更严谨的,你也可以认为用户进程和用户线程都是用户进程。 关于轻量级进程这个概念, 其实并不等价于线程 不同的操作系统中依据其实现的不同, 轻量级进程其实是一个不一样的概念 内核进程 内核中没有进程的概念 不存在的概念, 内核空间的进程共享内核空间的内存, 没有独立的地址空间, 不能算是进程, 所以没有内核进程这个概念. https://www.coder.work/article/6802420 内核中没有进程的概念,所以你的问题没有意义。 Linux 内核可以并且确实创建了完全在内核上下文中运行的线程,但是所有这些线程都运行在相同的地址空间中。尽管相关线程通常具有相关名称,但没有按 PID 对类似线程进行分组。 如果多个内核线程正在处理同一任务或以其他方式共享数据,则它们需要通过锁定或其他并发算法来协调对该数据的访问。当然,内核中不提供 pthreads API,但是可以使用内核互斥锁、等待队列等来获得与 pthread 互斥锁、条件变量等相同的功能。 将这些执行上下文称为“内核线程”是一个相当不错的名字,因为它们非常类似于用户空间进程中的多个线程。它们都共享(内核的)地址空间,但有自己的执行上下文(堆栈、程序计数器等),并且每个都被独立调度并并行运行。另一方面,内核实际上实现了所有漂亮的 POSIX API 抽象(在用户空间中的 C 库的帮助下),因此在该实现的内部我们没有完整的抽象。 内核线程 内核线程一般特指由内核自己启动的只运行在内核态的一些服务进程. 只运行在内核态 ps命令输出里名字带中括号 内核线程也可以叫内核任务,它们周期性地执行例如,磁盘高速缓存的刷新,网络连接的维护,页面的换入换出等等。 内核线程执行的是内核中的函数,而普通进程只有通过系统调用才能执行内核中的函数。 内核线程只运行在内核态,而普通进程既可以运行在用户态,也可以运行在内核态。 因为内核线程指只运行在内核态,因此,它只能使用大于PAGE_OFFSET (3G)的地址空间。另一方面,不管在用户态还是内核态,普通进程可以使用4GB的地址空间。 内核线程是由kernel_thread( )函数在内核态下创建的 内核线程和用户进程的区分, 内核线程运行在内核空间, 之所以称之为线程是因为它没有虚拟地址空间 内核线程就是内核的分身,一个分身可以处理一件特定事情。这在处理异步事件如异步IO时特别有用。内核线程的使用是廉价的,唯一使用的资源就是内核栈和上下文切换时保存寄存器的空间。支持多线程的内核叫做多线程内核(Multi-Threads kernel )。 ...

2017-06-02 · 13 min · 2746 words · -

CLH队列,CLH锁

CLH队列,CLH锁 CLH的发明人是: Craig,Landin and Hagersten。 CLH锁即 Craig, Landin, and Hagersten (CLH) locks CLH锁是一个自旋锁,能确保无饥饿性,提供先来先服务的公平性。 CLH锁是一种基于链表的可扩展、高性能、公平的自旋锁,申请线程只在本地变量上自旋,它不断轮询前驱的状态,如果发现前驱释放了锁就结束自旋。 CLH算法实现 CLH队列锁表示为QNode对象的链表,QNode中含有一个locked字段,该字段若为true表示该线程需要获取锁,且不释放锁,为false表示线程释放了锁。结点之间是通过隐形的链表相连,之所以叫隐形的链表是因为这些结点之间没有明显的next指针,每个线程通过一个线程局部变量pred指向其前驱,线程通过检测前驱结点的locked域来判断是否轮到自己。如果该域为true,则前驱线程要么已经获得锁要么正在等待锁;如果该域为false,则前驱进程已释放锁,轮到自己了。正常情况下,队列链中只有一个结点的locked域为false。CLHLock上还有一个尾指针,始终指向队列的最后一个结点。 当一个线程调用lock()方法想获得锁时,将自己的locked域置为true,表示该线程不准备释放锁,然后并将自己的结点加入到队列链尾部。最后就是在前驱的locked域上旋转,等待前驱释放锁。当这个线程调用unlock()方法要释放锁时,线程要将自己的locked域置为false,表示已经释放锁,然后将前驱结点作为自己的新结点以便日后访问。 //clh 1 class ClhSpinLock { private final ThreadLocal<Node> prev; private final ThreadLocal<Node> node; private final AtomicReference<Node> tail = new AtomicReference<Node>(new Node()); public ClhSpinLock() { this.node = new ThreadLocal<Node>() { protected Node initialValue() { return new Node(); } }; this.prev = new ThreadLocal<Node>() { protected Node initialValue() { return null; } }; } public void lock() { final Node node = this.node.get(); node.locked = true; // 一个CAS操作即可将当前线程对应的节点加入到队列中, // 并且同时获得了前继节点的引用,然后就是等待前继释放锁 Node pred = this.tail.getAndSet(node); this.prev.set(pred); while (pred.locked) {// 进入自旋 } } public void unlock() { final Node node = this.node.get(); node.locked = false;// 改变状态,让后续线程结束自旋 this.node.set(this.prev.get()); } private static class Node { private volatile boolean locked; } } //clh 2 import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; public class CLHLock { public static class CLHNode { private volatile boolean isLocked = true; // 默认是在等待锁 } @SuppressWarnings("unused" ) private volatile CLHNode tail ; private static final AtomicReferenceFieldUpdater<CLHLock, CLHNode> UPDATER = AtomicReferenceFieldUpdater .newUpdater(CLHLock.class, CLHNode .class , "tail" ); public void lock(CLHNode currentThread) { CLHNode preNode = UPDATER.getAndSet( this, currentThread); if(preNode != null) {//已有线程占用了锁,进入自旋 while(preNode.isLocked ) { } } } public void unlock(CLHNode currentThread) { // 如果队列里只有当前线程,则释放对当前线程的引用 (for GC) 。 if (!UPDATER.compareAndSet(this, currentThread, null)) { // 还有后续线程 currentThread.isLocked = false ;// 改变状态,让后续线程结束自旋 } } } NUMA与SMP SMP ( Symmetric Multi-Processor ) , 对称多处理器结构 对称多处理器结构,指服务器中多个CPU对称工作,每个CPU访问内存地址所需时间相同。其主要特征是共享,包含对CPU,内存,I/O等进行共享。SMP的优点是能够保证内存一致性,缺点是这些共享的资源很可能成为性能瓶颈,随着CPU数量的增加,每个CPU都要访问相同的内存资源,可能导致内存访问冲突,可能会导致CPU资源的浪费。常用的PC机就属于这种。 ...

2017-05-17 · 2 min · 230 words · -

语义化版本, semantic versioning

语义化版本, semantic versioning 版本号格式为v<major>.<minor>.<patch>,如v1.2.3。当有不兼容的改变时,需要增加 major 版本号,如v2.1.0。 MAJOR.MINOR.PATCH 版本格式: 主版本号.次版本号.修订号,版本号递增规则如下: 主版本号: 当你做了不兼容的API 修改, 次版本号: 当你做了向下兼容的功能性新增, 修订号: 当你做了向下兼容的问题修正。 先行版本号及版本编译信息可以加到"主版本号.次版本号.修订号"的后面,作为延伸。 semantic versioning https://semver.org/lang/zh-CN/

2015-08-13 · 1 min · 20 words · -

CPU

CPU CPU执行过的指令都遵循以下的流程: CPU首先依据指令指针取得(Fetch)将要执行的指令在代码段的地址,接下来解码(Decode)地址上的指令。解码之后,会进入真正的执行(Execute)阶段,之后会是"写回"(Write Back)阶段,将处理的最终结果写回内存或寄存器中,并更新指令指针寄存器指向下一条指令。 1982年,处理器中引入了指令缓存。 1989年,i486处理器引入了五级流水线。 1995年Intel发布Pentium Pro处理器时,加入了乱序执行核心(Out-of-order core, OOO core)。 乱序执行也并不一定100%达到顺序执行代码的效果。有些时候确实需要引入内存屏障来确保执行的先后顺序。 http://www.infoq.com/cn/articles/x86-high-performance-programming-pipeline?utm_campaign=infoq_content&utm_source=infoq&utm_medium=feed&utm_term=global 乱序执行 乱序执行 vs 顺序提交 我们知道,在cpu中为了能够让指令的执行尽可能地并行起来,从而发明了流水线技术。但是如果两条指令的前后存在依赖关系,比如数据依赖,控制依赖等,此时后一条语句就必需等到前一条指令完成后,才能开始。 cpu为了提高流水线的运行效率,会做出比如: 1)对无依赖的前后指令做适当的乱序和调度;2)对控制依赖的指令做分支预测;3)对读取内存等的耗时操作,做提前预读;等等。以上总总,都会导致指令乱序的可能。 指令在cpu核内部确实是乱序执行和调度的,但是它们对外表现却是顺序提交的。 Store Buffer https://zhuanlan.zhihu.com/p/141655129 store buffer是什么 在之前的文章介绍中,我们了解到每个CPU都会有自己私有L1 Cache。从我了解的资料来说,L1 Cache命中的情况下,访问数据一般需要2个指令周期。而且当CPU遭遇写数据cache未命中时,内存访问延迟增加很多。硬件工程师为了追求极致的性能,在CPU和L1 Cache之间又加入一级缓存,我们称之为store buffer。store buffer和L1 Cache还有点区别,store buffer只缓存CPU的写操作。store buffer访问一般只需要1个指令周期,这在一定程度上降低了内存写延迟。不管cache是否命中,CPU都是将数据写入store buffer。store buffer负责后续以FIFO次序写入L1 Cache。store buffer大小一般只有几十个字节。大小和L1 Cache相比,确实是小巫见大巫了。 Invalid Queue 超线程 超线程技术就是利用特殊的硬件指令,把一个物理芯片模拟成两个逻辑处理核心,让单个处理器都能使用线程级并行计算,进而兼容多线程操作系统和软件,减少了CPU的闲置时间,提高的CPU的运行效率。这种超线程技术(如双核四线程)由处理器硬件的决定,同时也需要操作系统的支持才能在计算机中表现出来 https://www.cnblogs.com/Survivalist/p/11527949.html ‘查CPU, 核心数’ lscpu cat /proc/cpuinfo |grep name # 总核数 = 物理CPU个数 X 每颗物理CPU的核数 # 总逻辑CPU数 = 物理CPU个数 X 每颗物理CPU的核数 X 超线程数 # 查看物理CPU个数 cat /proc/cpuinfo| grep "physical id"| sort| uniq| wc -l # 查看每个物理CPU中core的个数(即核数) cat /proc/cpuinfo| grep "cpu cores"| uniq # 查看逻辑CPU的个数 cat /proc/cpuinfo| grep "processor"| wc -l grep 'model name' /proc/cpuinfo | wc -l Intel CPU Cascade Lake 第二代智能英特尔® 至强® 可扩展处理器,前身为 Cascade Lake,搭载英特尔® C620 系列芯片组(前身为 Purley refresh), 配有内置英特尔® 深度学习加速(英特尔® DL Boost),可针对人工智能工作负载提供高性能推断和视觉。它可整合多元化的物联网工作负载、处理海量数据集并支持接近实时的交易。现在,借助英特尔® Distribution of OpenVINO™ Toolkit 等已进行 CPU 优化的软件工具套件和框架,您可以获得更好的内置深度学习功能,加快部署速度,并降低总拥有成本 (TCO)。 ...

2015-06-30 · 1 min · 140 words · -

Java LockSupport, park unpark

“Java LockSupport, park unpark” LockSupport.park 调用后,线程状态是 WAITING LockSupport.park() 和 unpark(),与object.wait()和notify()的区别? 面向的主体不一样。LockSuport主要是针对Thread进行阻塞处理,可以指定阻塞队列的目标对象,每次可以指定具体的线程唤醒。Object.wait()是以对象为纬度,阻塞当前的线程和唤醒单个(随机)或者所有线程。 实现机制不同。虽然LockSuport可以指定monitor的object对象,但和object.wait(),两者的阻塞队列并不交叉。object.notifyAll()不能唤醒LockSupport的阻塞Thread. 阻塞和唤醒是对于线程来说的,LockSupport的park/unpark更符合这个语义,以"线程"作为方法的参数, 语义更清晰,使用起来也更方便。而wait/notify的实现使得"线程"的阻塞/唤醒对线程本身来说是被动的,要准确的控制哪个线程、什么时候阻塞/唤醒很困难, 要不随机唤醒一个线程 (notify) 要不唤醒所有的 (notifyAll) 。 LockSupport.park() (以下简称 park() ) 可能是 java.util.concurrent 包最重要的函数了,因为很多 java.util.concurrent 中的功能类都是利用 park() 来实现它们各自的阻塞。在 park() 之前 Java 也有过类似功能的函数——suspend(),相应的唤醒函数是 resume()。不过 suspend() 有个严重的问题是父线程有可能在调用 suspend() 之前子线程已经调用了 resume(),那么这个 resume() 并不会解除在它之后的 suspend(),因此父线程就会陷入永久的等待中。相比于 suspend(),park() 可以在以下几种情况解除线程的等待状态: 在 park() 前曾经调用过该线程的 unpark() 进而获得了一次"继续执行的权利",此时调用 park() 会立即返回,并且消耗掉相应的"继续执行的权利"。 在 park() 进入等待状态之后,有其他线程以该线程为目标调用 unpark()。 在 park() 进入等待状态之后,有其他线程以该线程为目标调用 interrupt()。 在 park() 进入等待状态之后,有可能会没有理由地解除等待状态。 (这也是为什么推荐在循环体中调用 park(),并在返回之后再次检查条件是否满足。) 其中第一条就可以保证 park() 不会遇到和 suspend() 同样的问题。 最简单的使用 park() 是这样的 ...

2015-05-25 · 5 min · 1023 words · -