由于我这周大部分时间都在写原型,所以遇到的主要问题是对实际功能的理解不准确导致很多时间浪费在多次修改原型上,这也告诉我们,我们必须在进行之前明确实际需求。因为在之前的会议中已经多次提到线程,而我对线程也不是很了解,所以才有了下面几篇文章。为什么要使用多线程在我们开发系统的过程中,经常会处理一些比较耗时的任务(比如向数据库中插入大量数据),这时候就需要使用多线程了。springboot中是否封装了多线程方法,就是在spring中可以直接通过@Async实现多线程操作。如何通过配置线程池来控制线程运行中的各种参数。线程池ThreadPoolExecutor的执行规则如下,接下来我们尝试构造一个线程池:@Configuration@EnableAsyncpublicclassThreadPoolConfigimplementsAsyncConfigurer{/***核心线程池大小*/privatestaticfinalintCORE_POOL_SIZE=3;/***最大可以创建的线程数*/privatestaticfinalintMAX_POOL_SIZE=10;/***队列的最大长度*/privatestaticfinalintQUEUE_CAPACITY=10;/***线程池维护线程允许的空闲时间*/privatestaticfinalintKEEP_ALIVE_SECONDS=300;/***异步执行方法线程池**@return*/@Override@BeanpublicExecutorgetAsyncExecutor(){ThreadPoolTask??Executorexecutor=newThreadPoolTask??Executor();executor.setMaxPoolSize(MAX_POOL_SIZE);执行器.setCORE_Pool);executor.setQueueCapacity(QUEUE_CAPACITY);executor.setKeepAliveSeconds(KEEP_ALIVE_SECONDS);executor.setThreadNamePrefix("LiMingTest");//线程池对被拒绝任务(无可用线程)的处理策略executor.setRejectedExecutionHandler(newThreadPoolExecutor.CallerRunsPolicy());executor.initialize();回归执行人;}}ThreadPoolExecutor是JDK中线程池的实现。该类实现了线程池所需的各种方法。它提供了任务提交、线程管理、监控等方法。corePoolSize:线程池维护的最小核心线程数。默认情况下,核心线程创建后不会被回收(注意:设置allowCoreThreadTimeout=true后,空闲核心线程超过生存时间也会被回收)。大于核心线程数的线程在空闲时间超过keepAliveTime后会被回收。maximumPoolSize:最大线程数线程池允许创建的最大线程数。添加任务时,核心线程数已满,线程池未达到最大线程数,无空闲线程。当工作队列满时,创建一个新的线程,然后从工作队列的头部进行任务交接。它由一个新的线程处理,刚提交的任务放在工作队列的末尾。keepAliveTime:空闲线程生存时间当一个可以被回收的线程空闲时间超过keepAliveTime时,就会被回收。回收线程:设置allowCoreThreadTimeout=true核心线程。大于核心线程数的线程(非核心线程)。workQueue:工作队列的新任务提交后,如果核心线程数满了,会先加入到工作队列中,等任务调度的时候再从队列中取出任务。工作队列实现了BlockingQueue接口。handler:拒绝策略当线程池线程数满,工作队列达到限制时,使用拒绝策略处理新提交的任务。拒绝策略可以自定义,拒绝策略需要实现RejectedExecutionHandler接口。JDK有四种默认的拒绝策略:AbortPolicy:丢弃任务并抛出RejectedExecutionException。DiscardPolicy:丢弃任务,但不抛出异常。可能导致系统无法被发现的异常状态。DiscardOldestPolicy:丢弃队列头部的任务,重新提交被拒绝的任务。CallerRunsPolicy:任务由调用线程处理。当我们在非测试文件中直接使用newThread创建新线程时,编译器会发出警告:不要显式创建线程,请使用线程池。说明:使用线程池的好处是减少创建和销毁线程所花费的时间和系统资源的开销,解决资源不足的问题。如果不使用线程池,可能会导致系统创建大量相同类型的线程而导致内存耗尽或“过度切换”的问题publicclassTestServiceImplimplementsTestService{=LoggerFactory.getLogger(TestServiceImpl.class);@Overridepublicvoidtask(inti){logger.info("task:"+i);}}@AutowiredTestService测试服务;@Testpublicvoidtest(){for(inti=0;i<50;i++){testService.任务(i);我们可以看到一切都执行得很好;然后我对线程进行了一些测试:classTestServiceImplTest{@Testpublicvoidtest(){Threadadd=newAddThread();线程dec=newDecThread();添加.开始();十二月开始();添加.join();十二月加入();System.out.println(Counter.count);}staticclassCounter{publicstaticintcount=0;AddThreadextendsThread{publicvoidrun(){for(inti=0;i<10000;i++){Counter.count+=1;}}}classDecThreadextendsThread{publicvoidrun(){for(inti=0;i<10000;i++){Counter.count-=1;}}}一1个自增线程和1个自减线程对0进行相同次数的操作,结果应该还是0,但是每次执行结果都不一样。查找后发现,读写变量时,结果一定是正确的。必须保证是原子操作。原子操作是一个或一系列不能被中断的操作。例如,对于语句:n+=1;看似只有一行语句,却包含了3条指令:readn,n+1,storen;比如下面两个进程同时对1加10,说明在多线程模型下,为保证逻辑正确,在读写共享变量时,必须原子地执行一组指令:即当一个线程执行,其他线程必须等待。staticclassCounter{publicstaticfinalObjectlock=newObject();//每个线程都需要获取锁才能执行publicstaticintcount=0;}classAddThreadextendsThread{publicvoidrun(){for(inti=0;i<10000;i++){synchronized(Counter.lock){staticclassCounter{publicstaticfinalObjectlock=newObject();公共静态整数计数=0;}classDecThreadextendsThread{publicvoidrun(){for(inti=0;i<10000;i++){synchronized(Counter.lock){Counter.count-=1;}}}值得注意的是,每个类可以设置多个锁,如果获取的线程不是同一个锁,则无法执行上述功能;springBoot中也定义了很多类型的锁,这里就不一一解释了。我们现在能做的就是关注项目中的异步操作,观察操作使用的线程。以便在以后的项目中遇到此类问题时,能够及时发现并解决问题。
