如何正确使用信号量

时间:2014-11-25 01:35:35

标签: java multithreading concurrency semaphore

我在圣诞老人的工作室中遇到了一个问题,在送礼之前必须要做很多事情。其中一个要求就是11个精灵成功制作了100件礼物。我的主要课程如下:

public class Main {

    public volatile int toyList = 100;

    public int getToyList() {
        return toyList;
    }

    public void setToyList(int toyList) {
        this.toyList = toyList;
    }

    public static void main(String[] args){
        Main main = new Main();

        ...

        Elf elfOne = new Elf("Jim", main);
        Elf elfTwo = new Elf("John", main);
        Elf elfThree = new Elf("Eamonn", main);
        Elf elfFour = new Elf("Eoin", main);
        Elf elfFive = new Elf("Ronan", main);
        Elf elfSix = new Elf("Seamus", main);
        Elf elfSeven = new Elf("Rebecca", main);
        Elf elfEight = new Elf("Orla", main);
        Elf elfNine = new Elf("Tina", main);
        Elf elfTen = new Elf("Filly", main);
        Elf elfEleven = new Elf("Jess", main);


        ...

        elfOne.start();
        elfTwo.start();
        elfThree.start();
        elfFour.start();
        elfFive.start();
        elfSix.start();
        elfSeven.start();
        elfEight.start();
        elfNine.start();
        elfTen.start();
        elfEleven.start();

    }

我的Elf课程看起来像这样:

public class Elf extends Thread{

    Semaphore semaphore = new Semaphore(11);
    Random rng = new Random();
    String name;
    Main main;

    public Elf(String name, Main main){
        this.name = name;
        this.main = main;
    }

    public void run(){
        while(true){
                try {
                    semaphore.acquire();
                    makeToy();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    semaphore.release();
                }
        }
    }

    private void makeToy() throws InterruptedException{
        if (main.getToyList() != 0){
            if(rng.nextInt(99) <= 4){ //5% chance to fail
                System.out.println(name + " failed in making a toy!");
            }else{
                main.setToyList(main.getToyList() - 1);
                System.out.println(name + " created a toy! Toys remaining: " + main.getToyList());
            }
        } else {
            wakeSanta();
        }
    }

    private void wakeSanta() throws InterruptedException {

    }

}

我得到的样本输出是:

...
Eamonn created a toy! Toys remaining: 6
Jim created a toy! Toys remaining: 7
Eoin created a toy! Toys remaining: 8
Jess created a toy! Toys remaining: 9
Filly created a toy! Toys remaining: 0

我想得到的输出是它随机的Elf从100到0顺序倒计时。这是一个简单的事情,我的信号量获取是在错误的地方还是我完全误解了信号量?

2 个答案:

答案 0 :(得分:2)

是的,您的信号量未正确使用。信号量实例可以授予许多权限。

  • 线程可以acquire()权限 - 阻止,直到权限可用,然后减少可用权限的数量。
  • 线程可以release()权限 - 增加可用权限的数量。

在您的代码中,每个线程都有自己的信号量,并具有11权限。首先,作为一种同步手段,信号量通常只有在多个线程共享时才有意义。其次,如果你想让资源(在你的情况下toyList)完全排他性,那么信号量应该只提供一个权限。

这样想:为了制造玩具,精灵需要使用机器。现在你的场景要求只有一个精灵可以同时制作一个玩具,这相当于只有一个玩具制造机器的车间。所以 - 在代码中 - 你需要创建一台机器的等价物:一个Semaphore - 只有一个权限的实例。好的,我们将Semaphore放在Main - 类:

public class Main {

    Semaphore semaphore = new Semaphore(1);
    // the rest of Main is kept the same
}

现在精灵需要访问玩具制造机器,因此我们使用semaphore.acquire()代替main.semaphore.aquire()。但是如果机器在调用run()时被占用并且从未被释放,那么第一个开始工作的精灵将永远不会让任何其他精灵拥有该机器。因此圣诞老人对如何使用机器制定了一些规则:

  • 除非精灵试图制作玩具,否则不得占用机器
  • 一旦精灵(成功与否)完成制作玩具,必须释放机器

因此run()makeToy()的代码更改如下:

public void run(){
    while(true){ // sidenote: I would rearrange the nesting: Wrap the while-loop in the try-catch
        try {
            makeToy();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

private void makeToy() throws InterruptedException{
    main.semaphore.acquire();
    if (main.getToyList() != 0){
        if(rng.nextInt(99) <= 4){ //5% chance to fail
            System.out.println(name + " failed in making a toy!");
        }else{
            main.setToyList(main.getToyList() - 1);
            System.out.println(name + " created a toy! Toys remaining: " + main.getToyList());
        }
    } else {
        wakeSanta();
    }
    main.semaphore.release();
}

如果您想知道何时使用具有多个权限的信号量,请举例说明:

假设您拥有经常阅读且偶尔会被写入的资源。因为读取不会改变值(即,改变状态),所以让多个线程同时读取该值是安全的。假设我们有n个线程和一个具有n权限的信号量。一个线程现在必须获得一个读取权限 - 这意味着所有线程可以同时读取,但它必须获得允许写入的所有n权限 - 这意味着当一个线程正在写入时,没有其他线程可以访问资源(既不读也不写)。


就我个人而言,我认为获得并发布一个信号量只有synchronized块的单一权限。但是,请注意用户 Ralf 在注释中指出的重要区别:任何线程都可以释放信号量,无论哪个线程实际获得了权限。信号量更具表现力 - 缺点是更容易使用它们。

答案 1 :(得分:1)

从概念上讲,信号量维护一组许可证。信号量只保留可用数量的计数并相应地起作用。

调用acquire()将阻塞,直到许可可用(大于零)然后接受(倒数一)。每个版本()都会添加许可证。

你可以认为二年级学生是一个控制人们在门上访问的守卫(需要控制线程访问的逻辑)。他有一些钥匙(许可证)进入这扇门()。

当一个人(线程)需要进入这扇门时,他需要一把钥匙(acquire()),或者他等到他拿到钥匙(线程挡)。一个人离开门后,他应该给予密钥(release())以便其他人可以获得。

因此,在现有代码中,每个线程都有11个密钥,这是没有意义的,因为现在每个人都有一个拥有11个密钥的独立守卫。

如果您需要同时只控制一个线程makeToy()。您应该只使用一个许可证使用信号量。

Semaphore semaphore = new Semaphore(1);

这应该是线程共享的公共信号量,所以你可能不应该在线程中实例化信号量。

你可以试试这个:

    Main main = new Main();
    Semaphore semaphore = new Semaphore(1,true);//Only allow one thread to enter,so totally one permit is avaiable, and fairness =true 
    Elf elfOne = new Elf("Jim", main,semaphore);//pass the semaphore to all the threads
    Elf elfTwo = new Elf("John", main,semaphore);
    Elf elfThree = new Elf("Eamonn", main,semaphore);
    Elf elfFour = new Elf("Eoin", main,semaphore);
   .....


public class Elf extends Thread{

    Semaphore semaphore;
    Random rng = new Random();
    String name;
    Main main;

    public Elf(String name, Main main,Semaphore semaphore){// need semaphore in constructors
        this.name = name;
        this.main = main;
        this.semaphore=semaphore;
    }

以下是更改代码的输出:

...
Filly created a toy! Toys remaining: 79
Eamonn created a toy! Toys remaining: 78
Eoin created a toy! Toys remaining: 77
Jess created a toy! Toys remaining: 76
Ronan created a toy! Toys remaining: 75
Jim failed in making a toy!
John created a toy! Toys remaining: 74
Seamus created a toy! Toys remaining: 73
Rebecca created a toy! Toys remaining: 72
...