• 并发工具类——Semaphore


    本博客系列是学习并发编程过程中的记录总结。由于文章比较多,写的时间也比较散,所以我整理了个目录贴(传送门),方便查阅。

    并发编程系列博客传送门


    Semaphore([' seməf :(r)])的主要作用是控制线程并发的数量。我们可以将Semaphore想象成景区的一个门卫,这个门卫负责发放景区入园的许可证。

    景区为了游客的入园观赏体验,决定最多允许200个有个同时在园内观赏。那么这个门卫在每天开园的时候手中都会有200张许可证,每当一个游客要入园的时候门卫会给游客发放一张许可证,当门卫手中的许可证发完之后再有游客需要入园的话就必须等待。

    当游客观赏完毕之后,出园的时候需要将许可证交还到门卫手上。门卫将这些交还的许可证再发等待的游客,这些游客就能顺利入园了。

    Semaphore的API简介

    Semaphore的API使用起来也比较简单,常见的API简介如下:

    • Semaphore(int permits):构造方法,创建具有给定许可数的计数信号量并设置为非公平信号量。
    • Semaphore(int permits,boolean fair):构造方法,当fair等于true时,创建具有给定许可数的计数信号量并设置为公平信号量。
    • void acquire():从此信号量获取一个许可前线程将一直阻塞。相当于一辆车占了一个车位。
    • void acquire(int n):从此信号量获取给定数目许可,在提供这些许可前一直将线程阻塞。比如n=2,就相当于一辆车占了两个车位。
    • boolean tryAcquire(int permits, long timeout, TimeUnit unit):尝试获取,在给定的时间内没获取到资源超时
    • void release():释放一个许可,将其返回给信号量。就如同车开走返回一个车位。
    • void release(int n):释放n个许可。
    • int availablePermits():当前可用的许可数。

    Semaphore的常见用法

    下面给出一个Oracle官方文档中的列子代码:

    class Pool {
        // 可同时访问资源的最大线程数
        private static final int MAX_AVAILABLE = 100; 
        private final Semaphore available = new Semaphore(MAX_AVAILABLE, true);
        // 共享资源
        protected Object[] items = new Object[MAX_AVAILABLE];   
        protected boolean[] used = new boolean[MAX_AVAILABLE];
        public Object getItem() throws InterruptedException {
            available.acquire();
            return getNextAvailableItem();
        }
        public void putItem(Object x) {
            if (markAsUnused(x))
                available.release();
        }
        private synchronized Object getNextAvailableItem() {
            for (int i = 0; i < MAX_AVAILABLE; ++i) {
                if (!used[i]) {
                    used[i] = true;
                    return items[i];
                }
            }
            return null;
        }
        private 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;
        }
    }
    

    items数组可以看成是我们的共享资源,当有线程尝试使用共享资源时,我们要求线程先获得“许可”(调用Semaphoreacquire方法),这样线程就拥有了权限,否则就需要等待。当使用完资源后,线程需要调用Semaphorerelease方法释放许可。

    Semaphore并不能替代synchronized

    有些书中提到:如果将Semaphore的许可证数量设置成1的话,就能实现synchronized的功能。其实这种说法是不对的。

    下面使用Semaphore来控制对一个账户进行并发存钱和取钱的动作,如果Semaphore能实现synchronized的功能的话,账户最后的余额应该还是10000,但代码执行后的结果并不是这样。大家可以执行下面的代码看下结果。

    public static final int THREAD_COUNT = 100;
    
        public static void main(String[] args) {
            BankAccount myAccount = new BankAccount("accountOfMG", 10000.00);
            for (int i = 0; i < THREAD_COUNT; i++) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            int var = new Random().nextInt(100);
                            Thread.sleep(var);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        double deposit = myAccount.deposit(1000.00);
                        System.out.println(Thread.currentThread().getName() + " balance1:" + deposit);
                    }
                }).start();
            }
            for (int i = 0; i < THREAD_COUNT; i++) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            int var = new Random().nextInt(100);
                            Thread.sleep(var);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        double deposit = myAccount.withdraw(1000.00);
                        System.out.println(Thread.currentThread().getName() + " balance2:" + deposit);
    
                    }
                }).start();
            }
        }
    
        private static class BankAccount {
    
            Semaphore semaphore = new Semaphore(1);
    
            String accountName;
            double balance;
    
            public BankAccount(String accountName, double balance) {
                    this.accountName = accountName;
                    this.balance = balance;
            }
    
            public double deposit(double amount) {
                try {
                    semaphore.acquire();
                    balance = balance + amount;
                    return balance;
                } catch (Exception e) {
                    throw new RuntimeException("中断...");
                } finally {
                    semaphore.release();
                }
            }
    
            public double withdraw(double amount) {
                try {
                    semaphore.acquire();
                balance = balance - amount;
                return balance;
                } catch (Exception e) {
                    throw new RuntimeException("中断...");
                } finally {
                    semaphore.release();
                }
            }
    
        }
    

    这里Semaphore并不能实现synchronized的功能的原因是:Semaphore并不能保证共享变量的可见性。

    实现原理

    Semaphore底层原理还是基于AQS机制的。这边就不具体分析了,感兴趣的可以参考我前面关于AQS的文章

    简单总结

    • Semaphore只能用来做线程同步——控制线程的执行顺序,但是并不能保证线程安全;
    • Semaphore主要用来控制线程的并发数量,通常用在限流组件中。
    • Semaphore基于AQS机制实现。
  • 相关阅读:
    RQNOJ 34 紧急援救
    Codevs 2080 特殊的质数肋骨
    POJ2975 Nim
    Bzoj1016 最小生成树计数
    POJ3613 Cow Relays
    POJ1386 Play on Words
    [从hzwer神犇那翻到的模拟赛题] 合唱队形
    HDU2824 The Euler function
    HDU1576 A/B
    HDU2669 Romantic
  • 原文地址:https://www.cnblogs.com/54chensongxia/p/12871187.html
Copyright © 2020-2023  润新知