Goroutines vs Threads

Here are some of the advantages of Goroutines over threads:

You can run more goroutines on a typical system than you can threads.
Goroutines have growable segmented stacks.
Goroutines have a faster startup time than threads.
Goroutines come with built-in primitives to communicate safely between themselves (channels).
Goroutines allow you to avoid having to resort to mutex locking when sharing data structures.
Goroutines are multiplexed onto a small number of OS threads, rather than a 1:1 mapping.
You can write massively concurrent servers withouth having to resort to evented programming.

You can run more of them
On Java you can run 1000’s or tens of 1000’s threads. On Go you can run hundreds of thousands or millions of goroutines.

Java threads map directly to OS threads, and are relatively heavyweight. Part of the reason they are heavyweight is their rather large fixed stack size. This caps the number of them you can run in a single VM due to the increasing memory overhead.

Go OTOH has a segmented stack that grows as needed. They are “Green threads”, which means the Go runtime does the scheduling, not the OS. The runtime multiplexes the goroutines onto real OS threads, the number of which is controlled by GOMAXPROCS. Typically you’ll want to set this to the number of cores on your system, to maximize potential parellelism.

They let you avoid locking hell
One of the biggest drawback of threaded programming is the complexity and brittleness of many codebases that use threads to achieve high concurrency. There can be latent deadlocks and race conditions, and it can become near impossible to reason about the code.

Go OTOH gives you primitives that allow you to avoid locking completely. The mantra is don’t communicate by sharing memory, share memory by communicating. In other words, if two goroutines need to share data, they can do so safely over a channel. Go handles all of the synchronization for you, and it’s much harder to run into things like deadlocks.

No callback spaghetti, either
There are other approaches to achieving high concurrency with a small number of threads. Python Twisted was one of the early ones that got a lot of attention. Node.js is currently the most prominent evented frameworks out there.

The problem with these evented frameworks is that the code complexity is also high, and difficult to reason about. Rather than “straightline” coding, the programmer is forced to chain callbacks, which gets interleaved with error handling. While refactoring can help tame some of the mental load, it’s still an issue.

鲁迅 聪明人和傻子和奴才(思考网易裁员事件)

聪明人和傻子和奴才
The Wise Man, the Fool and the Slave

奴才总不过是寻人诉苦。只要这样,也只能这样。有一日,他遇到一个聪明人。
What a slave did was just to look for someone to listen to his own grievances. That was the only thing he wanted to do and also the only thing he could do. One day he came across a wise man.

“先生!”他悲哀地说,眼泪联成一线,就从眼角上直流下来①。“你知道的。我所过的简直不是人的生活②。吃的是一天未必有一餐,这一餐又不过是高粱皮,连猪狗都不要吃的,尚且只有一小碗……”
“Sir!” said he sadly, tears trickling down from the corners of his eyes. “As you can see, I lead a subhuman life. I’m not even assured of a single meal a day. If I have one, it’s only a small bowl of kaoliang husks, which even a pig or dog would disdain to eat …”

“这实在令人同情。”聪明人也惨然③说。
“What a wretched life you lead!” the wise man replied with pity.

“可不是么!”他高兴了。“可是做工是昼夜无休息的:清早担水晚烧饭,上午跑街夜磨面,晴洗衣裳雨张伞,冬烧汽炉夏打扇。半夜要煨银耳,侍候主人耍钱④;头钱从来没分,有时还挨皮鞭……”
“Isn’t it?” the slave followed up with exaltation. “And I toil day and night without rest. I carry water at dawn and cook dinner at dusk. I run errands all morning and grind wheat at night. I wash the clothes when it’s fine and hold an umbrella for my master when it’s rainy. I take care of the heating stove in winter and keep cooling my master with a fan in summer. I boil white fungus for him late at night. I wait on him at his gambling table without ever getting a tip. Instead I sometimes get a good thrashing …”

“唉唉……”聪明人叹息着,眼圈有些发红,似乎要下泪。
“Oh, dear!” the wise man said with a sigh, the rims of his eyes looking somewhat red as if he were about to shed tears.

“先生!我这样是敷衍不下去的⑤。我总得另外想法子。可是什么法子呢?……”
“Sir! I can’t put up with it any more. I’ve got to find a way out. But what can I do?…”

“我想,你总会好起来……⑥”
“I’m sure you’ll pull through sooner or later …”

“是么?但愿如此。可是我对先生诉了冤苦,又得你的同情和慰安,已经舒坦得不少了。可见天理没有灭绝……”
“Really? I hope so. But, sir, I already feel much better now as you’ve given me sympathy and encouragement after listening to my grievances. It’s thus clear that Heaven always upholds justice …”

但是,不几日,他又不平起来了,仍然寻人去诉苦。
A few days later, however, he again began to grumble and look for somebody to listen to his complaints.

“先生!”他流着眼泪说,“你知道的。我住的简直比猪窠还不如。主人并不将我当人⑦;他对他的叭儿狗还要好到几万倍……”
“Sir!” he cried out tearfully. “You know, I live in a place even lousier than a pigsty. My master treats me like dirt. He treats his Pekinese ten thousand times better …”

“混账!”那人大叫起来,使他吃惊了。那人是一个傻子。
“Damn it!” the listener swore in such a loud voice as to make the slave start. This man was a fool.

“先生,我住的只是一间破小屋,又湿,又阴,满是臭虫,睡下去就咬得真可以⑧。秽气冲着鼻子,四面又没有一个窗……”
“Sir, I live in a run-down small hut which is wet, dingy, stinking and full of bedbugs. They bite me all over when I lie down to sleep. And the place doesn’t even have a single window …”

“你不会要你的主人开一个窗的么?”
“Why not ask your master to have a window made?”

“这怎么行?……”
“How can I do that? …”

“那么,你带我去看去!”
“OK, you show me around!”

傻子跟奴才到他屋外,动手就砸那泥墙。
As soon as they came to the slave’s dwelling, the fool started to pound its mud wall.

“先生!你干什么?”他大惊地说。
“What the hell are you doing, sir?” the slave yelled with alarm.

“我给你打开一个窗洞来。”
“I’m trying to knock a hole to make a window for you.”

“这不行!主人要骂的⑨!”
“No, you can’t do that! The master will be mad at me!”

“管他呢!”他仍然砸。
“To hell with your master!” The fool continued pounding away.

“来人呀!强盗在毁咱们的屋子了!快来呀!迟一点可要打出窟窿来了!……”他哭嚷着,在地上团团地打滚。
“Help! A robber is breaking down our house! Hurry up, or he’ll knock a big hole in the wall! …” Sobbing and shouting at the top of his voice, the slave rolled round and round on the ground.

一群奴才都出来了⑩,将傻子赶走。
Thereupon, a whole troop of slaves arrived on the scene and drove away the fool.

听到了喊声,慢慢地最后出来的是主人。
The last one that came out unhurriedly on hearing the commotion was the master.

“有强盗要来毁咱们的屋子,我首先叫喊起来,大家一同把他赶走了。”他恭敬而得胜地说⑪。
“A robber came to smash up our house,” the slave spoke respectfully and smugly. “I was the first to shout the alarm. We together drove him away.”

“你不错。”主人这样夸奖他。
“You did well,” the master praised him.

这一天就来了许多慰问的人,聪明人也在内。
A great many people came that day to express their solicitude, among them the wise man.

“先生。这回因为我有功,主人夸奖了我了。你先前说我总会好起来,实在是有先见之明……”他大有希望似的高兴地说。
“Sir, I’ve just been praised by my master for my meritorious service,” the slave said to the wise man very happily and hopefully. “I remember you said the other day that I would pull through sooner or later. So you’re really a man of foresight …”

“可不是么……”聪明人也代为高兴似的回答他。
“Oh, yeah …” replied the wise man as if he, too, were happy for the sake of the slave.

说明

《聪明人和傻子和奴才》是鲁迅(1881—1936)写于1925年12月的一篇短文,选自他的散文诗集《野草》。正如该书其他一些篇章,此文也以揭露和冷讽社会相为特点,刻画聪明人的刁巧与奴才之不可救药。

注释

①“眼泪联成一线,就从眼角上直流下来”译为tears trickling down from the corners of his eyes,未译为tears falling down in a string from the corners of his eyes,因trickling down表达“一连串落下”或“一滴滴流下”之意。

②“我所过的简直不是人的生活”译为I lead a subhuman life,其中subhuman作“非人的”(more like an animal than a human being)解。此句也可译为I lead a dog’s life。

③“惨然”在此应作“怜悯地”解,故译with pity。

④“侍候主人耍钱”意即“侍候主人赌钱”,故译I wait on him at his gambling table(或mah-jong table、gambling parties等)。

⑤“我这样是敷衍不下去的”意即“我无法凑合下去了”或“我不能再忍受了”,故译I can’t put up with it any more。

⑥“我想,你总会好起来……”可按“你迟早会渡过难关的……”译为I’m sure you will pull through sooner or later …。此句也可译为I believe things will improve eventually …。

⑦“主人并不将我当人”可译为My master treats me like dirt或My master doesn’t treat me like a human being。

⑧“咬得真可以”可按“浑身都被咬了”译为They bite me all over。

⑨“主人要骂的”可按“主人会对我大发脾气”之意译为The master will be mad at me。也可直译为The master will curse me或The master will swear at me。

⑩“一群奴才都出来了”译为A whole troop of slaves arrived on the scene,其中troop比group可取,因前者有“一起行动”的含意。

⑪“他恭敬而得胜地说”可按“他恭敬而沾沾自喜地说”译为the slave spoke respectfully and smugly。

遥远的救世主

1、 “着相”的无缘。 当女人不再需要脱下裤子换取温饱奢侈的时候,男人也不再显得那么重要,男女也就有可能平等了…

2、中国为什么落后?……五千年的文化积淀足以让你怕着胸脯说:我们有文化。但是五千年积淀却不能让你挺着胸脯回答说:我们有什么文化?因为有文化和有什么文化不是一个概念。

3、 你不缺一颗杀我的心,只是缺一个杀我而不影响自我评价的理由。

4、 天下之道论到极致,百姓的柴米油盐;人生冷暖论到极致,男人和女人的一个情字。

5、 不可思议一词不是众生道里的对神秘事物的描述,而是如是、本来、就是如此,容不得你思议。告诉你不可以思议,由不得你思议。从数学逻辑上说,一加一等于二,容得了你思议吗?不容,这就告诉你了,一加一等于二是规律,规律不以人的意志为转移,你只能认识、遵循,不可思议。

6、 佛教包括了佛法,而佛法有别于佛教。佛教以佛法证一,进而证究竟,最终是为给心找个不苦的理由,成佛,无量寿,极乐。佛教以假度真的方便法门住福相、住寿相、住果相,是以无执无我为名相的太极我执,致使佛教具有了迷信、宿命、贪执的弱势文化特征,已然障蔽佛法。

7、 救世主是没有的,只有自己救自己;救主,不是人,是道,得救不是破了戒的狼吞虎咽,是觉悟;强势文化造就强者,弱势文化造就弱者。

8、 悟道方知天命,修行务取真经, 一生一灭一枯荣,皆有因缘注定。 袈裟原本清净,红尘即是性空, 幽幽古刹千年钟,解了几多痴梦!

9、 神就是道,道法自然,按照规律办事的人就是神。

10、 女人哪,好多贱东西是骨子里生的,只要你是女人就扔不掉。

11、 任何一种命运,归根到底都是本国文化属性的产物,不以人的意志为转移。

12、 什么是客观规律?一切以时间、地点和条件为转移。

13、 愚昧是智者的社会资源。

14、 神即道,道法自然,如来。

15、 不能超越本能的男人,不算好汉。

16、 丁元英说:”想干成点事就记住两句话,别把别人不当人了,别把自己太当人了。就这点规律而言,天下乌鸦一般黑。”

17、 原来能做到实事求是就是神话! 原来能说老实话、能办老实事的人就是神! 因此可见,让人做到实事求是有多难,让人做到说老实话、办老实事有多难,而做到的人却成了说鬼话、办鬼事,倒行逆施。 这个世界怎么了?

18、 女人不是因为被爱才可爱,而是因为可爱才被爱。

19、 5年以后我不嫌你老,你就可以不老了吗?5年以后我变成了一个色狼,值得你回头看一眼吗?

20、 生存法则很简单,就是忍人所不忍,能人所不能。忍是一条线,能是一条线,两者的间距就是生存机会。如果能忍人所不忍,能人所不能,就比别人多了一些生存机会。

21、 隐藏与压抑,你不知道?或者知道,又能怎样?

22、 强势文化就是遵循事物规律的文化,弱势文化就是依赖强者的道德期望破格获取的文化,也是期望救主的文化。强势文化在武学上被称为”秘笈”,而弱势文化由于易学、易懂、易用,成了流行品种。

23、 古城是留不住你的,我也没奢望天长地久。你给我留个念想,让我知道你曾经这样爱我,我曾经这样做过女人,别让我把记忆都留在床上。

24、 智玄大师沉默不语,静静地看着丁元英,过了许久黯然感叹道:得智的得智,化缘的化缘,烧香的烧香,坐禅的坐禅。

25、 人是有命运的,文化属性和作用在不同人身上的自然规律决定了命和运,改变客观条件,才能改变命运。你是你自己的救世主。

26、 本是后山人, 偶做前堂客。 醉舞经阁半卷书, 坐井说天阔。大志戏功名,海斗量福祸。 论到囊中羞涩时, 怒指乾坤错。

27、 如果我的能力只能让我穷困潦倒,那穷困潦倒就是我的价值。

28、 公司在法律条款面前是一天都生存不下去,到时候不是考虑如何经营的问题,而是考虑怎么伺候好这群爷。

29、 说魔说鬼都是个表述,本质是思维逻辑和价值观与众人不同,所谓的地狱之门也无非是价值观冲突所带来的精神痛苦。如果你是觉者,我尊敬你,向你学习;如果你是魔鬼,我鉴别你,弃你而去。即便是价值观不同,就真有那么可怕吗?

30、 你能给我什么不重要,重要的是你别剥夺我什么。

31、 一个纯洁到一尘不染的女声,仿佛从天国里倾泻而下,仿佛是一双上帝的眼睛,怜悯地注视着人类,只一声,我骤然有一种灵魂之门被撞开的颤栗。

32、 你是一块玉,但我不是匠人,我不过是一个略懂投机之道得混子。充其量挣几个打发凡夫俗子得铜板,你要求得是一种雄性文化得魂,我不能因为你没有说出来而装作不知道,接受你就接受了一种高度,我没有这个自信。

33、 问:”为人处世当怎么做?” 答曰 :”随缘惜缘不攀缘。”

34、 修为成佛,再求;悟为明性,在知。修行以行制性,悟道以性施行。觉者由心生律,修者以律制心。

35、 透视社会依次有三个层面, 技术、 制度、和文化。小到一个人,大到一个国家一个民族任何一种命运归根到底都是那种文化属性的产物。强势文化造就强者, 弱势文化造就弱者,这是规律。也可以理解为天道,不以人的意志为转移。

36、 人是有命运的,文化属性和作用在不同人身上的自然规律决定了命和运,改变客观条件,才能改变命运。你是你自己的救世主!

37、 启迪人的觉悟、震撼人灵魂的精神拯救的暴利与毒品麻醉的暴利完全等值,而且不必像贩毒那样耍花招,没有心理成本和法律风险。

38、 品性这东西,今天缺个角、明天裂个缝,也就离塌陷不远了。

39、 更高级的哲人独处着,这并不是因为他想孤独,而是因为在他周围找不到他的同类。

40、 弱势得救之道,也有也没有。没有竞争的社会就没有活力,而竞争必然会产生贫富、等级,此乃天道,乃社会进步的必然代价。无弱,强焉在?一个‘强’字,弱已经在其中了。故而,佛度心苦,修的是一颗平常心。

41、 永远都不会跟你吵架。他的每个毛孔里都渗透着对世俗文化居高临下的包容,包容到不屑跟你讲道理,包容到让你自己觉得低俗自卑。

42、 这个年代,执着于出人头地并不难,难的恰恰是不执着于出人头地 ;一颗阴暗的心永远托不起一张灿烂的脸。

43、 不落恶果者有信无证,住因住果、住念住心,如是生灭。不昧因果者无住而住,无欲无不欲,无戒无不戒,如是涅碦。

44、 红颜知己自古有之,这还得看男人是不是一杯好酒,自古以来能有几个男人把自己酿到淡而又淡的名贵 ,这不是为之而可为的事,能混就混吧。

45、 认识这个人就是开了一扇窗户,就能看到不一样的东西,听到不一样的声音,能让你思考、觉悟,这已经够了。其它还有很多,比如机会、帮助。

46、 生老病死,有谁因为怕就躲过去了?

47、 只要不是我觉到、悟到的,你给不了我,给了我也拿不住,叶晓明他们就是例子。只有我自己觉到、悟到的,我才有可能做到,我能做到的才是我的。

48、 扶贫的事若以次第而分,也有三个层面。一、天上掉馅饼的神话,实惠、破格,是为市井文化。二、最不道德的道德,明辨是非,是为哲人文化。三、不打碎点东西不足以缘起主题,大智大爱,是为英雄文化。

49、 佛乃觉性,非人,人人都有觉性不等于觉性就是人。人相可坏,觉性无生无灭,即觉即显,即障即尘蔽,无障不显,了障涅碦。觉行圆满之佛乃佛教人相之佛,圆满即止,即非无量。若佛有量,即非阿弥陀佛。佛法无量即觉行无量,无圆无不圆,无满无不满,亦无是名究竟圆满。

50、 强势文化就是遵循事物规律的文化,弱势文化就是依赖强者的道德期望破格获取的文化,也是期望救主的文化。强势文化在武学上被称为”秘笈”,弱势文化由于易学,易懂,易用成了流行品种。

51、 所谓真经,就是能够达到寂空涅盘的究竟法门,可悟不可修。修为成佛,在求。悟为明性,在知。修行以行制性,悟道以性施行,觉者由心生律,修者以律制心。不落恶果者有信无证,住因住果、住念住心,如是生灭。不昧因果者无住而住,无欲无不欲,无戒无不戒,如是涅盘。

52、 因为你不知道你,所以你是你。你知道了你,你就不是你。

53、 晚辈个人以为,佛教以次第而分,从精深处说是得道天成的道法,道法如来不可思议,即非文化。从浅义处说是导人向善的教义,善恶本有人相、我相、众生相,即是文化。

理解 raft 算法

raft 算法是一种共识算法,其解决的分布式环境中的数据一致性的问题

leader election

在 raft 算法中,每个节点有三种状态:

  • Follower
  • Candidate
  • Leader

所有的节点初始都是 follower ,如果 follower 节点没有收到任何 leader 节点的消息,这些节点将变成 candidate ,candidate 节点开始向其他节点请求投票,节点会返回投票信息,如果一个 candidate 获得所有节点中的多数投票,则他会变成 leader 状态,这个过程称之为 leader election

在 raft 中有两个 timeout 设置控制着 election 的进行。

第一个是 election timeout,意思是 follower 要等待成为 candidate 的时间,这个时间是一个介于 150ms 到 300ms 的值,这个时间结束之后 follower 变成 candidate 开始选举,首先是自己对自己投票,然后向其他节点请求投票,如果接收节点在收到投票请求时还没有参与过投票,那么他会把票投给这个请求投票的 candidate,然后重置自身的 election timeout,一旦一个 candidate 拥有所有节点中的大多数投票,他变成一个 leader。

第二个是 heartbeat timeout,一旦一个 candidate 成为 leader,他开始向其他 follower 发送 append entries,这些消息发送的频率是通过 heartbeat timeout 指定,follower 会响应每条的 append entry,整个 election 会一直进行直到 follower 停止接受 heartbeat 并且变成 candidate 开始下一轮 election。

假设 leader 故障了,follower 不再收到 heartbeats,新一轮 election 开始,整个过程重复上述步骤。

需要节点中的多数节点的投票才能成为 leader 保证了在每轮选举中只有一个 leader 可以胜出,如果一轮选举中有两个节点同时成为 candidate 将会导致 split vote 发生,如果此时两个 candidate 都收到了相同的票数,他们重置 election timeout 重新开启新一轮选举。

log replication

leader 成功选举之后,之后 client 的请求都先经过 leader,每个请求的更改以日志的形势保存在 leader 节点,但这些更改是 uncommitted 状态,为了对这些更改进行提交,leader 首先 replicate 这些更改到 follower,等到 follower 中的大部分提交之后才会 commit 这些更改,commit 之后通知 follower 更改已经 commited,这个系统现在达到了一致的状态,这个过程称之为 log replication

network partitions

raft 算法可以应对 network partitions。

比如由于网络分区导致了 C、D、E 和 A、B 隔离,各自分区中会重新开始选举形各自形成新的 leader

在各自分区之内,各自 leader 会收到不同的 client 发送的请求,由于在 B 分区内,leader 无法获得多数节点的投票,因而 leader B 上发生的更改不会被提交,等网络分区修复之后,A 和 B 的 term 比较小,他们会自动下线,回滚之前的提交,等待新的 leader 发送 hearbeat

参考

技术提升的一个方法

最近看到知乎上的专栏https://zhuanlan.zhihu.com/c_183152541,感触颇多。

要突破自己首先的热爱编程,敬畏程序。毕竟有道无术,术尚可求,有术无道,止于术也。

你发现你的开发过程中总是需要重复复制-稍微改吧改吧-粘贴这个过程,你有没有去尝试阅读并实践《重构》?

你有没有尝试去自己发布一个公用的库?JCenter/Maven Central都是免费的。

你发现你每次修改完代码要抄起Postman点来点去

你有没有尝试去编写一个集成测试,代替手工的劳动?

你发现你碰到了很多奇奇怪怪你搞不明白的问题,只能一次次地尝试每个搜索结果中提到的解决方案,期望其中的某一个好使。
你有没有尝试过去阅读相关的书籍,查阅相关文档?

如何突破自己
如何成为顶级程序员
跳出弱鸡循环1
跳出弱鸡循环2

总结下来提升手段:
1.实践自动化测试

当你觉得技术上遇到瓶颈的时候,可以通过学习和实践自动化测试,来补全自己在相关工程领域的技术短板,提升自己的技术能力
从测试中学到的不仅是写测试用例的能力,而是一整套的工程化、自动化的能力。

实践流程
第一步,去看一下《Maven实战》,了解一下Maven的测试是怎么工作的。之所以让你去研究Maven,是因为你们这种系统,99%不会采用Gradle这种新技术的。
第二步,写第一个测试,代码如下:

public class MyTest {
    @Test
    public void 跳出弱鸡循环() {
    }
}

第三步,去搞一份你们线上数据库的表结构。各种数据库都有相应的命令dump表结构。有困难的的话,手写建表语句。
第四步,本地用Docker启动一个临时的数据库。
第五步,去研究一下flyway,用自动化方式把表结构灌到这个临时数据库里。
第六步,去了解一下你们的应用是怎么部署的,你们上线的应用不可能是通过在IDE里面点绿色三角来部署的。把部署的命令行要过来。
第七步,研究一下这个命令行,尝试在本地启动起来。碰到数据库没起来的问题,就把连接串改成刚刚那个Docker的临时数据库。
第八步,你平时怎么在网页上点点点测试的,把它翻译成Java。比如你平时会手工测试登录接口,那就用HttpClient写一段代码,模拟登录。
第九步,把上面这些整合起来:

public class MyTest {
    @Test
    public void 跳出弱鸡循环() {
        启动测试数据库();
        把表结构灌进去();
        本地启动应用();
        自动化方式测试接口();
    }
}

继续阅读“技术提升的一个方法”

Java CountDownLatch 和 CyclicBarrier 示例

复习了一下 JCIP 回顾一下同步工具类的使用

CountDownLatch


import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CountDownLatchExample1 {

	private final static int threadCount = 200;

	public static void main(String[] args) throws Exception {

		ExecutorService exec = Executors.newCachedThreadPool();

		final CountDownLatch countDownLatch = new CountDownLatch(threadCount);

		for (int i = 0; i < threadCount; i++) {
			final int threadNum = i;
			exec.execute(() -> {
				try {
					test(threadNum);
				} catch (Exception e) {
					e.printStackTrace();
				} finally {
					countDownLatch.countDown();
				}
			});
		}
		countDownLatch.await();
		System.out.println("finish");
		exec.shutdown();
	}

	private static void test(int threadNum) throws Exception {
		Thread.sleep(100);
		System.out.println("{" + threadNum + "}");
		Thread.sleep(100);
	}
}

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class CountDownLatchExample2 {
	private final static int threadCount = 200;

	public static void main(String[] args) throws Exception {

		ExecutorService exec = Executors.newCachedThreadPool();

		final CountDownLatch countDownLatch = new CountDownLatch(threadCount);

		for (int i = 0; i < threadCount; i++) {
			final int threadNum = i;
			exec.execute(() -> {
				try {
					test(threadNum);
				} catch (Exception e) {
					e.printStackTrace();
				} finally {
					countDownLatch.countDown();
				}
			});
		}
                //超时之后不阻塞,直接继续运行
		countDownLatch.await(10, TimeUnit.MILLISECONDS);
		System.out.println("finish");
		exec.shutdown();
	}

	private static void test(int threadNum) throws Exception {
		Thread.sleep(100);
		System.out.println("{" + threadNum + "}");
	}
}
import java.util.concurrent.CountDownLatch;

public class TestCountDownLatch {

	public long timeTasks(int nThreads, final Runnable task) throws InterruptedException {

		final CountDownLatch startGate = new CountDownLatch(1);
		final CountDownLatch endGate = new CountDownLatch(nThreads);

		for (int i = 0; i < nThreads; i++) {
			Thread thread = new Thread() {
				@Override
				public void run() {
					try {
						startGate.await();
						try {
							task.run();
						} finally {
							endGate.countDown();
						}
					} catch (InterruptedException e) {

					}

				}
			};
			thread.start();
		}

		long start = System.nanoTime();
		startGate.countDown();
		endGate.await();
		long end = System.nanoTime();
		return end - start;
	}

	public static void main(String[] args) {
		int nTask = 10;
		TestCountDownLatch testCountDownLatch = new TestCountDownLatch();
		try {
			long timeUse = testCountDownLatch.timeTasks(nTask, new Task());
			System.out.println(nTask + " use time " + timeUse);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}

	}

}

class Task implements Runnable {

	@Override
	public void run() {
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

}

CyclicBarrier

import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CyclicBarrierExample1 {

	private static CyclicBarrier barrier = new CyclicBarrier(5);

	public static void main(String[] args) throws Exception {

		ExecutorService executor = Executors.newCachedThreadPool();

		for (int i = 0; i < 10; i++) {
			final int threadNum = i;
			Thread.sleep(1000);
			executor.execute(() -> {
				try {
					race(threadNum);
				} catch (Exception e) {
					e.printStackTrace();
				}
			});
		}
		executor.shutdown();
	}

	private static void race(int threadNum) throws Exception {
		Thread.sleep(1000);
		System.out.println("{" + threadNum + "} is ready ");
		barrier.await();
		System.out.println("{" + threadNum + "} continue ");
	}
}
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class CyclicBarrierExample2 {
	private static CyclicBarrier barrier = new CyclicBarrier(5);

	public static void main(String[] args) throws Exception {

		ExecutorService executor = Executors.newCachedThreadPool();

		for (int i = 0; i < 10; i++) {
			final int threadNum = i;
			Thread.sleep(1000);
			executor.execute(() -> {
				try {
					race(threadNum);
				} catch (Exception e) {
					e.printStackTrace();
				}
			});
		}
		executor.shutdown();
	}

	private static void race(int threadNum) throws Exception {
		Thread.sleep(1000);
		System.out.println("{" + threadNum + "} is ready");
		try {
                        //等待超时之后直接放开栅栏,让线程执行,不需要等到规定线程数都就位
			barrier.await(2000, TimeUnit.MILLISECONDS);
		} catch (Exception e) {
			e.printStackTrace();
		}
		System.out.println("{" + threadNum + "} continue");
	}
}
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CyclicBarrierExample3 {
        //开始之前执行回调函数
	private static CyclicBarrier barrier = new CyclicBarrier(5, () -> {
		System.out.println("callback is running");
	});

	public static void main(String[] args) throws Exception {

		ExecutorService executor = Executors.newCachedThreadPool();

		for (int i = 0; i < 10; i++) {
			final int threadNum = i;
			Thread.sleep(1000);
			executor.execute(() -> {
				try {
					race(threadNum);
				} catch (Exception e) {
					e.printStackTrace();
				}
			});
		}
		executor.shutdown();
	}

	private static void race(int threadNum) throws Exception {
		Thread.sleep(1000);
		System.out.println("{" + threadNum + "} is ready");
		barrier.await();
		System.out.println("{" + threadNum + "} continue");
	}
}

C 语言字符串操作

#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <stdlib.h>
#include <ctype.h>

char *strupr_t(char *str)
{
	char *orign=str;
	for (; *str!='\0'; str++)
		*str = toupper(*str);
	return orign;
}

char *strlowr_t(char *str)
{
	char *orign=str;
	for (; *str!='\0'; str++)
		*str = tolower(*str);
	return orign;
}

//字符串拷贝(strcpy, strncpy)
void testStrCpy(){
	char dest[1024] = {0};
	char *str = "abcde";
	//strcpy()函数会将源字符串中的结束符('\0')也拷贝到目的字符串中。
    //注意,strcpy()可能会导致溢出。
	strcpy(dest,str);

	printf("%s\n", dest);


	char dest2[7] = {0};
	char *src2 = "abcde";
	dest2[5] = 'A';
	//该函数从源字符串中拷贝n个字符到目的字符串;如果源字符串长度不足,则用 NULL 填充,以保证将n个字符写入目的字符串中;如果源字符串中前n个字符不包含字符串结束符,函数不会为目的字符串添加上结束符。
	//所以如果有需要,应该在拷贝后自己在目的字符串尾部添加结束符。
	strncpy(dest2, src2, 5);
	printf("%s\n", dest2);
}

void testStrCmp()
{
	char *s11 = "abcde";
	char *s12 = "abcef";
	char *s13 = "ad";
	//从这个结果可以发现,strcmp()是根据字典序来对字符串进行比较的。进一步的,还可以发现strcmp()的返回值是比较过程中最后一次比较时两个字符的值的差
	printf("compare(%s, %s) -> %d\n", s11, s12, strcmp(s11, s12));
	printf("compare(%s, %s) -> %d\n", s12, s13, strcmp(s12, s13));
	printf("compare(%s, %s) -> %d\n", s11, s13, strcmp(s11, s13));

	char *s21 = "abcde";
	char *s22 = "abcfg";
	char *s23 = "ad";
	//和strcmp()的区别是,strncmp()只对s1和s2的前n个字节进行比较。
	printf("compare(%s, %s, 4) -> %d\n", s21, s22, strncmp(s21, s22, 4));
	printf("compare(%s, %s, 2) -> %d\n", s21, s23, strncmp(s21, s23, 2));
	printf("compare(%s, %s, 3) -> %d\n", s22, s23, strncmp(s22, s23, 3));

	char *s31 = "AbcdE";
	char *s32 = "abcdE";
    //使用strcasecmp()应该包含 strings.h 而不是 string.h
    //strcasecmp()在比较时不区分大小写
    //strncasecmp()之于strcasecmp()就如strncmp()之于strcmp()
	printf("compare(%s, %s) with case -> %d\n", s31, s32, strcmp(s31, s32));
	printf("compare(%s, %s) ignore case -> %d\n", s31, s32, strcasecmp(s31, s32));
}

void testStrCat()
{
	char dest[1024] = "hello ";
	char *src = "world!";
	//strcat()首先会覆盖掉目的字符串的结束符,然后把源字符串的内容追加到后面,并在最后添加结束符。如果目的字符串缓冲区长度不够,将导致溢出。
    //strcat()在操作完成后,返回目的字符串的首地址,这样可以方便地进行链式操作。
	printf("%s\n", strcat(dest, src));

	char dest2[1024] = "hello ";
	char *src2 = "world!kkkkaaaa";
	//strncat()将最多n个字节的内容追加到目的字符串尾部,并且会在追加后添加终止符号。
    //同strcat()一样,它返回目的字符串的首地址。
	printf("%s\n", strncat(dest2, src2,6));
}

void testStrSearch()
{
	char *s1 = "hello world!";
	char c = 'l';
	//strchr()返回一个字符指针,指向指定字符在指定字符串中第一次出现的位置。如果在指定字符串中没有找到指定字符,则返回 NULL 。该函数的第二个参数按理来说应当是一个字符,不过标准库中确实是int类型。
	printf("%s\n", strchr(s1, c));
	//strrchr()和strchr()类似,但它返回的是指定字符在指定字符串中最后一次出现的位置。如果未找到,同样返回 NULL 。
	printf("%s\n", strrchr(s1, c));
	//strchrnul()的功能和strchr()只有细微的区别,那就是,当没有找到指定字符时,strchrnul()不返回 NULL ,而是返回字符串结束符的位置。
	//这里由于strchrnul()的特性,没办法通过打印字符串来了解strchrnul()的操作
	//'strchrnull' is invalid in C99
	//printf("%p, %p\n", s1, strchrnull(s1, 'f'));

	char *accept = "wo";
	//strpbrk()和strchr()的区别在于,strchr()是从字符串里搜索 一个字符 ,而strpbrk()则是在字符串里搜索 一个字符集中的字符 ,看第二个参数就明白了。strpbrk()遍历字符串,如果发现某个字符在指定的 字符集 中,则立即返回指向该字符的指针。如果最后没有找到任何在指定字符集中的字符,则返回 NULL 。
	printf("%s\n", strpbrk(s1, accept));
}

void testStrSplit()
{
	char s[1024] = "abc;lsdk:lskdj,;slsj";
	char *delm = ";:,";
	char *result = NULL;
	int len = strlen(s);
	int i = 0;
    //strtok()根据第二个参数指定的分隔符(可能存在多个不同的分隔符)将指定字符串分割成多个子串。通过多次调用strtok(),可以依次获得字符串的多个子串的首地址。要注意的是,除了第一次调用时将待分割字符串作为第一个参数,后续的调用要将第一个参数置为 NULL 。当字符串已经无法再分割时,strtok()返回 NULL 。
    //除了上面说过的strtok()的用法外,还要注意的是,作为待分割的字符串,它必须是 可更改的 。否则虽然可以通过编译,但运行会出错。要理解这个现象,首先要了解strtok()的内部机制。
	result = strtok(s, delm);
	while (result != NULL) {
		printf("Source:%s, Sub:%s\n", s, result);
		result = strtok(NULL, delm);
	}
}

/*
T	o	 	b	e	 	o	r	 	n	o	t	 	t	o	 	b	e
84	111	32	98	101	32	111	114	32	110	111	116	32	116	111	32	98	101
84	111	0	98	101	32	111	114	32	110	111	116	32	116	111	32	98	101
84	111	0	98	101	0	111	114	32	110	111	116	32	116	111	32	98	101
84	111	0	98	101	0	111	114	0	110	111	116	32	116	111	32	98	101
84	111	0	98	101	0	111	114	0	110	111	116	0	116	111	32	98	101
84	111	0	98	101	0	111	114	0	110	111	116	0	116	111	0	98	101
84	111	0	98	101	0	111	114	0	110	111	116	0	116	111	0	98	101

可以看到,s中的分隔符,逐次地被置为'\0'即字符串结束符。这就是strtok()分割字符串的内部原理了。而strtok()返回的指针,其实就是s中各个子串的起始位置了。如果s指向的内容是无法被修改的,那么strtok()自然也就无法将原先的分隔符置为字符结束符了。

当然了,由于源字符串会被修改,在实际中,如果需要,可以用strdup()来建立一个源字符串的副本。
*/
void testStrSplit2()
{
	char s[64] = "To be or not to be";
	char *delm = " ";
	char *result = NULL;
	int i =0,len = strlen(s);
	for (i =0;i<len;i++){
		printf("%c ", s[i]);
	}
	printf("\n");
	for (i=0;i<len;i++){
		printf("%d ",(int)s[i]);
	}
	printf("\n");

	result = strtok(s, delm);

	while (result != NULL){
		for (i=0;i<len;i++){
			printf("%d ",(int)s[i]);
		}
		printf("\n");
		result = strtok(NULL, delm);
	}
}

void testStrSplit3()
{
	char s[64] = "Hello World";
	char *delm = " ";
	char *result = NULL, *ptr = NULL;

	printf("Source:%p\n", s);
	//char *strtok_r(char *str, const char *delim, char **saveptr);
	//strtok_r()是Linux下的strtok()的可重入版本(线程安全版本),它比strtok()多了一个参数 saveptr ,这个参数用于在分割字符串时保存上下文。
	result = strtok_r(s, delm, &ptr);

	while (result != NULL){
		printf("Result:%p\t", result);
		printf("Saveptr:%p\t", ptr);
		printf("---%s\t", result);
		printf("---%s\n", ptr);
		//可以看到,saveptr这个指针在每次调用strtok_r()后就指向了未分割的部分的首地址。相对地,strtok()则是在内部有一个静态缓冲区,通过这个静态缓冲区来记录未处理的起始位置,所以strtok()不是线程安全的。
		result = strtok_r(NULL, delm, &ptr);
	}

}

//因为和strtok()的这个不同之处,strsep不需要区分第一次调用后后续的连续调用,可以用统一的操作来对字符串进行分割。
void testStrSplit4()
{
	char s[64] = "To be or not to be";
	char *source = s;
	char *delm = " ";
	char *result = NULL;

	while (source != NULL){
		printf("Source: %s | ", source);
		result = strsep(&source,delm);
		printf("result: %s | ", result);
	}
}

void testStrMatch()
{
	char *s = "To be or not to be.";
	char *p = "be";
	//strstr()返回字符串needle在字符串haystack中第一次出现的位置;如果没有匹配,则返回 NULL 。
	printf("%s\n", strstr(s, p));
}

void testStrDup()
{
	char *s = "aabbccddee";
	char *dup = strdup(s);
	//strdup()调用malloc()分配一块内存并将字符串s的内容拷贝进去,产生s的副本。要注意的是,在最后应该调用free()来释放副本。
	printf("%s\n",dup);
	free(dup);

	char *s1 = "poiuytrewq";
	//strndup()和strdup()类似,但最多只拷贝s的前n个字节。如果s的长度大于n,还会在副本后添加终止符。
	char *dup1 = strndup(s1, 4);
	printf("%s\n",dup1);
	free(dup1);

	//strdupa()和strdup()类似,但在分配内存时,它使用alloca()而不是malloc()。
	//strndupa()之于strdupa()就如strndup()之于strdup(),不再赘述。
}

/*
函数名: swab
功  能: 交换字节
用  法: void swab (char *from, char *to, int nbytes);
*/
void testStringSwab()
{
	char source[15] = "rFna koBlrna d";
	char target[15];

	swab(source,target,strlen(source));
	printf("%s\n",target);
}

/*
函数名: strupr
功  能: 将串中的小写字母转换为大写字母
用  法: char *strupr(char *str);
*/
void testStrUpper()
{
	char string[100] = "abcdefghijklmnopqrstuvwxyz";
	char *ptr;

   /* converts string to upper case characters */
	ptr = strupr_t(string);
	printf("%s\n", ptr);
}

/*
函数名: strtol
功  能: 将串转换为长整数
用  法: long strtol(char *str, char **endptr, int base);
*/
void testStrtol()
{
	char *string = "87654321", *endptr;
	long lnumber;

   /* strtol converts string to long integer  */
	lnumber = strtol(string, &endptr, 10);
	printf("string = %s  long = %ld\n", string, lnumber);
}

/*
函数名: strtod
功  能: 将字符串转换为double型值
用  法: double strtod(char *str, char **endptr);
*/
void testStrtod()
{
	char input[80], *endptr;
	double value;

	printf("Enter a floating point number:");
	gets(input);
	value = strtod(input, &endptr);
	printf("The string is %s the number is %lf\n", input, value);
}

/*
函数名: strspn
功  能: 在串中查找指定字符集的子集的第一次出现
用  法: int strspn(char *str1, char *str2);
*/
void testStrspan()
{
	char *string1 = "1234567890";
	char *string2 = "123DC8";
	int length;

	length = strspn(string1, string2);
	printf("Character where strings differ is at position %d\n", length);
}


/*
函数名: strset
功  能: 将一个串中的所有字符都设为指定字符
用  法: char *strset(char *str, char c);
*/
// void testStrset()
// {
// 	char string[10] = "123456789";
// 	char symbol = 'c';

// 	printf("Before strset(): %s\n", string);
// 	strset(string, symbol);
// 	printf("After strset():  %s\n", string);

// }

/*
函数名: strnset
功  能: 将一个串中的n个字符都设为指定字符
用  法: char *strnset(char *str, char ch, unsigned n);
*/
// void testStrnset()
// {
// 	char *string = "abcdefghijklmnopqrstuvwxyz";
// 	char letter = 'x';

// 	printf("string before strnset: %s\n", string);
// 	strnset(string, letter, 13);
// 	printf("string after  strnset: %s\n", string);
// }

void strrev(char *head)
{
  if (!head) return;
  char *tail = head;
  // find the 0 terminator, like head+strlen
  while(*tail) ++tail;
  // tail points to the last real char
  --tail;               
  // head still points to the first
  for( ; head < tail; ++head, --tail) {
      // walk pointers inwards until they meet or cross in the middle
      char h = *head, t = *tail;
      *head = t;           // swapping as we go
      *tail = h;
  }
}

void reverse(char s[])
{
    int length = strlen(s) ;
    int c, i, j;

    for (i = 0, j = length - 1; i < j; i++, j--)
    {
        c = s[i];
        s[i] = s[j];
        s[j] = c;
    }
}

/*
函数名: strrev
功  能: 串倒转
用  法: char *strrev(char *str);
*/
void testStrrev()
{
	char forward[15] = "string";

	printf("Before strrev(): %s\n", forward);
	strrev(forward);
	printf("After strrev():  %s\n", forward);
	reverse(forward);
	printf("After reverse():  %s\n", forward);
}


/*
函数名: strcspn
功  能: 在串中查找第一个给定字符集内容的段
用  法: int strcspn(char *str1, char *str2);
*/
void testStrcspn()
{
	char *string1 = "1234567890";
	char *string2 = "747DC8";
	int length;

	length = strcspn(string1, string2);
	printf("Character where strings intersect is at position %d\n", length);
}



/*
atof(将字符串转换成浮点型数)
atoi(将字符串转换成整型数)
atol(将字符串转换成长整型数)
strtod(将字符串转换成浮点数)
strtol(将字符串转换成长整型数)
strtoul(将字符串转换成无符号长整型数)
toascii(将整型数转换成合法的ASCII 码字符)
toupper(将小写字母转换成大写字母)
tolower(将大写字母转换成小写字母)
atof(将字符串转换成浮点型数)
*/

int main(int argc, char *argv[])
{
	testStrCpy();
	testStrCmp();
	testStrCat();
	testStrSearch();
	testStrSplit();
	testStrSplit2();
	testStrSplit3();
	testStrSplit4();
	testStrMatch();
	testStrDup();

	testStrspan();
	// testStrset();
	// testStrnset();
	testStrrev();
	testStrcspn();
	testStrtod();
	testStrtol();
	testStrUpper();
	return 0;
}

How do I install Java on Mac OSX allowing version switching?

原文链接

note: These solutions work for various versions of Java including Java 8 and the new Java 13, and for any other previous Java version covered by the listed version managers. This includes alternative JDK’s from OpenJDK, Oracle, IBM, Azul, Amazon Correto, Graal and more. Easily work with Java 7, Java 8, Java 9, Java 10, Java 11, Java 12, and Java 13!

You have a few options of how to do the installation as well as manage JDK switching. Installation can be done by Homebrew, SDKMANJabba, or a manual install. Switching can be done by JEnvSDKMANJabba, or manually by setting JAVA_HOME. All of these are described below.


Installation

First, install Java using whatever method you prefer including Homebrew, SDKMAN or a manual install of the tar.gz file. The advantages of a manual install is that the location of the JDK can be placed in a standardized location for Mac OSX.

Install with SDKMAN

This is a simple model in that it handles both installation and version switching, with a caveat that it installs the JDK into a non-standard directory.

<see below “Installing and Switching versions with SDKMAN”>

Install using Jabba

This is also a simple model in that both installation and version switching are handled by the same tool. The installations are made to a non-standard directory.

<see below “Installing and Switching versions with Jabba”>

Install manually from OpenJDK download page:

  1. Download OpenJDK for Mac OSX from http://jdk.java.net/ (for example Java 13)
  2. Unarchive the OpenJDK tar, and place the resulting folder (i.e. jdk-13.jdk) into your /Library/Java/JavaVirtualMachines/ folder since this is the standard and expected location of JDK installs. You can also install anywhere you want in reality.

Install with Homebrew

The version of Java available in Homebrew Cask previous to October 3, 2018 was indeed the Oracle JVM. Now however, it has now been updated to OpenJDK. Be sure to update Homebrew and then you will see the lastest version available for install.

  1. install Homebrew if you haven’t already. Make sure it is updated:
    brew update
  2. Add the casks tap, if you haven’t already (or you are not seeing older Java versions anymore with step #3):
    brew tap homebrew/cask-versions

    and for the AdoptOpenJDK versions, add that tap:

    brew tap adoptopenjdk/openjdk

    These casks change their Java versions often, and there might be other taps out there with additional Java versions.

  3. Look for installable versions:
    brew search java   

    or for AdoptOpenJDK versions:

    brew search jdk     
  4. Check the details on the version that will be installed:
    brew cask info java

    or for the AdoptOpenJDK version:

    brew cask info adoptopenjdk
  5. Install a specific version of the JDK such as java11adoptopenjdk8, or just java or adoptopenjdk for the current. For example:
    brew cask install java

    You can use the fully qualified path to older versions as well:

    brew cask install homebrew/cask-versions/java11

And these will be installed into /Library/Java/JavaVirtualMachines/ which is the traditional location expected on Mac OSX.

Other installation options:

Some other flavours of openJDK are:

Azul Systems Java Zulu certified builds of OpenJDK can be installed by following the instructions on their site.

Zulu® is a certified build of OpenJDK that is fully compliant with the Java SE standard. Zulu is 100% open source and freely downloadable. Now Java developers, system administrators, and end users can enjoy the full benefits of open source Java with deployment flexibility and control over upgrade timing.

Amazon Correto OpenJDK builds have an easy to use an installation package for version 8 or version 11 (other versions are coming), and installs to the standard /Library/Java/JavaVirtualMachines/ directory on Mac OSX.

Amazon Corretto is a no-cost, multiplatform, production-ready distribution of the Open Java Development Kit (OpenJDK). Corretto comes with long-term support that will include performance enhancements and security fixes. Amazon runs Corretto internally on thousands of production services and Corretto is certified as compatible with the Java SE standard. With Corretto, you can develop and run Java applications on popular operating systems, including Linux, Windows, and macOS.


Where is my JDK?!?!

To find locations of previously installed Java JDK’s installed at the default system locations, use:

/usr/libexec/java_home -V

Matching Java Virtual Machines (6):
13, x86_64: “OpenJDK 13” /Library/Java/JavaVirtualMachines/openjdk-13.jdk/Contents/Home 12, x86_64: “OpenJDK 12” /Library/Java/JavaVirtualMachines/jdk-12.jdk/Contents/Home
11, x86_64: “Java SE 11” /Library/Java/JavaVirtualMachines/jdk-11.jdk/Contents/Home
10.0.2, x86_64: “Java SE 10.0.2” /Library/Java/JavaVirtualMachines/jdk-10.0.2.jdk/Contents/Home
9, x86_64: “Java SE 9” /Library/Java/JavaVirtualMachines/jdk-9.jdk/Contents/Home
1.8.0_144, x86_64: “Java SE 8” /Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home

You can also report just the location of a specific Java version using -v. For example for Java 13:

/usr/libexec/java_home -v 13

/Library/Java/JavaVirtualMachines/jdk-13.jdk/Contents/Home

Knowing the location of the installed JDK’s is also useful when using tools like JEnv, or adding a local install to SDKMAN, or linking a system JDK in Jabba — and you need to know where to find them.

If you need to find JDK’s installed by other tools, check these locations:

  • SDKMAN installs to ~/.sdkman/candidates/java/
  • Jabba installs to ~/.jabba/jdk

Switching versions manually

The Java executable is a wrapper that will use whatever JDK is configured in JAVA_HOME, so you can change that to also change which JDK is in use.

For example, if you installed or untar’d JDK 13 to /Library/Java/JavaVirtualMachines/jdk-13.jdk if it is the highest version number it should already be the default, if not you could simply set:

export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk-13.jdk/Contents/Home

And now whatever Java executable is in the path will see this and use the correct JDK.

Using the /usr/libexec/java_home utility as previously described helps you to create aliases or to run commands to change Java versions by identifying the locations of different JDK installations. For example, creating shell aliases in your .profile or .bash_profile to change JAVA_HOME for you:

export JAVA_8_HOME=$(/usr/libexec/java_home -v1.8)
export JAVA_9_HOME=$(/usr/libexec/java_home -v9)
export JAVA_10_HOME=$(/usr/libexec/java_home -v10)
export JAVA_11_HOME=$(/usr/libexec/java_home -v11)
export JAVA_12_HOME=$(/usr/libexec/java_home -v12)
export JAVA_13_HOME=$(/usr/libexec/java_home -v13)

alias java8='export JAVA_HOME=$JAVA_8_HOME'
alias java9='export JAVA_HOME=$JAVA_9_HOME'
alias java10='export JAVA_HOME=$JAVA_10_HOME'
alias java11='export JAVA_HOME=$JAVA_11_HOME'
alias java12='export JAVA_HOME=$JAVA_12_HOME'
alias java13='export JAVA_HOME=$JAVA_13_HOME'

# default to Java 13
java13

Then to change versions, just use the alias.

java8
java -version

java version “1.8.0_144”

Of course, setting JAVA_HOME manually works too!


Switching versions with JEnv

JEnv expects the Java JDK’s to already exist on the machine and can be in any location. Typically you will find installed Java JDK’s in /Library/Java/JavaVirtualMachines/. JEnv allows setting the global version of Java, one for the current shell, and a per-directory local version which is handy when some projects require different versions than others.

  1. Install JEnv if you haven’t already, instructions on the site http://www.jenv.be/ for manual install or using Homebrew.
  2. Add any Java version to JEnv (adjust the directory if you placed this elsewhere):
    jenv add /Library/Java/JavaVirtualMachines/jdk-13.jdk/Contents/Home
  3. Set your global version using this command:
    jenv global 13

You can also add other existing versions using jenv add in a similar manner, and list those that are available. For example Java 8:

jenv add /Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home 
jenv versions

See the JEnv docs for more commands. You may now switch between any Java versions (Oracle, OpenJDK, other) at any time either for the whole system, for shells, or per local directory.

To help manage JAVA_HOME while using JEnv you can add the export plugin to do this for you.

$ jenv enable-plugin export
  You may restart your session to activate jenv export plugin echo export plugin activated

The export plugin may not adjust JAVA_HOME if it is already set, so you may need to clear this variable in your profile so that it can be managed by JEnv.

You can also use jenv exec <command> <parms...> to run single commands with JAVA_HOME and PATH set correctly for that one command, which could include opening another shell.


Installing and Switching versions with SDKMAN

SDKMAN is a bit different and handles both the install and the switching. SDKMAN also places the installed JDK’s into its own directory tree, which is typically ~/.sdkman/candidates/java. SDKMAN allows setting a global default version, and a version specific to the current shell.

  1. Install SDKMAN from https://sdkman.io/install
  2. List the Java versions available to make sure you know the version ID
    sdk list java
  3. Install one of those versions, for example, Java 13:
    sdk install java 13.0.0-open 
  4. Make 13 the default version:
    sdk default java 13.0.0-open

    Or switch to 13 for the session:

    sdk use java 13.0.0-open

When you list available versions for installation using the list command, you will see a wide variety of distributions of Java:

sdk list java

And install additional versions, such as JDK 8:

sdk install java 8.0.181-oracle

SDKMAN can work with previously installed existing versions. Just do a local install giving your own version label and the location of the JDK:

sdk install java my-local-13 /Library/Java/JavaVirtualMachines/jdk-13.jdk/Contents/Home

And use it freely:

sdk use java my-local-13

More information is available in the SDKMAN Usage Guide along with other SDK’s it can install and manage.

SDKMAN will automatically manage your PATH and JAVA_HOME for you as you change versions.


Installing and Switching versions with Jabba

Jabba also handles both the install and the switching. Jabba also places the installed JDK’s into its own directory tree, which is typically ~/.jabba/jdk.

  1. Install Jabba by following the instructions on the home page.
  2. List available JDK’s
    jabba ls-remote
  3. Install Java JDK 12
    jabba install openjdk@1.12.0
  4. Use it:
    jabba use openjdk@1.12.0

You can also alias version names, link to existing JDK’s already installed, and find a mix of interesting JDK’s such as GraalVM, Adopt JDK, IBM JDK, and more. The complete usage guide is available on the home page as well.

Jabba will automatically manage your PATH and JAVA_HOME for you as you change versions.