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
posted @ 2022-02-12 20:34  楼兰胡杨  阅读(338)  评论(0编辑  收藏  举报