LeetCode 1115. 交替打印 FooBar

文章介绍了如何在Java中使用信号量Semaphore和Thread.yield()方法来确保在两个线程共享一个FooBar实例时,foo和bar方法按照顺序各输出n次foobar。方法一是利用信号量控制线程执行顺序,方法二是使用volatile布尔变量控制线程切换。
摘要由CSDN通过智能技术生成

题目描述

给你一个类:

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" 将被输出两次。

 这道题是与多线程有关的问题,我们需要保证foo线程和bar线程有序调用。输出n个foobar,也就是两个线程各调用n次,且必须有序。

方法一  信号量Semaphore

信号量的原理具体可以看操作系统里的信号量机制,在这里简单概述一下。

信号量的使用可以适用于两个场景: 同步和互斥。

当信号量用于互斥的时候,初始值一般设为1,表示只有一个资源可以使用。当一个线程访问共享资源时,会执行获取的操作,使信号量减 1 ,当这个线程即将结束的时候,会执行释放的操作,使信号量加 1 。

当信号量用于同步的时候,举个小例子:

生产者-消费者问题:

生产者负责生产数据,消费者负责处理数据。这里的同步确保消费者不会再没有数据的时候还进行消费的操作,也保证生产者不会再缓冲区已经满的情况下生产数据。

我们用empty作为 缓冲区空闲的位置。用full作为缓冲区已经有的数据的数量。我们假设缓冲区最大容量为1。

生产者生产数据前,需要调用empty的获取操作,使其值减 1 , 生产完数据后,我们对full进行释放的操作,使其值加 1 ,表示缓冲区中有新的数据可以消费。

此时如果我的生产者进程想再次被调用的话,我们需要获取empty,但是empty的值已经为0,没有空闲位置,所以此时生产者进程不可被调用。

那么这个时候,如果调用消费者进程,他会先获取full , 使其值减 1 ,使 full 为 0 ,消费者执行完,会进行empty的释放操作,使empty 的值加 1 。表明缓冲区又有新的空闲位置,生产者可以进行生产。

如果这个时候,消费者进程再次被调用,消费者消费数据之前,会执行full的获取操作,可是此时full已经为0 , 所以消费者进程会被阻塞。只能生产者进程生产完数据之后,消费者进程才可以再次被调用。

这个流程就保证了生产者消费者的同步操作。

那么对于这道题也是相同。我们定义两个信号量表示:

如果foo线程想执行,就必须获取fooSema信号量,如果bar线程像执行,就必须获取barSema信号量。

private Semaphore fooSema = new Semaphore(1);
private Semaphore barSema = new Semaphore(0);

我们需要让 foo 线程先被调用,所以让fooSema的初始值为1,这就相当于生产者消费者问题,我们先让empty的值为1。

在进入 foo 线程之前,先获取fooSema , 让其值减 1 ,之后执行 foo 线程,执行完之后,释放barSema ,使其值加1。

for (int i = 0; i < n; i++) {
            fooSema.acquire();
        	// printFoo.run() outputs "foo". Do not change or remove this line.
        	printFoo.run();
            barSema.release();
        }

bar 线程同理。

  for (int i = 0; i < n; i++) {
            barSema.acquire();
            // printBar.run() outputs "bar". Do not change or remove this line.
        	printBar.run();
            fooSema.release();
        }

完整代码展示

class FooBar {
    private int n;
    private Semaphore fooSema = new Semaphore(1);
    private Semaphore barSema = new Semaphore(0);
    public FooBar(int n) {
        this.n = n;
    }

    public void foo(Runnable printFoo) throws InterruptedException {
        
        for (int i = 0; i < n; i++) {
            fooSema.acquire();
        	// printFoo.run() outputs "foo". Do not change or remove this line.
        	printFoo.run();
            barSema.release();
        }
    }

    public void bar(Runnable printBar) throws InterruptedException {
        
        for (int i = 0; i < n; i++) {
            barSema.acquire();
            // printBar.run() outputs "bar". Do not change or remove this line.
        	printBar.run();
            fooSema.release();
        }
    }
}

方法二 Thread.yield()

关键点

Thread.yield():使当前线程从执行状态(运行状态)变为可执行态(就绪状态)。但是需要注意,如果A线程使用这个方法后,变成就绪态,此时还是可以被调用的。

volatile 关键词定义的变量,如果发生变化,那么所有线程都可见这个变化。


方法二中我们使用这两个知识点来解决问题。

 /*
    如果fooExec为真,那么foo可以执行,如果为假,bar执行。
*/

volatile boolean fooExec = true;

 如果想 foo 线程想被执行,fooExec就必须为真,如果fooExec不为真,那么就让他变成就绪状态。直到 bar 线程执行完,让fooExec 为正之后,才可以继续执行 foo 线程。这就保证了线程之间的同步。

public void foo(Runnable printFoo) throws InterruptedException {
        
        for (int i = 0; i < n;) {
            if(fooExec){
                // printFoo.run() outputs "foo". Do not change or remove this line.
        	    printFoo.run();
                i++;
                fooExec = false;
            }else{
                Thread.yield();
            }
        	
        }
    }

bar线程同理。

 public void bar(Runnable printBar) throws InterruptedException {
        
        for (int i = 0; i < n;) {
            if(!fooExec){

                // printBar.run() outputs "bar". Do not change or remove this line.
        	    printBar.run();
                i++;
                fooExec = true;
            }else{
                Thread.yield();
            }
            
        }
    }

完整代码展示

class FooBar {
    private int n;
    volatile boolean fooExec = true;
    public FooBar(int n) {
        this.n = n;
    }

    public void foo(Runnable printFoo) throws InterruptedException {
        
        for (int i = 0; i < n;) {
            if(fooExec){
                // printFoo.run() outputs "foo". Do not change or remove this line.
        	    printFoo.run();
                i++;
                fooExec = false;
            }else{
                Thread.yield();
            }
        	
        }
    }

    public void bar(Runnable printBar) throws InterruptedException {
        
        for (int i = 0; i < n;) {
            if(!fooExec){

                // printBar.run() outputs "bar". Do not change or remove this line.
        	    printBar.run();
                i++;
                fooExec = true;
            }else{
                Thread.yield();
            }
            
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值