• Java基于线程池和AQS模拟高并发


    概述

    手写高并发下线程安全的单例模式》主要介绍使用枚举类实现JAVA单例模式,以及在高并发环境下验证此单例模式是线程安全的。本文借助ReentrantLock、CountDownLatch和Semaphore等,基于线程池验证如何创建线程安全和不安全的方法。

    实际项目中,我们有很多高并发的场景需要考虑、设计,在高并发领域有个耳熟能详的名词叫惊群效应。以喂鸽子为例进行说明,当你往一群鸽子中间扔一块食物时,虽然最终只有一只鸽子抢到食物,但所有鸽子都会被惊动从而飞过来争夺,没有抢到食物的鸽子只好回去继续睡觉, 等待下一块食物到来。这样,每扔一块食物,都会惊动所有的鸽子,即为惊群。也就是说,虽然只扔了一块食物,但是惊动了整个鸽群,最后却只有一只鸽子抢到食物,浪费了其它鸽子的能量。

    对于操作系统来说,多个进程/线程在等待同一资源时,也会产生类似的效果,其结果就是每当有可用资源时,所有的进程/线程都来竞争资源,造成了资源的浪费[2]

    • 系统对用户进程/线程频繁的做无效的调度、上下文切换,系统系能大打折扣。
    • 为了确保只有一个线程得到资源,用户必须对资源操作进行加锁保护,进一步加大了系统开销。

    下面基于ReentrantLock、CountDownLatch和Semaphore创建一个并发模拟工具,当被调用函数不出现惊群效应时,说明是线程安全的。

    CountDownLatch是一个能阻塞主线程,让其它线程满足特定条件下再继续执行的工具。比如倒计时3000,每当一个线程完成一次操作就让它执行countDown一次,直到count为0之后输出结果,这样就保证了其它线程一定是满足了特定条件(执行某操作5000次),模拟了并发执行次数。

    Semaphore信号量是一个能阻塞线程且能控制统一时间请求的并发量的工具。比如能保证同时执行的线程最多200个,模拟出稳定的并发量。每个acquire方法阻塞,直到有一个许可证可以获得然后拿走一个许可证;每个release方法增加一个许可证,这可能会释放一个阻塞的acquire方法。然而,其实并没有实际的许可证这个对象,Semaphore只是维持了一个可获得许可证的数量。

    模拟工具

    创建模拟工具,首先创建一个线程安全地售票的任务类:

    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.locks.ReentrantLock;
    
    /**
     * 线程安全地售票
     */
    public class SafeSale implements Runnable {
    
        //定义车票的数量
        private int ticket = 100;
        private ReentrantLock lock = new ReentrantLock();
    
        @Override
        public void run() {
            boolean a = true;
            while (a) {
                try {
                    //对操作共享数据的代码进行加锁
                    lock.lock();
                    if (ticket > 0) {
                        System.out.println(Thread.currentThread().getName() + ":" + "出售第" + ticket + "张车票");
                        //车票数量减一
                        ticket--;
                        //线程休眠,增加其他线程调用的机会
                        try {
                            TimeUnit.MILLISECONDS.sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    } else {
                        a = false;
                    }
                } finally {
                    lock.unlock(); //进行解锁
                }
            }
        }
    }
    

    紧接着,创建测试用例所需要的非线程安全函数和main函数:

    import java.util.concurrent.CountDownLatch;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.Semaphore;
    
    /**
     * 模拟高并发
     */
    public class ConcurrencyCheck {
        // 执行次数
        private static final int THREAD_COUNT = 3000;
        // 并发数
        private static final int CONCURRENT_COUNT = 200;
        // 全局变量,容易出幺蛾子
        private static int count = 10000;
    
        public static void main(String[] args) throws InterruptedException {
            lockSale();
            checkUtil();
        }
    
        /**
         * 基于lock方法售票
         */
        private static void lockSale() {
            ExecutorService executorService = Executors.newCachedThreadPool();
            SafeSale sale = new SafeSale();
            for (int i = 0; i < THREAD_COUNT; i++) {
                executorService.execute(sale);
            }
            executorService.shutdown();
        }
    
        /**
         * 模拟线程非安全,模拟工具主题
         * @throws InterruptedException
         */
        private static void checkUtil() throws InterruptedException {
            ExecutorService executorService = Executors.newCachedThreadPool();
            Semaphore semaphore = new Semaphore(CONCURRENT_COUNT);
            CountDownLatch countDownLatch = new CountDownLatch(THREAD_COUNT);
            for (int i = 0; i < THREAD_COUNT; i++) {
                executorService.execute(() -> {
                    try {
                        semaphore.acquire();
                        subtraction();
                        semaphore.release();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    countDownLatch.countDown();
                });
            }
            countDownLatch.await();
            executorService.shutdown();
            System.out.println("计数结果:" + count);
        }
    
        /**
         * 售票,非线程安全,被验证对象
         */
        private static void subtraction() {
            count--;
        }
    }
    

    如果多运行几次如上main函数就会发现,方法subtraction()并不是线程安全的,即其执行结果几乎都是大于7000,很少等于7000。

    Reference

    [1] https://www.jianshu.com/p/2da329ba5349
    [2] https://blog.csdn.net/shipfei_csdn/article/details/103110621
  • 相关阅读:
    nginx安装
    redis安装配置
    阿里云试用总结
    Starting MySQL....The server quit without updating PID file错误解决办法
    mysql du-master配置
    Centos6.5上的iptables
    Swift 函数提前返回
    Google 推出新搜索引擎以查找数据集
    为用户设计的产品,就应该用用户熟悉的语言
    「工具」三分钟了解一款在线流程绘制工具:Whimsical
  • 原文地址:https://www.cnblogs.com/east7/p/15887325.html
Copyright © 2020-2023  润新知