多个线程如何轮流打印ABC特定的次数?

之前的一篇文章,我给出了关于多线程应用的几个例子:

都是基于Java里面Lock锁实现的,分别是:

(1)两个线程轮流打印奇数和偶数

(2)多个线程模拟买票

(3)模拟生产者消费者

今天再抛砖引玉,思考一下如何在多个线程中,轮流打印特定顺序的信息多少次。

这类问题其实并不难,只要掌握了Java里面线程协作和锁的知识,就可以轻而易举的搞定:

根据这些,我们来假设一个场景,使用三个线程轮流打印ABC字符串3次。

解决思路:

首先需要声明3个线程,我们可以分别叫A线程,B线程,C线程:

在这里面:

A线程仅仅负责打印A。

B线程仅仅负责打印B。

C线程仅仅负责打印C。

但是呢,他们必须是有顺序,也就是说A打印完之后,才能打印B,B打印完后才行打印C,这就涉及线程协作和通信的知识了,A线程打印完毕之后,要通知B线程打印,B线程打印完之后要通知C线程打印,如果有多轮的话,C线程打印完毕之后,还要通知A线程。以及控制多轮次数的终结,不能让程序陷入死循环之中。

在仔细理一下:

(1)首先三个线程启动后,一定是A线程先打印。如果是其他线程先启动,则必须等待,线程间的通信,我们用共享变量来解决。(本质是通过共享内存)

(2)A运行的时候,B和C都在等待

(3)B运行的时候,A和C都在等待

(4)C运行的时候,A和B都在等待

(5)A运行结束通知B运行

(6)B运行结束通知C运行

(7)C运行结束通知A运行

(8)同时,如果要控制几轮打印,则需要在运行时控制循环次数,因为C线程是每一轮的结束标志,循环次数的加和要在C线程里面做。

ok,主要的逻辑基本理清了,我们看下如何用代码实现,先看核心的类:

定义了共享的监视器对象,计数器,共享变量,然后定义了三个方法分别负责打印A,B,C,功能的实现主要用了synchronized + 监视器的wait,notifyAll方法。

     static class PrintABC{

         final  Object monitor=new Object();
         volatile  int count=1;//轮次计数,从1开始,为了保证可见性,这里需要用volatile修饰
         String id="A";//贡献的
         int printCount ;

         public PrintABC(int printCount) {
             this.printCount = printCount;
         }

         public void printA() throws InterruptedException {
             while (count < printCount) {
                 synchronized (monitor) {
                     while (!id.equals("A")) {
                         monitor.wait();
                     }
                     System.out.println(Thread.currentThread().getName() + "打印: " + id);
                     id = "B";
                     monitor.notifyAll();
                 }

             }
         }

             public void printB() throws InterruptedException {
                 while (count < printCount) {
                     synchronized (monitor) {
                         while (!id.equals("B")) {
                             monitor.wait();
                         }
                         System.out.println(Thread.currentThread().getName() + "打印: " + id);
                         id = "C";
                         monitor.notifyAll();
                     }

                 }
             }

         public void printC() throws InterruptedException {
             while (count < printCount +1) {//最后一次终结线程,需要多加一次
                 synchronized (monitor) {
                     while (!id.equals("C")) {
                         monitor.wait();
                     }
                     System.out.println(Thread.currentThread().getName() + "打印: " + id+"\n");
                     id = "A";
                     count=count+1;
                     monitor.notifyAll();
                 }

             }
         }

    }

然后,我们看下,main方法如何编写:

    public static void main(String[] args) {

        PrintABC printABC=new PrintABC(3);

        Thread t1=new Thread(()->{
            try {
                printABC.printA();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        });
        t1.setName("A线程");


        Thread t2=new Thread(()->{
                try {
                    printABC.printB();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
        });
        t2.setName("B线程");

        Thread t3=new Thread(()->{
                try {
                    printABC.printC();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
        });
        t3.setName("C线程");

        t2.start();
        t3.start();
        t1.start();



    }

这里我们设置了3,也就是仅仅打印3轮,就终止程序。结果如下:

A线程打印: A
B线程打印: B
C线程打印: C

A线程打印: A
B线程打印: B
C线程打印: C

A线程打印: A
B线程打印: B
C线程打印: C

至此,这个问题就搞定了,我们思考下,因为这里面采用的是Java的内置锁synchronized来实现的,synchronized关键词虽然使用起来非常简单,但是由于它出现的早,本身也有一些缺点,细心的朋友可能已经发现,上面的通信代码处用的是:

monitor.notifyAll();

注意这个地方,明明我们只需要唤醒一个线程,为什么需要notifyAll()而不是用notify(),这么做的主要目的是因为synchronized的监视器唤醒的线程是随机的,没办法精确到某个线程,所以它必须唤醒所有的线程,然后重新参与锁的竞争,这样就导致部分线程调度没必要的被交换了一次。

这个地方恰内置锁synchronized的一个弊端,这也是为什么在jdk5之后引入的Lock这样高级锁接口,其相比synchronized在加锁的时候,主要优点是:

(1)提供了公平和非公平调度

(2)可中断

(3)可提供非阻塞

(4)可超时

(5)提供了Condition更细粒度的,锁唤醒条件队列

本文中的例子,完全可以用Lock接口+Condition来达到更细粒度的锁控制,也就是A线程执行完之后,仅仅只唤醒B线程,没有必要把C线程也唤醒,感兴趣的朋友可以思考下怎么实现。

为了方便更好的交流,互助,学习,讨论问题,欢迎加入我们的“攻城师互助学习交流群”微信群,为了保证交流环境,想加入的小伙伴,可关注公众号(woshigcs),然后后台发送关键词微信群,加我微信号,由我拉你进去。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
为了实现三个线程循环打印ABC,可以使用Synchronized同步方法和Object的wait()和notify()方法。首先,创建三个线程A、B、C,并设置它们的打印次数为10。然后,通过使用三个对象锁a、b、c来控制线程的执行顺序。A线程首先获得c对象锁,打印A后释放c对象锁,并通过notify()方法唤醒B线程。B线程等待a对象锁,获取到a对象锁后打印B,并释放a对象锁,然后通过notify()方法唤醒C线程。C线程等待b对象锁,获取到b对象锁后打印C,并释放b对象锁,并通过notify()方法唤醒A线程。这样就实现了三个线程循环打印ABC的需求。 以下是一个示例代码: ```java class PrintThread implements Runnable { private static final Object a = new Object(); private static final Object b = new Object(); private static final Object c = new Object(); private String name; public PrintThread(String name) { this.name = name; } @Override public void run() { for (int i = 0; i < 10; i++) { synchronized (name) { try { switch (name) { case "A": synchronized (c) { System.out.print("A"); c.notify(); } name.wait(); break; case "B": synchronized (a) { System.out.print("B"); a.notify(); } name.wait(); break; case "C": synchronized (b) { System.out.print("C"); b.notify(); } name.wait(); break; } } catch (InterruptedException e) { e.printStackTrace(); } } } } } public class Main { public static void main(String[] args) { Thread threadA = new Thread(new PrintThread("A")); Thread threadB = new Thread(new PrintThread("B")); Thread threadC = new Thread(new PrintThread("C")); threadA.start(); threadB.start(); threadC.start(); } } ``` 通过以上代码,三个线程将按照ABCABCABC的顺序循环打印10次。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [多线程交替打印ABC的多种实现方法](https://blog.csdn.net/xiaokang123456kao/article/details/77331878)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [三个线程轮流打印ABC](https://blog.csdn.net/yu1336199790/article/details/118725454)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值