• Java并发包源码学习系列:同步组件Semaphore源码解析


    Semaphore概述及案例学习

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

    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); //10个许可证数量,最大并发数为10
    
        public static void main(String[] args) {
            for(int i = 0; i < THREAD_COUNT; i ++){ //执行30个线程
                threadPool.execute(new Runnable() {
                    @Override
                    public void run() {
                        s.tryAcquire(); //尝试获取一个许可证
                        System.out.println("save data");
                        s.release(); //使用完之后归还许可证
                    }
                });
            }
            threadPool.shutdown();
        }
    }
    
    • 创建一个大小为30的线程池,但是信号量规定在10,保证许可证数量为10。
    • 每次线程调用tryAcquire()或者acquire()方法都会原子性的递减许可证的数量,release()会原子性递增许可证数量。

    类图结构及重要字段

    public class Semaphore implements java.io.Serializable {
        private static final long serialVersionUID = -3222578661600680210L;
        /** All mechanics via AbstractQueuedSynchronizer subclass */
        private final Sync sync;
        
        abstract static class Sync extends AbstractQueuedSynchronizer {
            // permits指定初始化信号量个数
            Sync(int permits) {
                setState(permits);
            }
            // ...
        }
        
        static final class NonfairSync extends Sync {...}
        
        static final class FairSync extends Sync {...}
        
        // 默认采用非公平策略
        public Semaphore(int permits) {
            sync = new NonfairSync(permits);
        }
    	
        // 可以指定公平策略
        public Semaphore(int permits, boolean fair) {
            sync = fair ? new FairSync(permits) : new NonfairSync(permits);
        }
        
        //...
    }
    
    • 基于AQS,类似于ReentrantLock,Sync继承自AQS,有公平策略和非公平策略两种实现。
    • 类似于CountDownLatch,state在这里也是通过构造器指定,表示初始化信号量的个数。

    本篇文章阅读需要建立在一定的AQS基础之上,这边推荐几篇前置文章,可以瞅一眼:

    void acquire()

    调用该方法时,表示希望获取一个信号量资源,相当于acquire(1)

    如果当前信号量个数大于0,CAS将当前信号量值减1,成功后直接返回。

    如果当前信号量个数等于0,则当前线程将被置入AQS的阻塞队列。

    该方法是响应中断的,其他线程调用了该线程的interrupt()方法,将会抛出中断异常返回。

        // Semaphore.java
    	public void acquire() throws InterruptedException {
            // 传递的 arg 为 1 , 获取1个信号量资源
            sync.acquireSharedInterruptibly(1);
        }
    	// AQS.java
        public final void acquireSharedInterruptibly(int arg)
                throws InterruptedException {
            // 线程被 中断, 抛出中断异常
            if (Thread.interrupted())
                throw new InterruptedException();
            // 子类实现, 公平和非公平两种策略
            if (tryAcquireShared(arg) < 0)
                // 如果获取失败, 则置入阻塞队列, 
                // 再次进行尝试, 尝试失败则挂起当前线程
                doAcquireSharedInterruptibly(arg);
        }
    
    

    非公平

        static final class NonfairSync extends Sync {
            private static final long serialVersionUID = -2694183684443567898L;
    
            NonfairSync(int permits) {
                super(permits);
            }
    
            protected int tryAcquireShared(int acquires) {
                // 这里直接调用Sync定义的 非公平共享模式获取方法
                return nonfairTryAcquireShared(acquires);
            }
        }
    
        abstract static class Sync extends AbstractQueuedSynchronizer {
            
            final int nonfairTryAcquireShared(int acquires) {
                for (;;) {
                    // 获取当前信号量的值
                    int available = getState();
                    // 减去需要获取的值, 得到剩余的信号量个数
                    int remaining = available - acquires;
                    // 不剩了,表示当前信号量个数不能满足需求, 返回负数, 线程置入AQS阻塞
                    // 还有的剩, CAS设置当前信号量值为剩余值, 并返回剩余值
                    if (remaining < 0 ||
                        compareAndSetState(available, remaining))
                        return remaining;
                }
            }
        }
    

    你会发现,非公平策略是无法保证【AQS队列中阻塞的线程】和【当前线程】获取的顺序的,当前线程是有可能在排队的线程之前就拿到资源,产生插队现象。

    公平策略就不一样了,它会通过hasQueuedPredecessors()方法看看队列中是否存在前驱节点,以保证公平性。

    公平策略

        static final class FairSync extends Sync {
            private static final long serialVersionUID = 2014338818796000944L;
    
            FairSync(int permits) {
                super(permits);
            }
    
            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;
                }
            }
        }
    

    void acquire(int permits)

    在acquire()的基础上,指定了获取信号量的数量permits。

        public void acquire(int permits) throws InterruptedException {
            if (permits < 0) throw new IllegalArgumentException();
            sync.acquireSharedInterruptibly(permits);
        }
    

    void acquireUninterruptibly()

    该方法与acquire()类似,但是不响应中断。

        public void acquireUninterruptibly() {
            sync.acquireShared(1);
        }
    	
        public final void acquireShared(int arg) {
            if (tryAcquireShared(arg) < 0)
                doAcquireShared(arg);
        }
    

    void acquireUninterruptibly(int permits)

    该方法与acquire(permits)类似,但是不响应中断。

        public void acquireUninterruptibly(int permits) {
            if (permits < 0) throw new IllegalArgumentException();
            sync.acquireShared(permits);
        }
    

    boolean tryAcquire()

    tryAcquire和acquire非公平策略公用一个逻辑,但是区别在于,如果获取信号量失败,或者CAS失败,将会直接返回false,而不会置入阻塞队列中。

    一般try开头的方法的特点就是这样,尝试一下,成功是最好,失败也不至于被阻塞,而是立刻返回false。

        public boolean tryAcquire() {
            return sync.nonfairTryAcquireShared(1) >= 0;
        }
        abstract static class Sync extends AbstractQueuedSynchronizer {
            final int nonfairTryAcquireShared(int acquires) {
                for (;;) {
                    int available = getState();
                    int remaining = available - acquires;
                    if (remaining < 0 ||
                        compareAndSetState(available, remaining))
                        return remaining;
                }
            }
        }
    

    boolean tryAcquire(int permits)

    相比于普通的tryAcquire(),指定了permits的值。

        public boolean tryAcquire(int permits) {
            if (permits < 0) throw new IllegalArgumentException();
            return sync.nonfairTryAcquireShared(permits) >= 0;
        }
    

    boolean tryAcquire(int permits, long timeout, TimeUnit unit)

    相比于tryAcquire(int permits),增加了超时控制。

        public boolean tryAcquire(int permits, long timeout, TimeUnit unit)
            throws InterruptedException {
            if (permits < 0) throw new IllegalArgumentException();
            return sync.tryAcquireSharedNanos(permits, unit.toNanos(timeout));
        }
    

    void release()

    将信号量值加1,如果有线程因为调用acquire方法而被阻塞在AQS阻塞队列中,将根据公平策略选择一个信号量个数满足需求的线程唤醒,线程唤醒后也会尝试获取新增的信号量。

    参考文章:Java并发包源码学习系列:AQS共享模式获取与释放资源

        // Semaphore.java
    	public void release() {
            sync.releaseShared(1);
        }
    	// AQS.java
        public final boolean releaseShared(int arg) {
            // 尝试释放锁
            if (tryReleaseShared(arg)) {
                // 释放成功, 唤醒AQS队列里面最先挂起的线程
                // https://blog.csdn.net/Sky_QiaoBa_Sum/article/details/112386838
                doReleaseShared();
                return true;
            }
            return false;
        }
    	// Semaphore#Sync.java
        abstract static class Sync extends AbstractQueuedSynchronizer {
            protected final boolean tryReleaseShared(int releases) {
                for (;;) {
                    // 获取当前信号量
                    int current = getState();
                    // 期望加上releases
                    int next = current + releases;
                    if (next < current) // overflow
                        throw new Error("Maximum permit count exceeded");
                    // CAS操作,更新
                    if (compareAndSetState(current, next))
                        return true;
                }
            }
        }
    

    void release(int permits)

    release()相比指定了permits的值。

        public void release(int permits) {
            if (permits < 0) throw new IllegalArgumentException();
            sync.releaseShared(permits);
        }
    

    其他方法

    Semaphore还提供其他一些方法,实现比较简单,这边就简单写一下吧:

        // 返回此信号量中当前可用的许可证数量, 其实就是得到当前的 state值  getState()
    	public int availablePermits() {
            return sync.getPermits();
        }
    
        // 将state更新为0, 返回0
        public int drainPermits() {
            return sync.drainPermits();
        }
    
    	// 减少reduction个许可证
        protected void reducePermits(int reduction) {
            if (reduction < 0) throw new IllegalArgumentException();
            sync.reducePermits(reduction);
        }
    
        // 判断公平策略
        public boolean isFair() {
            return sync instanceof FairSync;
        }
    
    	// 判断是否有线程证在等待获取许可证
        public final boolean hasQueuedThreads() {
            return sync.hasQueuedThreads();
        }
    
    	// 返回正在等待获取许可证的线程数
        public final int getQueueLength() {
            return sync.getQueueLength();
        }
    
    	// 返回所有等待获取许可证的线程集合
        protected Collection<Thread> getQueuedThreads() {
            return sync.getQueuedThreads();
        }
    

    总结

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

    • 基于AQS,类似于ReentrantLock,Sync继承自AQS,有公平策略和非公平策略两种实现。
    • 类似于CountDownLatch,state在这里也是通过构造器指定,表示初始化信号量的个数。

    每次线程调用tryAcquire()或者acquire()方法都会原子性的递减许可证的数量,release()会原子性递增许可证数量,只要有许可证就可以重复使用。

    参考阅读

    • 《Java并发编程之美》
    • 《Java并发编程的艺术》
  • 相关阅读:
    调试ASP
    规范的又一个例子
    不同单位转换问题
    客户处出现一个错误
    SQL视图嵌套视图很容易出错
    安装包得到大进步
    两年前的产品现在长得不错
    不用报表的理由
    PowerPoint笔记(五)
    文本编辑器vi
  • 原文地址:https://www.cnblogs.com/summerday152/p/14421396.html
Copyright © 2020-2023  润新知