Golang中检测 Data Race

Data Race概念 Data Race (数据竞争),是并发系统中最常见和最难调试的错误类型之一,当两个线程(协程)同时访问同一个变量并且至少其中一次访问是写入时,就会发生 Data Race 。Java 内存模型(JMM)定义了明确的 Happens-Before 规则和提供 Volatile 机制来初步保证程序的执行顺序,Go内存模型(GMM)同样也有相同的规则定义,此外 Golang 还提供了一种检测 Data Race 的机制,帮助开发者更好的分析并发代码的正确性。 go在1.1版本时支持data race 检测。 Data Race 和 Race Condition 的区别: 这两个概念非常容易混淆,stackoverflow上也有讨论这个问题,其实我认为这篇文章很好的区别了它们,所以我进行了翻译。 检测 Data Race 使用 -race 参数可以用来检测这种竞态: 1 2 3 4 $ go test -race mypkg // to test the package $ go run -race mysrc.go // to run the source file $ go build -race mycmd // to build the command $ go install -race mypkg // to install the package 1 2 3 4 5 6 7 8 9 10 11 12 13 func main() { c := make(chan int) m := make(map[string]int) go func() { m["a"] = 1 // 访问map冲突 c <- 1 }() m["a"] = 2 // 访问map冲突 <-c for k, v := range m { fmt.Printf("key = %v, val = %v\n", k, v) } } 示例程序中,两个 goroutine 同时读写 map 存在竞态,可能造成数据不正确的情况,但是错误难以发现,通过执行时添加 -race 选择可以检测: ...

2024-06-20 · 5 min · 1042 words · Hank

Data Race vs Race Condition

原文地址: https://blog.regehr.org/archives/490, 翻译并略作改动。 竞态条件(race Condition)是当事件的时间或顺序影响程序的正确性时发生的缺陷。一般来说,需要某种外部计时或排序非确定性来产生竞态条件;典型的例子有上下文切换、操作系统信号、多处理器上的内存操作和硬件 中断。 当程序中有两次内存访问时,就会发生数据竞争(Data Race): 目标为同一内存位置 由两个线程同时执行 不是读取操作 不是同步操作 上边这个定义来自微软研究院的 Sebastian Burckhardt。该定义的两个方面需要注意: “同时”意味着没有像锁这样的东西强制一个操作在另一个操作之前或之后发生。 “不是同步操作”是指程序可能包含特殊的内存操作,例如用于实现锁的操作,这些操作本身并不同步。 在实践中,它们两者存在相当大的重合:许多 Race Condition 是由 Data Race 引起的,并且许多 Data Race 导致 Race Condition。另一方面,两者也可以相互独立,可能产生没有 Data Race 的 Race Condition,也可能产生没有 Race Condition 的 Data Race。 让我们从一个在两个银行账户之间转移资金的简单函数开始: transfer1 (amount, account_from, account_to) { if (account_from.balance < amount) return NOPE; account_to.balance += amount; account_from.balance -= amount; return YEP; } 当然,这并不是银行真正转移资金的方式,但这个例子非常有用。我们知道,账户余额应该是非负的,并且转移之后不能凭空创造(多出)或损失(丢失)金钱。当在没有外部同步的情况下从多个线程调用时,该函数会产生 Data Race(多个线程可以同时尝试更新帐户余额)和 Race Condition(在并行上下文中它将创造或损失金钱)。 我们可以尝试这样修复它: transfer2 (amount, account_from, account_to) { atomic { bal = account_from.balance; } if (bal < amount) return NOPE; atomic { account_to.balance += amount; } atomic { account_from.balance -= amount; } return YEP; } 这里的“atomic”(原子性)是由语言运行时实现的,也许简单地通过在原子块开始时获取线程互斥体(Mutex)并在结束时释放它,也许使用某种事务(Transaction),或者也许通过禁用中断 —— 出于示例的目的,只要 atomic 块内的代码以原子方式执行就能解决竞争问题。 ...

2024-06-18 · 2 min · 235 words · Hank

Golang中singleflight的实现原理

我们知道了singleflight的用法,使用 singleflight 我们可以抑制多个请求,极大地节约带宽、增加系统吞吐量、提升性能。那么,singleflight 底层是如何实现的呢?本文我们来分析一番。 整体结构 singleflight 的核心是将同时段的多个请求抑制,只有一个请求能够真正请求资源,其他请求都阻塞。上一篇提到,singleflight的公开api仅包括: Group 对象: 它表示处理"相同数据"的一系列工作,在这里“重复请求”将会被抑制 Result 对象: 表示执行真正业务逻辑的结果对象 Do 方法: 执行请求抑制 DoChan 方法: 与Do相同,只是结果返回 <-chan Result 从这些api我们大致可以知道,调用 Do 或者 DoChan 方法就是我们所述的核心“请求”,可以猜测: 对于同一个 key,首先调用的会执行真正的逻辑,方法返回之前的后续所有相同的 key 调用都会阻塞,当第一个请求返回后,阻塞的这些调用就直接使用其返回值作为自己的返回值了。 顺着上述猜想的逻辑,我们看看singleflight的源码实现。代码不多,算上注释一共就200来行,我们来一一分析。 Group struct 首先看看 Group 的代码: 1 2 3 4 type Group struct { mu sync.Mutex // protects m m map[string]*call // lazily initialized } Group 表示处理相同数据的一系列工作,这些工作存储到一个 map[string]*call 的结构中,为了保证并发安全,Group 内部持有 sync.Mutex 锁用来保护这个 map 的读写。 Group 有一个非常重要的两个方法 Do 和 DoChan, 在上一篇已经介绍过了。 再来回顾一下 Do 方法的定义: 1 func (g *Group) Do(key string, fn func() (interface{}, error)) (v interface{}, err error, shared bool) 已经介绍过,这里再详细看看这些参数: ...

2024-06-10 · 4 min · 791 words · Hank

使用 Let‘S Encrypt 为您的网站申请免费证书

网站的https证书过期了,一直使用阿里云的免费ssl证书,但是现在阿里云调整了策略,证书有效期从1年缩短到3个月了,所以我决定放弃阿里云转而使用 Let’s Encrypt 申请免费证书。 简介 Let’s Encrypt 是一家免费、开放、自动化的公益性证书颁发机构(CA), 由互联网安全研究组(ISRG)运作,详细介绍可以看这里。 申请证书 按照官方文档,对于没有命令行访问权限的网站,比如wordpress、cPanel等,可以通过控制台设置或者申请后手动上传证书;而我的网站服务器托管在阿里云,拥有命令行访问权限,所以直接使用官方推荐的 Certbot ACME 客户端来管理证书,这里详细记录下步骤。 CertBot 文档有详细的安装步骤,我的 ubuntu14 上的步骤如下: 安装 snapd snap 是linux的应用程序包,可以从其自身的snap store中安装、管理软件,有点类似包管理器如 apt、yum,但是不依赖linux发行版,安全、跨平台且无依赖;而 snapd 则是一个自动管理和维护 snap 的后台服务,它们之间的区别请参阅官方文档。 Ubuntu 16.04LTS版及之上的版本已经集成了snapd,无需再安装了,而我的是 14,所以需要手动安装。 sudo apt update sudo apt install snapd 测试是否安装成功,可以安装官方的 hello-world 程序: $ snap install hello-world 2024-06-09T16:15:39+08:00 INFO Waiting for automatic snapd restart... hello-world 6.4 from Canonical✓ installed 执行并成功输出信息表示安装成功: $ hello-world Hello World! 删除 certbot-auto 和任何 Certbot OS 软件包 如果您使用操作系统包管理器(如 apt 、 dnf 或 yum 安装了任何 Certbot 包,则应在安装 Certbot snap 之前删除它们,以确保运行命令 certbot 时,将使用 snap,而不是从操作系统包管理器进行安装。执行此操作的确切命令取决于您的操作系统,但常见的示例是 sudo apt-get remove certbot 、 sudo dnf remove certbot 或 sudo yum remove certbot 。 ...

2024-06-09 · 2 min · 374 words · Hank

Golang中singleflight的用法

在开发过程中,尤其是web server,有时候我们需要并发的请求数据,对于某些数据,同时发起的多个请求其实拿到的数据都是相同的,如果不处理这类请求,那么每个请求都会获取一遍数据,这无疑是对资源的浪费。比如要从数据库查询相同 id 的一条数据,并发的多个请求都会执行一次 sql 语句,增加了数据库的压力。 有没有一种方案,当多个请求同时发起后,只有第一个请求去获取数据,在它返回之前,其他请求都各自阻塞等待直到真正执行数据获取的请求返回后,直接拿它的结果?我们把这种将同时发起的多个请求转为一个请求去执行真正业务逻辑的情况称为“请求抑制”。在 Go 中,singleflight 就是用来处理请求抑制的。 简介 singleflight 包全路径为 golang.org/x/sync/singleflight, 目前版本是 v0.7.0。 前边提到,singleflight 用于抑制同一时间获取相同数据的重复请求。当存在多个重复请求时,singleflight 保证只有一个请求能执行,其他请求阻塞,直到前边的请求返回后直接将其返回值共享(shared)给阻塞的这些请求。 在使用之前,首先要理解何为"重复请求",如何区分"相同数据"。 相同数据:指并发下当前时间段多个请求获取的数据是完全相同的,比如获取全局的配置、查询天气数据等。 重复请求:指处理相同数据时,在一个请求从发起到返回之前这段时间,又有其他多个请求发起,那么这些请求就是重复请求。 理解了这两点,现在我们来看看 singleflight 的用法。 用法 singleflight 整体设计比较简单,公开的 api包括: Group 对象: 它表示处理"相同数据"的一系列工作,在这里“重复请求”将会被抑制 Result 对象: 表示执行真正业务逻辑的结果对象 Do 方法: 执行请求抑制,后文详述 DoChan 方法: 与Do相同,只是结果返回 <-chan Result Do 方法 Do 方法表示执行请求抑制,其定义如下: 1 2 3 func (g *Group) Do(key string, fn func() (interface{}, error)) (v interface{}, err error, shared bool) { // ... } 首先,它需要在 singleflight.Group 实例下执行,用于抑制这一组请求。 两个参数: key: 这个参数用来指示"相同数据",也就是说相同的key代表相同数据,区分相同数据是正确使用 singleflight 的关键。 fn: 真正执行业务逻辑的方法,该方法返回一个任意对象 interface{} 和一个 error 返回结果: v: 就是 fn 方法返回的第一个值 err: fn 方法返回的第二值 error shared: 当抑制了其他请求,返回 true, 也就是说将真正执行业务逻辑的请求返回结果共享给其他请求后,该值为 true, 否则为 false DoChan 方法 DoChan 方法与 Do 类似,只是将结果封装为 Result 并返回它的 chan: ...

2024-05-18 · 3 min · 491 words · Hank

自由与梦想

今天终于读完了《我在北京送快递》一书,这本15万字的书前前后后读完居然花了两个多月的时间——习惯使然,我读什么书都喜欢字斟句酌,这一点让我有些苦恼:一方面是因为进度非常缓慢,想象一下,读一本人物传记我还在拿着笔一边阅读一边给不认识的字词标注笔记……,另一方面,由于阅读的周期很长,导致容易半途而废,可能前半部分阅读的很仔细,后半部分就走马观花甚至放弃了。不过这本书我还算比较认真的读完了,我想谈一谈我对“自由”的理解。 作者在尾声的“后记”一章中阐述了自己对“自由”的观点:这些年来自己经常处于一边工作、一边写作的生活状态中,工作时无法全心投入写作,而写作时也无法积极投身工作,作者自己希望的自由是“在高度发展上的个人意识的个人追求和自我实现”。他认为,追求自己热衷的事情,形成与别人不同的自我意识上的区别,那么这种追求是自由的;相反,表面上看似自由,却并没有实现自我价值,只是迫于某种原因而不得不去做,又或者是安于现状,非个人追求或无法实现自我,那么这些都不能算是真正的自由。 作者写到,在迫于生计而不得不付诸大量时间和精力在工作上时,自己便无法专注于自己喜欢的事情——写作,自身也被疲倦、困意、复杂的人际关系所折磨,,身心俱疲,就算挣到了钱但自己并不快乐,这种生活不能算是自由;而之后回到老家,全身心投入写作,不必为生计而担忧,每天作者自己喜欢的事情——阅读、写作、与其他作者交流写作创意等等,自己的阅读量、写作水平都有很大的提升,这在知识的高度上与其他人拉开了差距、形成了区别,自身的追求有了一定的成果,自己也觉得开心、快乐,这样的生活是自由的。 我很喜欢作者的这个观点。简而言之,作者所述的自由,在我看来是“做自己想做的事,走自己想走的路”。 每个人心中都会有梦想,那就是我们最想做的事情,最想走的路,只是多数时候我们为生活所迫东奔西走,仿佛随波逐流的沙粒,被生活磨平了棱角,早就忘记了自己的梦想是什么了。**“人们只有两条路,一条需要用心走,叫做梦想;另一条需要用脚走,叫做现实。”**诚然,现实与梦想仿佛总是相对立的两面。对于大多数人而言,梦想往往是高不可攀的,只能深埋于心,为了生计不得不放弃梦想而从事自己并不喜欢的工作的人不计其数,为追逐梦想而奋斗此生最终获得成功的人寥寥无几。 在人生的道路上,现实与梦想常常交织着,而在这交织之中,自由似乎成了一种奢侈。对许多人而言,梦想是一种遥不可及的幻影,而现实却是残酷的枷锁,将他们紧紧束缚。在这样的环境下,自由似乎只存在于幻想之中。 普通的农民每天的工作就是完成农务,整日与农田、二十四节气打交道,农忙时努力播种、收获,农闲时与朋友们一起打打牌、聊聊天。我相信,他们大多觉得生活本就如此,自给自足也挺好,除非生活实在拮据迫不得已才会跑出去打工赚钱。此时,或许他们早已习惯了这样的生活,早已忘记年轻时候自己想要做的事情了。对于他们而言,无论忙碌或是清闲,可能都不重要,年复一年重复着同样的事情,循规蹈矩地过着自己的生活,这就是最简单的自由和幸福吧。 在车水马龙的城里,白领们的知识水平比普通农民要高出许多,他们终日奔波忙碌,所选择的工作就是自己所追求的梦想吗?我相信大多连“事业”都称不上。他们所做的只是“为老板打工”,赚取自己的劳动报酬罢了,他们的工作心态总是“尽力完成我能力范围内的工作”的得过且过,很少有人会将自己的工作真正的当成一份事业来打拼,因为就算你再努力,那也是为别人赚钱,对自己并没有多大意义。所以,大多数白领会默默地选择平庸,只是在偶尔看到少数人成功之后才能激起心中的激情,也许那只是害怕自己被周围人超越的恐慌,然后疯狂地努力一段时间,随后逐渐发现无论自己多努力似乎都无法提升自己到一个新的高度,慢慢难以坚持最后果断放弃并又再次平庸下去,这就样反反复复,最终一无所成。 当我们回首自己的生活时,是否还记得小时候那个满怀梦想的自己?那个对未来充满信心、对世界充满好奇的自己是否还在内心深处?然而,随着年岁的增长,生活的压力和社会的现实往往会让我们逐渐放弃那些美好的梦想。我们被迫在现实的洪流中奔波,为了生计为了生活,不得不选择一条看似安稳但缺少激情的道路。 梦想的实现谈何容易?马云当初创业时,初创团队“十八罗汉”吃住都其家里,十几人就挤在几十平方的房子里,他们没日没夜的工作,饿了就吃泡面,困了就打地铺小睡一会儿……但是,团队人人都充满着激情,没有一个人轻言放弃,因为他们知道他们现在是在为了梦想而奋斗,奋斗着并奋不顾身。就在阿里巴巴上市的那一天,这十八罗汉人人都身价过亿、功成名就、梦想成真。回望这一路坎坷,我想他们应该是无怨无悔了吧?至少,为了梦想,再苦再累也都是甘之如饴吧。这样的生活,看似处处是桎梏,却总能令人无限神往,这又何尝不是一种自由呢?马云当然属于成功者,这些年义无反顾的追梦,等到如今大获成功之后,可能他才发现原来自己的人生还有其他更想做的事情,所以他开办学校、拍电影、搞演唱会……有钱真任性,但是他有任性的资本,试问此时,为所欲为的马云是否获得了真正的自由呢?答案只有他自己知道,看似风光无限,可能高处不胜寒的寂寞只有他自己懂得了。 当我们审视自己的生活,回顾梦想与现实的交织,或许我们会发现,自由并非遥不可及的幻影,而是一种内心的坚定和勇气。在生活的征途中,或许我们会因为生计而忙碌,会因为社会的压力而迷失,但只要我们保持对梦想的信念,不忘初心,奋力前行,那梦想的光芒就会一直照亮前行的路。或许成功并非是终点,而是一种过程,而真正的自由,其实就是坚持不懈地追寻内心的信仰,敢于实现自己的梦想,敢于选择一条不一样的道路。因为只有在追逐梦想的路上,我们才能找到真正属于自己的自由和幸福。愿我们都能保持对梦想的热爱,勇敢追寻内心的声音,活出真正的自己,活出自由的精彩人生。

2024-04-26 · 1 min · 11 words · Hank

这本区块链爱好者必读作品终于迎来第三版

学习区块链的书籍我推荐这两本,我在这里已经推荐过一次: Mastering Bitcoin Mastering Ethereum 比特币开创了一门新的技术——区块链,而以太坊则在比特币的理念基础上进行再创新和升华,二者是近十多年来区块链技术的代表。因此,学习区块链技术,绝对离不开它们。恰好,这两本书就完全阐述了比特币和以太坊的技术。 这两本书都由同一名作者编写,他是一名有十多年区块链研究经验的爱好者,两本书的传播度非常广,是学习、研究区块链技术的必读作品。 《Mastering Ethereum》翻译为《精通以太坊》,目前该书可以在 github 上免费阅读,地址在这里,翻译版在这里。 《Mastering Bitcoin》中文译作《精通比特币》,本书详细介绍了比特币底层的实现逻辑,目前版本是第二版。但是随着比特币2017、2021两次的重大升级,添加了隔离见证、Taproot、Schnorr签名等重大改进,不论是逻辑上还是技术上都有着很大的学习难度,第二版是在2017升级之前刊印的,现在未免显得有点过时了。如果你想学习比特币的底层知识,可以在这里阅读到本书的中文版。 让人兴奋的是,2023年《Mastering Bitcoin》迎来了它的第三个版本,它由原作者 Andreas Antonopoulos 和另一位区块链爱好者 David Harding 联合编著,在第二版的基础上补全了这几年比特币的多个升级技术。可惜的是,本书目前还没有中文版本,但你可以直接在 github 上阅读英文版,地址在这里。 另外,为了便于阅读,我也fork了本书,编译并搭建了在线阅读网站,打开即可直接阅读,可以选择单页阅读整书也可以分章节阅读。 区块链技术的学习是一个枯燥和费力的过程,但历史的车轮总是向前推进的,技术在进步,坚持下来者方能迎接蜕变。 相关阅读: Bitcoin’s Taproot Upgrade: What You Should Know https://github.com/ajtowns/taproot-review

2024-03-20 · 1 min · 34 words · Hank

ios如何同步obsidian笔记仓库?

对于技术人而言,经常需要记录、整理大量的笔记内容,形成自己的知识库。一款好的笔记软件我认为需要具备以下几个条件: 必须:跨平台,同时支持桌面电脑(Windows,Mac,Linux)和手机(Android,iOS) 必须:支持同步,在多台设备中打开任何一台都能接接着编写笔记 必须:实时存储,就算突然断电、司机也不会丢失已写笔记内容 必须:支持代码高亮,更便于友好地阅读代码 必须:支持 Markdown 格式,快速编写文档必备格式,谁用谁知道 必须:支持多重备份,最好是本地一份、远端一份,首选支持git同步的 可选:支持双向链接,这样笔记与笔记之间就可以形成关联关系,慢慢积累后就形成了自己的知识库 可选:支持笔记导出,比如导出 pdf 等格式,便于分享,如果能一键发布到常见博客如 hexo、wordpress、jekyll 更好 我用过诸多笔记软件,但都存在或多或少的问题,无法满足上述要求,后来一直使用网易的有道笔记,它支持一键保存笔记,不过编辑器实在难用,markdown的图片要自己搭建图床。 阮一峰推荐的笔记软件是 github.dev,可以看看他的这篇文章。不过我更喜欢原生的app软件,我选择 obsidian,因为它免费而且支持上述的大部分需求,尤其是第6点,对于不想将笔记存储保存给笔记软件厂商而言非常好,直接使用git同步。 现在,我基本上都是用 obsidian 来记笔记和写文章,它支持双向链接,很容易形成知识体系,而且具备丰富的插件支持,具体特性可以自己咨询网络。如果对于格式非常多的文章,我也会使用 AsciiDoc 格式来编写,相比于 Markdown 它更加强大,但是语法也更复杂。Obsidian 目前并不支持,非常遗憾。 在使用 Obsidian 时,最大的问题就是手机端的同步。我是用 Github 存储笔记,手机端没有很好的同步方案,官方的同步方式无法满足需求,而且需要付费。我的需求是,macOS、windows、iPhone、iPad 四种设备上需要从 github 同步我的笔记,没有安卓端同步的需要,所以我使用 iSH 这个 app,它开源免费,支持 ios,完美的解决了我的问题。 什么是 iSH 它是一个在 ios 上模拟 Linux 环境的 app,使用的是 alpine linux,在你的 iPhone、iPad上都可以运行并创建 Linux Shell 环境。正好我可以使用它来同步 git 仓库到我的 ios 设备上。 安装 ish app 从 ios 应用商店下载 iSH Shell,注意看名称和logo: 下载好后打开,你就得到了一个 Linux Shell 环境。整体界面与 Shell 一样,这里重点说一下工具栏: ...

2024-01-06 · 2 min · 368 words · Hank

2023年终回顾和2024新年展望

每至年终,总慨时光飞逝,悔不该浪费光阴而不自知。欲提笔总结一年之种种,却感人生多琐事、岁月少芳华,生活不过柴米油盐酱醋茶。常人者,虽一年之大事鲜有,却亦欲略举一二。 2023年,整个国际形势依旧不太妙,俄乌冲突、巴以冲突、缅甸内战,各国都在想法设法为自己牟利,战乱频发,新冠疫情之后本来就衰弱的国际经济形势更是雪上加霜。逐年变暖的气候,给我们的家园带来了严峻的挑战:火灾、洪水、泥石流、地震、台风…… 2023国际十大新闻 2023中国十大新闻 作为一介庶民,本该独善其身,”各人自扫门前雪,莫管他人网上霜“,然人是社会人,践行之何其难也! 博客 这一年,撰写的文章倒是很少。一方面,感觉没什么可写的,可能自己正慢慢变得懒散了;另一方面,每每要提笔写些什么,发现对要写的东西还了解的不够透彻,只好搁浅。新的一年,得试着多见多听多写,多思考。这是本年的文章列表: 又一个大佬辞世,技术人该何去何从? Redis集群中的 CROSSSLOT Keys Error Nginx Bad Gateway和no live upstreams错误分析 依赖注入库wire使用入门 使用cli框架开发CLI程序 (译)Go1.13中处理错误 (译)Go错误是值 (译)Go模块:管理依赖项 (译)Go 中的字符串、字节、符文和字符 (译)数组、切片和字符串 - “append” 原理 技能 2023除了继续 golang 之外,也复习了一下 python3,包括: 阅读了《Python编程 从入门到实践 第2版》,里边基础讲解不是很透彻,实例倒是不错 廖雪峰的Python教程 语言都是互通的,但建议技术人至少掌握一门静态语言(Java、Go、Rust等)和一门动态脚本语言(Python,Ruby等),以便应对不同的场景,比如使用脚本语言快速开发一些小工具。2024年的工作中应该会大量使用 Go 和 Python。 2024打算加强区块链的课程,包括: 北大肖臻区块链课程 精读《精通以太坊》,之前只是泛读了一遍 精读《精通比特币》 读书是充实自我、提升自我最好的方式。 2023年读了几本书: 《Go语言精进之路1》:掌握go语言编程思想的必要书籍,尤其是从 java 转 go,很多套路与 java 不同,参考本书可以快速掌握 go 的一些编程技巧,本书还有下册,准备2024研究 《朱元璋传》:描写朱元璋的一生,讲述其如何从一个被逼进入寺庙混口饭吃的和尚,到建国当皇帝开辟传奇道路。本书内容较多,适合泛读。 《任正非传》:讲述了任正非与华为的故事,想要了解华为、了解任正非的人必读。 《苏东坡传》:林语堂的作品,讲述大才苏东坡传奇的人生故事。读此书除了了解苏轼一生大才却屡遭贬谪、坎坷的一生,更应该学习其豁达、乐观的人生境界。 《乡土中国》:目前正在读,看费孝通用浅显的文字讲述什么是中国乡土社会。 2024年打算读这几本书: 《MySQL技术内幕:InnoDB存储引擎》 《Go语言精进之路2》 《我在北京送快递》,豆瓣评分很高,了解北漂一簇的辛酸历程。 《三体》:中国科幻巅峰作品,电视剧看了没感觉,必须来读书了。 《人性的弱点》 读一本经济学的图书,拒绝做经济小白 我的豆瓣读书:https://book.douban.com/people/hankmo/ 生活 2023年,生活上平平淡淡才是真,没什么起起落落的故事。 作为技术人,总有或这或那的职业病,现在也不想去想这些问题了,快乐生活每一天,因为明天和意外永远不知道哪一个先到,管他作甚?我们要做的,就是努力锻炼身体,保持充足的睡眠,勤于自律,戒骄戒躁! ...

2024-01-02 · 1 min · 77 words · Hank

又一个大佬辞世,技术人该何去何从?

陈皓,网名左耳朵耗子,技术圈亲切地称他为“耗子叔”、“皓哥”,是一位资深技术大咖、骨灰级程序员,前阿里云资深架构师、天猫开发总监、亚马逊高级研发经理、汤森路透基础架构师和高级研发经理,他的博客酷壳每篇文章的阅读量高达数十万,其发布的专栏课程购买量也近20万,可见其影响力惊人! 然而,这位技术大佬在2023年5月13日因心梗而与世长辞,享年47岁。英年早逝,无不令听者痛心、见者流泪! 我很早都在拜读他的文章了,之前他一直在csdn撰写了大量的技术分享类博文,后来又自建博客。其博文范围广泛,涉及架构、技术、管理、生活方方面面,其文风犀利、见解独到、个性鲜明,每每读之均令人深受启发。这位技术传道者除了爱写作、爱分享,而且还一直处于不断学习、提升自己的状态,其生前博客还发表着GoLang、Rust、区块链等等技术的文章,实乃吾辈之楷模! 有段时间没有翻阅老师的博客了,昨天一打开发现老师并没有更新文章,最近的一篇仍然停留在《是微服务架构不香还是云不香?》 翻阅博客之时,偶然看到评论中说老师此时的消息,难以置信,无比痛心,不得不感叹命运多变、世事无常。 作为技术人的我们,用技术改变着这个世界,却无法避免被这个世界所抛弃。一方面,技术人要被公司体制所折磨,公司的利益高于一切,技术只为公司的利益服务,只要有利可图,随时可以要求技术人加班加点干活,而不管需求是否合理、技术是否可达;另一方面,技术人大多是一个隐藏于后台的微小角色,他们不受重视、不被尊重,肩负着强于身体承受能力数倍的工作量,却因为一些技术问题被公司批评,甚至辞退。加班、熬夜、饮食不规律、运动少、职业病、亚健康等等词汇都是贴在每一个技术人身上的标签,年轻时不断地过度消耗着身体,殊不知随着年龄的增长,这些“负债”总是要一点点偿还,甚至是付出生命的代价。 正如陈皓在其痛恨手册中所述:“ 痛恨各种不从研发团队出发,不从团队和项目出发的流程、方法论、咨询师、SQA、流程部门。 痛恨那些为所欲为的,为了自己商业目标牺牲用户利益的中国IT企业。 痛恨中国的C2C式的那种简单的抄袭和复制。 ……” 在这喧杂的社会,我们不得不沉下心来思考:我们会不会是下一个陈皓?我们又该何去何从呢?推荐大家阅读《软技能:代码之外的生存指南》一书,该书从职业规划、自我营销、自我学习提升个人专业技能,到理财、健身、精神来修炼自身体质等多个层面向我们展示了技术人应该如何进行技能、身心双修。 这里大致罗列作者的观点,值得我们深思: 职业:每一位技术人都需要学会管理自己的职业生涯。树立明确的目标,指定可靠、周密的计划,什么时候做什么都经过精心的安排,然后朝着目标努力。 自我营销:你的营销手段决定了你的营销对象是受益还是受损。营销需要人们的关 注,以便让人们关注你,关注你的产品。优秀的营销会将人们的需要或者期待与能够满足此愿望的产品或服务关联起来。“实现价值在先,要求回报在后”。做自己的产品、写博客、发视频,让跟多的人认识你。 学习:“活到老,学到老”。尤其是技术人,在这个飞速变化的世界里,学习的能力是至关重要的。技术人绝不能固步自封,忽视自己的技能发展,否则就会被社会淘汰。 生产力:“外行静坐等待灵感,其他人则唤起激情努力工作”。如何克服拖延症,“做该做的事”,我们需要运用方法、使用工具、培养习惯,提升自己的生产力。 理财:技术人耗费青春拿到较高的薪酬,但是如果不会理财,纵使有万贯家财,最终也可能毁于一旦。 健身:“身体是革命的本钱”,长期的久坐令技术人大多处于亚健康的状态,我们需要健身,只有强健的身体才能更好的发展自己。我认为这是大部分技术人忽略的一项,但它确实重中之重! 精神:我们并不是简单的机器——我们是人类。我们不只是一个与思想相连的躯壳。我们不能只下达指示然后就期望身体能完成这些指令。这个世界存在着另一股很强大的力量,它能带领我们走 上成功之路,把我们推向成功。这种力量为——精神。身体强健了,还要求我们保持良好的精神,乐观、豁达的心态,积极上下的生活态度,都是技术人所必须的。我们不只是只在电脑上敲敲打打的码农,我们更是父母、儿女、丈夫和妻子,积极地融入生活、享受生活吧。 陈皓老师走了,仍然有无数个陈皓前仆后继地为技术而奋斗着。我们执着与技术的同时,别忘了我们仍然是这个社会的一份子,更需要积极、乐观的去享受生活的美好! 谨以此文悼念陈皓老师,一路走好!

2023-12-27 · 1 min · 24 words · Hank

Redis集群中的 CROSSSLOT Keys Error

场景 Redis单节点没有问题,切换到Redis Cluster后业务上某一功能出错: CROSSSLOT Keys in request don't hash to the same slot 出错的代码: 1 2 3 4 5 6 7 8 var ctx = context.TODO() _, err := uq.queueImpl.rc.TxPipelined(ctx, func(pip redis.Pipeliner) error { cmd := pip.LPush(ctx, uq.key, dest...) if cmd.Err() != nil { return cmd.Err() } return pip.SAdd(context.Background(), uq.setkey, dest...).Err() }) 这段代码的逻辑是向 list 中push一条数据,再向一个 set 加入数据,两步操作通过 pipline 在同一个事务中完成。 问题分析 错误的大概意思就是: 请求中跨槽的key没有被hash到相同的hash槽中。通过代码分析,事务中的两次操作的key并不相同,他们没有被hash到同一个hash槽从而出现上述错误。 什么是hash槽 Redis Cluster 规范中的 Key distribution model(key分布模型)说明如下: Redis集群中将key的存储空间划分为16384个slot,整个集群可以支持最大16384个主节点,实际上建议不超过1000个节点。每一个主节点又可以处理16384个 hash slot。整个集群将hash slot分布于不同的节点组成高可用集群,单个节点有多个副本以便在节点故障时将重新平衡节点,达到高可用的目的。关于可用性保证,可以看这里。 如下的公式用于计算key的hash slot: HASH_SLOT = CRC16(key) mod 16384 Redis将相同hash值的slot分布到同一个node下,如下图所示: 图片出自这里 可以看出,hash槽(slot)就是一个整数,通过key计算得来,它的作用就是决定key存储于哪一个节点中。 为什么会出现CROSSSLOT错误 主要原因是应用程序尝试在多个键上运行命令,但操作中涉及的键不在同一个哈希槽中,这会导致集群中不允许的“CROSSSLOT”操作。 比如,使用Set的SUION命令时,如果多个key的 hash slot 不在集群中的同一个node上,则会出现CROSSSLOT错误。 前边场景中,在同一个事务中操作多个key,集群环境下必须要保证这些被操作的key必须被hash到同一个slot,否则同样会抛出CROSSSLOT错误。 Redis这么做的主要原因还是在于避免分布式数据被破坏的风险,而且在同一个事务下或者同一个命令中操作多个跨节点的key,会因网络等因素带来性能损耗,所以Redis禁止这么做。如果有这种场景,Redis也提供了解决方案:使用Hash Tags。 ...

2023-10-17 · 1 min · 131 words · Hank

Nginx Bad Gateway和no live upstreams错误分析

最近项目的生产环境中客户端出现大量的Nginx 502 Bad Gateway错误,逐步排查最终定位到是由于被ddos攻击造成服务器资源耗尽无法响应造成的问题。遂整理过程著文以记之。 场景 线上4个节点,每个节点都有两个相同服务通过Nginx作负载均衡,均采用Nginx默认值,未做过多配置,配置类似: 1 2 3 4 upstream test-server { server 127.0.0.1:8002; server 127.0.0.1:8001; } 客户端出现大量的 502 Bad Gateway 信息,查看Nginx错误日志,信息如下: no live upstreams while connecting to upstream 初步定位问题,发现后台出现了很多莫名其妙的错误,查看Nginx错误日志发现也打印了很多上述错误。怀疑是后台某个接口请求出错导致返回了 500,导致Nginx将这个服务下线,后经过排查,后台确实存在一些错误信息,但是出错上述错误的时间不匹配。 后整理思路并认真思考,发现可能思路存在偏差:后台http状态码怎么会影响Nginx将服务下线呢? 因为Http状态码表示http响应的状态,也就是表示响应结果的正确与否,比如 2xx 表示服务端正确处理并响应了数据,4xx 表示客户端存在错误妨碍了服务端的处理,5xx 表示服务端错误导致处理失败了。但是,能够返回这些状态码说明服务端连接正常,只是由于特定原因导致服务端响应错误了。返回了这些状态码Nginx当真会将服务下线吗?试想一下,某一个客户端请求查询一条不存在的数据导致服务端处理时抛出异常并响应 500 状态码,然后Nginx认为服务不可用并将其下线,导致一定时间内所有的请求都返回上述错误,那么罪魁祸首到底是服务端还是客户端?Nginx这么处理是不是太过了呢?Nginx肯定不会这么来设计。 所以,我猜测Nginx不可能如此是非不分,应该是别的原因导致的。 查阅upstream官方文档,看到这么两个参数配置: fail_timeout=time: 设置多少时间内不能与下游服务成功通信 max_fails 次后可以认为下游服务不可用,这是一个周期时间,即每隔 fail_timeout 时间都会进行统计,默认为 10 秒。 max_fails=number: 设置在 fail_timeout 时间内与下游服务通信失败的次数,达到该次数后认为服务不可用,默认为1。 按照Nginx的默认设置,也就是说每个10秒统计下有服务的通信失败次数,达到1次就认为服务不可用,此时Ngingx会将其踢下线,后续所有转发到该服务的请求都会返回 no live upstreams while connecting to upstream,直到下一个10秒再重新处理(max_fails 为0又重新将服务上线)。 关键在于这个 通信失败 的理解。通信失败,表示Nginx转发请求给服务,但是服务没有任何响应,而不是一开始怀疑的 HTTP 状态不是200,能成功响应,不论是什么状态码,都应该认为与服务通信成功。 实践才能出真知,为了验证我的猜想,必须进行实验。 ...

2023-10-07 · 5 min · 990 words · Hank

依赖注入库wire使用入门

依赖注入(Dependency injection)不是一个新概念了,早在2004年 Martin Fowler 就在其文章Inversion of Control Containers and the Dependency Injection pattern提出了依赖注入模式。在Java的世界中,随处可见依赖注入的使用,如Spring框架的核心之一就是控制反转(IoC),而依赖注入就是 IoC 思想的一种技术实现,比如我们常用的 Spring 的注解 @Autowire,还有 Jsr250规范定义的 @Resource 注解都实现了依赖注入。 简单而言,依赖注入就是以构建松散耦合的程序为目的、以控制反转为基本思想而在软件工程实现的一种编程技术,它使得程序不需要关注对象的创建逻辑,只是通过函数或者属性告诉程序自己需要什么对象,关注所依赖对象的使用逻辑,从而将对象的创建和使用过程分离开。可见,依赖注入技术遵循依赖倒置原则。 虽然 GO 不是面向对象的语言,但是它也有依赖注入实现,常用的依赖注入框架包括 google 自身出品的 wire、Uber的dig、Facebook的inject等等。本文将介绍 wire 的基本使用。 不使用依赖注入时 在开始介绍 wire 之前,我们来编写一个简单的程序,该程序可以创建问候者并向您问好。 我们创建一个 greeter.go 文件,它包含以下几个结构体: 1 2 3 4 5 6 7 8 9 type Message string type Greeter struct { Msg Message } type Event struct { Greeter Greeter } Message 表示消息,Greeter 表示问候者,它需要一个 Message,Event 表示一个事件,它用来触发问候。 现在,我们需要增加创建这些结构体的方法: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 func NewMessage(name string) Message { return Message(fmt.Sprintf("hello, %s!", name)) } func NewGreeter(msg Message) Greeter { return Greeter{Msg: msg} } func NewEvent(g Greeter) Event { return Event{Greeter: g} } func (g Greeter) Greet() Message { return g.Msg } func (e Event) Start() { msg := e.Greeter.Greet() fmt.Println(msg) } 通过 NewMessage 创建消息,通过 NewGreeter 创建一个问候者,通过 NewEvent 创建事件,然后就可以调用 Start 方法来发起问候了,其实底层最终调用的是 Greeter 的 Greet 方法。 ...

2023-08-06 · 4 min · 745 words · Hank

使用cli框架开发CLI程序

Go语言编译后的程序本身就是一个可用于命令行的可执行文件,而且Go天生支持CLI程序(command line interface),这得益于Go精简的语法以及自身支持Flag来解析命令行的选项参数等。但是,基于Go原始能力开发CLI程序仍然非常繁琐,如解析参数就是一个非常麻烦的工作。幸好,有许多非常强大的库可以用来简化我们的工作: cobra: 一个非常强大的用于构建CLI程序的库,官方地址见这儿 urfave/cli: 另一个使用广泛的CLI开发库,同样足够强大且简单易上手,官方地址见这儿 survey: 一个强大的构建交互式命令行程序的库,详情见这里 在开发CLI之前,你可以阅读Go官方的构建CLI程序指南。本文介绍如何使用 urfave/cli 库开发完整的CLI程序。 CLI程序 命令行界面(CLI,command line interface) 是一种通过用户或客户端发出的命令以及设备或程序以文本行形式做出的响应与设备或计算机程序进行交互的方式。 以上是维基百科的解释,简单而言就是控制台程序,我们需要通过控台执行程序并输入程序内置支持的选项、参数等完成与程序的交互以实现功能。 一般而言,CLI都具备这些功能: 命令:一个CLI程序应该至少支持一个命令,才能用来实现功能,大多CLI都支持多个命令,而且命令下还支持多个的子命令,用来将功能细分 选项:选项分为全局选项和命令选项,全局选项表示对所有命令都可以使用的选项,而命令选项这仅对特定命令有效 参数:CLI支持用户通过控制台传入参数告诉其特定信息,一般情况会通过选项指定参数来区分不同的用途,也可以直接传递给命令 帮助:展示给用户如何使用当前程序的帮助信息 输出:程序处理完成后展示给用户的结果信息 别名:命令和选项都应该支持别名,当命令和选项太长时用来简化输入 当然,CLI还包括程序退出码、错误等信息,不再一一列举。 cli框架简介 urfave/cli 是一个简单、快速且有趣的包,用于在 Go 中构建命令行应用程序。目标是使开发人员能够以富有表现力的方式编写快速且可分发的命令行应用程序。 目前最新支持的版本是 v2,这也是目前使用最广泛、功能强大的版本。 官方使用文档: https://cli.urfave.org/v2/getting-started/ 仓库地址: https://github.com/urfave/cli 接下来,我们将创建一个CLI应用并逐步完善它。 创建应用 创建一个cli目录,然后初始化go模块: 1 2 3 $ mkdir cli $ cd cli $ go mod init 编辑 go.mod 文件,将模块名称改为 cli_demo,然后安装 urfave/cli: 1 $ go get github.com/urfave/cli/v2 新建 main.go 文件作为程序的入口,编写代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package main import ( "fmt" "github.com/urfave/cli/v2" "os" ) func main() { cliApp := cli.NewApp() cliApp.Name = "demo-cli" cliApp.Usage = "cli usage demo" cliApp.Version = "0.0.1" err := cliApp.Run(os.Args) // app退出不会调用 os.Exit,所以默认退出代码都是0,可以通过 cli.Exit方法指定退出信息和退出码 if err != nil { fmt.Printf("demo-cli execute error: %v\n", err) os.Exit(-1) } } 首先,我们使用 cli.NewApp() 创建 *cli.App 实例,然后分别设置了程序的名称、使用说明、版本,最后使用 cliApp.Run(os.Args) 方法运行程序,传入系统参数并处理错误信息。 ...

2023-07-16 · 5 min · 1024 words · Hank

(译)Go1.13中处理错误

Damien Neil 和 Jonathan Amsterdam, 2019 年 10 月 17 日, 原文地址: https://go.dev/blog/go1.13-errors 1. 介绍 在过去的十年中,Go 将 "错误作为值" 来处理 ,这对我们很有帮助。尽管标准库对错误的支持很少 —— 只有 errors.New 和 fmt.Errorf 函数,它们产生的错误只包含一条消息 —— 内置 error 接口允许 Go 程序员添加他们想要的任何信息。它所需要的只是一个实现 Error 方法的类型: type QueryError struct { Query string Err error } func (e *QueryError) Error() string { return e.Query + ": " + e.Err.Error() } 像这样的错误类型无处不在,它们存储的信息千差万别,从时间戳到文件名再到服务器地址。通常,该信息包括另一个较低级别的错误以提供额外的上下文。 一个错误包含另一个错误的模式在 Go 代码中非常普遍,经过 广泛讨论 后,Go 1.13 添加了对它的明确支持。这篇文章描述了提供该支持的标准库的新增内容:errors 包中的三个新函数,以及 fmt.Errorf. 在详细描述更改之前,让我们回顾一下在该语言的先前版本中如何检查和构造错误。 2. Go 1.13 之前的错误 2.1. 检查错误 Go 错误是值。程序以几种方式根据这些值做出决策。最常见的是比较错误以 nil 查 看操作是否失败。 ...

2023-05-05 · 4 min · 828 words · Hank

(译)Go错误是值

Rob Pike,2015 年 1 月 12 日 ,原文地址: https://go.dev/blog/errors-are-values Go 程序员(尤其是刚接触该语言的程序员)讨论的一个共同点是如何处理错误,这些讨论最终往往都将回归于无数次出现的代码片段: if err != nil { return err } 如上所示,但我们最近扫描了所有我们能找到的开源项目,发现这个片段每页或每两页只出现一次,比某些人认为的要少。尽管如此,许多程序员仍然认为必须键入如下代码来处理错误: if err != nil 一直以来,人们觉得一定有什么地方出了问题,而明显的目标就是 Go 本身。 这是不幸的、误导性的,而且很容易纠正。也许刚接触 Go 的程序员会问,“我该如何处理错误?”,然后学习这种模式,最后就此打住。在其他语言中,可能会使用 try-catch 块或其他类似机制来处理错误。因此,程序员认为,我在我的旧语言中可以使用 try-catch 时,但在 go 中我只能输入 if err != nil 来处理错误。随着时间的推移,Go 代码收集了许多这样的片段,结果感觉很笨拙。 不管这种解释是否成立,很明显这些 Go 程序员忽略了关于错误的一个基本观点: 错误是值。 可以对值进行编程,并且由于错误是值,因此可以对错误进行编程。 当然,涉及错误值的常见语句是测试它是否为 nil,但是错误值可以做无数其他事情,应用其中一些其他事情可以使您的程序更好,从而消除大部分样板代码,避免多次使用 if 语句检查每个错误。 一个简单示例是 bufio 包中的 Scanner。它的 Scan 方法执行底层 I/O,这当然可能导致错误。然而,该 Scan 方法根本不会暴露错误。相反,它返回一个布尔值和一个单独的方法,在扫描结束时运行,报告是否发生错误。客户端代码如下所示: scanner := bufio.NewScanner(input) for scanner.Scan() { token := scanner.Text() // process token } if err := scanner.Err(); err != nil { // process the error } ...

2023-04-30 · 2 min · 364 words · Hank

(译)Go模块:管理依赖项

原文地址: https://go.dev/doc/modules/managing-dependencies 当您的代码使用外部包时,这些包(作为模块分发)成为依赖项。随着时间的推移,您可能需要升级或更换它们。Go 提供了依赖管理工具,可帮助您在合并外部依赖项时确保 Go 应用程序的安全。 本文介绍如何执行一些任务来管理您代码中的依赖项,您可以使用 Go tools 执行其中的大部分操作。本主题还介绍了如何执行其他一些您可能会觉得有用的依赖相关任务。 相关阅读 如果您不熟悉模块和依赖,请查看 入门教程 以获得简要介绍。 使用该 go 命令管理依赖项有助于确保您的需求保持一致,并且您的 go.mod 文件的内容是有效的。有关命令的参考,请参阅 go命令文档。您还可以通过键入 go help 命令名称` 从命令行获取帮助,如 go help mod tidy. 使用 Go 命令更改依赖项时会编辑 go.mod 文件。有关文件内容的更多信息,请参阅 go.mod 文件参考文档。 让您的编辑器或 IDE 能够感知 Go 模块可以让您更轻松地管理它们。有关支持 Go 的编辑器的更多信息,请参阅 编辑器插件和 IDE。 本主题不描述如何开发、发布和版本模块以供其他人使用。有关更多信息,请参阅 开发和发布模块。 1. 使用和管理依赖项的工作流程 您可以通过 Go tools 获取和使用有用的包。在 pkg.go.dev 上,您可以搜索您觉得有用的包,然后使用 go 命令将这些包导入您自己的代码中以调用它们的功能。 下面列出了最常见的依赖管理步骤: 在 pkg.go.dev上 查找有用的包。 在代码中导入所需的包。 将您的代码添加到模块以进行依赖跟踪(如果它不在模块中)。请参阅 启用依赖项跟踪 [添加外部包作为依赖项,以便您可以管理它们。 随着时间的推移,根据需要 升级或降级依赖版本。 2. 依赖项作为模块管理 在 Go 中,依赖项作为包含导入包的模块来管理。此过程由以下功能支持: 用于发布和检索模块的去中心化系统。这使得开发人员定义模块版本号并发布模块,其他开发人员就可以在自己的存储库中使用这些模块了。 包搜索引擎和文档浏览器 (pkg.go.dev),您可以在其中搜索模块。请参阅 查找和导入有用的包。 模块版本编号约定可帮助您了解模块的稳定性和向后兼容性保证。请参阅 Go模块版本编号。 Go tools 可以让您更轻松地管理依赖项,包括获取模块的源代码、升级等。有关更多信息,请参阅本文相关部分。 3. 查找和导入包 您可以在 pkg.go.dev 上搜索以查找您所需要的软件包。 ...

2023-04-19 · 3 min · 636 words · Hank

(译)Go 中的字符串、字节、符文和字符

原文地址: https://go.dev/blog/slices 作者: Rob Pike 日期: 2013 年 9 月 26 日 1. 简介 这篇文章讨论了 Go 中的字符串。起初,字符串对于一篇博文来说似乎太简单了,但要很好地使用它们,不仅需要了解它们的工作原理,还需要了解字节、字符和符文之间的区别,Unicode 和 UTF- 8、字符串和字符串字面量的区别,以及其他更细微的区别。 处理该话题的一种方法首先是回答这个问题:“当我在位置 n 检索 Go 字符串时,为什么我没有得到第 n 个字符?” 正如您将看到的,这个问题引导我们了解有关文本在现代世界中如何工作的许多细节。 2. 什么是字符串? 让我们从一些基础知识开始。 在 Go 中,字符串实际上是只读的字节切片。如果您完全不确定字节切片是什么或它是如何工作的,请阅读 数组、切片和字符串 一文。 重要的是首先要明确一个字符串包含_任意_多个字节,不论字符串是否包含 Unicode 文本、UTF-8 文本或任何其他预定义格式。就字符串的内容而言,它完全等价于一个字节切片([]byte)。 下边是一个字符串(稍后详述),它使用 \xNN 符号来定义一个字符串常量,其中包含一些特殊的字节值(字节的取值范围从十六进制值 00 到 FF)。 const sample = "\xbd\xb2\x3d\xbc\x20\xe2\x8c\x98" 3. 打印字符串 由于上边我们的示例字符串 sample 中的某些字节不是有效的 ASCII,甚至不是有效的 UTF-8,所以直接打印字符串会产生奇怪的输出。简单的打印语句如下: fmt.Println(sample) 产生这种乱码(其确切外观因环境而异)输出: ��=� ⌘ 为了找出 sample 字符串底层到底是什么,我们需要把它拆开检查一下。有几种方法可以做到这一点。最明显的是循环其内容并单独提取字节,如以下`for`循环所示: for i := 0; i < len(sample); i++ { fmt.Printf("%x ", sample[i]) // 输出为十六进制格式 } ...

2023-03-25 · 4 min · 682 words · Hank

(译)数组、切片和字符串 - “append” 原理

原文地址: https://go.dev/blog/slices 作者: Rob Pike 日期: 2013 年 9 月 26 日 介绍 过程编程语言最常见的特征之一是数组的概念。数组看起来很简单,但在将它们添加到语言时必须回答许多问题,例如: 固定尺寸还是可变尺寸? 大小是类型的一部分吗? 多维数组是什么样的? 空数组有意义吗? 这些问题的答案会影响数组是否只是语言的一个特性还是其设计的核心部分。 在 Go 的早期开发中,在设计感觉正确之前,花了大约一年的时间来确定这些问题的答案。关键步骤是引入 slices,它建立在固定大小的 数组 之上,以提供灵活、可扩展的数据结构。然而,直到今天,刚接触 Go 的程序员经常对切片的工作方式感到困惑,也许是因为其他语言的经验影响了他们的思维。 在这篇文章中,我们将尝试消除混淆。我们将通过构建片段来解释 append 内置函数是如何工作的,以及为什么它会以这种方式工作。 数组 数组是 Go 中的一个重要构建块,但就像建筑物的基础一样,它们通常隐藏在更可见的组件之下。在我们继续讨论更有趣、更强大、更突出的切片概念之前,我们必须简单地讨论一下它们。 数组在 Go 程序中并不常见,因为数组的大小是其类型的一部分,这限制了它的表达能力。 以下代码: var buffer [256]byte 声明了一个数组变量 buffer ,[256]byte 表示它持有的数据类型为 byte,长度为 256。如果想声明 512 个字节的数组可以这样: [512]byte。 与数组关联的数据就是 数组中的元素。上边声明的数组缓冲区在内存中看起来像这样: buffer: byte byte byte ... 256 times ... byte byte byte 也就是说,该变量只保存 256 个字节的数据,仅此而已。我们可以使用熟悉的索引语法 buffer[0]、buffer[1] 到 buffer[255] 来访问它的元素。(索引范围 0 到 255 涵盖 256 个元素)尝试使用超出此范围的索引值访问 buffer 会使程序崩溃。 ...

2023-02-09 · 8 min · 1504 words · Hank

(译)进入Go模糊测试的世界

原文地址: https://go.dev/doc/tutorial/fuzz 本教程介绍了 Go 中模糊测试的基础知识。模糊测试会针对您的测试准备一些随机数据然后运行测试时使用它们,以尝试找出漏洞或导致崩溃的输入。可以通过模糊测试发现的一些漏洞示例包括 SQL 注入、缓冲区溢出、拒绝服务和跨站点脚本攻击(XSS)。 在本教程中,您将为一个简单的函数编写一个模糊测试,运行 go 命令,并调试和修复代码中的问题。 有关本教程中术语的帮助,请参阅 "词汇表"。 您将逐步完成以下部分: 为您的代码创建一个文件夹 添加代码进行测试 添加单元测试 添加模糊测试 修复无效字符串错误 修复双反错误 结论 注意 更多 Go 教程,请参阅 教程。 Go fuzzing 当前支持 Go Fuzzing 文档 中列出的内置类型的子集,并支持将来添加的更多内置类型。 1. 先决条件 Go 1.18 或更高版本的安装。 有关安装说明,请参阅 安装 Go。 用于编辑代码的工具。 您拥有的任何文本编辑器都可以正常工作。 一个命令终端。 Go 在 Linux 和 Mac 上的任何终端以及 Windows 中的 PowerShell 或 cmd 上都能很好地工作。 支持模糊测试的环境。 目前仅在 AMD64 和 ARM64 架构上使用覆盖检测进行模糊测试。 2. 为您的代码创建一个文件夹 首先,为您要编写的代码创建一个文件夹。 1、 打开命令提示符并切换到您的主目录。 在 Linux 或 Mac 上: $ cd ...

2022-12-19 · 9 min · 1738 words · Hank

(译)初始Go模糊测试

原文地址: https://go.dev/security/fuzz/ 从 Go 1.18 开始,Go 在其标准工具链中支持模糊测试。Native Go 模糊测试受 OSS-Fuzz 支持。 Go模糊测试详细教程见:进入Go模糊测试的世界 一文。 1. 概述 Fuzzing 是一种自动化测试,它不断地操纵程序的输入以查找错误。Go fuzzing 使用覆盖率指导来智能地不断重复执行模糊测试的代码,以发现并向用户报告问题。由于它可以覆盖人类经常错过的边缘情况,因此模糊测试对于发现安全漏洞特别有价值。 下面是一个 模糊测试 的例子,突出了它的主要组成部分。 上图显示整体模糊测试的示例代码,其中包含一个模糊目标( fuzz target )。 在模糊目标之前调用 f.Add 添加种子语料库,模糊目标的参数高亮显示为fuzzing参数。 2. 编写模糊测试 2.1. 要求 以下是模糊测试必须遵循的规则。 模糊测试必须是一个形如 FuzzXxx 的函数,以 Fuzz 作为前缀,它只接受一个 *testing.F 参数并且没有返回值。 模糊测试必须在 *_test.go 文件中才能运行。 调用 (testing.F).Fuzz 方法时的匿名函数参数称之为 模糊目标,形如 func(t *testing.T, xxx), 它必须是一个函数,接受一个 *testing.T 作为第一个参数,其他后续参数称为模糊参数,且该函数没有返回值。 每个模糊测试必须只有一个模糊目标。 所有 种子语料库 条目必须具有与 模糊参数 相同的类型,并且顺序相同。这适用于调用 (*testing.F).Add 添加的种子语料库和 testdata/fuzz 目录中已有的语料库文件。 模糊测试参数只能是以下类型: string, []byte int, int8, int16, int32/rune, int64 uint, uint8/byte, uint16, uint32, uint64 float32, float64 bool ...

2022-10-26 · 2 min · 426 words · Hank

GoLang教程——常量

golang 中常量设计摒弃了 C 语言中常量被诟病的问题,比如类型安全问题、定义和使用复杂、编译器替换字面值等,而是进行了简化。在 go 中,常量一旦声明则在编译期其值就已知且不可更改。 1. 常量的定义 常量的定义很简单,使用 const 关键字声明即可: // 单行声明常量 const c1 = 1 const c2 = -100 const c3 = "hello" 与变量一样,同样可以组合以简化声明: const ( c4 = 3.14 c5 = 1.2 + 12i // 复数 ) 但是需要注意,组合声明时后续的常量如果没有赋值则会重复最近一个非空表达式的值,例如: const ( m = 1 // 1 n // 1 k // 1 l = m + 1 // 2 i // 2 ) fmt.Println(m, n, k, l, i) ...

2022-09-10 · 3 min · 585 words · Hank

GoLang教程——变量

本质上,变量是一个持有一类值的数据存储空间的命名,我们声明变量其实就是在申请存储空间,之后引用变量其实就是在使用这块存储空间中的值。GoLang是一门强类型语言,每一个声明的变量都对应着一种类型,但是声明变量的方式却有多种。 1. 变量的定义 Go语言规范中对变量的定义如下: 变量是保存着值的一块存储空间,存储什么样的值由变量的类型决定。 变量的概念在各个语言中都有,本质上变量就是为一块存储空间取一个名字,以便开发者后续引用这个名字来使用存储空间中存储的值。至于存储什么样的值,这必须有变量的类型来决定,所以Go、Java这种强类型语言在变量声明时必须指定变量类型。 2. 变量的声明 上一篇中提到:变量声明过后,必须被使用,否则编译错误。Go中变量的声明有两种方式: 使用 var 声明一个变量,适用于大多数情况,可以不用赋初始值时 使用 := 声明变量并初始化它,适用于需要为变量直接赋初始值时,比如直接声明变量接收方法的返回值 第一种,使用 var 关键字声明变量: var v0 rune // 声明字符类型的变量 v0 var v1 int // 声明整型变量 v1 var v2 string // 声明字符串类型的变量 v2 v0 = 'A' // 给变量赋初始值 这种方式可以先声明变量然后赋值,也可以直接在声明时赋值,此时可以省略类型,编译器会自动推导出类型: var i1 = 1 fmt.Printf("i1 = %d\n", i1) 在声明多个变量时,可以使用如下简写的语法: var ( v3 [2]int // 数组 v4 []int // 数组切片 v5 struct{ i int } // 结构体 v6 *int // 指针 v7 map[string]int // map ) ...

2022-08-02 · 2 min · 310 words · Hank

GoLang教程——代码风格

Go作为编程语言中的后起之秀,在语言规范上吸收了诸多语言的优点,并形成了自身独特的语言风格。本文探讨一下Go语言的代码风格。 1. main方法 main 方法同样是go程序的入门函数,但是其定义非常简单,比如上一篇中的: func main() { fmt.Println("Hello, world!") } 直接用 func 关键字定义一个名为 main 的方法即可,没有任何参数。 那么,如何获取传递给 main 的参数呢,可以使用 os.Args: func main() { fmt.Println("hello go!") for i, arg := range os.Args { fmt.Println(i, "=", arg) } } os.Args 获取命令行传递的参数,第一个始终为执行程序的名称,一个示例执行结果如下: $ go run main_func.go a b hello go! 0 = /var/folders/8b/y3pklwbs1wj_cq7hm_yjwgpr0000gn/T/go-build3458283994/b001/exe/main_func 1 = a 2 = b 当然,通常情况不会使用 os.Args 来解析命令行参数,而是使用 go 标准库的 flag 包。 2. go的关键字 go的内置关键字有25个: ...

2022-07-30 · 3 min · 526 words · Hank

GoLang教程——初识Go语言

Go 语言是最近几年发展最快、最火热的编程语言之一,由 google 公司出品,其学习成本低、天生支持高并发、语法简洁高效、强大的标准库以及日益丰富的生态特细等特性使得其非常适合开发后端服务程序,并逐渐挑战着 Java 在服务端编程语言中的领导地位。 1. Go语言的诞生 Go 语言的前身被认为是一种名为 Limbo 的编程语言,它是由 Unix 之父、C语言之父 肯·汤普逊(Ken Thompson) 和 丹尼斯·里奇(Dennis Ritchie) 这两位计算机灵魂人物领衔并在一个名为 Plan 9 的的操作系统研究项目中发明的。 Go语言起源于2007年,当时还是身在 Google 的 Plan 9 项目原班人马在业余时间编写完成的,后来 Google 意识到了 Go 的巨大潜力并全力支持这个项目。Go 在2009年11月正式对外开放,并在2012年3月28日发布了第一个正式版本。 Go 语言的主要开发者有 肯·汤普逊(Ken Thompson)、罗布·派克(Rob Pike)、罗伯特·格里泽默(Robert Griesemer)等,每一位都是赫赫有名的大师级人物: 肯·汤普逊:与丹尼斯·里奇在贝尔实验室发明了Unix操作系统,以及用于该系统的 C编程语言 Figure 1. Ken Thompson (left) and Dennis Ritchie (right) 罗布·派克:Unix小组成员,参与了Unix后续的Plan 9和Inferno操作系统,同时也是Limbo语言和Go语言共同的发明者 罗伯特·格里泽默:协助制作Java的HotSpot编译器和Chrome浏览器的JavaScript引擎V8,Go语言的共同创造者 2. Go的特点 Go 始于 2007 年 9 月,当时 Robert Griesemer、Ken Thompson 和我开始讨论一种新语言,以解决我们和 Google 的同事在日常工作中面临的工程挑战。 当我们在 2009 年 11 月首次向公众发布 Go 时,我们不知道该语言是否会被广泛采用,或者它是否会影响未来的语言。回顾 2020 年,Go 在两个方面都取得了成功:它在 Google 内外广泛使用,其网络并发和软件工程方法对其他语言及其工具产生了显着影响。 ...

2022-07-21 · 3 min · 433 words · Hank

(译)Go并发模式——Context

原文地址: https://go.dev/blog/context 作者:Sameer Ajmani 时间:29 July 2014 1. 简介 [1] 在 Go 服务器中,每个传入的请求都在其自己的 goroutine 中处理。请求处理程序通常会启动额外的 goroutine 来访问数据库和 RPC 服务等后端。处理请求的一组 goroutine 通常需要访问特定于请求的值,例如最终用户的身份、授权令牌和请求的截止日期。当请求被取消或超时时,所有处理该请求的 goroutines 都应该快速退出,以便系统可以回收它们正在使用的任何资源。 在 Google,我们开发了一个 context 包,可以轻松地将请求范围的值、取消信号和截止日期等跨 API 边界传递给正在处理请求的所有 goroutine。 该软件包 作为context公开可用 。本文介绍了如何使用该包并提供了一个完整的工作示例。 2. Context context 包的核心是 Context 类型: type Context interface { (1) Done() <-chan struct{} (2) Err() error (3) Deadline() (deadline time.Time, ok bool) (4) Value(key interface{}) interface{} (5) } 1 Context 携带截止日期、取消信号和请求范围的跨越 API 边界的值,多个 goroutine 同时使用它的方法是安全的。 2 Done 返回一个在此 Context 取消或超时时的通道(chan) 3 Err 错误信息说明 context 为什么被取消, 在 Done 返回的 chan 被关闭之后获取 4 Deadline 返回 Context 被取消的时间 5 Value 返回参数 key 关联的值,没有则返回 nil ...

2022-07-19 · 5 min · 887 words · Hank

以太坊入门之交易

"上一篇" 介绍了MetaMask钱包的安装和使用,并成功获取到部分测试以太币(ETH)并向另外一个账户发起了一笔转账,这笔转账称之为"交易",我们将在本篇来介绍它。 1. 以太坊的账户 首先,在学习交易之前,我们需要了解以太坊的账户体系。 以太坊的账户分为两种:外部账户和合约账户。 外部账户:外部账户一般由钱包创建,具有私钥,通过私钥控制和区块链网络和智能合约的访问; 合约账户:以太坊中智能合约部署后对应着一个合约账户,合约账户拥有智能合约代码,并受其逻辑控制。 简单而言,外部账户就是区块链的用户通过钱包创建的账户,而合约账户由合约代码编写者所有,对应着一个智能合约,由 EVM 执行。 合约账户和外部账户地址没有明显的区别,都是长度 20字节、160 位、转为十六进制(0x开头)后为 40 个字符的一个串,形如 0x829BD824B016326A401D083B33D092293333A830 两者的区别: 启动交易者(首先发起交易的一方)必须是拥有私钥的外部账户,也就是说只有用户可以启动交易;但是智能合约可以通过调用别的智能合约来对交易做出响应,比如,用户A通过智能合约A发起了一笔交易,虽然智能合约A调用智能合约B也可以发起了一笔交易,但是这整个过程中交易的启动者还是用户A的外部账户 外部账户和合约账户都可以发送和接收区块链货币(如以太币),但是交易目标如果是合约账户,则会使得合约账户拥有的智能合约代码在 EVM 中执行,外部账户则不会 合约账户没有私钥,智能合约编写者通过代码逻辑来保证安全性,外部账户拥有私钥,用户需要通过妥善的保管私钥来保证账户的安全性 2. 以太币单位 交易与货币息息相关,以太坊发行的货币为以太币(Ether, ETH,符号 Ξ),其基本单位为 wei,ETH与wei的关系如下: 1 ETH = 10^18 wei 此外,还有比较常用的 gwei (gigawei): 1 ETH = 10^9 gwei 1 gwei = 10^9 wei Table 1. 以太币的面额和单位 值(以wei为单位) 指数 通用名称 标准名称 1 1 wei wei 1,000 103 babbage kilowei or femtoether 1,000,000 106 lovelace megawei or picoether 1,000,000,000 109 shannon gigawei or nanoether 1,000,000,000,000 1012 szabo microether or micro 1,000,000,000,000,000 1015 finney milliether or milli 1,000,000,000,000,000,000 1018 ether ether 1,000,000,000,000,000,000,000 1021 grand kiloether 1,000,000,000,000,000,000,000,000 1024 megaether ...

2022-07-16 · 2 min · 373 words · Hank

以太坊入门之MetaMask钱包的安装和使用

区块链中,与用户直接相关的一个重要组件是钱包。用户通过钱包来访问链上的资金、查看账户地址、转账交易等操作。可以说,没有钱包,用户就不能正常访问链上资金,可见其重要程度。而诸多钱包中,MetaMask是一款可以直接在浏览器中使用的、实现了钱包标准 BIP-39 的钱包,也是大多数使用者选择的入门级钱包。 1. 区块链钱包简介 与普通的实物钱包不同,区块链钱包的核心目的是用来保存用户的公钥和私钥,从而保证访问区块链中账户资金的权限,即:证明钱是你的,而不是存储资金。 Figure 1. 区块链钱包(图片来源网络) 钱包的一个重要的误区是:钱包存储资金。其实,钱包并不会直接存储区块链上的资金。可以将其看做一个银行账户,当你需要取钱时,只需告诉银行要取多少钱,然后银行就可以检查账户的余额是否充足并进行取款,账户只是一个逻辑划分,钱始终是存在银行。同理,区块链钱包也只是在区块链上开了一个账户,钱始终在区块链网络中。 开通银行账户,我们必须设置交易密码,已验证你有访问账户的权限;同样的,区块链钱包中,私钥就相当于账户密码,每产生一笔交易时,都需要通过钱包的私钥来进行签名,已验证你拥有操作账户资金的权限。 但是,与银行账户不同,银行是中心化的资金机构,除了验证账户密码,还有其他手段比如身份证来验证取款人身份,忘记密码还可以重置。但在区去中心化的区块链中,私钥是你唯一的凭证,一旦私钥泄露,那么任何人都可以访问你的账户。 因此,钱包可以看成是一个私钥圈,保存了多组公钥私钥对。现在的钱包大多采用随机串生成助记词,再加密生成种子密钥,最后再以种子密钥为根生成密钥树的方式来生成密钥。这里仅说明钱包的概念,关于钱包的底层原理,后续在详细讨论。 Figure 2. 钱包中的密钥树(图片来源网络) 2. 什么是MetaMask MetaMask 是一款简单易用的区块链钱包,除了 App 版本的钱包,它还提供了基于浏览器的插件,包括 Chrome、Firefox等,而且它内置了 Web3,可以直接与以太坊区块链交互并开发 DApp 网页应用,这也是大多开发者选择它的原因。 3. 安装MetaMask 安装 MetaMask 很简单,以 Chrome 为例,直接从 Google 商店安装即可,前提是需要自带梯子。步骤如下: 1、进入google应用商店,直达地址在 这里,也可以自己搜索MetaMask,第一个小狐狸头像的就是 2、点击 "添加至Chrome",然后弹出框点击"添加至扩展"即可,然后会下载 Chrome 插件,耐心等待 3、下载完成后,Chrome会自动安装,完成后会进入扩展插件地址 点击开始使用,进入钱包创建页面 4、创建钱包 如果您以前有钱包,并记得助记词,那么可以直接通过助记词导入钱包,没有则点击"创建钱包"按钮新建,下一步点击"我同意",进入密码设置页面: 设置并牢记自己的密码,以后登录钱包是需要用到,然后点击"创建"按钮后,会出现一个介绍的视频页面,直接点击"下一步" 5、备份助记词 这一步很重要,助记词作为恢复秘钥的唯一手段,需要十分安全地做备份,一般建议手抄写在纸上并保存,其他电子存储方式均存在泄漏风险 什么是助记词? 按照密钥生成策略分,钱包分为两类:非确定性钱包和确定性钱包,前者的每一个私有都是由不同的随机数生成的,私钥间没有任何关联,备份和恢复非常麻烦;而确定性钱包则是通过一个随机串作为种子密钥来生成各种私钥,只要备份种子密钥就可以恢复出所有的私钥。 但是,由于种子密钥长度长而且毫无规律,非常难以记录和输入,因此人们为这个种子密钥按照一定的算法来为其匹配单词表中的一组顺序固定、数量固定(与种子密钥长度有关)的单词,这样便于备份和恢复,这有点类似密码本,这些单词就成为助记词(mnemonic)。 因此,助记词可以看做种子密钥的别名,其安全性与之等同,必须安全保存。 ...

2022-06-11 · 1 min · 123 words · Hank

什么是以太坊

上一篇 简单介绍了什么是区块链,也介绍了以太坊的产生和基本概念,其中提到: 以太坊 是一个去中心化的、开源的、图灵完备的、有智能合约功能的区块链开放平台。作为开放平台和”世界超级计算机”,以太坊以智能合约为核心为去中心化应用(DApp)建设提供了整套解决方案。这一篇我们将详细介绍什么是以太坊。 1. 为什么要学习以太坊 以太坊是目前最大的区块链开发平台,也是“世界计算机”。以太坊是区块链开发工作的必经之路,原因如下: 以太坊是目前最大的区块链开发平台,拥有庞大的用户群和社区 以太坊发展早,技术相对成熟,资料、文档众多 以太坊生态体系完备,除了智能合约、DApp、DAO、DeFi,还提供了 ENS、Swarm等DApp构建所需技术体系 以太坊减缓了区块链陡峭的学习曲线,以应用入手,使得区块链的开发上手快速、简单 以太坊与 JavaScript 结合紧密,开发者学习难度小 总之,以太坊好比微信开放平台,DApp开发好比基于微信公众号、小程序的开发,我们不用太关注底层的东西,而是使用以太坊和微信已经提供好的功能来开发自己的DApp和微信公众号,只是以太坊是基于去中心化的区块链,而微信则是中心化应用。 2. 以太坊的诞生 上一篇 介绍了什么是区块链,其中提到, Vitalik Buterin (V神)在2013年发表了《以太坊:一个下一代智能合约和去中心化应用平台》,提出了以太坊的基本构想。 V神是比特币的狂热粉丝,他不断思考着如何扩展比特币和其智能合约协议 Mastercoin,并提出了一种可替代 Mastercoin 的专用合约语言的更灵活和脚本的智能合约机制,这只是一个他的初步构想,尽管比特币团队非常感兴趣,但是这种想法过于超前,而且与比特币的开发计划存在冲突,所以并没有得到支持。 V神 2013年12月,V神发表了 以太坊白皮书,勾勒出以太坊背后的思想:一个图灵完备的通用目的的区块链。以太坊白皮书发布后,不断有区块链爱好者向V神提交并反馈自己的想法,帮助其不断完善这个想法。其中,Gavin Wood(Mastering Ethereum的作者之一)在C++语言上对以太坊给与了极大地帮助,后来成为了以太坊的联合创始人和CTO,并编写了 以太坊黄皮书。 Gavin Wood 白皮书与黄皮书 白皮书由V神编写,旨在提出改进比特币背后区块链技术的一种构想,使其成为一种通用的、适用于多个方面而不仅仅是虚拟货币的区块链技术,这仅仅是一种理论,在以太坊的成长过程中得以逐步实现; 黄皮书是由以太坊CTO Gavin Wood 博士编写,旨在定义以太坊的技术规范,详细描述了以太坊背后的技术实现细节,其中包含了大量的数学公式,比如,黄皮书定义了产生每一笔交易包含的通用字段、如何验证交易,以太坊虚拟机规范(EVM)等等。黄皮书着重于以太坊技术规范的制定,包含大量数学公式,专业性很强,比较难懂,这里[1]有一个面向普通大众的浅黄版本。 Figure 1. 白皮书 vs 黄皮书 V神与Gavin Wood一起,不断完善以太坊的协议层和技术体系,逐步实现“可编程的、安全的、通用的区块链”这一目标。2015年7月30日,第一个以太坊区块被成功挖出,以太坊作为“世界计算机”正式运行并对外提供服务。 3. 以太坊发展的四大阶段 以太坊的发展并不是一帆风顺的,以太坊的发展整体上分为四个阶段 [2],代号分别为:Frontier(边疆)、Homestead(家园)、Metropolis(都会) 和 Serenity(宁静),每一个阶段都以"硬分叉"的方式发布,因此新版本不能与老版本兼容。并且,每一个阶段都在不断完善其新功能和安全性,比如2016年以太坊遭受DAO攻击,而分叉了 "以太坊经典" 和 "以太坊" 这样的相互竞争、并行的以太坊版本。 阶段和硬分叉按照区块高度编号并标注日期,包括: Frontier: 区块高度为0,以太坊的初始阶段,持续时间为2015年7月30日至2016年3月。 Ice Age: 冰河期,区块高度为 200,000,引入指数难度增加的硬分叉,以便在准备就绪时将激励机制过度到Pos(权益证明)。 Homestead:家园,区块高度 1,150,000, 以太坊的第二阶段,发布于2016年3月。 DAO: 区块高度 1192000,以太坊遭受严重的攻击而不得不硬分叉,从而产生了 以太坊经典(Ethereum Classic, ETC) 和 以太坊(ETH) 两个相互竞争的系统 [3]。 Tangerine Whistle:橘子口哨,区块高度 2,463,000,改变某些IO密集操作的燃气计算方法和清除拒绝服务攻击(利用这些操作的低燃气成本)累积状态的硬分叉。 Spurious Dragon:伪龙,区块高度 2,675,000,2016 年 11 月 22 日发生的另一次计划外的硬分叉,包括四项提案,用以解决一些攻击问题: EIP 155: 简单重放攻击保护 EIP 160: 提升EXP操作码的费用 EIP 161: 状态树清理 (不变量保持替代) EIP 170: 调整智能合约的最大字节数限制 ...

2022-05-09 · 2 min · 238 words · Hank

什么是区块链

区块链是这几年非常火热的一个话题,越来越多的人加入到”区块“大军之中。他们其中一些人,积极投入到挖矿、炒币行列之中,甚至发行自己的数字货币来创收,这部分人社称之为”币圈“;另一部分人,深入探究区块链底层技术,期望不断改进并将区块链技术运用到更多领域中,这部分人社称之为”链圈“。越来越多的人积极群涌入区块链,游弋于“币圈"和"链圈"之间,忙的不亦乐乎。 本文将简单介绍区块链的一些入门知识,并介绍区块链开发所需要具备的基础知识,希望对准备进入区块链的开发者们会有一些帮助。 1. 什么是区块链 区块链(Blockchain)源于 比特币,它其实是对比特币的底层技术的一种扩展和创新。2008年,一位网名“中本聪”的日本大佬发布了一篇名为 《比特币:一种点对点的电子现金系统 的论文(比特币 白皮书),文中总结了之前的数字货币发明,提出了一种完全去中心化的数字货币系统,即比特币。文中并没有提及“区块链”一词,而是使用了“区块”(Block)和“链”(Chain)来描述比特币底层技术,后来人们将区块、链合并在一起,正式提出“区块链”(Blockchian)这一概念。 区块链是一系列技术的组合,包括P2P动态组网、密码学、基于密码学的共享账本、共识机制、智能合约等。简单而言,区块链可以看做一个全球互联的超级账本,链中的每一台计算机通过P2P组网连接并保留了账本的副本,这样,链中的计算机都有一份账本。每一笔交易都需要记录到账本中,当一笔交易产生后,产生这笔交易的计算机将交易信息广播出去,其他链上的计算机就会收到交易信息,它们会进行一系列复杂计算,最终由率先计算完成的计算机将这笔交易信息记录到账本上。具体的计算机制其实是由不同的区块链设计好的,与该区块链的结构相关。比如按照比特币的设计,计算机需要将10分钟内的全部交易打包,再加上一个随机数,然后穷举随机数计算一个256位的哈希值,使得其符合一定的条件,这样就可以获得交易记账权,一旦计算完成并记账,该计算机要快速将计算的区块广播出去,由其他计算机来验证计算的正确性,防止他人篡改。 每产生一笔交易并不是立即就要被区块的各个节点记账,而是按设计要求被打包到一起,比特币设计为每10分钟将所有新交易打包(而以太坊时间更短,大约9秒左右),形成了一个区块(Block),然后将区块广播出去,区块链中的计算机都需要验证区块的正确性,并将其写入自己的账本中,完成记账。区块链则是由一个个相互连接的区块组成的,每一个区块都记录了上一个区块的 HASH 值,第一个区块没有上一个区块的 HASH 值,称之为”创世区块“。计算哈希值竞争记账权的计算机称为“矿工”,计算的过程称为”挖矿“,挖矿成功则获得交易记账权,记录打包好的交易信息并写入最新的区块,称为”出块“,最新出块的矿工可以获得一定的手续费收入。 区块链中的计算机都有账本,那么如果确定各个账本数据的一致性呢?区块链采用”共识机制“来保证大部分诚实计算机的账本一致性,这样就实现了完全去中心化。常用的共识机制有Pow(工作量证明机制,例如挖矿机制)、PoS(权益证明)、DPos(股权授权证明)和分布式一致性算法(如拜占庭容错算法、Paxos、Raft)。 与传统的中心化应用相比,区块链具有如下特点: 去中心化:区块链本质是基于P2P对等网络的分布式系统,链上的每一个节点都存有相同的数据,通过共识机制来决定交易数据的同步,不需要中心化服务器。但是目前,还不能做到完全的去中心化,某些场景还是具备中心化的特征。 高度开放:公有链上的区块链系统通常都是开源的,任何人都可以查询区块链上的数据,智能合约代码高度公开、透明,数据存储与链上所有节点中,不用担心中心化应用的资金安全等一系列问题。 高度可用:区块链应用由区块链上的每一个节点共同维护,任何一个或几个节点宕机不影响整个应用的可用性。 高度安全:区块链技术采用一系列加密措施(如非对称加密、哈希算法、椭圆曲线算法等)来对交易进行加密和签名,保证交易不能被伪造、篡改,然后借助分布式系统中各个节点的共识算法形成强大算力来抵御破坏者的攻击,从而保证链上数据的安全性。 匿名性:区块链上的用户信息都是匿名的,仅对外暴露钱包公钥和地址,就可以完成交易,完全不需要用户的真实身份信息。 2. 什么是以太坊 比特币作为一种虚拟数字货币,为金融领域开创了一种全新的去中心化货币系统。比特币的狂热粉们一直在探索,比特币技术能否使用到其他非金融领域呢? 以太坊 的出现,为区块链技术迎来了崭新的时代。 2013年,比特币的忠实拥护者 Vitalik Buterin (人称V神)提出了一种构想:区块链不应该仅用于金融领域,而是应该开放出来形成一个开放平台,供人们开发更多的去中心化应用,为此他提出了以太坊,并发表了《以太坊:一个下一代智能合约和去中心化应用平台》,也就是后来的 《以太坊白皮书》。 以太坊 是一个去中心化的、开源的、图灵完备的、有智能合约功能的区块链开放平台。 首先,以太坊是一条发行有自己货币(ether,以太币)的区块链;其次,以太坊具备开发去中心化应用功能,它具有能够在以太坊虚拟机(EVM)上运行的 智能合约 功能,开发者可以编写智能合约来自主控制去中心化应用;第三,以太坊优化了PoW(工作量证明机制),并提供了PoS权益证明共识机制,大大加快了区块链交易确认速度(比特币确定一笔交易需要10分钟,而以太坊减少到9秒左右),为去中心化应用开发提供了必要条件。 总之,以太坊是一个区块链开放平台,通过它可以开发自己的区块链 去中心化(Decentralization)应用(Dapps)。 以太坊被称为"第二代的区块链平台", 这个网站 用车站的例子动态、形象地展示了以太坊和比特币之间的一些差别,非常生动有趣。 3. 区块链中的一些基本概念 了解了区块链和以太坊,现在我们来看看区块链中的一些概念。 区块链:Blockchain,共享的分布式账本,交易附加到区块上存储并记录。 区块:Block,多个交易被打包为区块并存储,区块之间相互连接,形成一个链状结构,每一个区块都有一个哈希值加以区分,区块在链中的长度称为区块高度。 钱包:Wallet,存储用户虚拟货币的地方,对外暴露公共地址和公钥,内部包含有绝对私密的私钥信息。 以太坊:Ethereum,一个基于区块链的去中心化运行智能合约的平台,旨在解决与审查,欺诈和第三方干扰相关的问题。 主链:Mainnet,正式运行的区块链的主网络,一个区块链项目经过前期的技术开发后,最终都会发布到区块链主网上线,比如 以太坊主网。 侧链:Sidechains,侧链实质上不是特指某个区块链,而是指遵守侧链协议的所有区块链,该名词是相对于主链而言的。侧链协议是指可以让主币安全地从区块链主链转移到其他区块链,又可以从其他区块链安全地返回主链的一种协议。侧链与主链共存,并可以扩展主链的功能、提高交易速度等,比如ETM就是以太坊ETH的侧链。 测试链:Testnet,与主链功能相同的测试网络,以测试为目的搭建,可以免费从水龙头获得测试币在测试链上进行开发测试,如 币安测试链。 交易:由原始帐户签署的提交到以太坊区块链的数据,并以特定地址为目标。交易包含元数据,例如交易的燃气限额(Gas limit)。 区块链浏览器:查询区块链中数据的工具网站,可以详细查询区块链上的详细信息,如 以太坊浏览器、 币安链浏览器等等。 Dapp:去中心化应用,基于区块链技术开发的应用程序,数据存储于区块链中。 DAO:去中心化自治组织,一种将组织的管理和运营规则以智能合约的形式编码在区块链上,从而在没有集中控制或第三方干预的情况下自主运行的组织形式。DAO 有望成为应对不确定、多样、复杂环境的一种新型有效组织。 以太币:Ether,以太坊生态系统中使用的本地货币,在执行智能合约时承担gas费用,它的符号是 Ξ. 以太坊虚拟机:EVM,Ethereum Virtual Machine, 基于栈的、执行智能合约字节码的虚拟机。 智能合约:Smart Contract,在以太坊的虚拟机中执行的程序,有开发者编写以控制链上业务逻辑。 水龙头:Faucet,一个网站,用来向测试用户提供一定数量的测试币,供测试使用,测试币除了测试并没有真正的价值。 Solidity:过程式(命令式)编程语言,语法类似于 Javascript, C++ 或 Java,以太坊智能合约最流行和最常使用的语言。由以太坊架构师 Gavin Wood 发明。 图灵完备:Turing Complete,在计算理论中,如果数据操纵规则(如计算机的指令集,程序设计语言或细胞自动机)可用于模拟任何图灵机,则它被称为图灵完备或计算上通用的。这个概念是以英国数学家和计算机科学家阿兰图灵命名的。 ...

2022-04-17 · 1 min · 213 words · Hank

Typora For Mac、Windows破解版和免费版下载

Typora是一个所见即所得(WYSIWYG)的markdown编辑器,由于其快捷、方便,并且配备强大的快捷键来快速编写markdown文档,一经发布就深受大批用户(尤其是程序猿)的喜爱,也是迄今为止我用过的最好用的markdown编辑器了! Typora有如下特性: 轻量级,性能非常高,启动时间在秒级 所见即所得,也是其最大的特性,它只需要一个界面就可以完美展示渲染后的markdown格式,而不需要像大多数markdown编辑器需要两个界面,一个显示源码,一个显示渲染结果 支持表格、UML图、数学公式,编写方便 支持大量展示主题,可以下载使用他人发布的主体,也支持自定义主题 支持markdown自动目录 支持图片拖拽调整位置,支持调整图片显示大小,支持搭建自定义图床 支持导出PDF格式文件 Typora在1.0之前的版本都是免费使用的,从1.0开始,官方宣布Typora收费,价格14.99$,人民币89元,价格不算太贵,有条件请支持正版! 本站从网络上搜索好了一些平台的破解版本和最后的免费版本,请根据需要自行下载,下载地址如下: Typora for Mac 1.2.3破解版下载:链接: https://pan.baidu.com/s/1g-xT_AaagWBvzvKVBDGkmg 密码: grvk Typora for Windows 1.2.4破解版下载:链接: https://pan.baidu.com/s/1-tfx_Bz0ScExaqrixlFVzg 密码: 5it7 Typora for Mac 0.11.18免费版下载:链接: https://pan.baidu.com/s/1Xlt6fhEF_TLJpXBryPRI6w 密码: mrei Typora for Windows 0.11.18免费版下载:链接: https://pan.baidu.com/s/1DRorRI4gX9b_sgk0WdhxOA 提取码: ttsx Typora for Linux 0.11.18免费版下载:链接: https://pan.baidu.com/s/1O1aA0p_DTzzWkMCAD0g5cQ 提取码: hm84 下载声明 本站所有下载资源均来自互联网,由站长搜集整理,版本归原作者所有,如有侵权请联系站长删除。 本站所有下载资源仅供个人学习和研究使用,请勿用于商业用途,请在下载后24小时内删除。

2022-04-14 · 1 min · 49 words · Hank

读《Java并发》— Java内存模型

"上一章" 介绍了线程安全性,这一章称为"对象的共享",书中重点介绍了如何共享和发布对象,这两章是并发编程中非常基础却很重要的部分。在本章,首先介绍了什么是可见性问题,然后介绍了Java内存模型,讨论什么是内存可见性以及java保证内存可见性的方式,在此基础上介绍如何设计线程安全的对象,如何使用线程封闭技术和设计不可变对象来避免同步,最后再重点探讨如何安全地发布对象。由于内容较多,我将这一章分拆为几篇来阐述自己对本章的理解,这是第一篇。 Java的并发机制基于共享内存,要理解对象间的共享关系,则离不开对象间的内存关系,这涉及到本章要介绍的一个重要概念:Java内存模型,又称 JMM。 1. 内存可见性 上边提到,Java的并发机制是采用的是 共享内存模型,因此,在并发环境中保证对象间的内存可见性是并发编程解决的主要问题。 什么是内存可见性?可见性是一个复杂的问题,它表示程序中的变量在写入值后是否能够立即读取到。在单线程环境中,由于写入变量和读取变量都是在单线程中进行的,因此能够保证总能读取到修改后的值。但在多线程环境下,却无法保证,可能一个线程修改变量的值,而另外的线程并不能正确读取被修改的变量的值,除非我们使用变量同步等机制来保证可见性。 为什么多线程环境下变量就不能保证可见性了呢?稍后介绍JMM时再来讨论,先看一个示例。 内存可见性(Memory Visibility):某些线程修改了变量的值,正在读该变量的线程能够立即读取到修改后的值。 @NotThreadSafe public class UnsafeSequence { private int count; public void increment() { count++; } public int getCount() { return count; } } 上边的示例,count变量被多个线程共享,因此不能保证 getCount() 总能读取到 increment() 增加后的值。那是不是在 increment() 方法上使用 synchronized 进行同步就能保证可见性了呢?答案是不行。虽然使用同步能够保证只有一个线程修改count的值,但是其他多个线程仍然可能读到 失效的值,因此必须在 getCount() 上也使用同步,见 "这里"。 再看一个示例,如下边的代码: @NotThreadSafe public class NoVisibility { private static boolean ready; private static int anInt; private static class VariableReader implements Runnable { @Override public void run() { while (!ready) { Thread.yield(); } System.out.println(anInt); } } public static void main(String[] args) { new Thread(new VariableReader(), "read").start(); anInt = 47; (1) ready = true; (2) } } ...

2022-03-28 · 3 min · 578 words · Hank

读《Java并发》— 线程安全性

上一章 介绍过,并发编程会带来诸多挑战,最基本的就是 线程安全性,但这又是一个非常复杂的主题。这一章重点介绍了什么是线程安全性、原子性、竞态条件等概念,以及在Java中如何通过加锁来确保线程安全性。 1. 什么是线程安全性 1.1. 正确性 要理解什么是线程安全性,必须先明白什么是正确性,正确性是线程安全性的核心概念。 正确性的含义是,某个类的行为与其规范完全一致。 — Java并发编程实战 这个定义从静态视角出发强调了类的规范重要性。通常,我们不会定义类的详细规范,但是我们应该为类和方法提供文档注释,来说明类是否是线程安全的,以及对于线程安全性如何保证。尤其在方法上,应该明确规定该方法是否已经保证了线程安全,调用者是否应该在同步机制内调用该方法等等。 下边是 ArrayList 的类文档注释的节选,它告诉调用者关于线程安全的内容: Note that this implementation is not synchronized. If multiple threads access an ArrayList instance concurrently, and at least one of the threads modifies the list structurally, it must be synchronized externally. (A structural modification is any operation that adds or deletes one or more elements, or explicitly resizes the backing array; merely setting the value of an element is not a structural modification.) This is typically accomplished by synchronizing on some object that naturally encapsulates the list. ...

2022-01-25 · 6 min · 1177 words · Hank

读《Java并发》— 并发简史

这一章主要讲述线程的发展历史,以及并发编程带来的优势和挑战。 1. 线程的发展 1946年第一台计算机诞生,一直到20世纪50年代中期,这时候的计算机没有操作系统的概念,采用手工操作的方式工作,每次只能有一个人使用计算机。此时的手工操作方式,用户独占全机,昂贵的计算机资源得不到充分利用。 后来,随着计算机的发展,出现了批处理系统、多道程序系统,它们都提升了计算机的资源利用率。1961年,分时系统(Time Sharing System)出现,此时的一台计算机可以供多个用户终端同时连接并使用,就好像自己在独占计算机一样。分时系统将CPU的运行时间分成很短的时间片,按时间片分配给不同的连接终端使用,这充分利用了计算机资源,看起来就好像多个用户在同时使用计算机一样。 再后来,通用操作系统出现(General Operating System),它隐藏了硬件调用接口,为应用程序员提供调用硬件资源的更好、更简单、更清晰的系统调用接口,程序员就可以直接面向操作系统来编写程序,而不用关注硬件。 计算机的发展 [1],都在试图解决三个问题: 资源利用率:尽可能的利用昂贵的计算机资源,如CPU、内存,而不是使其大部分时间处于等待状态 公平性:不同的用户对计算机都有同等使用权,而不是排队等待 便利性:可以同时执行多个程序,每个程序执行一个任务并在必要时相互通信 这三个问题促使进程和线程的出现。计算机可以同时运行多个程序,每个程序一个进程;而同一个进程中同样存在多个线程(轻量级进程),它们共享进程范围内的资源,如内存句柄和文件句柄,每个线程都有自己的程序计数器、局部变量等,而且同一个进程的多个线程都可以被调度到CPU上运行。但是,线程之间存在着共享资源(进程的内存),如果没有协同机制,那么多个线程在同时处理共享内存时会存在安全问题,比如一个线程读某个变量而一个线程同时在写这个变量,如果没有线程同步机制,则该变量的值变得不可预测。 举个例子,假设进程为多条高速公路,它们之间彼此独立,而线程就好比高速公路的多个车道,它们共享这条高速路的资源,如指示牌、测速设备等,线程中执行的任务就是一辆小汽车,它们相安无事的飞驰在自己的车道上,一旦它们开始抢夺道路资源,不遵守交通规则,超速、违规变道,那么后果可想而知。 2. 多线程的优势 Java是门支持并发编程的语言,充分利用其提供的并发特性来编写高并发程序,是信息化时代发展所需。多线程程序的优势可以从几个方面来说明: 多线程程序能够极大的发挥处理器的强大能力。现代的计算机,CPU核心越来越多、处理频率越来越快、制造工艺越来越精,计算机的价格也越来越亲民,个人普通电脑都普遍是四核心、八核心甚至配置更高。在单核心CPU中,可能多线程程序不但没能提升处理性能,反而由于线程上下文切换的开销导致程序性能降低;但是,在多核心时代,多线程能够发挥处理器的强大能力,每个核心都能同时处理一个任务而不会造成上下文切换,如果还停留在单线程程序,这无疑是极大的浪费计算机资源,如果有100个CPU,那么单线程程序会造成99%的CPU都处于空闲状态。 建模更简单。首先要明确,编写多线程程序与单线程程序相比,肯定是复杂且容易出错的,那么为什么说建模更简单呢?这需要从管理线程中执行的任务角度去分析。在单线程中执行多个任务,与多个线程每个线程仅执行一个任务相比,哪个更容易管理呢?很明显是后者。虽然多线程中的任务彼此可能存在通信,但是从模型上看,一个线程只管理一个任务,职责更单一。 异步事件简化处理。这一点可以理解为编程模型的简化。单线程和多线程程序,在IO模型的选择上存在很大的不同。单线程程序,要提高并发请求数,底层需要使用异步IO、非阻塞IO或者IO多路复用模型 [2],但他们都是由操作系统底层支持,编程难度大;而多线程程序,由于应用层面本身采用了多线程模型,底层的IO则可以选择同步IO(异步的事情多线程做了),这就降低了开发难度。比如单线程模型中,Nginx采用多进程单线程和IO多路复用模型的架构,具备极高的并发处理能力;Redis采用单线程和IO多路复用模型,同样具备很高的处理效率。 响应更灵敏的用户界面。Java的GUI框架AWT和Swing采用事件分发线程来替代传统的主事件循环,并可将将耗时的处理任务放到单独的线程中异步执行,从而提高用户界面的响应速度。 3. 多线程的挑战 多线程是一把双刃剑,带来诸多优势时也让开发者面临更大的挑战。 1、开发难度问题 由于多线程程序运行的不确定性,给开发难度带来很大的挑战。相对于单线程程序,多线程编程更难以掌握,不仅需要充分理解编程语言级的多线程支持,还需要掌握操作系统层级的多线程模型。尽管如此,多线程程序仍然难以调试和跟踪,尤其是产生 "活跃性" 问题时往往无法重现。 2、安全性问题 什么是安全性?安全性指的是程序永远不会返回错误的结果。看下边的示例: public class UnsafeSequence { private int count; public int increment() { return count++; } public int getCount() { return count; } } 多个线程调用 increment 方法,由于 count++ 包含了 读取、修改、写入三步内存操作,这三步不是一个原子操作,存在多个线程重复写同一值的情况,如下图示意: Figure 1. 线程竞争导致错误的结果 由于线程在同时操作相同的count域,一个线程可能会覆盖另一个线程写入的值(线程竞争),最终count得到错误的值6,而不是预期的7. 线程安全本身是一个非常复杂的问题,由于线程之间存在资源共享,如果多个线程交替执行又没有有效的同步机制,执行结果将难以预料。尽管Java提供了足够的同步机制来保证线程安全性,但是这就要求开发者必须完全理解多线程程序的执行原理和Java的同步机制,这给程序开发带来很大的难度。 3、活跃性问题: ...

2022-01-22 · 1 min · 125 words · Hank

多线程活跃性——哲学家就餐问题及死锁

死锁是多线程编程中最常见的一种"活跃性问题",除了死锁还包括"饥饿"和"活锁",这些活跃性问题给并发编程带来极大的挑战。比如出现死锁时,定位和分析问题相对困难,一旦出现死锁,通常只能重启应用程序。本文通过死锁最经典的"哲学家就餐问题"来介绍死锁的产生原因和解决办法。 1. 死锁 死锁指的是多个线程相互等待彼此而进入永久暂停状态。比如,线程 T1 持有锁 L1 去申请锁 L2,但是线程 T2 持有锁 L2 申请锁 L1,此时它们都在等待对象释放锁,从而进入永久阻塞状态。这就好比两个小朋友,他们各有一个玩具,但都不愿意分享给对方,却希望获得对方的玩具,最终互不相让,只能彼此干瞪眼了。 写一个死锁的程序很简单,比如下边的代码: public class DeadLockTest { private static final Object lock1 = new Object(); private static final Object lock2 = new Object(); public static void main(String[] args) { new Thread(new MyThread1(lock1, lock2)).start(); new Thread(new MyThread2(lock1, lock2)).start(); } } class MyThread1 implements Runnable { private final Object lock1; private final Object lock2; public MyThread1(Object lock1, Object lock2) { this.lock1 = lock1; this.lock2 = lock2; } @Override public void run() { while (true) { synchronized (lock1) { (1) System.out.println("using lock1"); synchronized (lock2) { System.out.println("using lock2"); } } } } } class MyThread2 implements Runnable { private final Object lock1; private final Object lock2; public MyThread2(Object lock1, Object lock2) { this.lock1 = lock1; this.lock2 = lock2; } @Override public void run() { while (true) { synchronized (lock2) { (2) System.out.println("using lock2"); synchronized (lock1) { System.out.println("using lock1"); } } } } } ...

2022-01-20 · 6 min · 1208 words · Hank

读《Java并发》— 简介

这是个人的《Java并发编程实战》阅读笔记整理系列的第一篇文章,这个系列包括对书中并发编程内容的整理、总结以及个人的一些感悟和实践心得。 1. 简介 《Java并发编程实战》是 Brian Goetz 等 6 位 Java 大师合著的介绍 Java 并发编程的经典著作,这部名著由浅入深的介绍了 Java 并发编程的诸多知识,是一本完美的Java并发参考手册, 豆瓣评分 9.0,可见其受欢迎程度。 1.1. 内容概要 《Java并发编程实战》从并发性和线程安全性的基本概念出发,介绍了如何使用类库提供的基本并发构建块,用于避免并发危险、构造线程安全的类及验证线程安全的规则,如何将小的线程安全类组合成更大的线程安全类,如何利用线程来提高并发应用程序的吞吐量,如何识别可并行执行的任务,如何提高单线程子系统的响应性,如何确保并发程序执行预期任务,如何提高并发代码的性能和可伸缩性等内容,最后介绍了一些高级主题,如显式锁、原子变量、非阻塞算法以及如何开发自定义的同步工具类。 这本书介绍的是 Java 中 "并发编程" 这样的高级主题,不太适合初学者阅读。书中诸多的内容都较难理解,比如线程的 "竞态条件"、"同步机制"、"活跃性" 等等,如果对并发知之甚少,不熟悉操作系统层面的相关内容,阅读起来比较吃力。 1.2. 作者简介 本书作者都是Java Community Process JSR 166专家组(并发工具)的主要成员,并在其他很多JCP专家组里任职。Brian Goetz有20多年的软件咨询行业经验,并著有至少75篇关于Java开发的文章。Tim Peierls是“现代多处理器”的典范,他在BoxPop.biz、唱片艺术和戏剧表演方面也颇有研究。Joseph Bowbeer是一个Java ME专家,他对并发编程的兴趣始于Apollo计算机时代。David Holmes是《The Java Programming Language》一书的合著者,任职于Sun公司。Joshua Bloch是Google公司的首席Java架构师,《Effective Java》一书的作者,并参与著作了《Java Puzzlers》。Doug Lea是《Concurrent Programming》一书的作者,纽约州立大学 Oswego分校的计算机科学教授。 也许你对这几位大师不怎么熟悉,但是你一定读过它们的著作,尤其是 Joshua Bloch 的《Effective Java》,这本书也是 Java 开发者必读的书籍,目前已经出到了第三版。 如果经常编写并发程序,那么对于 Doug Lea 一定不会陌生,因为 Java 5 开始的很多并发工具(它们都在 java.util.concurrent 包中,简称 juc 包)都出自他的手笔,如 AbstractQueuedSynchronizer、AtomicXxx 原子类、BlockingQueue、ConcurrentMap、ExecutorService 等等,几乎整个并发工具包的类都是他实现的。 1.3. 阅读建议 前边说过,尽管《Java并发编程实战》是经典著作,但是由于其内容的特殊性,存在一定的阅读门槛。如果英文过关,建议直接阅读英文版,对于大多数人只能阅读翻译的中文版本了,但中文版存在一些问题,诸多概念介绍的很官方,内容不够充实、通俗易懂,理解起来费力,也存在一些翻译不通顺的问题。 ...

2022-01-20 · 1 min · 125 words · Hank

Java设计模式(24)-职责链模式

1. 引言 您有没有遇到各种需要走流程的事情?比如,请假申请,假设公司规定,3天以内的请假申请组长可以直接审批,而4到7天的请假申请必须要让部门经理来审批了,超过7天的请假申请只能由公司总经理来审批。类似的场景还有很多,尤其在工作流中,比如物资审批、报账审批、资金审批等等…… 这里场景中,请求要发送给多个处理者,处理者要能够向后继续转发请求。存在着两种情况: 一方面,每一个处理者有自身能够处理的权限范围,超过权限范围的请求自身不处理,而是将请求转交给上级来处理,如果处理了则终止; 另一方面,每一个处理者可以部分处理请求,处理后可以再将请求发送给上级,也可以决定是否终止。 这样,可能多个处理者处理同一个请求,也有可能只有一个处理者能够处理请求,如下图所示: 多个处理者连成了一条链,请求沿着这条链可以向后传递,这里就会用到今天要讲的职责链模式。我么以请假流程为例,看看使用职责链模式前后的变化,感受职责链模式带来的好处。 2. 请假流程初步 根据前边的场景描述:3天以内的请假申请组长可以直接审批,而4到7天的请假申请必须要让部门经理来审批了,超过7天的请假申请只能由公司总经理来审批。具备面向对象思维的我们不难写出如下的代码: 1、定义请假请求 请假请求类 class LeaveRequest { private final int days; (1) private final String name; (2) public LeaveRequest(int days, String name) { this.days = days; this.name = name; } // …… 省略getter和toString } 1 请假的天数,它来决定谁可以审批这个请假请求 2 请假人的名称 2、既然请假要求领导审批,我们来抽象一个领导的接口 抽象的领导接口,默认实现了请假审批逻辑 interface Leader { default void handle(LeaveRequest request) { (1) Random random = new Random(47); if (canHandle(request)) { (2) if (random.nextBoolean()) { System.out.println(this.getClass() + " [通过] 了请假请求: " + request); } else { System.out.println(this.getClass() + " [拒绝] 了请假请求: " + request); } return; } throw new RuntimeException(this.getClass() + " 不能处理请假请求: " + request); } boolean canHandle(LeaveRequest request); (3) } ...

2022-01-17 · 3 min · 613 words · Hank

Java设计模式(23)-备忘录模式

中国有句古话:"千金难买后悔药"。生活中很多时候,我们做过了的事情,虽然后悔,却无济于事。但是,在软件世界,我们却可以自己制作"后悔药"。比如,以前玩"仙剑",每每遇到Boss战,那必须要存档的,就算没打过也可以恢复存档再来一次,否则,玩过的人都知道,哭去吧……这里的游戏存档,就会用到今天说的——备忘录模式。 其实还有很多这种例子,比如Word等办公软件的撤销操作,撤销后需要恢复到前一状态;又比如,虚拟机或者今天的云服务器都支持创建快照,如果系统出问题了,可以直接恢复到快照版本…… 1. 概念 备忘录,顾名思义用来做备份的,后续可能按照备份数据进行恢复。DP 对备忘录模式的定义如下: 定义 备忘录模式(Memento Pattern):在 不破坏封装性 的前提下,捕获一个对象的内部状态,将在 该对象之外 保存这个状态。这样以后就可将该对象恢复到原先保存的状态。 这个定义比较好理解,其基本思想就是要将对象的内部状态保存下来,便于之后恢复,但是保存的逻辑不由对象本身负责,而是单独抽出取来,减轻对象的职责,同时也便于扩展。这个思想与前边的 迭代器模式] 非常类似。 需要保存内部状态的这个对象,我们称为 原发器 (Originator),抽取出来的保存原发器状态的对象我们称为 备忘录 (Memento),我们来看看它们之间的关系。 2. 结构 备忘录模式的结构如下: 可以看到,备忘录模式中有三种角色: Originator: 即原发器,持有内部状态,提供创建备忘录的方法,以保存某个时刻内部的全部或部分状态,同时还提供还原的方法,从而支持后续可以还原到保存的状态 Memento: 备忘录对象,它存储 Originator 的状态,但要求它能够防止除了 Originator 之外的对象访问备忘录 Caretaker: 管理者,它负责管理备忘录,包括存储、删除操作,但是要求它本身不能访问和修改备忘录 备忘录的宽窄接口 备忘录对象它要求能够防止除了 Originator 之外的对象访问备忘录,而管理者又不能访问和修改备忘录,因此,要求备忘录能够具有 宽窄两种接口,Originator 能够通过 宽接口 创建、访问、修改备忘录对象,而管理者只能使用 窄接口 存储、删除和允许他人查询备忘录,本身不能修改和访问备忘录。 备忘录模式具有如下的优缺点: 抽取了备忘录对象,用来保存状态,减轻了原发器的职责 备忘录和原发器都可以再次抽象出单独的接口,便于扩展 备忘录会创建多个状态的副本,可能造成很大的开销 管理者管理多个备忘录,但它并不知道备忘录的内部状态,一个小的管理者可能存储和删除很大的备忘录,带来大的开销 备忘录模式适用于以下场景: 对象需要保存自身某一时刻的状态,以便后续进行恢复 对象保存自身状态时不暴露其实现细节,需要保持其封装性不被破坏 3. 实现 备忘录模式的实现大概有三种方式,每一种都有其适用场景。 3.1. 标准实现 标准实现方式,它抽取备忘录为单独的对象,由管理者来存储。我们以仙剑游戏为例,看看示例代码实现,如下: 1、定义备忘录对象 public class Memento { private State state; public Memento(State state) { (1) this.state = state; } public State getState() { return state; } public void setState(State state) { this.state = state; } } ...

2022-01-12 · 3 min · 614 words · Hank

Java设计模式(22)-迭代器模式

生活中,存在这样的场景:需要逐个遍历一堆对象,然后判断对象是否符合要求,做出相应的处理。例如,乘火车时,检票员站在门口挨个检票,没有买票的人不能乘车。类似的场景还有很多,比如乘地铁、乘公交、从书架上找书、食堂排队打饭等等…… 我们把需要逐个遍历的这一堆对象称为 对象集合,把挨个遍历的过程称为 迭代。迭代时,如果能将迭代过程从对象集合中抽取出来单独实现,让对象集合只负责管理自身状态,而不用负担迭代的任务,这就减轻了对象集合的职责,这就是今天要说的——迭代器模式。 也许你会想,对象集合不就是 List 吗,直接使用 "增强for循环" 遍历不就完成迭代过程了吗?说的没错!迭代器模式在很多面向对象的语言中都已经内置了,比如 Iterator,所以我们大多数情况下不会再自己实现它,但是学习模式本身的原理还是非常有必要。 1. 概念 DP对迭代器模式的定义如下:迭代器模式提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。 这个定义还是比较抽象,简单而言:迭代器模式,提倡将聚合对象(就是前边提的 "对象集合")的迭代逻辑抽取成一个 迭代器,聚合对象本身只需要提供一个返回迭代器的方法即可,而不需要关注迭代逻辑。客户端拿到对象集合的迭代器,就可以遍历了。 这种由客户端来决定如何遍历的方式,被称为 外部(主动)迭代器;还有一种迭代方式,客户端调用一个方法告诉聚合对象它的每一个元素该如何处理,然后由聚合对象内部负责迭代,这被称为 内部(被动)迭代器。Java 两种都支持,比如外部用增强for循环遍历 List,就是外部迭代器,而调用 List 的 forEach(Consumer<? super T> action) 方法,或者调用 Iterator 的 forEachRemaining(Consumer<? super E> action) 方法就属于内部迭代器。 聚合对象中的元素并不一定是有序存储的,比如 Set。此外,迭代器除了从头开始遍历聚合对象,还可以实现从尾部开始遍历,这取决于具体实现。比如,Java 提供了 ListIterator 支持双向迭代,它继承自 Iterator。 2. 结构 迭代器模式的结构如下图所示: 这里我使用的Java的泛型方式来定义的类,标准的迭代器模式显然没有泛型。从图可知,迭代器模式一共分为四个角色: Aggregate<T>: 抽象聚合对象,定义了通用的接口方法,通常是一个对象集合,支持存储多个对象,并支持创建一个迭代器。如 Java的 List、Set、Coleection 等接口 Iterator<T>: 抽象迭代器,定义了迭代的接口方法,如 next 迭代下一个对象,hasNext() 返回是否还有下一个对象等等 ConcreteIterator<T>: 具体迭代器实现,一个 Aggregate 可能存在多个迭代器实现 ConcreteAggregate<T>: 具体聚合对象,如 Java中的 ArrayList,HashSet 等等 ...

2022-01-11 · 3 min · 571 words · Hank

Java设计模式(21)-中介者模式

如果您有租房的经历,那么您对中介并不陌生。租房时,我们先去房屋租赁中介登记,告诉它您的租房需求,然后中介会按照您的要求为您筛选适合的房子。房子确定后,您会与中介签订租赁合同,缴纳费用,然后拿到钥匙……整个过程中,所有事项都由中介一手包办,入住以后房屋有任何问题,您都直接去找中介,您甚至可能并不知道房东是谁。这就是我们今天要说的模式——中介者模式。 中介者模式来源于生活中的各个中介机构,不如前边提到的房产中介、贷款中介、票务中介等等。 1. 概念 DP 对中介者模式的定义是这样的:用一个 中介对象 来封装一系列的对象交互,各个对象 不显式地相互引用,从而使其 松散耦合,而且可以独立的改变它们之间的交互。这完全符合 迪米特法则。 简而言之,中介者模式将多个相互引用的对象解耦,使得它们不直接交互,而是"有事找中介"。 除了生活中的中介机构外,中介者模式在软件中应用也很多,比如,服务网关,各个服务不直接通信,而是都通过网关来互相调用;又比如,MVC 模式中,service 层就是使用典型的中介者模式,因为 service 层依赖了多个 dao 层的类,而 dao 层的类之间并不直接交互…… 2. 结构 中介者模式的类图如下: 存在4个角色: Mediator: 抽象中介者,定义了通用接口让同事类之间进行通信 ConcreteMediator: 具体中介者,实现 Mediator 的具体业务来支持同事类之间的通信 Colleague: 抽象同事类,定义同事类通用接口,内部聚合 Mediator ,通过它与其他同事类交互 ConcreteColleague: 具体同事类,调用 Mediator 实现与其他具体同事类的通信 在中介者模式中,同事类之间互不认识,但是他们都认识中介者,就好比租房时您不认识房东,但是房东和您都认识中介,通过中介来完成通信,因此,中介者是作为一个中心化的角色存在。 优点: 同事类之间完全结构,符合 迪米特法则] 同事类可以扩展和复用,并且减少了它们的子类数量 简化了对象模型,由多对多关系变成了一对多关系 中介者决定同事类之间如何交互,而抽象中介者对这种交互进行了抽象,便于扩展 缺点: 同事类之间的交互完全由中介者来控制,中介者复杂度增加 中心化的中介者出现问题,则同事类之间无法交互,即存在单点问题 3. 适用场景 中介者模式适用于以下场景: 多个对象间存在复杂的依赖关系,结构混乱且难以理解 一个对象引用其他多个对象,并直接与它们通信,导致难以复用该对象 一个对象被多个对象引用,难以扩展,又不想为其生成子类 4. 与其他模式的区别 与观察者模式 上一篇 讲了观察者模式,与中介者模式非常类似。 结构类似,都定义了类似的4个角色,两个抽象角色和两个具体角色 两者实现的功能不同,观察者模式适用于一个对象改变而其他对象也需要做出改变的场景,而中介者模式重点在于解耦对象间多对多的复杂通信关系 中介者模式可以与观察者模式组合使用,如:中介者也作为通知发送者 与外观模式 中介者模式与 外观模式 也类似: ...

2022-01-08 · 2 min · 303 words · Hank

Java设计模式(20)-观察者模式

很快又到年底了,一年一度的春节除了可以享受愉快地享受假期之外,每年的"春节联欢晚会"也是让人倍感期待。然而,大多数人都没能坐在电视机跟前等着它的开始,有的人在厨房忙着准备丰富的食物,有的在一起打麻将、玩牌,还有的可能在玩电脑游戏……大家都想看晚会,于是派出一个小朋友,告诉他:"如果晚会开始了,你要立刻来通知我们哦!"虽然,大家就可以继续做自己的事情,小朋友就坐在电视跟前,当晚会开始的时候,他就立即跑去挨个通知大家…… 观察者模式示意(图片来源网络) 模式源于生活,上边的这个场景,就是一个典型的观察者模式的例子。 1. 概念 先来看看观察者模式的定义。 DP 对观察者模式的定义 观察者模式(Observer),又叫发布-订阅(publish-subscribe)模式,定义了对象间的一种一对多的依赖关系,当一个对象发生改变时,所有依赖它的对象都得到通知并被自动更新。 生活中,有很多这样的一个对象依赖其他多个对象(一对多关系)的例子,该对象状态发生变化了,其他关联的对象都需要知道并且做出行动。如上边的小孩儿看到晚会开始了,要立即通知其他人,其他人可以选择去看春晚,也可以选择继续做自己的事情。又比如,路口红绿灯变绿时,意思是告诉大家可以通行了;微信群有人发消息了,群成员都可以看到这条消息,等等…… 在软件领域,观察者模式的运用非常多,如:消息队列(MQ) 可以视为观察者模式实现;又比如,响应式编程 的核心就是观察者模式。 2. 结构 观察者模式类图如下: 可以看到,观察者模式有4个角色: Subject: 抽象主题对象,又叫做被观察者(Observable),或者目标对象,定义了添加、删除和通知观察者的方法,知道注册的观察者列表 ConcreteSubject: 具体主题对象,实现 Subject,有自身的状态,状态变化后向各个 Observer 发出通知 Observer: 抽象观察者,定义观察者的通用接口,包含一个用于更新的 update 方法 ConcreteObserver: 具体观察者,实现 Observer,依赖 ConcreteSubject ,当 ConcreteSubject 变化时得到通知,自身也可以更改 ConcreteSubject 的状态,此时可以通知其他观察者更新 上边的几个角色,将抽象和实现相分离,主题对象和观察者都可以各自进行扩展。另外,并不总是主题对象调用 notify 方法去通知观察者们,观察者也可以调用它从而通知其他观察者。 优点: 观察者模式降低了对象间的耦合性,提高了复用性,符合依赖倒置原则 主题对象广播通知给所有观察者,并不关心具体的观察者是谁,易于添加和删除观察者,而具体的更新与否以及如何更新由观察者自身实现 缺点: 观察者自身并不知道其他观察者的存在,它对更改主题状态的代价一无所知,因此需要定义和维护依赖准则,否则可能引起错误更新 主题和观察者之间仍然存在耦合性(没有完全解耦),存在相互依赖关系甚至可能造成循环依赖 3. 适用场景 观察者模式的使用场景: 对象间存在一对多的依赖关系,双方都需要独立的扩展和复用 一个对象的改变会同时改变其他依赖对象 一个对象必须通知其他对象,但是并不知道其他对象具体是谁 4. 示例 接下来看看观察者模式的示例代码(注意,下边的代码没有考虑并发安全性)。 1、定义观察者 public interface Observer { void update(); (1) } ...

2022-01-06 · 3 min · 489 words · Hank

2021过去了,我都干了些啥?

2021年又过去了,不得不让人感叹岁月如梭、光阴似箭!正值元旦假期,刚好有时间梳理一下这过去一年的种种,思来想去,还是不过"平凡"二字罢了。 1. 去年计划完成情况 每年的年底,我都会做一下年终总结,也定制一下新的一年的计划。现在回过头去看,发现去年的计划完成度也只有40%,很多事情还是没有能够完成。一方面,计划可能定得过高,完成起来有一定的难度;另一方面,自身的自制力还有待提高,很多事情没有能够坚持去完成;再者,诸如读书这样的事情,如果阅读了但是没有去理解、梳理和总结,还是很难形成系统的经久不忘的知识。 先从工作和生活上来聊聊计划之内的事情,然后,作为一个技术人,着重从技术和个人能力修炼上自我剖析一下去年一年所完成的技能。 1.1. 生活篇 从2021年开始,我计划每年带父母、老婆和孩子们出去旅游至少一次,2021年完成了这一目标,虽然走的不远,但是回想起来还是满满的幸福感。 作为技术人,平时难免加班加点的工作,陪伴家人的时间很少。所以,一家人出去旅游一下也是一种不错的选择。我们在端午节花了几天时间去了峨眉山、乐山大佛,本想着错开人流高峰时间,结果没想到去乐山大佛的时候还是遇到了人流洪峰,整整排队3个小时才看到了大佛😅。两天下来,从攀登峨眉山的兴奋,到看到金顶的激动,到漫长排队看大佛的百无聊赖,到看到大佛的肃然起敬,再到后来的精疲力尽,真的是波澜起伏、人间百味!玩了两天下来,我们都已经疲惫不堪,不过远离尘世的喧嚣,静下心来仔细品味一下生活,享受人生中的酸甜苦辣,何尝不是一件幸福的事情呢? Figure 1. 峨眉山金顶(图片来源网络) 生活习惯上,仍然没有太大的变化。虽然计划着要改变自己晚睡的习惯,然后还是没有做到。明明知道熬夜带来的危害,但还是深夜十二点还在电脑上敲敲打打的写着代码……今年,希望自己能够克服这个毛病,养成早睡早起的习惯,做到严格自律,以免将来后悔。正如《自律的人生更自由》里说的:与其躺到病床上后悔,不如现在就做出改变。 1.2. 工作篇 年初时,老总制定了明确的目标,每个阶段完成的任务都列的清清楚楚,可是完成情况却很糟糕,主要是酒店行业的产品在疫情影响下推动起来相当困难。比如,我们推出的一套酒店服务人员综合素质能力培养的产品,本来预计下半年应该会有比较上规模的用户使用,可惜在疫情影响下,酒店经营者自身尚且难保,更别说在员工的软硬技能上再下功夫了。这一点在2020年全年已经体现的非常突出了,可以说新冠疫情对酒店行业的冲击是常人难以想象的,虽然2021年酒店业有一些复苏,但是同2020年比还是增长不大,而且实现营收增长的多为企业级连锁酒店。 在公司待了5年了,目前而言公司的业务线还没有真正地走上正轨,前期的产品铺垫几乎没有收到任何效果,尤其是在这几年疫情的影响下,每迈出一步都显得那么沉重。今年,我们着重再打造酒店人员实习平台,解决职业院校学生实习管理难、数字化程度低、信息化建设落后等问题,尽管现在产品已经足够完备,但是缺少验证她的用户群,22年的工作重心必定在产品的推广和优化上。希望新的一年,公司能够顺利推动业务,打磨出更高效、更易用的产品。 1.3. 技术篇 学习的技术,最好的入口是官方文档,大多的技术官网都会有n分钟guide,可以教你一步步由浅入深入的学习。当然,前提是英语必须要好,否则阅读起来非常吃力,而且会影响我们的阅读的积极性。当然,也可以借助 Chrome 的翻译工具来阅读,不过这种机器翻译准确性很难保证,所以我经常是先阅读英文,不懂的地方或者不重要的地方在通过翻译工具去阅读。技术的学习离不开实践,如果只阅读不实践,最后读过的东西也成了过眼云烟。 去年,我学习了这些技术,很多技术都是经常使用,但是没有系统地梳理它们,所以花了一些时间专门去进行系统性的学习。现在看来,在技术上花费的时间还是太少,今年需要付出更多的时间来继续钻研技术。 Spring Cloud 去年,我将 Spring Cloud 从原来的 Edgware 版本升级到了 Hoxton 版本,将 Spring Cloud 的很多组件都换成了 Spring Cloud Alibaba 的,比如注册中心从 Eureka 换成了 Nacos。升级过程中,还是遇到了很多问题,最后都一一解决了。一个最大的问题是网关,原来的是 Zuul 1.x 版本,后来换成了 Spring Cloud Gateway,但是后者是基于 WebFlux 的,这是一个实现了响应式标准 Reactive Streams 的异步响应式编程框架,底层默认使用 reactor 框架,这对于我们团队而言是一个全新的技术,我们不得不下功夫去学习。 WebFlux 与响应式编程 响应式编程是一门较新的技术,目前的响应式框架,除了较早的 RxJava 和 Vert.x 之外,还包括专用于开发云原生应用的 Quarkus,以及 Spring 家族的 WebFlux。 在项目中,使用 Spring Cloud Gateway,必须先去学习 WebFlux 和响应式编程,目前来说,对其掌握还不够系统和全面,所以今年的目标是要系统的去学习 WebFlux 和响应式编程。 ...

2022-01-03 · 1 min · 173 words · Hank

什么是时序攻击Timing Attack

最近在维护一个老项目的时候,看到一段密码匹配的代码,感觉很奇怪,于是遍寻资料,最终还是很有收获。 1. 密码匹配 事情是这样的,一个项目遇到输入的密码总是提示密码错误的问题,于是debug跟踪了一下代码,发现该项目使用了 Spring Security 3.2.2 的 PasswordEncoder 来做密码MD5加密和密码匹配验证,PasswordEncoder 只是一个接口,其实现使用的是 StandardPasswordEncoder 类,它的密码匹配代码引起了我的注意: public boolean matches(CharSequence rawPassword, String encodedPassword) { (1) byte[] digested = decode(encodedPassword); byte[] salt = subArray(digested, 0, saltGenerator.getKeyLength()); return matches(digested, digest(rawPassword, salt)); } private boolean matches(byte[] expected, byte[] actual) { (2) if (expected.length != actual.length) { return false; } int result = 0; for (int i = 0; i < expected.length; i++) { result |= expected[i] ^ actual[i]; (3) } return result == 0; } ...

2021-12-30 · 2 min · 293 words · Hank

Java反射之AnnotatedType接口

上一篇Java反射之获取注解的AnnotatedElement接口介绍了 AnnotatedElement 接口,它是用反射获取注解的顶级接口。其实现类或子接口包括 Class、TypeVariable、Parameter、Package、AccessibleObject、GenericDeclaration 等,这些在前边的文章中已经介绍过。另外,它还有一个重要的接口 AnnotatedType,本篇将介绍它。 1. 再说注解 JDK1.5推出了注解功能,给Java语言带来非常强大的诸多特性。Java注解有几种保留策略:源码、运行时、类,可以在创建注解时通过 @Retention 元注解来指定,它需要引用 RetentionPolicy 中定义的策略: SOURCE: 注解只在源码层面保留,编译后被丢弃 CLASS: 默认的保留策略,注解将由编译器记录在类文件中,但在运行时不需要由 JVM 保留 RUNTIME: 注解不仅保留在类文件,在运行时 JVM 也会保留它,因此可以通过反射获取注解 注解的作用大概可以分为以下几种: 提供编译期特性:Java提供的一些注解,可以进行编译期特性支持,比如 @SuppressWarnings, @Deprecated 和 @Override 等可以提供编译期检查,@FunctionalInterface 标注函数式接口等 生成帮助文档:Java的 @Documented 注解可以用来标记生成 javadoc 时保留类或接口上的注解 运行时获取注解实现特定逻辑:这个是使用最多的场景,使用注解,并通过反射获取注解,来实现自身业务逻辑,大多数框架都使用注解来实现高级特性,比如 Spring 的 @Bean、 @Service 等注解 一句话,有了注解,我们可以方便的按需实现自身业务逻辑。在JDK1.8之前呢,注解所能表示标注的位置有限,JDK1.8又新增了两种标注位置,支持将注解标注在任何类型上。 2. 再说注解标注的位置 还记得 上一篇 介绍注解标注的位置信息吗?文中提到,通过 ElementType 枚举来定义注解可标注的位置信息,其他包括下边两项: ElementType.TYPE_PARAMETER: 标注在类型参数上,比如泛型参数T、E上 ElementType.TYPE_USE: 使用类型,注解可以标注在任何类型变量上 它们都是在JDK1.8时新增的,TYPE_PARAMETER 主要目的在于支持将注解标注在泛型类型的参数上,而 TYPE_USE 范围更广,可以将注解标注在任何Java类型上。也就是说,JDK1.8之前,注解只能标注在特定的位置(类、方法、包、参数、域、构造器、本地变量、注解),不能标注在泛型参数上,JDK1.8之后,可以标注在任何类型上。 使用TYPE_USE就可以省略其他 ElementType 元素吗? 既然 TYPE_USE 可以表示标注在所有类型上,那么是不是就不需要其他 ElementType 元素了呢?肯定不是!TYPE_USE 有其特殊的目的,它表示 “类型使用” 类型,其实就是指任何Java类型,但是它只能用 AnnotatedType 来描述并获取注解信息(见这里)。 ...

2021-11-25 · 5 min · 1042 words · Hank

Java反射之获取注解的AnnotatedElement接口

在Java反射的体系结构一文中说过,AnnotatedElement 接口定义了通过反射获取注解信息的方法,它是一个顶层接口,能够被标记注解的反射类都实现了该接口从而得到反射获取注解的能力,比如 Class、Field、Constructor、Method、Parameter 都实现了该接口。本文将详细介绍 AnnotatedElement。 1. 再说注解 注解是JDK1.5推出的新特性,用于标注Java中的特定元素,以便通过反射来获取它们,并实现不同的功能。本文不会讨论注解的基本知识,有兴趣的可以自行查阅相关资料。但是注解有一些特性需要细说一番。 1.1. 注解标注的位置 Java中,注解可以标注在多个位置上,常见的包括类、接口上、方法(构造函数)、方法参数(构造函数参数)、成员域上以及标注在注解上(注解的注解),甚至可以将注解标注在 package-info.java 文件中定义的包上,这些位置是在定义注解时通过元注解 @Target 来指定的,比如向下边这样: @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PACKAGE, ElementType.TYPE, ElementType.FIELD}) public @interface CustomAnno { String value() default ""; } 可以看到,通过 ElementType 来指定注解可以标注的位置,它定义了以下几种位置: ElementType.TYPE: 标注在类型上,包括接口、类、枚举等 ElementType.FIELD: 标注在类、接口的成员域上,包括枚举的元素 ElementType.CONSTRUCTOR: 标注在构造器上 ElementType.ANNOTATION_TYPE: 标注在其他注解上 ElementType.METHOD: 标注在方法上 ElementType.PARAMETER: 标注在方法参数上 ElementType.LOCAL_VARIABLE: 标注本地变量上,即构造器、静态代码块、方法等的内部变量 ElementType.PACKAGE: 标注在 packag-info.java 文件中定义的包上 ElementType.TYPE_PARAMETER: 标注在类型参数上,比如泛型参数T、E上 ElementType.TYPE_USE: 使用类型,注解可以标注在任何类型变量上 上边的最后两个是在JDK1.8新增的,用于在注解标注在泛型上的情况,涉及到 AnnotatedType 接口,我们将在下一篇专门行文来介绍它。 注解用于包上的例子: package-info.java @CustomAnno("package") package com.belonk.lang.reflect; 其他标注位置比较常见,就不在赘述了。 1.2. 注解的继承 Java中定义注解时,可以指定其是否可以被继承。注解继承指的是父类标注的注解可以作用于子类,而不是说一个注解继承另一个注解。比如,自定义如下注解: @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Inherited (1) @interface MyAnno { String value() default ""; } ...

2021-11-21 · 3 min · 569 words · Hank

Java反射之获取泛型类定义的具体类型

通过上一篇Java反射之表示所有类型的Type接口一文,我们知道 Type 接口及其五大组件是用来描述Java的各种类型,主要包括原始类型和泛型类型。很多时候,我们需要获取到泛型类上定义的具体类型,从而完成一些业务逻辑。比如,最常见的情景就是JSON的反序列化,需要将JSON字符串反序列化为泛型的具体类型,比如反序列化为 List<User>,这就要求每一个 List 中的元素都是 User 对象。那么,如何获取到泛型类上定义的具体类型呢?这就是本文要阐述的内容。 1. 通过Class能获取泛型类型吗? 在/2021/11/09/reflect-class.html中说过,Class 类上有一个 getTypeParameters() 方法用来获取类上的类型变量。通过它能不能获取到泛型类型呢?看下边的示例: class Parent<T> { (1) } 1 定义一个泛型类 这里定义一个泛型类,现在尝试通过它的实例对象来获取泛型类型,代码如下: public void printParent() { Parent<String> parent = new Parent<>(); (1) TypeVariable<? extends Class<? extends Parent>>[] parentTypeParameters = parent.getClass().getTypeParameters(); (2) System.out.println(Arrays.toString(parentTypeParameters)); } 1 创建 Parent 类的实例 2 获取类型变量 上边的示例并不能拿到实例上的 String 类型,而只能拿到 Parent 类上定义的泛型类型 T,因此,输出结果为: [T] 很显然,这个结果不是我们想要的,我们想要获取到 String 这个类。在 Java反射之创建泛型数组 一文中说过,泛型是编译期特性,在运行时泛型会被擦除。因此,我们想通过`Class` 在运行时用反射直接去获取泛型类型,看起来是不行的。 ...

2021-11-15 · 3 min · 495 words · Hank

Java反射之表示所有类型的Type接口

前边说过,JDK1.5推出了反射功能,并介绍了反射的类设计结构,其中提到了 Type 接口,它是JDK1.5新增的接口,表示Java中的所有类型。本文介绍这个接口及其五大组件。 Figure 1. Type及其组件 1. Type的五大组件概览 Type 接口表示Java中的类型,这些类型包括原始类型、参数化类型、数组类型、类型变量和基本类型,前边介绍的 Class 就是其组件之一。Type 及其组件用于通过反射来描述Java各种类型,比如描述泛型的定义,获取泛型参数个数、类型、泛型通配符的上下边界等等。一个典型的应用场景是获取真实的泛型类型,我们将在后续文章单独介绍。 Type 用其五大组件来描述上边提到这些类型,类图如下: Figure 2. Type类图 ParameterizedType: 表示参数化类型,即整个泛型的定义,如 List<String> 可以用 ParameterizedType 来描述; TypeVariable: 表示类型变量,即泛型申明中的具体类型,比如: List<E> 用参数化类型来描述,而其中的 E 就用 TypeVariable 来描述; WildcardType: 表示泛型通配符类型,即泛型申明中带 ? 通配符,比如:List<? extends Number> 中 ? extends Number 就用 WildcardType 来描述; GenericArrayType: 表示泛型数组,比如申明一个泛型数组的域 T[] array,它就用 GenericArrayType 来描述; Class: 表示运行时的类或接口,在前边Java反射的体系结构一文中已经介绍过了,不再赘述。 一句话总结,Type 的 Class 组件用来描述Java类或接口等原始类型和基本类型,其他几个组件描述泛型类型,包括泛型的变量、通配符以及泛型数组。 现在,来看看这几个组件的用法。 2. 参数化类型ParameterizedType 什么是参数化类型?其实就是指泛型,具体而言,JDK5推出泛型之后,原来的具体的类型可以申明为泛型,变得"可被参数化"了,比如:List 是一个具体类型,但是可以申明为 List<String>、List<Integer> ,就仿佛可以申明不同的参数,类似方法可以申明不同的参数一样(其实泛型是编译期特性,它两实际上是同一类型,都是 List,这在 Java反射之创建泛型数组 已经说过了)。 所以,ParameterizedType 用来描述泛型类型,比如:List<T>、List<String>、User<T extends Number>、List<? super User> 都可以用它来描述。ParameterizedType 的定义如下: ...

2021-11-14 · 4 min · 804 words · Hank

Java反射之创建泛型数组

前边介绍了Java泛型类设计体系中的一些常用类,本篇将着重介绍 Array 类和如何创建泛型数组。 1. Java能创建泛型数组吗? 1.1. 泛型类型擦除 首先,我们知道,泛型是JDK1.5推出的编译期特性,什么是编译期?即是说在编译的时候有效,编译代码时编译器能够对泛型进行检查和校验。但是如果在运行时,泛型其实已经无效了,泛型类型被擦除了。比如下面的代码: 代码清单1 List<String> stringList = new ArrayList<>(); List<Integer> integerList = new ArrayList<>(); System.out.println(stringList.getClass() == integerList.getClass()); 结果输出为 true。stringList 只能添加 String 类型的数据,而 integerList 也只能添加 Integer,不能加入别的类型的数据。为什么?因为它们申明了泛型类型,编译器就会对类型兼容性进行检查,类型不符则会抛出异常。但是,他们的 Class 确是相同的,即是说,在运行时它们是相同的类型,都是 ArrayList,而泛型类型被擦除了。 1.2. 尝试创建泛型数组 那么,我们申明泛型数组会怎么样呢?看下边的代码: 代码清单2 class GenericArray1<T> { private final T[] array; (1) private int index; public GenericArray1(int size) { this.array = (T[]) new Object[size]; (2) } public void add(T item) { (3) array[index++] = item; } public T[] array() { return array; } } ...

2021-11-10 · 3 min · 469 words · Hank

Java反射的体系结构

反射是JDK5推出的非常重要的特性,通过反射,开发者可以在运行时动态的创建类实例,获取、更改、调用类的相关信息、方法等。反射也是各大框架使用的主要技术,如知名的Spring framework框架。 本文将介绍反射的类设计体系,并配备一些简单示例来介绍反射常用API的使用,本文示例都是采用JDK1.8编写。 1. 反射类设计 Java专门为反射设计了名为java.lang.reflect包,官方API文档在这里: 反射官方文档,该包提供了一系列类共同组成反射体系。 1.1. 反射类概览 既然反射是在运行时动态描述类信息的,那么描述类信息到底是些什么东西?类信息除了类、接口、数组、枚举等常见类型的基本信息外,还包括类型中的域、方法、构造器等以及它们的修饰符(public、static、volatile、private、final、native、synchronized 等),另外,还包括方法参数和注解。反射类如下表所示: Table 1. 反射中的类 类 描述 Class 位于java.lang包,描述类、接口的运行时状态,枚举、数组也是一种类,而注解是一种特殊的接口。每一个类型都有一个Class对象与之对应 Field 描述类型中的域信息 Constructor 描述类型中的构造函数信息 Method 类型中的方法信息描述 Parameter JDK8新增,提供有关方法参数的信息,包括其名称和修饰符。它还提供了一种获取参数属性的替代方法。 Modifier 类、域、方法、构造函数等的访问修饰信息工具类 AnnotatedElement 反射获取注解的顶层接口 Type Java中的所有类型的顶层接口 Array 用于动态创建和处理数组 Proxy 动态代理的支持类 这里暂时介绍基础的Class、Field、Constructor、Method、Parameter、Modifier,其他几个类会在后续文章中单独介绍。现在,我们来看看类图的设计。 1.2. 反射类图 整个反射的类设计见下图: Figure 1. 反射类图 图中只列出了比较基础且重要的类,从图中可以看到,反射的类结构并不算复杂,顶层主要包括几个重要接口和类: AnnotatedElement: 定义了反射获取java注解的方法,如 getAnnotations() 等,实现该接口的类意味着可以获取注解信息; Member: 定义获取类成员(域、构造函数、方法)相关的标识符信息的接口,比如获取名称、标识符等; Type: 所有类型的顶层接口,用来描述java中的类型,比如泛型类型、类字节码等,包含五大组件,其中包括最重要和常见的 Class 对象,后续文章将单独介绍这个接口和其组件; GenericDeclaration: 表示泛型申明的接口,继承自 AnnotatedElement,内部定义了 getTypeParameters() 方法,用于获取类型变量(TypeVariable,该接口为 Type 接口的一个子组件); AccessibleObject: 表示可访问对象,Field、Method 和 Constructor 对象的基类,用来检查和修改它们的访问权限:公共、包、私有访问,比如:调用其 setAccessible(boolean) 方法修改域的访问权限,然后才能修改域的值; Executable: 表示可执行的对象,其实就是方法和构造函数的基类; ...

2021-11-09 · 5 min · 869 words · Hank

MindManager windows 2020中文版本下载,带正版激活码

MindManger是Mindjet公司开发一款思维导图软件,目前已经更新到了2021版本。与其他几大思维导图软件相比,个人最钟爱MindManger,因为他功能更强大、界面更友好,支持强大的备注体系,插图片、查表格都可以,可以直接用来做知识体系整理,非常方便。 这里介绍的是2020版本,提供激活码,能够正常激活使用。 官网地址: https://www.mindjet.com/ 下载地址 由于版权限制,请关注公众号并在后台回复6001获取下载地址! 如果链接失效,请在公众号后台留言,我会第一时间补上。 软件截图: 扫码关注公众号,查看更多推荐资源! 下载声明 本站所有下载资源均来自互联网,由站长搜集整理,版本归原作者所有,如有侵权请联系站长删除。 本站所有下载资源仅供个人学习和研究使用,请勿用于商业用途,请在下载后24小时内删除。

2021-07-14 · 1 min · 12 words · Hank