多线程知识:三个线程如何交替打印ABC循环100次

多线程,知识,三个,线程,如何,交替,打印,abc,循环 · 浏览次数 : 564

小编点评

**内容摘要** 本文介绍了多线程之间的同步和通信方法,包括使用CyclicBarrier cyclicBarrier对象来实现多个线程之间的屏障。详细介绍了以下五种方法: * 使用AtomicInteger AtomicInteger对象来表示当前应该打印哪个字母。 * 使用CyclicBarrier cyclicBarrier对象来实现多个线程之间的屏障。 * 使用CyclicBarrier cyclicBarrier对象来实现多个线程之间的屏障。 * 使用CyclicBarrier cyclicBarrier对象来实现多个线程之间的屏障。 * 使用JDK的AtomicInteger AtomicInteger对象来表示当前应该打印哪个字母。 **方法介绍** * 使用AtomicInteger AtomicInteger对象来表示当前应该打印哪个字母。 * 使用CyclicBarrier cyclicBarrier对象来实现多个线程之间的屏障。 * 使用CyclicBarrier cyclicBarrier对象来实现多个线程之间的屏障。 * 使用CyclicBarrier cyclicBarrier对象来实现多个线程之间的屏障。 * 使用JDK的AtomicInteger AtomicInteger对象来表示当前应该打印哪个字母。 **代码示例** ```java // 使用AtomicInteger AtomicInteger对象来表示当前应该打印哪个字母 AtomicInteger AtomicInteger atomicInteger = new AtomicInteger(0); // 使用CyclicBarrier cyclicBarrier对象来实现多个线程之间的屏障 CyclicBarrier cyclicBarrier = new CyclicBarrier(3, new Runnable() { @Override public void run() { switch (atomicInteger.get()) { case 0: System.out.println("A"); break; case 1: System.out.println("B"); break; case 2: System.out.println("C"); break; } } }); // 使用CyclicBarrier cyclicBarrier对象来实现多个线程之间的屏障 CyclicBarrier cyclicBarrier = new CyclicBarrier(3, new Runnable() { @Override public void run() { try { for (int i = 0; i < 100; i++) { System.out.println(atomicInteger.get()); atomicInteger.get(); } } catch (InterruptedException | BrokenBarrierException e) { e.printStackTrace(); } } }); ``` **总结** 本文介绍了多线程之间的同步和通信方法,包括使用CyclicBarrier cyclicBarrier对象来实现多个线程之间的屏障。详细介绍了五种方法,每个方法都有其优缺点。选择方法时需要根据实际的场景和需求来决定。

正文

本文博主给大家讲解一道网上非常经典的多线程面试题目。关于三个线程如何交替打印ABC循环100次的问题。

下文实现代码都基于Java代码在单个JVM内实现。

问题描述

给定三个线程,分别命名为A、B、C,要求这三个线程按照顺序交替打印ABC,每个字母打印100次,最终输出结果为:

A
B
C
A
B
C
...
A
B
C

推荐博主开源的 H5 商城项目waynboot-mall,这是一套全部开源的微商城项目,包含三个项目:运营后台、H5 商城前台和服务端接口。实现了商城所需的首页展示、商品分类、商品详情、商品 sku、分词搜索、购物车、结算下单、支付宝/微信支付、收单评论以及完善的后台管理等一系列功能。 技术上基于最新得 Springboot3.0、jdk17,整合了 MySql、Redis、RabbitMQ、ElasticSearch 等常用中间件。分模块设计、简洁易维护,欢迎大家点个 star、关注博主。

github 地址:https://github.com/wayn111/waynboot-mall

解决思路

这是一个典型的多线程同步的问题,需要保证每个线程在打印字母之前,能够判断是否轮到自己执行,以及在打印字母之后,能够通知下一个线程执行。为了实现这一目标,博主讲介绍以下5种方法:

  • 使用synchronized和wait/notify
  • 使用ReentrantLock和Condition
  • 使用Semaphore
  • 使用AtomicInteger和CAS
  • 使用CyclicBarrier

方法一:使用synchronized和wait/notify

synchronized是Java中的一个关键字,用于实现对共享资源的互斥访问。wait和notify是Object类中的两个方法,用于实现线程间的通信。wait方法会让当前线程释放锁,并进入等待状态,直到被其他线程唤醒。notify方法会唤醒一个在同一个锁上等待的线程。

我们可以使用一个共享变量state来表示当前应该打印哪个字母,初始值为0。当state为0时,表示轮到A线程打印;当state为1时,表示轮到B线程打印;当state为2时,表示轮到C线程打印。每个线程在打印完字母后,需要将state加1,并对3取模,以便循环。同时,每个线程还需要唤醒下一个线程,并让自己进入等待状态。

具体的代码实现如下:

public class PrintABC {

    // 共享变量,表示当前应该打印哪个字母
    private static int state = 0;

    // 共享对象,作为锁和通信的媒介
    private static final Object lock = new Object();

    public static void main(String[] args) {
        // 创建三个线程
        Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    // 循环100次
                    for (int i = 0; i < 100; i++) {
                        // 获取锁
                        synchronized (lock) {
                            // 判断是否轮到自己执行
                            while (state % 3 != 0) {
                                // 不是则等待
                                lock.wait();
                            }
                            // 打印字母
                            System.out.println("A");
                            // 修改状态
                            state++;
                            // 唤醒下一个线程
                            lock.notifyAll();
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread threadB = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    for (int i = 0; i < 100; i++) {
                        synchronized (lock) {
                            while (state % 3 != 1) {
                                lock.wait();
                            }
                            System.out.println("B");
                            state++;
                            lock.notifyAll();
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread threadC = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    for (int i = 0; i < 100; i++) {
                        synchronized (lock) {
                            while (state % 3 != 2) {
                                lock.wait();
                            }
                            System.out.println("C");
                            state++;
                            lock.notifyAll();
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        // 启动三个线程
        threadA.start();
        threadB.start();
        threadC.start();
    }
}

方法二:使用ReentrantLock和Condition

ReentrantLock是Java中的一个类,用于实现可重入的互斥锁。Condition是ReentrantLock中的一个接口,用于实现线程间的条件等待和唤醒。ReentrantLock可以创建多个Condition对象,每个Condition对象可以绑定一个或多个线程,实现对不同线程的精确控制。

我们可以使用一个ReentrantLock对象作为锁,同时创建三个Condition对象,分别绑定A、B、C三个线程。每个线程在打印字母之前,需要调用对应的Condition对象的await方法,等待被唤醒。每个线程在打印字母之后,需要调用下一个Condition对象的signal方法,唤醒下一个线程。

具体的代码实现如下:

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class PrintABC {

    // 共享变量,表示当前应该打印哪个字母
    private static int state = 0;

    // 可重入锁
    private static final ReentrantLock lock = new ReentrantLock();

    // 三个条件对象,分别绑定A、B、C三个线程
    private static final Condition A = lock.newCondition();
    private static final Condition B = lock.newCondition();
    private static final Condition C = lock.newCondition();

    public static void main(String[] args) {
        // 创建三个线程
        Thread threaA = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    // 循环100次
                    for (int i = 0; i < 100; i++) {
                        // 获取锁
                        lock.lock();
                        try {
                            // 判断是否轮到自己执行
                            while (state % 3 != 0) {
                                // 不是则等待
                                A.await();
                            }
                            // 打印字母
                            System.out.println("A");
                            // 修改状态
                            state++;
                            // 唤醒下一个线程
                            B.signal();
                        } finally {
                            // 释放锁
                            lock.unlock();
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread threaB = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    for (int i = 0; i < 100; i++) {
                        lock.lock();
                        try {
                            while (state % 3 != 1) {
                                B.await();
                            }
                            System.out.println("B");
                            state++;
                            C.signal();
                        } finally {
                            lock.unlock();
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread threaC = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    for (int i = 0; i < 100; i++) {
                        lock.lock();
                        try {
                            while (state % 3 != 2) {
                                C.await();
                            }
                            System.out.println("C");
                            state++;
                            A.signal();
                        } finally {
                            lock.unlock();
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        // 启动三个线程
        threaA.start();
        threaB.start();
        threaC.start();
    }
}

方法三:使用Semaphore

Semaphore是Java中的一个类,用于实现信号量机制。信号量是一种计数器,用于控制对共享资源的访问。Semaphore可以创建多个信号量对象,每个信号量对象可以绑定一个或多个线程,实现对不同线程的精确控制。

我们可以使用三个Semaphore对象,分别初始化为1、0、0,表示A、B、C三个线程的初始许可数。每个线程在打印字母之前,需要调用对应的Semaphore对象的acquire方法,获取许可。每个线程在打印字母之后,需要调用下一个Semaphore对象的release方法,释放许可。

具体的代码实现如下:

import java.util.concurrent.Semaphore;

public class PrintABC {
    private static int state = 0;

    // 三个信号量对象,分别表示A、B、C三个线程的初始许可数
    private static final Semaphore A = new Semaphore(1);
    private static final Semaphore B = new Semaphore(0);
    private static final Semaphore C = new Semaphore(0);

    public static void main(String[] args) {
        // 创建三个线程
        Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    // 循环100次
                    for (int i = 0; i < 100; i++) {
                        // 获取许可
                        A.acquire();
                        // 打印字母
                        System.out.println("A");
                        // 修改状态
                        state++;
                        // 释放许可
                        B.release();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread threadB = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    for (int i = 0; i < 100; i++) {
                        B.acquire();
                        System.out.println("B");
                        state++;
                        C.release();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread threadC = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    for (int i = 0; i < 100; i++) {
                        C.acquire();
                        System.out.println("C");
                        state++;
                        A.release();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        // 启动三个线程
        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取模,以便循环。

具体的代码实现如下:

import java.util.concurrent.atomic.AtomicInteger;

public class PrintABC {

    // 共享变量,表示当前应该打印哪个字母
    private static AtomicInteger state = new AtomicInteger(0);

    public static void main(String[] args) {
        // 创建三个线程
        Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                // 循环100次
                for (int i = 0; i < 100; ) {
                    // 判断是否轮到自己执行
                    if (state.get() % 3 == 0) {
                        // 打印字母
                        System.out.println("A");
                        // 修改状态,使用CAS操作保证原子性
                        state.compareAndSet(state.get(), state.get() + 1);
                        // 计数器加1
                        i++;
                    }
                }
            }
        });

        Thread threadB = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 100; ) {
                    if (state.get() % 3 == 1) {
                        System.out.println("B");
                        state.compareAndSet(state.get(), state.get() + 1);
                        i++;
                    }
                }
            }
        });

        Thread threadC = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 100; ) {
                    if (state.get() % 3 == 2) {
                        System.out.println("C");
                        state.compareAndSet(state.get(), state.get() + 1);
                        i++;
                    }
                }
            }
        });

        // 启动三个线程
        threadA.start();
        threadB.start();
        threadC.start();
    }
}

方法五:使用CyclicBarrier

CyclicBarrier是Java中的一个类,用于实现多个线程之间的屏障。CyclicBarrier可以创建一个屏障对象,指定一个参与等待线程数和一个到达屏障点时得动作。当所有线程都到达屏障点时,会执行屏障动作,然后继续执行各自的任务。CyclicBarrier可以重复使用,即当所有线程都通过一次屏障后,可以再次等待所有线程到达下一次屏障。

我们可以使用一个CyclicBarrier对象,指定三个线程为参与等待数,以及一个打印字母的到达屏障点动作。每个线程在执行完自己的任务后,需要调用CyclicBarrier对象的await方法,等待其他线程到达屏障点。当所有线程都到达屏障点时,会执行打印字母的屏障动作,并根据state的值判断应该打印哪个字母。然后,每个线程继续执行自己的任务,直到循环结束。需要注意得就是由于打印操作在到达屏障点得动作内执行,所以三个线程得循环次数得乘以参与线程数量,也就是三。

具体的代码实现如下:

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class PrintABC {

    // 共享变量,表示当前应该打印哪个字母
    private static int state = 0;

    // 参与线程数量
    private static int threadNum = 3;

    // 循环屏障,指定三个线程为屏障点,以及一个打印字母的屏障动作
    private static final CyclicBarrier barrier = new CyclicBarrier(threadNum, new Runnable() {
        @Override
        public void run() {
            // 根据state的值判断应该打印哪个字母
            switch (state) {
                case 0:
                    System.out.println("A");
                    break;
                case 1:
                    System.out.println("B");
                    break;
                case 2:
                    System.out.println("C");
                    break;
            }
            // 修改状态
            state = (state + 1) % 3;
            System.out.println(state);
        }
    });

    public static void main(String[] args) {
        // 创建三个线程
        Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    // 循环100次
                    for (int i = 0; i < threadNum * 100; i++) {
                        // 执行自己的任务
                        // ...
                        // 等待其他线程到达屏障点
                        barrier.await();
                    }
                } catch (InterruptedException | BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread threadB = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    for (int i = 0; i < threadNum * 100; i++) {
                        // 执行自己的任务
                        // ...
                        // 等待其他线程到达屏障点
                        barrier.await();
                    }
                } catch (InterruptedException | BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread threadC = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    for (int i = 0; i < threadNum * 100; i++) {
                        // 执行自己的任务
                        // ...
                        // 等待其他线程到达屏障点
                        barrier.await();
                    }
                } catch (InterruptedException | BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }
        });

        // 启动三个线程
        threadA.start();
        threadB.start();
        threadC.start();
    }
}

总结

到此,本文内容已经讲解完毕,以上的这五种方法都可以利用不同的工具和机制来实现多线程之间的同步和通信,从而保证按照顺序交替打印ABC。这些方法各有优缺点,具体的选择需要根据实际的场景和需求来决定。

最后本文讲解代码是在单个JVM内的实现方法,如果大家对涉及到多个JVM来实现按照顺序交替打印ABC的话,可以私信博主,博主再给大家出一期文章进行讲解。

关注公众号【waynblog】每周分享技术干货、开源项目、实战经验、高效开发工具等,您的关注将是我的更新动力!

与多线程知识:三个线程如何交替打印ABC循环100次相似的内容:

多线程知识:三个线程如何交替打印ABC循环100次

本文博主给大家讲解一道网上非常经典的多线程面试题目。关于三个线程如何交替打印ABC循环100次的问题。 > 下文实现代码都基于Java代码在单个JVM内实现。 ## 问题描述 给定三个线程,分别命名为A、B、C,要求这三个线程按照顺序交替打印ABC,每个字母打印100次,最终输出结果为: ``` A

Java多线程-ThreadPool线程池-1(三)

开完一趟车完整的过程是启动、行驶和停车,但老司机都知道,真正费油的不是行驶,而是长时间的怠速、频繁地踩刹车等动作。因为在速度切换的过程中,发送机要多做一些工作,当然就要多费一些油。 而一个Java线程完整的生命周期就包括: 1、T1:创建(启动) 2、T2:运行(行驶) 3、T3:销毁(停车) 而T

模拟.NET应用场景,综合应用反编译、第三方库调试、拦截、一库多版本兼容方案

模拟.NET实际应用场景,综合应用三个主要知识点:一是使用dnSpy反编译第三库及调试,二是使用Lib.Harmony库实现第三库拦截、伪造,三是实现同一个库支持多版本同时引用。

剖析 Kafka 消息丢失的原因

Kafka消息丢失的原因通常涉及多个方面,包括生产者、消费者和Kafka服务端(Broker)的配置和行为。下面将围绕这三个关键点,详细探讨Kafka消息丢失的常见原因,并提供相应的解决方案和最佳实践。总的来说,Kafka消息丢失是一个涉及多个环节的问题,需要从生产者、Broker和消费者三个层面综...

加密的手机号,如何模糊查询?

前言 前几天,知识星球中有位小伙伴,问了我一个问题:加密的手机号如何模糊查询? 我们都知道,在做系统设计时,考虑到系统的安全性,需要对用户的一些个人隐私信息,比如:登录密码、身份证号、银行卡号、手机号等,做加密处理,防止用户的个人信息被泄露。 很早之前,CSDN遭遇了SQL注入,导致了600多万条明

[转帖]linux内存分析工具pmap

👦博主介绍:程序员悟啦(乌拉~) ✍个人仓库:码云 🔊座右铭:“懒”对一个人的毁灭性有多大,早起的重要性就多大。 📚免责声明:文章由博主原创、部分文章整理于网络,仅供学习和知识分享 💬相遇是缘,既然来了就拎着小板凳🪑坐下来一起唠会儿👁‍🗨,如果在文中有所收获,请别忘了一键三连,动动你发

为什么程序员老喜欢买云服务器?

前言 不知道你发现了没有?作为程序员或者测试工程师的你,身边的同事或者同学,都喜欢买一台或者多台云服务器。 为什么呢? 他们是为了耍酷? 答:当然不是。 云服务器对我们来说真的非常有用。 不信继续往下看。 今天这篇文章就跟大家一起聊聊,云服务器的一些用途,为什么程序员老爱买云服务器,希望对你会有所帮

C#学习笔记--面向对象三大特征

C#核心 面向对象--封装 用程序来抽象现实世界,(万物皆对象)来编程实现功能。 三大特性:封装、继承、多态。 类与对象 声明位置:namespace中 样式:class 类名{} 命名:帕斯卡命名法(首字母大写) 实例化对象:根据类来新建一个对象。Person p=new Person(); 成员

LLM 大模型学习必知必会系列(三):LLM和多模态模型高效推理实践

LLM 大模型学习必知必会系列(三):LLM和多模态模型高效推理实践 1.多模态大模型推理 LLM 的推理流程: 多模态的 LLM 的原理: 代码演示:使用 ModelScope NoteBook 完成语言大模型,视觉大模型,音频大模型的推理 环境配置与安装 以下主要演示的模型推理代码可在魔搭社区免

C#异步有多少种实现方式?

前言 微信群里的一个提问引发的这个问题,有同学问:C#异步有多少种实现方式?想要知道C#异步有多少种实现方式,首先我们要知道.NET提供的执行异步操作的三种模式,然后再去了解C#异步实现的方式。 .NET异步编程模式 .NET 提供了执行异步操作的三种模式: 基于任务的异步模式 (TAP) ,该模式