金不三,银不四的高频面试题,Java 中顺序打印 A1B2C3 是多线程中的一个经典面试问题,其解决方法可以锻炼程序员的多线程编程能力。本文将从多个角度,介绍 Java 中顺序打印 A1B2C3 的实现方式,总共分为如下6种方式:
synchronized 和 wait/notify
ReentrantLock和Condition
Semaphore
Phaser
AtomicInteger
BlockingQueue
1. 六大解法
1.1 Synchronized 和 wait/notify
synchronized 和 wait/notify 是 Java 中最基本的线程同步和顺序控制机制,可以用来实现顺序打印 A1B2C3。具体实现方式如下:
publicclassPrintA1B2C3_sync_wait_notify {
privatestaticObjectlock=newObject();
privatestaticvolatilebooleanprintNumber=false;
privatestaticintnum=1;
publicstaticvoidmain(String[] args) {
ThreadA=newThread(() -> {
synchronized (lock) {
for (charc='A'; c <= 'Z'; c++) {
while (printNumber) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print(c);
printNumber = true;
lock.notify();
}
}
});
ThreadB=newThread(() -> {
synchronized (lock) {
while (num <= 26) {
while (!printNumber) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print(num);
num++;
printNumber = false;
lock.notify();
}
}
});
A.start();
B.start();
}
}
复制代码
以上代码中,线程 A 负责打印字母,线程 B 负责打印数字,通过共享一个锁和一个 volatile 的布尔值,实现了两个线程的顺序控制。
1.2 ReentrantLock 和 Condition
ReentrantLock 和 Condition 是 Java 中更高级的线程同步和顺序控制机制,可以用来实现顺序打印 A1B2C3。具体实现方式如下:
publicclassPrintA1B2C3_lock_condition {
privatestaticReentrantLocklock=newReentrantLock();
privatestaticConditionletter= lock.newCondition();
privatestaticConditionnumber= lock.newCondition();
privatestaticintnum=1;
publicstaticvoidmain(String[] args) {
ThreadA=newThread(() -> {
lock.lock();
try {
for (charc='A'; c <= 'Z'; c++) {
System.out.print(c);
number.signal();
if (c < 'Z') letter.await();
}
number.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
});
ThreadB=newThread(() -> {
lock.lock();
try {
for (inti=1; i <= 26; i++) {
System.out.print(i);
letter.signal();
if (i < 26) number.await();
}
letter.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
});
A.start();
B.start();
}
复制代码
以上代码中,线程 A 和线程 B 分别使用了两个 Condition 对象,通过 lock 来保证线程同步,实现了两个线程的顺序控制。
1.3 Semaphore
Semaphore 是 Java 中另一种常用的线程同步机制,可以用来实现顺序打印 A1B2C3。具体实现方式如下:
publicclassPrintA1B2C3_semaphore {
privatestaticSemaphoreletter=newSemaphore(1);
privatestaticSemaphorenumber=newSemaphore(0);
publicstaticvoidmain(String[] args) {
ThreadA=newThread(() -> {
for (charc='A'; c <= 'Z'; c++) {
try {
letter.acquire();
System.out.print(c);
number.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
ThreadB=newThread(() -> {
for (inti=1; i <= 26; i++) {
try {
number.acquire();
System.out.print(i);
letter.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
A.start();
B.start();
}
}
复制代码
以上代码中,线程 A 和线程 B 分别使用了两个 Semaphore 对象,通过 acquire 和 release 方法来保证线程同步,实现了两个线程的顺序控制
1.4 Phaser
Phaser 是 Java 7 中引入的线程同步机制,可以用来实现顺序打印 A1B2C3。具体实现方式如下:
publicclassPrintA1B2C3_Phaser{
privatestaticPhaserphaser=newPhaser(1);
publicstaticvoidmain(String[] args) {
ThreadA=newThread(() -> {
for (charc='A'; c <= 'Z'; c++) {
System.out.print(c);
phaser.arriveAndAwaitAdvance();
phaser.register();
}
});
ThreadB=newThread(() -> {
for (inti=1; i <= 26; i++) {
phaser.arriveAndAwaitAdvance();
System.out.print(i);
phaser.register();
}
});
A.start();
B.start();
}
}
复制代码
以上代码中,线程 A 和线程 B 分别使用了一个 Phaser 对象,通过 arriveAndAwaitAdvance 和 register 方法来保证线程同步,实现了两个线程的顺序控制
1.5 AtomicInteger
也可以使用 Java 的 AtomicInteger类来实现顺序打印 A1B2C3 这个字符串,下面是实现的示例代码:
publicclassPrintA1B2C3_AtomicIntegerextendsThread {
privatestaticAtomicIntegerindex=newAtomicInteger(0);
privatestaticfinalStringstr="A1B2C3D4E5";
privateint threadId;
publicPrintThread(int threadId) {
this.threadId = threadId;
}
@Overridepublicvoidrun() {
while (index.get() < str.length()) {
intcurrentIdx= index.getAndIncrement();
if (currentIdx % 2 == threadId) {
System.out.println(str.charAt(currentIdx));
}
}
}
publicstaticvoidmain(String[] args) {
PrintThreadthread1=newPrintThread(0);
PrintThreadthread2=newPrintThread(1);
thread1.start();
thread2.start();
}
}
复制代码
代码的主要思路是使用一个静态的 AtomicInteger 类型的变量 index,它会被两个线程共享。每个线程会通过自旋的方式不断地从 index 中获取当前的值,并使用 getAndIncrement() 方法来原子地增加它的值。如果当前的值是奇数,那么线程1就打印字母;如果当前的值是偶数,那么线程2就打印数字。这样就能保证 A1B2C3 这个字符串被顺序地打印出来。
1.6 BlockingQueue
阻塞队列是一种特殊的队列,当队列为空时,从队列中获取元素的操作会被阻塞;当队列已满时,向队列中添加元素的操作会被阻塞。阻塞队列常用于生产者-消费者模式中,可以有效地协调生产者和消费者之间的速度差异,以下是借助BlockingQueue实现打印A1B2C3的代码实现:
publicclassPrintA1B2C3_BlockingQueue {
publicstaticvoidmain(String[] args) {
BlockingQueue<String> queue = newLinkedBlockingQueue<>(1);
newThread(() -> {
String[] chars = {"A", "B", "C"};
for (String c : chars) {
try {
queue.put(c + (1 + chars[c.charAt(0) - 'A']));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
newThread(() -> {
while (true) {
try {
Stringstr= queue.take();
System.out.print(str.charAt(0));
System.out.print(str.charAt(1));
if (str.length() > 2) {
System.out.print(str.charAt(2));
}
} catch (InterruptedException e) {
e.printStackTrace();
}
if (queue.isEmpty()) {
break;
}
}
}).start();
}
}
复制代码
在该实现中,我们使用了LinkedBlockingQueue作为阻塞队列,创建了一个大小为1的队列,因此生产者线程在往队列中放入元素时会被阻塞,直到消费者线程取出了队列中的元素。消费者线程在取出元素时也会被阻塞,直到生产者线程往队列中放入了元素
2. 总结
顺序打印A1B2C3问题是一个典型的线程间通信和同步问题,对于这个问题,常见的实现方式包括使用锁、阻塞队列、信号量,Atomc原子操作类,需要根据实际需求进行选择。同时,也需要注意多线程编程中常见的问题,如线程安全、死锁、内存泄漏等,从而保证代码的质量和稳定性。
作者:喝咖啡的工匠