Java并发:Semaphore(信号量):多线程环境下的高速收费站

每篇一格言:
秩序就是正确的规律和事物永久的合理性
——Henry Fielding

前言

lock操作在某一时刻只允许一个任务访问资源(例如写文件)。而Semaphore允许多个任务在同一时刻访问资源。本篇以Android代码为实例学习Semaphore的使用方法。

1.Semaphore概念

Semaphore翻译为信号量,但是这个名称不是很直观。更直观的称呼是许可证拥有者。下面我们用更形象的方式做类比。

task——汽车
访问资源 —— 上高速公路
Semaphore —— 高速公路收费站

我们知道,汽车驶入高速公路前需请求收费站发放通行证,驶出高速前归还通行证。为了避免高速公路被挤爆,所以通行证是有限的。
类似的,task想要访问资源需先用acquire()获得许可证,访问结束后release()归还许可证。

Semaphore只需维护当前许可证的个数,实现起来效率很高。

JDK1.8中的注释:

 * A counting semaphore.  Conceptually, a semaphore maintains a set of
 * permits.  Each {@link #acquire} blocks if necessary until a permit is
 * available, and then takes it.  Each {@link #release} adds a permit,
 * potentially releasing a blocking acquirer.
 * However, no actual permit objects are used; the {@code Semaphore} just
 * keeps a count of the number available and acts accordingly.
 *
 * <p>Semaphores are often used to restrict the number of threads than can
 * access some (physical or logical) resource.

2.Semaphore使用方法

在JDK中还有一段示例代码:

* class Pool {
 *   private static final int MAX_AVAILABLE = 100;
 *   private final Semaphore available = new Semaphore(MAX_AVAILABLE, true);
 *
 *   public Object getItem() throws InterruptedException {
 *     available.acquire();
 *     return getNextAvailableItem();
 *   }
 *
 *   public void putItem(Object x) {
 *     if (markAsUnused(x))
 *       available.release();
 *   }
 *
 *   // Not a particularly efficient data structure; just for demo
 *
 *   protected Object[] items = ... whatever kinds of items being managed
 *   protected boolean[] used = new boolean[MAX_AVAILABLE];
 *
 *   protected synchronized Object getNextAvailableItem() {
 *     for (int i = 0; i < MAX_AVAILABLE; ++i) {
 *       if (!used[i]) {
 *          used[i] = true;
 *          return items[i];
 *       }
 *     }
 *     return null; // not reached
 *   }
 *
 *   protected synchronized boolean markAsUnused(Object item) {
 *     for (int i = 0; i < MAX_AVAILABLE; ++i) {
 *       if (item == items[i]) {
 *          if (used[i]) {
 *            used[i] = false;
 *            return true;
 *          } else
 *            return false;
 *       }
 *     }
 *     return false;
 *   }
 * }}</pre>

这段代码非常直观,getItem前要先获取许可证(调用acquire()),putItem后增加一个许可证(调用release())。假设每个Item代表1元硬币,pool就相当于能放100个硬币的存钱罐。如果只取不存,最多只能花100元就没钱了。很显然,这段代码诠释了“量入为出”的含义。

new Semaphore(MAX_AVAILABLE, true)表示最多有MAX_AVAILABLE(这里是100)个许可证。
思考:如果改成new Semaphore(20, true)会怎样呢?
这表示虽然你有100元,但是最多只能花20元。可以看出,Semaphore在初始化时已经对访问资源的线程数量进行了限制。

下面具体讨论几个重要方法

new Semaphore(MAX_AVAILABLE, true)
初始化100个许可证,true表示按公平模式,也就是FIFO

acquire();
阻塞的获取一个许可证。具体是:
1 如果存在并立即返回了一个许可证,则当前许可证数量减1.
2.如果没有许可证,当前线程阻塞,直到:
2.1.其他线程释放了一个许可证,并且当前线程排在等待队列最前,
2.2 或者其他线程中断了当前线程。

release()
释放一个许可证给semaphore

3.Android中Semaphore的使用

下面我们具体看看Semaphore在Android中使用的例子。
代码路径:
SecurityLogMonitor.java (frameworks\base\services\devicepolicy\java\com\android\server\devicepolicy)

1.初始化一个Semaphore:

/** Semaphore used to force log fetching on request from adb. */
    private final Semaphore mForceSemaphore = new Semaphore(0 /* permits */);

这里许可证数量是0.

2. SecurityLogMonitor是一个Runnable,在它的run方法中尝试获取一个许可证:

public void run() {
    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
    final ArrayList<SecurityEvent> newLogs = new ArrayList<>();
    while (!Thread.currentThread().isInterrupted()) {
            try {
                //mark 1:
                final boolean force = mForceSemaphore.tryAcquire(POLLING_INTERVAL_MS, MILLISECONDS);
                getNextBatch(newLogs);
                。。。
                //mark 2:
                notifyDeviceOwnerIfNeeded(force);
            }
            。。。

mark 1:
tryAcquire(POLLING_INTERVAL_MS, MILLISECONDS)表示在POLLING_INTERVAL_MS时间以内阻塞并等待获取一个许可证。它和acquire的区别是阻塞有时间限制,超过设定的时间则不再阻塞,继续执行。
mark 2:
如果在限制时间内得到许可证,则force值是true,表示允许log;否则不允许log。

由于Semaphore在初始化时许可证数量是0,所以要想成功获得许可证,必然需要其他线程释放一个许可证。

3. forceLogs()方法释放一个许可证

public long forceLogs() {
        final long nowNanos = System.nanoTime();
        // We only synchronize with another calls to this function, not with the fetching thread.
        synchronized (mForceSemaphore) {
            final long toWaitNanos = mLastForceNanos + FORCE_FETCH_THROTTLE_NS - nowNanos;
            if (toWaitNanos > 0) {
                return NANOSECONDS.toMillis(toWaitNanos) + 1; // Round up.
            }
            mLastForceNanos = nowNanos;
            // There is a race condition with the fetching thread below, but if the last permit is
            // acquired just after we do the check, logs are forced anyway and that's what we need.
            if (mForceSemaphore.availablePermits() == 0) {
                mForceSemaphore.release();
            }
            return 0;
        }
    }

这个方法调用mForceSemaphore.release();释放了一个许可证。

而调用forceLogs()的是DevicePolicyManagerService,如果感兴趣可以查看这段代码:
DevicePolicyManagerService.java (code\frameworks\base\services\devicepolicy\java\com\android\server\devicepolicy)

本文为博主原创。看完请点赞/评论,让我们共同进步~

原创文章 31 获赞 24 访问量 2万+

猜你喜欢

转载自blog.csdn.net/GentelmanTsao/article/details/105050512