4.三个线程交替打印ABC,循环100次,如何实现?用wait/notify和Lock/Condition分别实现。
方案一:使用 synchronized 和 wait/notify 实现
synchronized是Java中的一个关键字,用于实现对共享资源的互斥访问。wait和notify是Object类中的两个方法,用于实现线程间的通信。wait方法会让当前线程释放锁,并进入等待状态,直到被其他线程唤醒。notify方法会唤醒一个在同一个锁上等待的线程。
使用一个共享变量state来表示当前应该打印哪个字母,初始值为0。当state为0时,表示轮到A线程打印;当state为1时,表示轮到B线程打印;当state为2时,表示轮到C线程打印。每个线程在打印完字母后,需要将state更换为下一个要打印的值。同时,每个线程还需要唤醒下一个线程,并让自己进入等待状态。
java
public class PrintABCWithWaitNotify {
// 共享对象,作为锁和通信的媒介
private final Object lock = new Object();
// 共享变量,表示当前应该打印的字母
private volatile int state = 0; // 0: 打印A, 1: 打印B, 2: 打印C
public void printA() {
synchronized (lock) {
for (int i = 0; i < 100; i++) {
while (state != 0) {
try {
lock.wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
System.out.print("A");
state = 1;
lock.notifyAll();
}
}
}
public void printB() {
synchronized (lock) {
for (int i = 0; i < 100; i++) {
while (state != 1) {
try {
lock.wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
System.out.print("B");
state = 2;
lock.notifyAll();
}
}
}
public void printC() {
synchronized (lock) {
for (int i = 0; i < 100; i++) {
while (state != 2) {
try {
lock.wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
System.out.print("C");
state = 0;
lock.notifyAll();
}
}
}
public static void main(String[] args) {
PrintABCWithWaitNotify printer = new PrintABCWithWaitNotify();
Thread threadA = new Thread(printer::printA);
Thread threadB = new Thread(printer::printB);
Thread threadC = new Thread(printer::printC);
threadA.start();
threadB.start();
threadC.start();
}
}
方案二:使用 ReentrantLock/Condition 实现
ReentrantLock是Java中的一个类,用于实现可重入的互斥锁。Condition是ReentrantLock中的一个接口,用于实现线程间的条件等待和唤醒。ReentrantLock可以创建多个Condition对象,每个Condition对象可以绑定一个或多个线程,实现对不同线程的精确控制。
使用一个ReentrantLock对象作为锁,同时创建三个Condition对象,分别绑定A、B、C三个线程。每个线程在打印字母之前,需要调用对应的Condition对象的await方法,等待被唤醒。每个线程在打印字母之后,需要调用下一个Condition对象的signal方法,唤醒下一个线程。
java
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class PrintABCWithLockCondition {
// 可重入锁
private final ReentrantLock lock = new ReentrantLock();
// 三个条件对象,分别绑定A、B、C三个线程
private final Condition conditionA = lock.newCondition();
private final Condition conditionB = lock.newCondition();
private final Condition conditionC = lock.newCondition();
// 共享变量,表示当前应该打印的字母
private volatile int state = 0; // 0: 打印A, 1: 打印B, 2: 打印C
public void printA() {
lock.lock();
try {
for (int i = 0; i < 100; i++) {
while (state != 0) {
conditionA.await();
}
System.out.print("A");
state = 1;
conditionB.signal(); // 唤醒线程B
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
lock.unlock();
}
}
public void printB() {
lock.lock();
try {
for (int i = 0; i < 100; i++) {
while (state != 1) {
conditionB.await();
}
System.out.print("B");
state = 2;
conditionC.signal(); // 唤醒线程C
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
lock.unlock();
}
}
public void printC() {
lock.lock();
try {
for (int i = 0; i < 100; i++) {
while (state != 2) {
conditionC.await();
}
System.out.print("C");
state = 0;
conditionA.signal(); // 唤醒线程A
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
PrintABCWithLockCondition printer = new PrintABCWithLockCondition();
Thread threadA = new Thread(printer::printA);
Thread threadB = new Thread(printer::printB);
Thread threadC = new Thread(printer::printC);
threadA.start();
threadB.start();
threadC.start();
}
}
方案三:使用 Semaphore 实现
Semaphore是Java中的一个类,用于实现信号量机制。信号量是一种计数器,用于控制对共享资源的访问。Semaphore可以创建多个信号量对象,每个信号量对象可以绑定一个或多个线程,实现对不同线程的精确控制。
使用三个Semaphore对象,分别初始化为1、0、0,表示A、B、C三个线程的初始许可数。每个线程在打印字母之前,需要调用对应的Semaphore对象的acquire方法,获取许可。每个线程在打印字母之后,需要调用下一个Semaphore对象的release方法,释放许可。
java
import java.util.concurrent.Semaphore;
public class PrintABCWithSemaphore {
private final Semaphore semaphoreA = new Semaphore(1); // 初始允许A先执行
private final Semaphore semaphoreB = new Semaphore(0);
private final Semaphore semaphoreC = new Semaphore(0);
public void printA() {
for (int i = 0; i < 100; i++) {
try {
semaphoreA.acquire(); // 获取信号量
System.out.print("A");
// 释放B的信号量
semaphoreB.release();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
}
public void printB() {
for (int i = 0; i < 100; i++) {
try {
semaphoreB.acquire(); // 获取信号量
System.out.print("B");
// 释放C的信号量
semaphoreC.release();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
}
public void printC() {
for (int i = 0; i < 100; i++) {
try {
semaphoreC.acquire(); // 获取信号量
System.out.print("C");
// 释放A的信号量
semaphoreA.release();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
}
public static void main(String[] args) {
PrintABCWithSemaphore printer = new PrintABCWithSemaphore();
Thread threadA = new Thread(printer::printA);
Thread threadB = new Thread(printer::printB);
Thread threadC = new Thread(printer::printC);
threadA.start();
threadB.start();
threadC.start();
}
}
方案四:使用 AtomicInteger 和 CAS 实现
AtomicInteger是Java中的一个类,用于实现原子性的整数操作。CAS是一种无锁的算法,全称为Compare And Swap,即比较并交换。CAS操作需要三个参数:一个内存地址,一个期望值,一个新值。如果内存地址的值与期望值相等,就将其更新为新值,否则不做任何操作。
使用一个AtomicInteger对象来表示当前应该打印哪个字母,初始值为0。当state为0时,表示轮到A线程打印;当state为1时,表示轮到B线程打印;当state为2时,表示轮到C线程打印。每个线程在打印完字母后,需要使用CAS操作将state加1,并对3取模,以便循环。
java
import java.util.concurrent.atomic.AtomicInteger;
public class PrintABCWithAtomicInteger {
private final AtomicInteger state = new AtomicInteger(0); // 0: 打印A, 1: 打印B, 2: 打印C
public void printA() {
int count = 0;
while (count < 100) {
if (state.get() == 0) {
if (state.compareAndSet(0, 1)) {
System.out.print("A");
count++;
}
}
}
}
public void printB() {
int count = 0;
while (count < 100) {
if (state.get() == 1) {
if (state.compareAndSet(1, 2)) {
System.out.print("B");
count++;
}
}
}
}
public void printC() {
int count = 0;
while (count < 100) {
if (state.get() == 2) {
if (state.compareAndSet(2, 0)) {
System.out.print("C");
count++;
}
}
}
}
public static void main(String[] args) {
PrintABCWithAtomicInteger printer = new PrintABCWithAtomicInteger();
Thread threadA = new Thread(printer::printA);
Thread threadB = new Thread(printer::printB);
Thread threadC = new Thread(printer::printC);
threadA.start();
threadB.start();
threadC.start();
}
}
各种方法对比总结
| 方法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| wait/notify | JDK原生支持,简单易懂 | 会唤醒所有等待线程,效率较低 | 学习和理解同步机制 |
| Lock/Condition | 精确控制线程唤醒,性能好 | 代码相对复杂 | 高并发场景 |
| Semaphore | 控制流程清晰,易于理解 | 信号量初始值设置需注意 | 资源访问控制 |
| AtomicInteger/CAS | 无锁设计,性能高 | CPU消耗较高,可能出现忙等 | 高性能要求场景 |
【参考文献】