【问题标题】:How to implement a Binary Semaphore Class in Java?如何在 Java 中实现二进制信号量类?
【发布时间】:2011-11-27 15:04:45
【问题描述】:

我可以看到如何在 Java 中实现“标准”信号量类。但是,我看不到如何在 Java 中实现二进制信号量类。这种实施如何运作?我应该什么时候调用唤醒和通知方法来唤醒和停止信号量上的线程? 我了解二进制信号量是什么,但我不知道如何对其进行编码。

编辑说明:意识到我说的是“BINARY”信号量类。我已经做过标准的 Semaphore 类,并且我知道它是正确的,所以标准的 Semaphore 类对我不感兴趣。

【问题讨论】:

  • 你有没有尝试过;借鉴现有信号量类的源代码,如果它只有 1 的许可,它将如何表现。
  • 你读过(例如在维基百科上)(二进制)信号量的定义吗?你能解释一下为什么给定的答案(尤其是 Vikas 的答案或更广泛的 Alexander 答案)是不够的吗?否则,您可以尝试解释用例(出于什么目的您需要二进制信号量/互斥量?)

标签: java multithreading concurrency semaphore concurrent-programming


【解决方案1】:

这是我为二进制信号量做的一个简单实现:

public class BinarySemaphore {

    private final Semaphore countingSemaphore;

    public BinarySemaphore(boolean available) {
        if (available) {
            countingSemaphore = new Semaphore(1, true);
        } else {
            countingSemaphore = new Semaphore(0, true);
        }
    }

    public void acquire() throws InterruptedException {
        countingSemaphore.acquire();
    }

    public synchronized void release() {
        if (countingSemaphore.availablePermits() != 1) {
            countingSemaphore.release();
        }
    }
}

此实现具有二进制信号量的一个属性,您无法通过计数只有一个许可的信号量来获得 - 多次调用释放仍将仅留下一个可用资源。此属性被提及here

【讨论】:

  • 这是不安全的——只有 release() 是同步的,所以如果获取并发运行释放,在你的 if(countingSemaphore.availablePermits() != 1) 之后,在你调用释放之前,你最终可能会出现意想不到的行为。此外,使获取同步也不起作用,因为锁随后被锁定,阻止您释放二进制信号量,停止应用程序。
  • @SamHeather 我只是在想你所说的,如果非同步方法获取就像你在 != 1 检查之后所说的那样开始,那么将没有可用的插槽并且内部获取-调用将停止,线程将等待,上下文切换,另一个释放线程可以继续调用countingSemaphore.release(); 只是想,如果这仍然是一个问题?我认为所有可能的请求线程都会卡在内部countingSemaphore.acquire();-call 中。你不这么认为吗?同步release()-方法只是避免了过多的空闲槽..
【解决方案2】:

我认为您在谈论互斥锁(或互斥锁)。如果是这样,您可以使用内部锁。 Java中的这种锁充当互斥锁,这意味着最多一个线程可以拥有锁:

synchronized (lock) { 
    // Access or modify shared state guarded by lock 
}

其中 lock 是一个模拟对象,仅用于锁定。


编辑:

这里有一个实现——不可重入互斥锁类,它使用值 0 表示解锁状态,使用值 1 表示锁定状态。

class Mutex implements Lock, java.io.Serializable {

    // Our internal helper class
    private static class Sync extends AbstractQueuedSynchronizer {
      // Report whether in locked state
      protected boolean isHeldExclusively() {
        return getState() == 1;
      }

      // Acquire the lock if state is zero
      public boolean tryAcquire(int acquires) {
        assert acquires == 1; // Otherwise unused
        if (compareAndSetState(0, 1)) {
          setExclusiveOwnerThread(Thread.currentThread());
          return true;
        }
        return false;
      }

      // Release the lock by setting state to zero
      protected boolean tryRelease(int releases) {
        assert releases == 1; // Otherwise unused
        if (getState() == 0) throw new IllegalMonitorStateException();
        setExclusiveOwnerThread(null);
        setState(0);
        return true;
      }

      // Provide a Condition
      Condition newCondition() { return new ConditionObject(); }

      // Deserialize properly
      private void readObject(ObjectInputStream s)
          throws IOException, ClassNotFoundException {
        s.defaultReadObject();
        setState(0); // reset to unlocked state
      }
    }

    // The sync object does all the hard work. We just forward to it.
    private final Sync sync = new Sync();

    public void lock()                { sync.acquire(1); }
    public boolean tryLock()          { return sync.tryAcquire(1); }
    public void unlock()              { sync.release(1); }
    public Condition newCondition()   { return sync.newCondition(); }
    public boolean isLocked()         { return sync.isHeldExclusively(); }
    public boolean hasQueuedThreads() { return sync.hasQueuedThreads(); }
    public void lockInterruptibly() throws InterruptedException {
      sync.acquireInterruptibly(1);
    }
    public boolean tryLock(long timeout, TimeUnit unit)
        throws InterruptedException {
      return sync.tryAcquireNanos(1, unit.toNanos(timeout));
    }
  }

如果您想知道应该在哪里致电wait()notify(),请查看sun.misc.Unsafe#park()。它在 java.util.concurrent.locks 包中使用(AbstractQueuedSynchronizer

希望这会有所帮助。

【讨论】:

  • 你说的是我应该放在二进制信号量类中。我已经知道我应该在某个时候这样做,但我不知道应该在哪里调用 notify 和 wait 调用。
  • 您不需要深入研究这样的细节(但如果您愿意,请查看sun.misc.Unsafe 课程,正如我在帖子中提到的那样)。您可以通过派生的便利实现来实现您的目标。
【解决方案3】:

这里直接来自Java site

由 Doug Lea 在 JSR-166 中领导的并发实用程序库是一个 将流行的并发包特别发布到 J2SE 5.0 中 平台。它提供了强大的高级线程结构, 包括执行器,这是一个线程任务框架,线程安全 队列、定时器、锁(包括原子锁)和其他 同步原语。

一种这样的锁是众所周知的信号量。信号量可用于 与现在使用等待的方式相同,以限制对块的访问 代码。信号量更灵活,也可以允许多个 并发线程访问,以及允许您在之前测试锁 获得它。以下示例仅使用一个信号量,也 称为二进制信号量。请参阅 java.util.concurrent 包了解 更多信息。

final  private Semaphore s= new Semaphore(1, true);

    s.acquireUninterruptibly(); //for non-blocking version use s.acquire()

try {     
   balance=balance+10; //protected value
} finally {
  s.release(); //return semaphore token
}

我认为,使用 Semaphore 类等高级抽象的全部原因是您不必调用低级 wait/notify

【讨论】:

    【解决方案4】:

    是的,你可以。具有单个许可的信号量是二进制信号量。它们控制对单个资源的访问。它们可以被视为某种互斥体/锁。

    Semaphore binarySemaphore = new Semaphore(1);
    

    【讨论】:

    • 它没有帮助,我需要一个二进制信号量类,而不是标准的。
    • 如果它只有一个许可,它一个二进制信号量。您的评论就像在说“这辆车不适合:它可以达到 180 公里/小时,而我需要以 90 公里/小时的速度行驶。
    • 您可以扩展标准信号量并创建自己的二进制信号量类,如下所示。公共类 BinarySemaphore 扩展信号量{ BinarySemaphore (){ super(1); } }
    • @JBNizet 在计数信号量中将许可数设置为 1 不会自动生成二进制信号量,因为通过多次调用 release(),线程仍然可以将可用许可数增加到一个值大于一。
    【解决方案5】:

    我在 Java 中有自己的二进制信号量实现。

    import java.util.concurrent.Semaphore;
    import java.util.concurrent.TimeUnit;
    
    /**
     * A binary semaphore extending from the Java implementation {@link Semaphore}.
     * <p>
     * This semaphore acts similar to a mutex where only one permit is acquirable. Attempts to acquire or release more than one permit
     * are forbidden.
     * <p>
     * Has in {@link Semaphore}, there is no requirement that a thread that releases a permit must have acquired that permit. However,
     * no matter how many times a permit is released, only one permit can be acquired at a time. It is advised that the program flow
     * is such that the thread making the acquiring is the same thread making the release, otherwise you may end up having threads
     * constantly releasing this semaphore, thus rendering it ineffective.
     * 
     * @author Pedro Domingues
     */
    public final class BinarySemaphore extends Semaphore {
    
        private static final long serialVersionUID = -927596707339500451L;
    
        private final Object lock = new Object();
    
        /**
         * Creates a {@code Semaphore} with the given number of permits between 0 and 1, and the given fairness setting.
         *
         * @param startReleased
         *            <code>true</code> if this semaphore starts with 1 permit or <code>false</code> to start with 0 permits.
         * @param fairMode
         *            {@code true} if this semaphore will guarantee first-in first-out granting of permits under contention, else
         *            {@code false}
         */
        public BinarySemaphore(boolean startReleased, boolean fairMode) {
            super((startReleased ? 1 : 0), fairMode);
        }
    
        @Override
        public void acquire(int permits) throws InterruptedException {
            if (permits > 1)
                throw new UnsupportedOperationException("Cannot acquire more than one permit!");
            else
                super.acquire(permits);
        }
    
        @Override
        public void acquireUninterruptibly(int permits) {
            if (permits > 1)
                throw new UnsupportedOperationException("Cannot acquire more than one permit!");
            else
                super.acquireUninterruptibly(permits);
        }
    
        @Override
        public void release() {
            synchronized (lock) {
                if (this.availablePermits() == 0)
                    super.release();
            }
        }
    
        @Override
        public void release(int permits) {
            if (permits > 1)
                throw new UnsupportedOperationException("Cannot release more than one permit!");
            else
                this.release();
        }
    
        @Override
        public boolean tryAcquire(int permits) {
            if (permits > 1)
                throw new UnsupportedOperationException("Cannot acquire more than one permit!");
            else
                return super.tryAcquire(permits);
        }
    
        @Override
        public boolean tryAcquire(int permits, long timeout, TimeUnit unit) throws InterruptedException {
            if (permits > 1)
                throw new UnsupportedOperationException("Cannot release more than one permit!");
            else
                return super.tryAcquire(permits, timeout, unit);
        }
    }
    

    如果您在代码中发现任何错误,请告诉我,但到目前为止它一直运行良好! :)

    【讨论】:

      【解决方案6】:

      我宁愿使用Lock

      除了命名匹配之外,Java Semaphore 无法实现 BinarySemaphore,使用 Object wait/notify 或 synchronize 非常原始。

      相反,Lock 类提供了与 Semaphore 几乎相同的锁定语义及其锁定/解锁(相对于 Semaphore 的获取/释放),但它专门用于解决临界区功能,其中预期只有一个线程进入一次。

      值得注意的是,由于tryLock 方法,Lock 还提供了带有超时语义的 try。

      【讨论】:

        【解决方案7】:

        也许使用 AtomicBoolean 实现它是个好主意。 如果不是,请告诉我。

        import java.util.concurrent.atomic.AtomicBoolean;
        
        public class BinarySemaphore {
            
            private final AtomicBoolean permit;
            
            public BinarySemaphore() {
                this(true);
            }
            
            /**
             * Creates a binary semaphore with a specified initial state
             */
            public BinarySemaphore(boolean permit) {
                this.permit = new AtomicBoolean(permit);
            }
        
            public void acquire() {
                boolean prev;
                do {
                    prev = tryAcquire();
                } while (!prev);
            }
        
            public boolean tryAcquire() {
                return permit.compareAndSet(true, false);
            }
        
            /**
             * In any case, the permit was released
             */
            public void release() {
                permit.set(true);
            }
        
            public boolean available(){
                return permit.get();
            }
        }
        

        【讨论】:

          【解决方案8】:

          您可以查看 Semaphore 类的 Java 实现的源代码(或者直接使用它?)

          【讨论】:

          • 我需要二进制信号量而不是那个。
          猜你喜欢
          • 1970-01-01
          • 2017-10-03
          • 1970-01-01
          • 2012-10-07
          • 2021-06-09
          • 2020-12-23
          • 1970-01-01
          • 2011-11-20
          • 2016-02-10
          相关资源
          最近更新 更多