「这是我参与2022首次更文挑战的第18天,活动详情查看:2022首次更文挑战」
前言
笔者除了大学时期选修过《算法设计与分析》和《数据结构》还是浑浑噩噩度过的(当时觉得和编程没多大关系),其他时间对算法接触也比较少,但是随着开发时间变长对一些底层代码/处理机制有所接触越发觉得算法的重要性,所以决定开始系统的学习(主要是刷力扣上的题目)和整理,也希望还没开始学习的人尽早开始。
系列文章收录《算法》专栏中。
问题描述
给你一个类:
class FooBar {
public void foo() {
for (int i = 0; i < n; i++) {
print("foo");
}
}
public void bar() {
for (int i = 0; i < n; i++) {
print("bar");
}
}
}
两个不同的线程将会共用一个 FooBar 实例:
- 线程 A 将会调用 foo() 方法,而
- 线程 B 将会调用 bar() 方法
- 请设计修改程序,以确保 "foobar" 被输出 n 次。
示例 1:
输入:n = 1
输出:"foobar"
解释:这里有两个线程被异步启动。其中一个调用 foo() 方法, 另一个调用 bar() 方法,"foobar" 将被输出一次。
示例 2:
输入:n = 2
输出:"foobarfoobar"
解释:"foobar" 将被输出两次。
提示:
1 <= n <= 1000
确定学习目标
对《交替打印 FooBar》的算法过程进行剖析。
剖析
看到这个问题的时候我觉得比较简单,交替打印,无非就是给个标识让线程A先打印再让线程B打印,在A打印的时候B不能答应,在B打印的时候A不能打印,拿我们需要解决一下问题:
- 怎么设置标记?
- 在标识不能打印的时候怎么处理?
接下来我们一一解决下
怎么设置标记?
比较简单,直接使用bollean类型就好,true的时候允许A线程打印,false的时候线程B打印。但是标识需要在线程之间保持及时的可见效所以我们使用volatile修饰一下就好。
private volatile static boolean flag = true;
标识不能打印的时候怎么处理?
我一开始想的是就像自旋一样使用没有条件的循环,这样能快速的检测到标识进而进行执行任务。但是很容易发现有个弊端就是线程一直在跑,如果分配单核CPU跑两个线程的话,需要来回切换CPU并且是线程占用一个时间片后才切换,所以我们需要主动释放CPU的占用,所以我们使用Thread.yield()就好了。
两个问题都解决了,下面直接上代码。
代码
import lombok.SneakyThrows;
class FooBar {
private int n;
//直接使用bollean类型就好,true的时候允许A线程打印,false的时候线程B打印。但是标识需要在线程之间保持及时的可见效所以我们使用volatile修饰一下就好。
private volatile static boolean flag = true;
public FooBar(int n) {
this.n = n;
}
public void foo(Runnable printFoo) throws InterruptedException {
for (int i = 0; i < n; i++) {
for (; ; ) {
if (flag) {
// printFoo.run() outputs "foo". Do not change or remove this line.
printFoo.run();
flag = false;
break;
} else {
//如果分配单核CPU跑两个线程的话,需要来回切换CPU并且是线程占用一个时间片后才切换,所以我们需要主动释放CPU的占用,所以在else的时候我们使用`Thread.yield()`就好。
Thread.yield();
}
}
}
}
public void bar(Runnable printBar) throws InterruptedException {
for (int i = 0; i < n; i++) {
for (; ; ) {
if (!flag) {
// printBar.run() outputs "bar". Do not change or remove this line.
printBar.run();
flag = true;
break;
} else {
//如果分配单核CPU跑两个线程的话,需要来回切换CPU并且是线程占用一个时间片后才切换,所以我们需要主动释放CPU的占用,所以在else的时候我们使用`Thread.yield()`就好。
Thread.yield();
}
}
}
}
@SneakyThrows
public static void main(String[] args) {
FooBar fooBar = new FooBar(5);
Thread threadA = new Thread(new Runnable() {
@SneakyThrows
@Override
public void run() {
fooBar.foo(new Runnable() {
@Override
public void run() {
System.out.print("foo");
}
});
}
});
threadA.start();
Thread threadB = new Thread(new Runnable() {
@SneakyThrows
@Override
public void run() {
fooBar.bar(new Runnable() {
@Override
public void run() {
System.out.print("bar");
System.out.println();
}
});
}
});
threadB.start();
}
}