如何编写简单的公平信号量?

时间:2012-03-29 15:43:05

标签: java concurrency semaphore

我发现了信号量(我的CustomSemaphore)的简单实现,据我所知这是'不公平',因为进入安全块只能一次输入第一个线程(我不确定)。 如何编写公平的信号量(类似于并发new Semaphore(1, true);

   public class SimpleSemaphoreSample2 {
    CustomSemaphore cSem = new CustomSemaphore(1);

    public static void main(String[] args) {
        SimpleSemaphoreSample2 main = new SimpleSemaphoreSample2();
        Semaphore sem = new Semaphore(1, true);
        Thread thrdA = new Thread(main.new SyncOutput(sem, "Thread1"), "Thread1");
        Thread thrdB = new Thread(main.new SyncOutput(sem, "Thread2"), "Thread2");

        thrdA.start();
        thrdB.start();

        try {
            thrdB.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("END");
    }

    class SyncOutput implements Runnable {
        private Semaphore sem;
        private String msg;

        public SyncOutput(Semaphore s, String m) {
            sem = s;
            msg = m;
        }

        @Override
        public void run() {
            while (true) {
                try {
//                  sem.acquire();
                    cSem.acquire();
                    System.out.println("Before");
                    Thread.sleep(500);
                    System.out.println(msg);
                    Thread.sleep(500);
                    System.out.println("After");
                    Thread.sleep(500);
                } catch (Exception exc) {
                    exc.printStackTrace();
                }
//              sem.release();
                cSem.release();
            }
        }
    }

    public class CustomSemaphore {
        private int counter;

        public CustomSemaphore() {
            this(0);
        }

        public CustomSemaphore(int i) {
            if (i < 0)
                throw new IllegalArgumentException(i + " < 0");
            counter = i;
        }

        public synchronized void release() {
            if (counter == 0) {
                this.notify();
            }
            counter++;
        }

        public synchronized void acquire() throws InterruptedException {
            while (counter == 0) {
                this.wait();
            }
            counter--;
        }
    }
}
enter code here

3 个答案:

答案 0 :(得分:5)

根据Java Concurrency In Practice,它说明了

  

内在锁定不提供确定性公平性保证

这里的内在锁定是使用synchronized。因此,如果不将synchronized替换为Lock lock = new ReentrantLock(true);

,则无法使此信号量示例公平

true作为构造函数参数告诉ReentrantLock fair

根据@trutheality的评论进行编辑

如果您确实希望它在不使用ReentrantLock的情况下正确,您可以实现从AbstractQueuedSynchronizer继承同步原语的信号量。这将证明是非常复杂的,如果你能用ReentrantLock正确地写它,我会建议。注意:ReentrantLock将其同步委托给AQS。

答案 1 :(得分:5)

你的信号量不公平,因为线程有可能永远等待。想想用于通过3个线程写入值的互斥(二进制信号量)。 T1获取,T2等待和T3等待。现在在发布期间,您通知并在T2和T3之间获取信号量(假设为T2)。现在T1回来等待。当T2通知时,T1接受它。它可能会发生尽可能多的次数,T3将永远不会有信号量。

一个变化可能是在信号量中使用简单的FIFO。当线程必须等待时,您将在队列中添加其ID。现在,当您通知时,您将通知所有线程。进展的唯一线程是队列头部的线程。

答案 2 :(得分:0)

我有一个可重入信号量的例子,但它仅适用于2个伪装者。如果您希望将代码扩展到2个以上,则必须实现一个简单的列表并进行一些更改,包括wait()方法中aquire()的测试。

package nmscd.utils;

/**
 * A simple, non-reentrant, one permit, FAIR semaphore
 * @author cosmo
 */
public class SimpleSemaphore {

    private boolean aquired = false;
    private Thread currThread;
    private Thread releasedThread;
    private int pretendersCount = 0;

    public synchronized void aquire() throws InterruptedException {
        while ((Thread.currentThread() != currThread && aquired) || (pretendersCount > 0 && Thread.currentThread() == releasedThread)) {
            pretendersCount++;
            try {
                wait();
            } finally {
                pretendersCount--;
            }
        }
        aquired = true;
        currThread = Thread.currentThread();
    }

    public synchronized void release() {
        if (Thread.currentThread() == currThread) {
            aquired = false;
            currThread = null;
            releasedThread = Thread.currentThread();
            notifyAll();
        }
    }

}

这个类中的关键是在aquire方法中测试查看线程是否是你想要的线程,所有其他线程必须等待。因此,如果您有足够的信息来确定该线程,您可以选择从aquire()

返回的线程