【Java并发工具】Semaphore

目录

1 前言

2 应用场景

3 其它方法

4 实现原理

4.1 实现的同步器

4.2 执行过程


1 前言

本人使用jdk8版本。

Semaphore(信号量)是用来控制同时访问特定资源的线程数量,它通过协调各个线程,以保证合理的使用公共资源。

举个例子,比如××马路要限制流量,只允许同时有一百辆车在这条路上行使,其他的都必须在路口等待,所以前一百辆车会看到绿灯,可以开进这条马路,后面的车会看到红灯,不能驶入××马路,但是如果前一百辆中有5辆车已经离开了××马路,那么后面就允许有5辆车驶入马路,这个例子里说的车就是线程,驶入马路就表示线程在执行,离开马路就表示线程执行完成,看见红灯就表示线程被阻塞,不能执行。

2 应用场景

Semaphore可以用于做流量控制,特别是公用资源有限的应用场景,比如数据库连接。

假如有一个需求,要从本地磁盘读取几万个文件的数据,因为都是IO密集型任务,我们可以启动几十个线程并发地读取,但是如果读到内存后,还需要存储到数据库中(必须通过数据库连接来写),而数据库的连接数只有10个,这时我们必须控制只有10个线程同时获取数据库连接保存数据,否则会报错无法获取数据库连接。

public class SemaphoreTest {
    private static final int THREAD_COUNT = 30;
    private static ExecutorService threadPool = Executors.newFixedThreadPool(THREAD_COUNT);
    private static Semaphore s = new Semaphore(10);

    public static void main(String[] args) {
        for (int i = 0; i < THREAD_COUNT; i++) {
            threadPool.execute(new MyRunnable());
        }
        threadPool.shutdown();
    } 
}

    class MyRunnable implements Runnable {

        @Override
        public void run() {
            try {
                s.acquire();
                System.out.println("save data -- ";
                s.release();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

在代码中,虽然有30个线程在执行,但是只允许10个并发执行。Semaphore的构造方法Semaphore(int permits)接受一个整型的数字,表示可用的许可证数量。Semaphore(10)表示允许10个线程获取许可证,也就是最大并发数是10

Semaphore的用法也很简单,首先线程使用Semaphoreacquire()方法获取一个许可证,使用完之后调用release()方法归还许可证。还可以用tryAcquire()方法尝试获取许可证。

3 其它方法

  • int availablePermits():返回此信号量中当前可用的许可证数。
  • int getQueueLength():返回正在等待获取许可证的线程数。
  • boolean hasQueuedThreads():是否有线程正在等待获取许可证。
  • void reducePermits(int reduction):减少 reduction个许可证,是个protected方法。
  • Collection<Thread> getQueuedThreads():返回所有等待获取许可证的线程集合,是个protected方法。

4 实现原理

Semaphore内部实现了一个AbstractQueuedSynchronizer的子类,并重写它的tryAcquireShared()、tryReleaseShared()等方法来实现“控制同时访问特定资源的线程数量”的功能。显然,实现基于同步器的共享锁,在构造Semaphore时传入的int参数就是用来指定共享锁可以被获取的最大数,超过最大数后再获取线程会阻塞,同时一旦一个线程释放自己的共享锁,阻塞队列中所有的等待线程会被唤醒来竞争锁。多个线程怎么来竞争锁呢?Semaphore里也引入同步器里的公平锁和非公平锁的机制,在构造Semaphore时也可以指定。

4.1 实现的同步器

公平锁与非公平锁只在获取锁时有区别,释放时用的是统一方法。

    abstract static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 1192457210091910933L;

        Sync(int permits) {            // 初始化锁状态,设置锁获取的最大数
            setState(permits);
        }

        final int getPermits() {        // 返回锁状态
            return getState();
        }

        final int nonfairTryAcquireShared(int acquires) {    // 尝试获取非公平锁
            for (;;) {
                int available = getState();
                int remaining = available - acquires;        
                if (remaining < 0 ||
                    compareAndSetState(available, remaining))
                    return remaining;
            }
        }

        protected int tryAcquireShared(int acquires) {        // 尝试获取公平锁
            for (;;) {
                // 判断是否有前驱节点,是就表情有线程比它更早请求锁,请求失败
                if (hasQueuedPredecessors())        
                    return -1;
                int available = getState();
                int remaining = available - acquires;
                if (remaining < 0 ||
                    compareAndSetState(available, remaining))
                    return remaining;
            }
        }

        // 尝试释放锁,没有公平与非公平之分
        protected final boolean tryReleaseShared(int releases) {    
            for (;;) {
                int current = getState();
                int next = current + releases;
                if (next < current) // overflow
                    throw new Error("Maximum permit count exceeded");
                if (compareAndSetState(current, next))
                    return true;
            }
        }
    }

4.2 执行过程

通过上面的数据库例子来介绍,构造Semaphore时将锁的状态设为10,然后同时开启30个线程往数据库中写,所有线程都会先调用Semaphore.acquire(),前10个线程会成功获取锁,每当获取成功时锁的状态会-1,进而执行保存数据的操作,最终锁的状态为0。当而另外20个线程再获取锁会失败进而阻塞,若此前10个线程中有线程保存完数据释放锁了,锁的状态+1,会唤醒这个20个线程去竞争锁(公平与非公平)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章