交替打印 FooBar

338 阅读3分钟

「这是我参与2022首次更文挑战的第18天,活动详情查看:2022首次更文挑战

前言

笔者除了大学时期选修过《算法设计与分析》和《数据结构》还是浑浑噩噩度过的(当时觉得和编程没多大关系),其他时间对算法接触也比较少,但是随着开发时间变长对一些底层代码/处理机制有所接触越发觉得算法的重要性,所以决定开始系统的学习(主要是刷力扣上的题目)和整理,也希望还没开始学习的人尽早开始。

系列文章收录《算法》专栏中。

问题描述

给你一个类:

class FooBar {
  public void foo() {
    for (int i = 0; i < n; i++) {
      print("foo");
    }
  }

  public void bar() {
    for (int i = 0; i < n; i++) {
      print("bar");
    }
  }
}

两个不同的线程将会共用一个 FooBar 实例:

  • 线程 A 将会调用 foo() 方法,而
  • 线程 B 将会调用 bar() 方法
  • 请设计修改程序,以确保 "foobar" 被输出 n 次。

 

示例 1:

输入:n = 1
输出:"foobar"
解释:这里有两个线程被异步启动。其中一个调用 foo() 方法, 另一个调用 bar() 方法,"foobar" 将被输出一次。

示例 2:

输入:n = 2
输出:"foobarfoobar"
解释:"foobar" 将被输出两次。

 

提示:

  • 1 <= n <= 1000

确定学习目标

对《交替打印 FooBar》的算法过程进行剖析。

剖析

看到这个问题的时候我觉得比较简单,交替打印,无非就是给个标识让线程A先打印再让线程B打印,在A打印的时候B不能答应,在B打印的时候A不能打印,拿我们需要解决一下问题:

  • 怎么设置标记?
  • 在标识不能打印的时候怎么处理?

接下来我们一一解决下

怎么设置标记?

比较简单,直接使用bollean类型就好,true的时候允许A线程打印,false的时候线程B打印。但是标识需要在线程之间保持及时的可见效所以我们使用volatile修饰一下就好。

private volatile static boolean flag = true;

标识不能打印的时候怎么处理?

我一开始想的是就像自旋一样使用没有条件的循环,这样能快速的检测到标识进而进行执行任务。但是很容易发现有个弊端就是线程一直在跑,如果分配单核CPU跑两个线程的话,需要来回切换CPU并且是线程占用一个时间片后才切换,所以我们需要主动释放CPU的占用,所以我们使用Thread.yield()就好了。

两个问题都解决了,下面直接上代码。

代码

import lombok.SneakyThrows;

class FooBar {
    private int n;
    //直接使用bollean类型就好,true的时候允许A线程打印,false的时候线程B打印。但是标识需要在线程之间保持及时的可见效所以我们使用volatile修饰一下就好。
    private volatile static boolean flag = true;

    public FooBar(int n) {
        this.n = n;
    }

    public void foo(Runnable printFoo) throws InterruptedException {

        for (int i = 0; i < n; i++) {
            for (; ; ) {
                if (flag) {
                    // printFoo.run() outputs "foo". Do not change or remove this line.
                    printFoo.run();
                    flag = false;
                    break;
                } else {
                    //如果分配单核CPU跑两个线程的话,需要来回切换CPU并且是线程占用一个时间片后才切换,所以我们需要主动释放CPU的占用,所以在else的时候我们使用`Thread.yield()`就好。
                    Thread.yield();
                }
            }
        }
    }

    public void bar(Runnable printBar) throws InterruptedException {

        for (int i = 0; i < n; i++) {
            for (; ; ) {
                if (!flag) {
                    // printBar.run() outputs "bar". Do not change or remove this line.
                    printBar.run();
                    flag = true;
                    break;
                } else {
                    //如果分配单核CPU跑两个线程的话,需要来回切换CPU并且是线程占用一个时间片后才切换,所以我们需要主动释放CPU的占用,所以在else的时候我们使用`Thread.yield()`就好。
                    Thread.yield();
                }
            }
        }

    }


    @SneakyThrows
    public static void main(String[] args) {
        FooBar fooBar = new FooBar(5);
        Thread threadA = new Thread(new Runnable() {
            @SneakyThrows
            @Override
            public void run() {
                fooBar.foo(new Runnable() {
                    @Override
                    public void run() {
                        System.out.print("foo");
                    }
                });
            }
        });
        threadA.start();

        Thread threadB = new Thread(new Runnable() {
            @SneakyThrows
            @Override
            public void run() {
                fooBar.bar(new Runnable() {
                    @Override
                    public void run() {
                        System.out.print("bar");
                        System.out.println();
                    }
                });
            }
        });
        threadB.start();
    }
}