信号量(Semaphore)总结

本文深入解析信号量Semaphore的工作原理及应用场景,通过LeetCode题目1115、1116、1117、1195的实战代码示例,展示如何利用Semaphore进行多线程同步控制,确保线程安全与正确执行。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Semaphore

信号量( S e m a p h o r e Semaphore Semaphore)可以指定多个线程同时访问某一个资源。构造函数:

public Semaphore(int permits)
public Semaphore(int permits, boolean fair)

主要逻辑方法:

public void acquire()
public void acquire(int permits)
public void acquireUninterruptibly()
public void acquireUninterruptibly(int permits)
public boolean tryAcquire()
public boolean tryAcquire(int permits)
public boolean tryAcquire(long timeout, TimeUnit unit)
public boolean tryAcquire(int permits, long timeout, TimeUnit unit)
public boolean isFair()
public int availablePermits()
public void release()
public void release(int permits)

应用

leetcode-1115

我们提供一个类:
class FooBar {
\quad public void foo() {
\qquad for (int i = 0; i < n; i++) {
\quad\qquad print(“foo”);
\qquad }
\quad }
\quad public void bar() {
\qquad for (int i = 0; i < n; i++) {
\quad\qquad print(“bar”);
\qquad }
\quad }
}
两个不同的线程将会共用一个 FooBar 实例。其中一个线程将会调用 foo() 方法,另一个线程将会调用 bar() 方法。
请设计修改程序,以确保 "foobar"被输出 n 次。

来源:leetcode-1115
代码:

class FooBar {
    private int n;
    private Semaphore spF, spB;

    public FooBar(int n) {
        this.n = n;
        spF = new Semaphore(1);
        spB = new Semaphore(0);
    }

    public void foo(Runnable printFoo) throws InterruptedException {
        
        for (int i = 0; i < n; i++) {
            spF.acquire();
            printFoo.run();
            spB.release();
        }
    }

    public void bar(Runnable printBar) throws InterruptedException {
        
        for (int i = 0; i < n; i++) {
            spB.acquire();
            printBar.run();
            spF.release();
        }
    }
}

leetcode-1116

假设有这么一个类:
class ZeroEvenOdd {
\qquad public ZeroEvenOdd(int n) { … } // 构造函数
\qquad public void zero(printNumber) { … } // 仅打印出 0
\qquad public void even(printNumber) { … } // 仅打印出 偶数
\qquad public void odd(printNumber) { … } // 仅打印出 奇数
}
相同的一个 ZeroEvenOdd 类实例将会传递给三个不同的线程:
线程 A 将调用 zero(),它只输出 0 0 0
线程 B 将调用 even(),它只输出偶数。
线程 C 将调用 odd(),它只输出奇数。
每个线程都有一个 printNumber 方法来输出一个整数。请修改给出的代码以输出整数序列 010203040506… ,其中序列的长度必须为 2n。

来源:
leetcode-1116
代码:

class ZeroEvenOdd {
    private int n;
    private Semaphore sp0, sp1, sp2;
    
    public ZeroEvenOdd(int n) {
        this.n = n;
        sp0 = new Semaphore(1);
        sp1 = new Semaphore(0);
        sp2 = new Semaphore(0);
    }

    // printNumber.accept(x) outputs "x", where x is an integer.
    public void zero(IntConsumer printNumber) throws InterruptedException {
        int i;

        for(i = 0; i < n; i++){
            sp0.acquire();
            printNumber.accept(0);
            if((i & 1) == 0){
                sp1.release();
            }else{
                sp2.release();
            }
        }
    }

    public void even(IntConsumer printNumber) throws InterruptedException {
        int i;

        for(i = 2; i <= n; i += 2){
            sp2.acquire();
            printNumber.accept(i);
            sp0.release();
        }
    }

    public void odd(IntConsumer printNumber) throws InterruptedException {
        int i;

        for(i = 1; i <= n; i += 2){
            sp1.acquire();
            printNumber.accept(i);
            sp0.release();
        }
    }
}

leetcode-1117

现在有两种线程,氢 oxygen 和氧 hydrogen,你的目标是组织这两种线程来产生水分子。
存在一个屏障(barrier)使得每个线程必须等候直到一个完整水分子能够被产生出来。
氢和氧线程会被分别给予 releaseHydrogenreleaseOxygen 方法来允许它们突破屏障。
这些线程应该三三成组突破屏障并能立即组合产生一个水分子。
你必须保证产生一个水分子所需线程的结合必须发生在下一个水分子产生之前。
换句话说:
如果一个氧线程到达屏障时没有氢线程到达,它必须等候直到两个氢线程到达。
如果一个氢线程到达屏障时没有其它线程到达,它必须等候直到一个氧线程和另一个氢线程到达。
书写满足这些限制条件的氢、氧线程同步代码。

来源:leetcode-1117
代码:

class H2O {
    private Semaphore spH, spO;

    public H2O() {
        spO = new Semaphore(0);
        spH = new Semaphore(2);
    }

    public void hydrogen(Runnable releaseHydrogen) throws InterruptedException {
        spH.acquire();
        // releaseHydrogen.run() outputs "H". Do not change or remove this line.
        releaseHydrogen.run();
        spO.release();
    }

    public void oxygen(Runnable releaseOxygen) throws InterruptedException {
        
        spO.acquire(2);
        // releaseOxygen.run() outputs "O". Do not change or remove this line.
		releaseOxygen.run();
        spH.release(2);
    }
}

leetcode-1195

编写一个可以从 1 到 n 输出代表这个数字的字符串的程序,但是:
如果这个数字可以被 3 整除,输出 “fizz”。
如果这个数字可以被 5 整除,输出 “buzz”。
如果这个数字可以同时被 3 和 5 整除,输出 “fizzbuzz”。
例如,当 n = 15,输出: 1, 2, fizz, 4, buzz, fizz, 7, 8, fizz, buzz, 11, fizz, 13, 14, fizzbuzz。
假设有这么一个类:
class FizzBuzz {
\quad public FizzBuzz(int n) { … } // constructor
\quad public void fizz(printFizz) { … } // only output “fizz”
\quad public void buzz(printBuzz) { … } // only output “buzz”
\quad public void fizzbuzz(printFizzBuzz) { … } // only output “fizzbuzz”
\quad public void number(printNumber) { … } // only output the numbers
}
请你实现一个有四个线程的多线程版 FizzBuzz, 同一个 FizzBuzz 实例会被如下四个线程使用:
线程A将调用 fizz() 来判断是否能被 3 整除,如果可以,则输出 fizz。
线程B将调用 buzz() 来判断是否能被 5 整除,如果可以,则输出 buzz。
线程C将调用 fizzbuzz() 来判断是否同时能被 3 和 5 整除,如果可以,则输出 fizzbuzz。
线程D将调用 number() 来实现输出既不能被 3 整除也不能被 5 整除的数字。

来源:leetcode-1195
代码:

class FizzBuzz {
    private int n;
    private Semaphore spN, spF, spB, spFB;

    public FizzBuzz(int n) {
        this.n = n;
        spN = new Semaphore(1);
        spF = new Semaphore(0);
        spB = new Semaphore(0);
        spFB = new Semaphore(0);
    }

    // printFizz.run() outputs "fizz".
    public void fizz(Runnable printFizz) throws InterruptedException {
        for(int i = 3; i <= n; i += 3){
            if(i % 5 == 0)
                continue;
            spF.acquire();
            printFizz.run();
            spN.release();
        }
    }

    // printBuzz.run() outputs "buzz".
    public void buzz(Runnable printBuzz) throws InterruptedException {
        for(int i = 5; i <= n; i += 5){
            if(i % 3 == 0)
                continue;
            spB.acquire();
            printBuzz.run();
            spN.release();
        }
    }

    // printFizzBuzz.run() outputs "fizzbuzz".
    public void fizzbuzz(Runnable printFizzBuzz) throws InterruptedException {
        for(int i = 15; i <= n; i += 15){
            spFB.acquire();
            printFizzBuzz.run();
            spN.release();
        }
    }

    // printNumber.accept(x) outputs "x", where x is an integer.
    public void number(IntConsumer printNumber) throws InterruptedException {
        for(int i = 1; i <= n; i++){
            spN.acquire();
            if(i % 5 != 0 && i % 3 != 0){
                printNumber.accept(i);
                spN.release();
            }else if(i % 5 != 0){
                spF.release();
            }else if(i % 3 != 0){
                spB.release();
            }else{
                spFB.release();
            }
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值