项目中使用多线程

项目中使用多线程

线程池

1、线程池配置

项目中如果要使用线程池,必须先进行配置,如果使用spring 默认的线程池配置可能会导致OOM异常

  • 注意要在spring启动项 上加上@EnableAsync 开启异步支持
  • 线程池要配置@Configuration注解交给spring管理
  • 配置线程池主要参数
    • corePoolSize 核心线程
    • maximumPoolSize 最大线程
    • keepAliveTime 空闲线程存活时间
    • unit 空闲线程存活时间单位
    • workQueue 工作队列(4种)
      • ArrayBlockingQueue 新任务进来后,会放到该队列的队尾,有界的数组可以防止资源耗尽问题。当线程池中线程数量达到corePoolSize后,再有新任务进来,则会将任务放入该队列的队尾,等待被调度。如果队列已经是满的,则创建一个新线程,如果线程数量已经达到maxPoolSize,则会执行拒绝策略。
      • LinkedBlockingQuene 当线程池中线程数量达到corePoolSize后,再有新任务进来,会一直存入该队列,而基本不会去创建新线程直到maxPoolSize
      • SynchronousQuene 新任务进来时,不会缓存,而是直接被调度执行该任务,如果没有可用线程,则创建新线程,如果线程数量达到maxPoolSize,则执行拒绝策略。
      • PriorityBlockingQueue 具有优先级的无界阻塞队列,优先级通过参数Comparator实现。
    • threadFactory 线程工厂
    • handler 拒绝策略(4种)
      • CallerRunsPolicy 该策略下,在调用者线程中直接执行被拒绝任务的run方法,除非线程池已经shutdown,则直接抛弃任务。
      • AbortPolicy 该策略下,直接丢弃任务,并抛出RejectedExecutionException异常。
      • DiscardPolicy 该策略下,直接丢弃任务,什么都不做。
      • DiscardOldestPolicy 该策略下,抛弃进入队列最早的那个任务,然后尝试把这次拒绝的任务放入队列

线程池配置:

@Configuration
@EnableAsync
public class ExecutorTaskPoolConfig {
    /**
     * 核心线程数
     * 默认的核心线程数为1
     *
     */
    private static final int CORE_POOL_SIZE = 5;
    /**
     * 最大线程数
     * 默认的最大线程数是Integer.MAX_VALUE 即2<sup>31</sup>-1
     */
    private static final int MAX_POOL_SIZE = 20;
    /**
     * 缓冲队列数
     * 默认的缓冲队列数是Integer.MAX_VALUE 即2<sup>31</sup>-1
     */
    private static final int QUEUE_CAPACITY = 100;

    /**
     * 允许线程空闲时间
     * 默认的线程空闲时间为60秒
     */
    private static final int KEEP_ALIVE_SECONDS = 60;

    /**
     * 线程池前缀名
     */
    private static final String THREAD_NAME_PREFIX = "Lamp_Service_Async_";

    /**
     * allowCoreThreadTimeOut为true则线程池数量最后销毁到0个
     * allowCoreThreadTimeOut为false
     * 销毁机制:超过核心线程数时,而且(超过最大值或者timeout过),就会销毁。
     * 默认是false
     */
    private boolean allowCoreThreadTimeOut = true;

    @Bean("taskExecutor")
    public ThreadPoolTaskExecutor taskExecutor() {

        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setCorePoolSize(CORE_POOL_SIZE);
        taskExecutor.setMaxPoolSize(MAX_POOL_SIZE);
        taskExecutor.setQueueCapacity(QUEUE_CAPACITY);
        taskExecutor.setKeepAliveSeconds(KEEP_ALIVE_SECONDS);
        taskExecutor.setThreadNamePrefix(THREAD_NAME_PREFIX);
        taskExecutor.setAllowCoreThreadTimeOut(allowCoreThreadTimeOut);
        taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        //线程池初始化
        taskExecutor.initialize();
        return taskExecutor;
    }
}

2、实战使用

在spring 项目中 如果需要使用多线程来完成逻辑,则需要优先考虑线程安全问题,如果使用同一个对象 或者使用静态公有属性则极有可能会造成线程安全问题,所以使用中,多考虑 ,多测试模拟后再去使用

以下作为参考:

如果要使用线程池,就要在方法上加上@Async 注解,

需要标识是个异步方法

@EnableAsync来开启异步的支持

@Async来对某个方法进行异步执行

异步方法:

@Async("taskExecutor")
public void historyAllProcessing(LampChartDTO lampChartDTO, Map<Integer, BaseExportDTO> map1, Map<Integer, BaseExportDTO> map2, Map<Integer, BaseExportDTO> map3, Map<Integer, BaseExportDTO> map4,String lampId,String lampNo) {
        long start = System.currentTimeMillis();
        //1.查灯控
        CompletableFuture<Void> lightControlFuture = CompletableFuture.runAsync(() -> {
            BaseExportDTO result = new BaseExportDTO();
            //这里为了线程安全,new新对象,将对象重新赋值
            LampChartDTO l1 = new LampChartDTO();
            l1.setType(0);
            l1.setId(lampChartDTO.getId());
            l1.setEDate(lampChartDTO.getEDate());
            l1.setSDate(lampChartDTO.getSDate());
            result = getLampHistoryDatQueryAllData(result,l1,lampId,lampNo);
            map1.put(l1.getId(),result);
        });
        //2.查充放
        CompletableFuture<Void> chargeDischargeFuture = CompletableFuture.runAsync(() -> {
            BaseExportDTO result = new BaseExportDTO();
            LampChartDTO l2 = new LampChartDTO();
            l2.setType(1);
            l2.setId(lampChartDTO.getId());
            l2.setEDate(lampChartDTO.getEDate());
            l2.setSDate(lampChartDTO.getSDate());
            result = getLampHistoryDatQueryAllData(result,l2,lampId,lampNo);
            map2.put(l2.getId(),result);
        });
        //3.查蓄电
        CompletableFuture<Void> storageBatteryFuture = CompletableFuture.runAsync(() -> {
            BaseExportDTO result = new BaseExportDTO();
            LampChartDTO l3 = new LampChartDTO();
            l3.setType(2);
            l3.setId(lampChartDTO.getId());
            l3.setEDate(lampChartDTO.getEDate());
            l3.setSDate(lampChartDTO.getSDate());
            result = getLampHistoryDatQueryAllData(result,l3,lampId,lampNo);
            map3.put(l3.getId(),result);
        });
        //4.查太阳能板
        CompletableFuture<Void> solarEnergyFuture = CompletableFuture.runAsync(() -> {
            BaseExportDTO result = new BaseExportDTO();
            LampChartDTO l4 = new LampChartDTO();
            l4.setType(3);
            l4.setId(lampChartDTO.getId());
            l4.setEDate(lampChartDTO.getEDate());
            l4.setSDate(lampChartDTO.getSDate());
            result = getLampHistoryDatQueryAllData(result,l4,lampId,lampNo);
            map4.put(l4.getId(),result);
        });
        //5.等待所有线程执行完毕
        CompletableFuture.allOf(lightControlFuture, chargeDischargeFuture, storageBatteryFuture, solarEnergyFuture).join();
        long end = System.currentTimeMillis();
        log.info("Lamp系统 ----> 线程:" + Thread.currentThread().getName() + " , " + lampNo + "的设备 , 数据查询,耗时 :" + (end - start) + "ms");
    }

可以看到以上参考使用了CompletableFuture 这个目前了解到如果使用了CompletableFuture 那么在后面使用如下代码

CompletableFuture.allOf(lightControlFuture, chargeDischargeFuture, storageBatteryFuture, solarEnergyFuture).join();

这个是表示所有任务都完成的情况下才会结束,但凡有一个线程没有结束,这里就会一直等。这个使用可以保证线程中如果需要批量处理数据但又想知道结果的方案上,CompletableFuture 还是非常好用的,具体参考:CompletableFuture

CountDownLatch

1、CountDownLatch介绍

CountDownLatch 是 Java 中的一个同步工具类,它允许一个或多个线程等待其他线程完成操作。CountDownLatch 的主要思想是,一个线程等待其他线程完成一组操作,它在倒计时计数器的基础上工作,计数器的初始值是一个正整数,每当一个线程完成一项操作,计数器的值减一。当计数器的值变为零时,等待的线程被释放,可以继续执行。

2、CountDownLatch源码解析

public class CountDownLatch {
    // 构造方法
    public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }
	// 用于阻塞当前线程,直到计数器的值变为零
    public void await() throws InterruptedException {
        // 在 Sync 类中实现,用于尝试获取共享许可,如果计数器为零,则直接返回,否则会进入等待队列等待。
        sync.acquireSharedInterruptibly(1);
    }
    // 用于等待一段时间,超时后返回
    public boolean await(long timeout, TimeUnit unit)
        throws InterruptedException {
        // 用于尝试在规定的时间内获取共享许可,如果在超时前获取到许可则返回 true,否则返回 false。
        return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
    }
    // 用于将计数器的值减一,表示有一个线程完成了一项操作
    public void countDown() {
        // 在 Sync 类中实现,用于释放共享许可
        sync.releaseShared(1);
    }
	// 用于获取当前计数器的值
    public long getCount() {
        // 返回当前的计数值
        return sync.getCount();
    }
}

Sync类

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

    Sync(int count) {
        setState(count);
    }
	// 获取当前计数值的方法 
    int getCount() {
        return getState();
    }
    // 尝试获取共享许可的方法 
    protected int tryAcquireShared(int acquires) {
        return (getState() == 0) ? 1 : -1;
    }
	// 尝试释放共享许可的方法
    protected boolean tryReleaseShared(int releases) {
        // Decrement count; signal when transition to zero
        for (;;) {
            int c = getState();
            if (c == 0)
                return false;
            int nextc = c-1;
            if (compareAndSetState(c, nextc))
                return nextc == 0;
        }
    }
}

3、CountDownLatch应用场景

  • 多线程任务协同: CountDownLatch 可以用于多个线程协同完成某项任务。一个主线程等待其他多个线程完成任务,每个子线程完成一部分工作后调用 countDown() 方法减少计数值,主线程通过 await() 方法等待计数值变为零。
  • 并行任务的性能测试: 可以用 CountDownLatch 来测量并行任务的性能,主线程可以等待多个并行任务同时开始执行。
  • 等待多个服务初始化完成: 在系统启动时,有时需要等待多个服务初始化完成后再继续执行。每个服务初始化完成后调用 countDown()。
  • 分布式系统中的协同: 在分布式系统中,CountDownLatch 可以用于等待多个节点的某个事件的发生,以协同分布式系统的操作。

4、示例

普通示例

public class CountDownLatchTest {

    public static void main(String[] args) {
        final CountDownLatch latch = new CountDownLatch(2);
        System.out.println("主线程开始执行…… ……");
        //第一个子线程执行
        ThreadUtil.execute(() -> {
                try {
                    Thread.sleep(3000);
                    System.out.println("子线程:"+Thread.currentThread().getName()+"执行");
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                latch.countDown();
            });
       
        //第二个子线程执行
        ThreadUtil.execute(() -> {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("子线程:"+Thread.currentThread().getName()+"执行");
                latch.countDown();
            });
        
        System.out.println("等待两个线程执行完毕…… ……");
        try {
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("两个子线程都执行完毕,继续执行主线程");
    }
}

结果集

主线程开始执行…… ……
等待两个线程执行完毕…… ……
子线程:pool-1-thread-1执行
子线程:pool-2-thread-1执行
两个子线程都执行完毕,继续执行主线程

模拟拼团活动

public class CountDownLatchDemo {

    public static void main(String[] args) {
        pt();
    }

    private static void pt() {
        // 模拟拼团活动
        final CountDownLatch countDownLatch = new CountDownLatch(10);
        List<String> ids = new ArrayList<>();

        // 目前有10个人进行拼团
        for (int i = 0; i < 30; i++) {
            new Thread(() -> {
                if (countDownLatch.getCount() > 0) {
                    synchronized (ids) {
                        if (countDownLatch.getCount() > 0) {
                            ids.add(Thread.currentThread().getName());
                            System.out.println(Thread.currentThread().getName() + "拼团成功!!!");
                            countDownLatch.countDown();
                        }
                    }
                }
                System.out.println(Thread.currentThread().getName() + "请求拼团!!!商品剩余" + countDownLatch.getCount());
            }, String.valueOf(i + 1)).start();
        }

        new Thread(() -> {
            try {
                countDownLatch.await();
                System.out.println("拼团结束============================================================");
                System.out.println("拼团人员id"+ids);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }, "拼团").start();
    }
}

结果集

1拼团成功!!!
1请求拼团!!!商品剩余9
4拼团成功!!!
4请求拼团!!!商品剩余8
8拼团成功!!!
8请求拼团!!!商品剩余7
6拼团成功!!!
14拼团成功!!!
6请求拼团!!!商品剩余6
5拼团成功!!!
5请求拼团!!!商品剩余4
14请求拼团!!!商品剩余5
7拼团成功!!!
7请求拼团!!!商品剩余3
3拼团成功!!!
3请求拼团!!!商品剩余2
2拼团成功!!!
22拼团成功!!!
2请求拼团!!!商品剩余1
19请求拼团!!!商品剩余0
24请求拼团!!!商品剩余0
13请求拼团!!!商品剩余0
25请求拼团!!!商品剩余0
9请求拼团!!!商品剩余0
20请求拼团!!!商品剩余0
28请求拼团!!!商品剩余0
22请求拼团!!!商品剩余0
30请求拼团!!!商品剩余0
21请求拼团!!!商品剩余0
27请求拼团!!!商品剩余0
17请求拼团!!!商品剩余0
11请求拼团!!!商品剩余0
10请求拼团!!!商品剩余0
12请求拼团!!!商品剩余0
15请求拼团!!!商品剩余0
23请求拼团!!!商品剩余0
16请求拼团!!!商品剩余0
18请求拼团!!!商品剩余0
拼团结束============================================================
26请求拼团!!!商品剩余0
29请求拼团!!!商品剩余0
拼团人员id[1, 4, 8, 6, 14, 5, 7, 3, 2, 22]
  • 12
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值