最近有同事收到阿里的面試邀請,因地點在杭州就先進行了一輪線上筆試,其中有這樣一道題,和大家分享一下。
題目是這樣的:用多個線程交替打印字符,如字符串"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();
}
打印結果: