阅读 149

如何只使用信号量通过leetcode的多线程题目

题目链接
leetcode-cn.com/problemset/…
leetcode.com/problemset/…

Semaphore 回顾

控制流量

Semaphore可以控制某个资源可被同时访问的个数,通过 acquire() 获取一个许可,如果没有就等待,而 release() 释放一个许可。 可以通过构造方法设置是否公平.

public Semaphore(int permits) {}

public Semaphore(int permits, boolean fair){}
复制代码

一个典型的例子是.

public class SemapDemo implements Runnable {
    // 最大的并发是5.
    final Semaphore semp = new Semaphore(5);

    @Override
    public void run() {
        try {
            
            semp.acquire();
            //----------------临界区--------------
            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getId() + ":done!");
            //----------------临界区--------------
            semp.release();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 总共20个线程,系统会以5个线程一组为单位,依次执行并输出
     *
     * @param args
     */
    public static void main(String args[]) throws  InterruptedException{
        ExecutorService executorService = Executors.newFixedThreadPool(20);
        final SemapDemo demo = new SemapDemo();
        for (int i = 0; i < 20; i++) {
            executorService.submit(demo);
        }
        // 不然线程池没退出
        Thread.sleep(10000);
        executorService.shutdown();
    }
}
复制代码

通知信息

信号量当然还可以当作信号量用,比如我们new Semaphore(0),许可证数量为0. 在我们需要的时候release()一下,许可证+1,等待的线程就可以得到通知,继续执行.

注意细节

在任何release操作之后,你都可能面临并发,release前的代码在临界区中,release后可不是,可能已经有其他线程拿到许可做事情了.

解题

题目序号为1114-1117,我们都可以只用信号量解决他们.

解法链接: imatvoid.github.io/tag/N14GFPb…

leetcode -- 1115. Print FooBar Alternately

交替打印Foo Bar
leetcode.com/problems/pr…
leetcode-cn.com/problems/pr…

描述

两并发线程交替打印Foo Bar.启动顺序不保证.

思路

这种交替打印的,没有比信号量更简单得了

import java.util.concurrent.Semaphore;
class FooBar {
    private int n;

    Semaphore foo = new Semaphore(1);
    Semaphore bar = new Semaphore(0);

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

    public void foo(Runnable printFoo) throws InterruptedException {

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

    public void bar(Runnable printBar) throws InterruptedException {

        for (int i = 0; i < n; i++) {
            bar.acquire();  // bar + 1
            // printBar.run() outputs "bar". Do not change or remove this line.
            printBar.run();
            foo.release();  // foo - 1
        }
    }
}
复制代码

leetcode -- 1114. Print in Order

按顺序打印one,two,three
leetcode.com/problems/pr…

描述

三个不同的线程将会共用一个 Foo 实例 。

线程 A 将会调用 one() 方法
线程 B 将会调用 two() 方法
线程 C 将会调用 three() 方法
请设计修改程序,以确保 two() 方法在 one() 方法之后被执行,three() 方法在 two() 方法之后被执行。

不保证线程启动顺序

思路

这种交替打印的,没有比信号量更简单得了,用信号量通知.


class Foo {

    Semaphore one = new Semaphore(1);
    Semaphore two = new Semaphore(0);
    Semaphore three = new Semaphore(0);
    public Foo() {

    }
    public void first(Runnable printFirst) throws InterruptedException {
        one.acquire();

        // printFirst.run() outputs "first". Do not change or remove this line.
        printFirst.run();
        two.release();
    }

    public void second(Runnable printSecond) throws InterruptedException {
        two.acquire();
        // printSecond.run() outputs "second". Do not change or remove this line.
        printSecond.run();
        three.release();
    }

    public void third(Runnable printThird) throws InterruptedException {
        three.acquire();
        // printThird.run() outputs "third". Do not change or remove this line.
        printThird.run();
    }
}
复制代码

leetcode -- 1116. Print Zero Even Odd

打印zero odd zero even n次.
leetcode.com/problems/pr…

描述

假设有这么一个类:

class ZeroEvenOdd {
  public ZeroEvenOdd(int n) { ... }      // 构造函数
  public void zero(printNumber) { ... }  // 仅打印出 0
  public void even(printNumber) { ... }  // 仅打印出 偶数
  public void odd(printNumber) { ... }   // 仅打印出 奇数
}
复制代码

相同的一个 ZeroEvenOdd 类实例将会传递给三个不同的线程:

线程 A 将调用 zero(),它只输出 0 。
线程 B 将调用 even(),它只输出偶数。
线程 C 将调用 odd(),它只输出奇数。
每个线程都有一个 printNumber 方法来输出一个整数。请修改给/出的代码以输出整数序列 010203040506... ,其中序列的长度必须为 2n。

示例 1:

输入:n = 2
输出:"0102"
复制代码

说明:三条线程异步执行,其中一个调用 zero(),另一个线程调用 even(),最后一个线程调用odd()。正确的输出为 "0102"。 示例 2:

输入:n = 5
输出:"0102030405"
复制代码

思路

信号量

import java.util.concurrent.Semaphore;
class ZeroEvenOdd {
    private int n;
    Semaphore zero = new Semaphore(1);
    Semaphore odd = new Semaphore(0);
    Semaphore even = new Semaphore(0);
    //这是一个共享变量,一定要在临界区中操作.release后就继续执行了.有并发要小心
    int count = 1;


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

    // printNumber.accept(x) outputs "x", where x is an integer.
    public void zero(IntConsumer printNumber) throws InterruptedException {
        while (true){
            // 这里一定要在开头 
            zero.acquire();
            
            if (count > n) {
                break;
            }
             
            printNumber.accept(0);
            if ((count & 1) == 1) {  
                odd.release();
            } else {
                even.release();
            }
          

        }

    }


     public void even(IntConsumer printNumber) throws InterruptedException {
        for (int i = 0; i < n/2; i++) {
            even.acquire();
            printNumber.accept(count++);
            zero.release();
        }
    }

    public void odd(IntConsumer printNumber) throws InterruptedException {
        for (int i = 0; i < n/2+n%2; i++) {
            odd.acquire();
            printNumber.accept(count++);
            zero.release();
        }
    }
}
复制代码

leetcode -- 1117. Building H2O

按组输出 leetcode-cn.com/problems/bu… leetcode.com/problems/bu…

描述

现在有两种线程,氢 oxygen 和氧 hydrogen,你的目标是组织这两种线程来产生水分子。

存在一个屏障(barrier)使得每个线程必须等候直到一个完整水分子能够被产生出来。

氢和氧线程会被分别给予 releaseHydrogen 和 releaseOxygen 方法来允许它们突破屏障。

这些线程应该三三成组突破屏障并能立即组合产生一个水分子。

你必须保证产生一个水分子所需线程的结合必须发生在下一个水分子产生之前。

换句话说:

如果一个氧线程到达屏障时没有氢线程到达,它必须等候直到两个氢线程到达。 如果一个氢线程到达屏障时没有其它线程到达,它必须等候直到一个氧线程和另一个氢线程到达。 书写满足这些限制条件的氢、氧线程同步代码。 示例 1:

输入: "HOH"
输出: "HHO"
解释: "HOH""OHH" 依然都是有效解。
复制代码

示例 2:

输入: "OOHHHH"
输出: "HHOHHO"
解释: "HOHHHO", "OHHHHO", "HHOHOH", "HOHHOH", "OHHHOH", "HHOOHH", "HOHOHH""OHHOHH" 依然都是有效解。
复制代码

限制条件: 输入字符串的总长将会是 3n, 1 ≤ n ≤ 50; 输入字符串中的 “H” 总数将会是 2n; 输入字符串中的 “O” 总数将会是 n。

思路

我们把H原子当成一种资源,用容器装起来,并统计个数.任何时间都可以在容器中加入H原子.这里使用Semaphore来当容器,来一个就release()一下.许可证+1.

我们把O原子当成输出的依据,串行化.来一个O原子就去取两个H原子的许可证,如果暂时拿不到也没关系.容器里的H早晚会蓄起来.

这样我们总是可以2个H,一个O地输出.

代码如下:

import java.util.concurrent.Semaphore;

/**
 * @Author: yangxu
 * @Date: 19-7-21 下午9:06
 */
public class H2O {

    // 串行话氧原子,一个氧原子没操作完的时候,阻塞住其他氧原子的操作.
    Semaphore olock = new Semaphore(1);
    
    // 判定两个氢原子都已输出
    Semaphore hfin = new Semaphore(0);

    Semaphore os = new Semaphore(0);
    Semaphore hs = new Semaphore(0);

    public H2O() {

    }

    public void hydrogen(Runnable releaseHydrogen) throws InterruptedException {


        // 氢原子许可+1  容器加一个H
        hs.release();
        // 需要氧原子许可
        os.acquire();

        // releaseHydrogen.run() outputs "H". Do not change or remove this line.
        releaseHydrogen.run();
        // 一个H原子已经输出完毕
        hfin.release();
    }

    public void oxygen(Runnable releaseOxygen) throws InterruptedException{
        // 串行化氧原子操作
        olock.acquire();
        // 取两个H原子
        hs.acquire(2);

        // releaseOxygen.run() outputs "H". Do not change or remove this line.
        releaseOxygen.run();
        // 通知两个H原子可以做了.
        os.release(2);
        // 等待两个H原子输出
        hfin.acquire(2);
        // 允许下一个氧原子到来
        olock.release();

    }
}
复制代码
关注下面的标签,发现更多相似文章
评论