三个线程交替打印ABC

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消耗较高,可能出现忙等 高性能要求场景

【参考文献】

1、https://cloud.tencent.com/developer/article/2212386

2、https://cloud.tencent.com/developer/article/2300487

相关推荐
Raink老师15 小时前
【AI面试临阵磨枪-99】纯浏览器 Agent:记忆、工具、RAG、流式、安全如何实现?
人工智能·安全·面试
Demon1_Coder15 小时前
Day4-微服务-Seata默认事务
java·数据库·微服务
Oo_行者_oO15 小时前
Spring Authorization Server 下 Token 刷新流程自定义实现
后端·面试
Sunia15 小时前
《AgentX 专栏》08-工作流引擎:AgentWorkflow怎么把工具记忆流程串成一条流水线
java·架构
huipeng92615 小时前
企业级微服务开发实战(二):微服务基础设施搭建与中间件部署
java·redis·mysql·spring cloud·微服务·nacos·rabbitmq
Jabes.yang15 小时前
Java电商订单系统面试全流程解析:接口设计、数据库、微服务与分布式事务实战
java·微服务·mybatis·分布式事务·电商·订单系统·接口设计
码语智行15 小时前
导入模板下载
java
摇滚侠15 小时前
IDEA 创建 Java 项目 推送到远程 Git 仓库
java·git·intellij-idea
lcj251115 小时前
【list】【手撕 STL】List 容器全解析!迭代器 / 增删改查 / 去重排序,面试必背的核心考点!
c++·面试·list
可乐ea15 小时前
【知识获取与分享社区项目 | 项目日记第 24 天】终章总结:从认证、发布、计数、Feed、搜索到 RAG:完整复盘一个知识社区后端系统
java·spring boot·redis·mysql·elasticsearch·ai·kafka