并发编程:springboot并发编程的相关问题及解决方案(持续更新...)

一、常见并发的实现方式

(一)LinkedBlockingDeque(阻塞队列) + ThreadPoolExecutor(线程池)实现并发

容易出现的问题:

  • 1.LinkedBlockingDeque内方法take会导致线程阻塞
  • 2.使用poll方法,size为0时会抛异常
@Slf4j
public class ThreadsTest {
    private static final ExecutorService executorService = Executors.newFixedThreadPool(11);
    private static final int total = 500;
    private static final AtomicInteger pageNum = new AtomicInteger(0);
    private static final int size = 50;
    private static final AtomicInteger remain = new AtomicInteger(50);

    public static void main(String[] args) throws InterruptedException {
        //模拟待处理的数据
        LinkedBlockingDeque<Map<String, Object>> cacheMaps = new LinkedBlockingDeque<>(50);
        while (pageNum.incrementAndGet() * size <= total) {
            remain.set(50);
            //信号量工具类
            CountDownLatch countDownLatch = new CountDownLatch(10);
            //生产端,单个线程
            executorService.execute(() -> produce(cacheMaps));
            //多消费
            for (int i = 0; i < 10; i++) {
                executorService.execute(() -> consumer(cacheMaps, countDownLatch));
            }
            countDownLatch.await();
        }
    }

    private static void consumer(LinkedBlockingDeque<Map<String, Object>> cacheMaps, CountDownLatch countDownLatch) {
        while (remain.decrementAndGet() >= 0) {
            try {
                System.out.println("消费线程:" + cacheMaps.take()+"\t"+Thread.currentThread().getName());
            } catch (InterruptedException e) {
                log.info("消费线程被打断!!!");
            }
        }
        countDownLatch.countDown();
        System.out.println("消费线程:" + new AbstractMap.SimpleEntry<>("countDownLatch", countDownLatch.getCount())+ "\t"+Thread.currentThread().getName());
    }

    private static void produce(LinkedBlockingDeque<Map<String, Object>> cacheMaps) {
        for (int i = 0; i < size; i++) {
            HashMap<String, Object> stringObjectHashMap = new HashMap<>();
            stringObjectHashMap.put("key:" + ((pageNum.get()-1) * size+ i) , i);
            try {
                cacheMaps.put(stringObjectHashMap);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

(二)futureTasks(可回调的线程注册池)+ThreadPool实现并发(推荐)

import com.baomidou.mybatisplus.core.toolkit.IdWorker;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;

/**
 * @author yilv
 * @version 1.0
 * @description: 并发同步信息
 * @date 2023/1/8 17:52
 */
public class DataSyncTask<T> {
    /**
     * 现场注册池
     */
    private final Map<Long, FutureTask<Void>> futureTasks = new HashMap<>();
    /**
     * 线程池
     */
    private final ExecutorService executorService = Executors.newFixedThreadPool(10);

    public static void main(String[] args) {

    }
    public void sync(List<T> data){

        data.forEach(f -> {
            FutureTask<Void> voidFutureTask = new FutureTask<>(() -> {
                // todo 同步数据库目录信息
                return null;
            });
            //放入注册池、用于回调
            futureTasks.put(IdWorker.getId(), voidFutureTask);
            executorService.submit(voidFutureTask);
        });
        futureTasks.forEach((id, futureTask) -> {
            try {
                //未启动或者已启动的状态时,调用FutureTask对象的get方法会将导致调用线程阻塞。当FutureTask处于已完成的状态时,调用FutureTask的get方法会立即放回调用结果或者抛出异常
                futureTask.get();
            } catch (InterruptedException | ExecutionException e) {
                //todo 处理异常信息
            }
        });
    }
}

二、相关问题处理

(一)线程获取容器中的bean??

解决方案:创建spring工具类,手动获取。或者使用hutool里面的工具类

/**
 * @author yilv
 * @version 1.0
 * @description: TODO 通用bean获取类
 * @date 2021/7/21 16:17
 */
@Component
public class ApplicationContextProvider implements ApplicationContextAware {
    /**
     * 上下文对象实例
     */
    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
    /**
     * 获取applicationContext
     *
     * @return
     */
    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }
    /**
     * 通过name获取 Bean.
     *
     * @param name
     * @return
     */
    public static <T> T getBean(String name) {
        return (T)getApplicationContext().getBean(name);
    }
    /**
     * 通过class获取Bean.
     *
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> T getBean(Class<T> clazz) {
        return getApplicationContext().getBean(clazz);
    }
    /**
     * 通过name,以及Clazz返回指定的Bean
     *
     * @param name
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> T getBean(String name, Class<T> clazz) {
        return getApplicationContext().getBean(name, clazz);
    }
}

(二)springboot单实例多线程,作用域问题??

springboot单实例多线程下获取同一个bean,获取的对象引用相同,业务逻辑互相产生覆盖。最常见的场景是多次启动同一个定时任务

1. 配置ThreadPoolTaskScheduler线程池

/**
 * @author yilv
 * @version 1.0
 * @description: 启用定时任务&配置线程池
 * @date 2023/1/13 18:43
 */
@Configuration
@EnableScheduling
public class TaskConfig {
    @Bean
    public ThreadPoolTaskScheduler threadPoolTaskScheduler() {
        ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
        threadPoolTaskScheduler.setPoolSize(20);
        threadPoolTaskScheduler.setThreadNamePrefix("taskExecutor-");
        threadPoolTaskScheduler.setAwaitTerminationSeconds(60);
        return threadPoolTaskScheduler;
    }
}

2. 编写任务用例

/**
 * @author yilv
 * @version 1.0
 * @description: 测试单例通用成员变量
 * @date 2023/1/13 18:03
 */
@Component
public class DemoTask implements Runnable {

    public Integer count=20;
    @Override
    public void run() {
        //todo
        while (count>0){
            try {
                Thread.sleep(2000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(DateUtil.now()+":"+Thread.currentThread().getName()+"\t"+count--);
        }

    }
}

3. 编写测试用例

@Slf4j
@EnableScheduling
@SpringBootTest
class DemoTaskTest {
    @Resource
    private ThreadPoolTaskScheduler threadPoolTaskScheduler;

    @Test
    public void test01(){
        HashMap<String, ScheduledFuture<?>> scheduledFutureMap = new HashMap<>();
        String everyDay = "* * * * * ?";
        for (int i = 0; i < 10; i++) {
            String idStr = IdWorker.getIdStr();
            ScheduledFuture<?> schedule = threadPoolTaskScheduler.schedule(SpringUtil.getBean(DemoTask.class), m -> new CronTrigger(everyDay).nextExecutionTime(m));
            scheduledFutureMap.put(idStr,schedule);
        }

        //任务是否结束
        scheduledFutureMap.forEach((k,v)->{
            try {
                Object o = v.get();
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
            log.debug("任务结束:{}",k);
        });
    }
}

4. 测试结果

2023-01-13 18:49:58:taskExecutor-9	20
2023-01-13 18:49:58:taskExecutor-10	16
2023-01-13 18:49:58:taskExecutor-3	15
2023-01-13 18:49:58:taskExecutor-1	14
2023-01-13 18:49:58:taskExecutor-8	17
2023-01-13 18:49:58:taskExecutor-7	13
2023-01-13 18:49:58:taskExecutor-5	12
2023-01-13 18:49:58:taskExecutor-6	18
2023-01-13 18:49:58:taskExecutor-4	19
2023-01-13 18:49:58:taskExecutor-2	11
2023-01-13 18:50:00:taskExecutor-9	10
2023-01-13 18:50:00:taskExecutor-1	8
2023-01-13 18:50:00:taskExecutor-3	9
2023-01-13 18:50:00:taskExecutor-10	10
2023-01-13 18:50:00:taskExecutor-8	7
2023-01-13 18:50:00:taskExecutor-7	6
2023-01-13 18:50:00:taskExecutor-5	5
2023-01-13 18:50:00:taskExecutor-6	4
2023-01-13 18:50:00:taskExecutor-4	3
2023-01-13 18:50:00:taskExecutor-2	2
2023-01-13 18:50:02:taskExecutor-6	1
2023-01-13 18:50:02:taskExecutor-2	1
2023-01-13 18:50:02:taskExecutor-4	1
2023-01-13 18:50:02:taskExecutor-3	-5
2023-01-13 18:50:02:taskExecutor-10	-4
2023-01-13 18:50:02:taskExecutor-8	-4
2023-01-13 18:50:02:taskExecutor-7	-3
2023-01-13 18:50:02:taskExecutor-9	-2
2023-01-13 18:50:02:taskExecutor-5	-1
2023-01-13 18:50:02:taskExecutor-1	0
2023-01-13 18:50:02.133 DEBUG 21728 --- [           main] com.yilv.web.task.DemoTaskTest           : 任务结束:1613850825032507395
2023-01-13 18:50:03.007 DEBUG 21728 --- [           main] com.yilv.web.task.DemoTaskTest           : 任务结束:1613850825032507394
2023-01-13 18:50:04.008 DEBUG 21728 --- [           main] com.yilv.web.task.DemoTaskTest           : 任务结束:1613850825032507393
2023-01-13 18:50:05.008 DEBUG 21728 --- [           main] com.yilv.web.task.DemoTaskTest           : 任务结束:1613850825032507399
2023-01-13 18:50:06.007 DEBUG 21728 --- [           main] com.yilv.web.task.DemoTaskTest           : 任务结束:1613850825032507398
2023-01-13 18:50:07.007 DEBUG 21728 --- [           main] com.yilv.web.task.DemoTaskTest           : 任务结束:1613850825032507397
2023-01-13 18:50:08.007 DEBUG 21728 --- [           main] com.yilv.web.task.DemoTaskTest           : 任务结束:1613850824965398530
2023-01-13 18:50:09.007 DEBUG 21728 --- [           main] com.yilv.web.task.DemoTaskTest           : 任务结束:1613850825032507396
2023-01-13 18:50:10.007 DEBUG 21728 --- [           main] com.yilv.web.task.DemoTaskTest           : 任务结束:1613850825032507401
2023-01-13 18:50:11.007 DEBUG 21728 --- [           main] com.yilv.web.task.DemoTaskTest           : 任务结束:1613850825032507400

5. 解决方案

指定bean的作用域为@Scope(“prototype”),采用多例模式拒绝使用一个bean

处理结果

2023-01-13 18:57:54:taskExecutor-2	20
2023-01-13 18:57:54:taskExecutor-9	20
2023-01-13 18:57:54:taskExecutor-7	20
2023-01-13 18:57:54:taskExecutor-10	20
2023-01-13 18:57:54:taskExecutor-8	20
2023-01-13 18:57:54:taskExecutor-6	20
2023-01-13 18:57:54:taskExecutor-5	20
2023-01-13 18:57:54:taskExecutor-3	20
2023-01-13 18:57:54:taskExecutor-1	20
2023-01-13 18:57:54:taskExecutor-4	20
2023-01-13 18:57:56:taskExecutor-1	19
2023-01-13 18:57:56:taskExecutor-4	19
2023-01-13 18:57:56:taskExecutor-3	19
2023-01-13 18:57:56:taskExecutor-9	19
2023-01-13 18:57:56:taskExecutor-2	19
......................................
2023-01-13 18:58:32:taskExecutor-8	1
2023-01-13 18:58:32:taskExecutor-6	1
2023-01-13 18:58:32:taskExecutor-7	1
2023-01-13 18:58:32:taskExecutor-5	1
2023-01-13 18:58:32:taskExecutor-10	1
2023-01-13 18:58:32:taskExecutor-1	1
2023-01-13 18:58:32:taskExecutor-9	1
2023-01-13 18:58:32.257 DEBUG 28412 --- [           main] com.yilv.web.task.DemoTaskTest           : 任务结束:1613852823530352641
2023-01-13 18:58:33.008 DEBUG 28412 --- [           main] com.yilv.web.task.DemoTaskTest           : 任务结束:1613852823530352642
2023-01-13 18:58:34.007 DEBUG 28412 --- [           main] com.yilv.web.task.DemoTaskTest           : 任务结束:1613852823530352650
2023-01-13 18:58:35.007 DEBUG 28412 --- [           main] com.yilv.web.task.DemoTaskTest           : 任务结束:1613852823530352645
2023-01-13 18:58:36.008 DEBUG 28412 --- [           main] com.yilv.web.task.DemoTaskTest           : 任务结束:1613852823530352646
2023-01-13 18:58:37.006 DEBUG 28412 --- [           main] com.yilv.web.task.DemoTaskTest           : 任务结束:1613852823530352643
2023-01-13 18:58:38.007 DEBUG 28412 --- [           main] com.yilv.web.task.DemoTaskTest           : 任务结束:1613852823530352644
2023-01-13 18:58:39.007 DEBUG 28412 --- [           main] com.yilv.web.task.DemoTaskTest           : 任务结束:1613852823530352649
2023-01-13 18:58:40.008 DEBUG 28412 --- [           main] com.yilv.web.task.DemoTaskTest           : 任务结束:1613852823530352647
2023-01-13 18:58:41.008 DEBUG 28412 --- [           main] com.yilv.web.task.DemoTaskTest           : 任务结束:1613852823530352648

(三)并发中异常导致栈溢出

异常描述:
java hotspot 64-bit server vm warning:info:os::commit_memory(0xxxxxxxxxxxx,19xxxxx,0) failed;error=‘not enough space’
failed to start thread - pthread_create failed for attributes: stacksize:2048k,guardize:ok,detached

解决方法

  • 排查线程中流操作是否有关闭,是否e.printStackTrace();导致栈溢出
  • 排查是否存在非池内线程多次创建,并且存在无法被回收的风险
  • 排查各种客户端是否多次创建
  • 推荐使用arthas 进行线程排查

(四)java.util.concurrent.RejectedExecutionException

Lambda$534/0x0000000800546840@cbd40c1 rejected from java.util.concurrent.ThreadPoolExecutor@4fa86cb8[Terminated, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 11]

设置的线程池队列满了,可以扩大线程池增加溢出策略来解决

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值