最近有同事收到阿里的面试邀请,因地点在杭州就先进行了一轮线上笔试,其中有这样一道题,和大家分享一下。
题目是这样的:用多个线程交替打印字符,如字符串"ali",一个线程打印a,一个线程打印l,一个线程打印i , 一个线程打印空格,如:ali ali ali …
代码支持拓展,能打印任意字符串"alibaba",如:alibaba alibaba alibaba alibaba …
是一道经典的线程交替打印相关的题目,和网上轮流打印ABC的题目很相似,先说一下轮流打印ABC的解题思路,再回过头来看阿里的这道题。
关键点主要有两个,一是如何让对应的线程打印对应的字符,一是如何控制打印的顺序。
一般的解决思路是用可重入锁 ReentrantLock 加 Condition,代码如下:
public class AlternativePrint {
private ReentrantLock lock = new ReentrantLock();
private Condition conditionA = lock.newCondition();
private Condition conditionB = lock.newCondition();
private Condition conditionC = lock.newCondition();
// 控制打印顺序
private int number = 1;
public void loopA(){
lock.lock();
try {
// 未到打印时机,等待
if (number != 1){
conditionA.await();
}
System.out.print(Thread.currentThread().getName());
number = 2;
// 唤醒下一个打印线程
conditionB.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void loopB(){
lock.lock();
try {
if (number != 2){
conditionB.await();
}
System.out.print(Thread.currentThread().getName());
number = 3;
conditionC.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void loopC(){
lock.lock();
try {
if (number != 3){
conditionC.await();
}
System.out.print(Thread.currentThread().getName());
number = 1;
conditionA.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
测试类:
public static void main(String[] args) {
AlternativePrint alternativePrint = new AlternativePrint();
new Thread("A"){
@Override
public void run() {
for (int i = 0; i < 20; i++) {
alternativePrint.loopA();
}
}
}.start();
new Thread("B"){
@Override
public void run() {
for (int i = 0; i < 20; i++) {
alternativePrint.loopB();
}
}
}.start();
new Thread("C"){
@Override
public void run() {
for (int i = 0; i < 20; i++) {
alternativePrint.loopC();
}
}
}.start();
}
相信这道题难不倒大家,那回头再来看阿里这道题。这道题不仅需要解决上面两个问题,还需要用字符长度的线程去打印,我的解决思路是这样的:
- 以字符串长度创建线程,再加一个打印空格的线程
- 将线程和打印的字符封装成一个对象
- 为了控制打印的顺序,将对象之间的关系维护成单向循环链表
代码如下:
public class Print {
private ReentrantLock lock;
private String str;
private int printTimes;
private void print() {
List<Item> list = new ArrayList<>();
for (int i = 0; i < str.length(); i++) {
list.add(new Item(str.charAt(i), printTimes, lock));
}
list.add(new Item((char) 32, printTimes, lock));
for (int i = 0; i < list.size(); i++) {
Item item = list.get(i);
if (i == list.size() - 1) {
item.setNext(list.get(0));
}
else {
item.setNext(list.get(i + 1));
if (i == 0) {
// 激活第一个线程
item.setActive(true);
}
}
}
list.forEach(Thread::start);
}
public Print(String str, int printTimes) {
this.str = str;
this.printTimes = printTimes;
this.lock = new ReentrantLock();
}
static class Item extends Thread {
private char ch;
private int printTimes;
private ReentrantLock lock;
private Condition condition;
private Item next;
private boolean active;
@Override
public void run() {
for (int i = 0; i < printTimes; i++) {
lock.lock();
try {
if (!active) {
condition.await();
}
System.out.print(ch);
active = false;
if (!next.isActive()) {
next.setActive(true);
next.getCondition().signal();
}
}
catch (Exception e) {
e.printStackTrace();
}
finally {
lock.unlock();
}
}
}
public Item(char ch, int printTimes, ReentrantLock lock) {
this.ch = ch;
this.printTimes = printTimes;
this.lock = lock;
this.condition = lock.newCondition();
}
public Condition getCondition() { return condition; }
public Item getNext() { return next; }
public void setNext(Item next) { this.next = next; }
public void setActive(boolean active) { this.active = active; }
public boolean isActive() { return active; }
}
}
测试类:
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
System.out.print("请输入字符串:");
String next = input.next();
System.out.print("请输入打印次数:");
int printTimes = input.nextInt();
Print print = new Print(next, printTimes);
print.print();
}
打印结果:
上海米哈游内推,福利好待遇高,五险二金,早晚餐零食水果下午茶烧烤,吃货天堂,还有奶茶咖啡券,旅游基金,内推奖励,看我这么卖力打广告就知道奖励力度有多大了,更有周年礼物年会抽奖等你来拿,欢迎大家自荐和推荐:https://app.mokahr.com/recommendation-apply/mihoyo/26460?recommendCode=NTAKBmA#/jobs?from=genPoster