程序员后浪和前浪的战争
点击上方蓝字关注我们
月圆之夜,洛洛山。
洛洛山坐落于多线程王国的一个偏远小镇,山上绿树环绕,风光灵秀。踏着蜿蜒的小路循迹而上,花香、鸟啼、青柳、虫鸣,共同演奏者一曲天地之歌,大自然的鬼斧神工和洛洛山大当家——张麻子的笑声一样豪放不羁。张麻子占领洛洛山十年有余,寨子创立之初,他便定下规矩:“三不劫”——老少不劫、农民不劫、妇人不劫。只对路过的富商、官宦等有钱有势的人家下手,收取过路费用。在他多年用心经营之下,寨子已小有规模。寨中壮丁上千许,库内酒钱上万贯。张麻子作为大当家,正是意气风发之时,问苍茫大地,我主沉浮!终日饮酒作乐,好不快活。
人到无求品自高,张麻子在洛洛山势力足够大时,便很少再管束寨子中的事,渐渐地任其自由生长。洛洛山的手下们长期过着优渥的生活,不免沾染上些纨绔之气。寨子从最开始的劫富济贫发展到后来,反倒成了一方恶霸,手下的人不规矩,老少劫、农民劫、妇人也劫,镇上人民苦不堪言。张麻子本人却越来越不问世事,心怀通达,见到谁都是满面笑容,让人如沐春风之中。
不过,在你眼里张麻子的笑就不是那么让人欢喜了。男儿何不带吴钩?作为一名有志青年,你也曾纵马横刀,决心占山为王。然而终敌不过洛洛山传承已久,底蕴深厚。最终你只能在次一等的皮皮山安营扎寨,皮皮山地势险峻,易守难攻。物产又极其匮乏,张麻子不屑于为皮皮山上的蝇头小利大动干戈,这些年来,两座山头倒也相安无事。
然而山珍海味摆在眼前,谁又能将就得了咸菜萝卜。你早已在心中暗暗发誓,势必占领洛洛山头!正值十五月圆夜,谋事月黑风高时。你率领手下八百余人悄然围攻洛洛山,之所以选择十五是因为你知道月儿越明,星星越稀少,若再遇上乌云遮蔽月色,端的是伸手不见五指,正是动手的好时机。你决定趁着今夜云蒸雾绕,对洛洛山发动总攻。
洛洛山西边背靠悬崖,最是险峻。你派出六指、麻袋、小东西三名心腹率人分别围住东、南、北三个方向,你亲自带领一批秘密培养的精英部队从西面悬崖攀援而上,打他张麻子一个釜底抽薪。
六指,我率领部队先爬上西面悬崖,蛰伏在草丛中。待我准备好之后,我会给你发出信号。一会你收到我给你的信号后,先从东边佯攻吸引火力,不求杀敌数量,但一定要把声势搞大,并尽可能减少弟兄们的伤亡。洛洛山此时没有防备,慌乱中必定派出大部队迎战,你只要拖住洛洛山上的大部队十分钟即可。待大部分敌人到你那里之后,你给麻袋、小东西一人一个信号,麻袋和小东西收到信号之后,从南北两面冲上去,我会在西面配合你们。这次,咱们一定要一口气端了张麻子的老巢!
大哥,六指收到!西面凶险,大哥一定要好生小心!
麻袋收到,南面交给我,我一定杀他们个片甲不留!
...
小东西,你那边怎么样?
老大...抱歉,我没怎么听明白,太多信号了,我脑子有点乱了。
我再给你们解释一遍,我和我带领的部队会先爬上西面悬崖,六指、麻袋和小东西你们三个先在三面蛰伏,你们每个人都需要收到一个信号才行动。我爬上悬崖后,会给六指一个信号,六指收到信号后开始从东边佯攻吸引火力。等火力被吸引过去后,六指给麻袋和小东西一人一个信号,这时麻袋和小东西你们两个再从南北两面行动,我会在西面和你们里应外合。听明白了吗?
老大,明白了!
老大,明白了!
...
小东西,你还有什么问题吗?
老大,我以前是个破写代码的,我敲了一份伪代码,你看下是不是这样的,我觉得我们四个部队就像四个线程一样。
你拿过小东西递过来的高端轻奢笔记本 Macbook Pro,看到小东西写了一份这样的代码:
Java 实现
publicclassFighting {
publicstaticvoidmain(String[] args) {
new Thread(() -> {
// 老大的线程
boss();
}).start();
new Thread(() -> {
// 六指的线程
sixFingers();
}).start();
new Thread(() -> {
// 麻袋的线程
bag();
}).start();
new Thread(() -> {
// 小东西的线程
smallThing();
}).start();
}
privatestaticvoidboss() {
System.out.println("老大率领部队正在洛洛山西面爬悬崖...");
// 模拟爬悬崖耗时
Thread.sleep(1000);
System.out.println("老大爬上了西面山头!");
老大给六指一个信号,通知六指行动
}
privatestaticvoidsixFingers() {
六指正在等待信号,收到信号后才开始执行下面的操作
System.out.println("六指收到了信号,开始佯攻!");
// 模拟佯攻吸引火力耗时
Thread.sleep(1000);
System.out.println("洛洛山大部队已被吸引过来!");
六指给麻袋一个信号
六指给小东西一个信号
}
privatestaticvoidbag() {
麻袋正在等待信号,收到信号后才开始执行下面的操作
System.out.println("麻袋收到了信号,从南面冲上去!");
}
privatestaticvoidsmallThing() {
小东西正在等待信号,收到信号后才开始执行下面的操作
System.out.println("小东西收到了信号,从北面冲上去!");
}
}
没错,我们的计划就是这样的,但你丫的当初信号量没学好吧,还要靠伪代码。这玩意儿用信号量写就可以了。信号量在 Java 中对应 Semaphore 类,正在等待信号就是 acquire() 方法,给别人发送信号就是 release() 方法。让我来给你改成真实代码。
你端着电脑,噼里啪啦敲出了下面的代码。
Java 实现
publicclassFighting {
// 给六指的信号
privatestatic Semaphore semaphoreToSixFingers = new Semaphore(0);
// 给麻袋的信号
privatestatic Semaphore semaphoreToBag = new Semaphore(0);
// 给小东西的信号
privatestatic Semaphore semaphoreToSmallThing = new Semaphore(0);
publicstaticvoidmain(String[] args) {
new Thread(() -> {
try {
// 老大的线程
boss();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
try {
// 六指的线程
sixFingers();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
try {
// 麻袋的线程
bag();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
try {
// 小东西的线程
smallThing();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
privatestaticvoidboss() throws InterruptedException {
System.out.println("老大率领部队正在洛洛山西面爬悬崖...");
Thread.sleep(1000);
System.out.println("老大爬上了西面山头!");
System.out.println("老大给六指一个信号");
semaphoreToSixFingers.release();
}
privatestaticvoidsixFingers() throws InterruptedException {
semaphoreToSixFingers.acquire();
System.out.println("六指收到了信号,开始佯攻!");
Thread.sleep(1000);
System.out.println("洛洛山大部队已被吸引过来!");
System.out.println("六指给麻袋和小东西一人一个信号");
semaphoreToBag.release();
semaphoreToSmallThing.release();
}
privatestaticvoidbag() throws InterruptedException {
semaphoreToSmallThing.acquire();
System.out.println("麻袋收到了信号,从南面冲上去!");
}
privatestaticvoidsmallThing() throws InterruptedException {
semaphoreToBag.acquire();
System.out.println("小东西收到了信号,从北面冲上去!");
}
}
老大率领部队正在洛洛山西面爬悬崖...
老大爬上了西面山头!
老大给六指一个信号
六指收到了信号,开始佯攻!
洛洛山大部队已被吸引过来!
六指给麻袋和小东西一人一个信号
麻袋收到了信号,从南面冲上去!
小东西收到了信号,从北面冲上去!
“老大🐂🍺!我明白了!
信号量就是用来控制线程执行顺序的,不会用的话回去好好练练去!
老大,我也想练啊,可是我不知道在哪里练习,咱平时打家劫舍也见不到。
力扣上不是有多线程的题吗?你去刷一遍就会了。
publicclassFoo {
publicvoidone() { print("one"); }
publicvoidtwo() { print("two"); }
publicvoidthree() { print("three"); }
}
三个不同的线程将会共用一个 Foo 实例。
先让打印 "two" 和打印 "three" 的线程等待信号 "one" 打印完成后,通知 "two" 开始打印 "two" 打印完成后,通知 "three" 开始打印
import java.util.concurrent.Semaphore;
classFoo{
private Semaphore sem2 = new Semaphore(0);
private Semaphore sem3 = new Semaphore(0);
publicFoo(){
}
publicvoidfirst(Runnable printFirst)throws InterruptedException {
// printFirst.run() outputs "first". Do not change or remove this line.
printFirst.run();
// 通知 two 开始打印
sem2.release();
}
publicvoidsecond(Runnable printSecond)throws InterruptedException {
// 等待信号
sem2.acquire();
// printSecond.run() outputs "second". Do not change or remove this line.
printSecond.run();
// 通知 three 开始打印
sem3.release();
}
publicvoidthird(Runnable printThird)throws InterruptedException {
// 等待信号
sem3.acquire();
// printThird.run() outputs "third". Do not change or remove this line.
printThird.run();
}
}
classFooBar {
publicvoidfoo() {
for (int i = 0; i < n; i++) {
print("foo");
}
}
publicvoidbar() {
for (int i = 0; i < n; i++) {
print("bar");
}
}
}
两个不同的线程将会共用一个 FooBar 实例。其中一个线程将会调用 foo() 方法,另一个线程将会调用 bar() 方法。
先让打印 "bar" 的线程等待信号 打印 "foo" 完成后,通知 "bar" 开始打印,然后打印 "foo" 的线程等待信号 打印 "bar" 完成后,通知 "foo" 开始打印,然后打印 "bar" 的线程等待信号 这样交替执行,直到输出 n 次 "foobar"
import java.util.concurrent.Semaphore;
classFooBar{
privateint n;
private Semaphore semFoo = new Semaphore(0);
private Semaphore semBar = new Semaphore(0);
publicFooBar(int n){
this.n = n;
}
publicvoidfoo(Runnable printFoo)throws InterruptedException {
for (int i = 0; i < n; i++) {
// printFoo.run() outputs "foo". Do not change or remove this line.
printFoo.run();
// 通知 "bar" 开始打印
semBar.release();
// 等待信号
semFoo.acquire();
}
}
publicvoidbar(Runnable printBar)throws InterruptedException {
for (int i = 0; i < n; i++) {
// 等待信号
semBar.acquire();
// printBar.run() outputs "bar". Do not change or remove this line.
printBar.run();
// 通知 "foo" 开始打印
semFoo.release();
}
}
}
就是这样,其他的多线程题也是类似的,只要用信号量就可以轻松解决。
new Semaphore(0)
里面的 0 是什么意思呢?意思就是初始的时候有多少个信号,如果已经有至少 1 个信号,acquire() 方法就无需等待,而是直接消耗一个信号继续执行。也就是说每调用一次 release() 方法,就会添加一个信号,每调用一次 acquire() 方法,就会减少一个信号。当信号数量为 0 时,acquire() 方法就会等待。所以上面的代码也可以改写成下面这样,显得 foo 方法和 bar 方法的格式看起来更加统一。
Java 实现
classFooBar{
privateint n;
// 初始时有一个 Foo 信号
private Semaphore semFoo = new Semaphore(1);
private Semaphore semBar = new Semaphore(0);
publicFooBar(int n){
this.n = n;
}
publicvoidfoo(Runnable printFoo)throws InterruptedException {
for (int i = 0; i < n; i++) {
// 等待信号,第一次的时候,因为已经有一个 Foo 信号,所以这里会直接消耗一个信号,继续执行
semFoo.acquire();
// printFoo.run() outputs "foo". Do not change or remove this line.
printFoo.run();
// 通知 "bar" 开始打印
semBar.release();
}
}
publicvoidbar(Runnable printBar)throws InterruptedException {
for (int i = 0; i < n; i++) {
// 等待信号
semBar.acquire();
// printBar.run() outputs "bar". Do not change or remove this line.
printBar.run();
// 通知 "foo" 开始打印
semFoo.release();
}
}
}
这次我是真听明白了!信号量真是解决多线程问题的利器,谢谢老大!
你丫的回去一定要把多线程的题目好好练完!
点个在看,少个 bug👇
关键词
方法
代码
System..println
多线程
e.printStackTrace
最新评论
推荐文章
作者最新文章
你可能感兴趣的文章
Copyright Disclaimer: The copyright of contents (including texts, images, videos and audios) posted above belong to the User who shared or the third-party website which the User shared from. If you found your copyright have been infringed, please send a DMCA takedown notice to [email protected]. For more detail of the source, please click on the button "Read Original Post" below. For other communications, please send to [email protected].
版权声明:以上内容为用户推荐收藏至CareerEngine平台,其内容(含文字、图片、视频、音频等)及知识版权均属用户或用户转发自的第三方网站,如涉嫌侵权,请通知[email protected]进行信息删除。如需查看信息来源,请点击“查看原文”。如需洽谈其它事宜,请联系[email protected]。
版权声明:以上内容为用户推荐收藏至CareerEngine平台,其内容(含文字、图片、视频、音频等)及知识版权均属用户或用户转发自的第三方网站,如涉嫌侵权,请通知[email protected]进行信息删除。如需查看信息来源,请点击“查看原文”。如需洽谈其它事宜,请联系[email protected]。