一、多线程并行和并发有什么区别?
并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔发生。如我和你同时吃了午饭,中午我吃了米饭和肉还有蔬菜。
并行是在不同实体上的多个事件,并发是在同一实体上的多个事件。如多台机器多实例部署,单台机器多实例部署。
在一台处理器上“同时”处理多个任务,在多台处理器上同时处理多个任务。如hadoop分布式集群 任务调度。
并发编程的目标是充分的利用CPU处理器的每一个核,以达到最高的处理性能
二、线程和进程的区别?
简而言之,进程是程序运行和资源分配的基本单位,一个程序至少有一个进程,一个进程至少有一个线程。进程在执行过程中拥有独立的内存单元,而多个线程共享内存资源,减少切换次数,从而效率更高。线程是进程的一个实体,是cpu调度和分派的基本单位,是比程序更小的能独立运行的基本单位。同一进程中的多个线程之间可以并发执行。
三、创建线程有哪几种方式?
- 继承Thread类创建线程类
创建Thread子类的实例,即创建了线程对象
调用线程对象的start()方法来启动该线程
- 通过Runnable接口创建线程类
定义runnable接口的实现类
调用线程对象的start()方法来启动该线程
- 通过Callable和Future创建线程
创建Callable实现类的实例
使用FutureTask对象作为Thread对象的target创建并启动新线程
调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
- 通过线程池创建线程
(1). newFixed
ThreadPool(int nThreads)
创建一个固定长度的线程池,每当提交一个任务就创建一个线程,直到达到线程池的最大数量,这时线程规模将不再变化,当线程发生未预期的错误而结束时,线程池会补充一个新的线程。
(2). newCachedThreadPool()
创建一个可缓存的线程池,如果线程池的规模超过了处理需求,将自动回收空闲线程,而当需求增加时,则可以自动添加新线程,线程池的规模不存在任何限制。
(3). newSingleThreadExecutor()
这是一个单线程的Executor,它创建单个工作线程来执行任务,如果这个线程异常结束,会创建一个新的来替代它;它的特点是能确保依照任务在队列中的顺序来串行执行。
(4). newScheduledThreadPool(int corePoolSize)
创建了一个固定长度的线程池,而且以延迟或定时的方式来执行任务,类似于Timer。
(5).ThreadPoolTaskExecutor和ThreadPoolExecutor
ThreadPoolExecutor 这个类是JDK中的线程池类,继承自Executor, Executor 是专门用来处理多线程相关的一个接口
ThreadPoolTaskExecutor是Spring推出的线程池工具
ThreadPoolTaskExecutor本质依然是ThreadPoolExecutor来实现基本的线程池工作,不同的是前者更关注自己实现的增强扩展部分,让线程池具有更多特性可供使用
ThreadPoolExecutor是一个不受Spring管理生命周期、参数装配的Java类,而有了ThreadPoolTaskExecutor的封装 后面就可以随时从Spring容器取出使用和管理
ThreadPoolTaskExecutor实现方式
通过配置类的方式配置线程池
通过配置类方式配置线程池 折叠源码
```java
@Configuration
public class ExecutorConfig {
private static final Logger logger = LoggerFactory.getLogger(ExecutorConfig.class);
@Bean("asyncServiceExecutor")
public ThreadPoolTaskExecutor asyncServiceExecutor() {
logger.info("start create threadPoolTaskExecutor");
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
//配置核心线程数
executor.setCorePoolSize(3);
//配置最大线程数
executor.setMaxPoolSize(10);
//配置队列大小
executor.setQueueCapacity(5000);
executor.setKeepAliveSeconds(60);
//配置线程池中的线程的名称前缀
executor.setThreadNamePrefix("AsyncTask-");
// rejection-policy:当pool已经达到max size的时候,如何处理新任务
// CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
//执行初始化
executor.initialize();
return executor;
}
}
通过自动注入的方式注入线程池进行使用
```java
@Resource(name = "asyncServiceExecutor")
private ThreadPoolTaskExecutor threadPoolTaskExecutor;
线程池执行流程
四、使用多线程进行大数据量内存统计产生OOM问题分享
问题背景:大数据向mysql表同步百万级数量,我们需要将大数据表里同步过来的数据分别向三张业务表做数据同步,最终将业务表的数据展示给前台进行相关业务操作。
数据量级
大数据表:最开始700w数据量
三张业务表分别需要同步:23w、80w、680w
完成上述三张业务表插入数据后,我还需要对其中一张23w的表里两个字段做统计,统计的内容是对另一张业务表的80w数据根据客编号进行订单主体数和付款人数量进行统计
使用多线程,多线程配置没考虑服务器jvm配置 直接开启了100个并发线程 同时核心线程数也设置了100个 导致jvm内存资源不足 产生OOM
在内存里进行大数据量的统计是否合适
多线程配置:
注:IO密集型(某大厂实践经验)
核心线程数 = CPU核数 / (1-阻塞系数)
或着
CPU密集型:核心线程数 = CPU核数 + 1
IO密集型:核心线程数 = CPU核数 * 2
//获取当前机器的核数
public static final int cpuNum = Runtime.getRuntime().availableProcessors();
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(cpuNum);//核心线程大小
taskExecutor.setMaxPoolSize(cpuNum * 2);//最大线程大小
taskExecutor.setQueueCapacity(500);//队列最大容量
//当提交的任务个数大于QueueCapacity,就需要设置该参数,但spring提供的都不太满足业务场景,可以自定义一个,也可以注意不要超过QueueCapacity即可
taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
taskExecutor.setWaitForTasksToCompleteOnShutdown(true);
taskExecutor.setAwaitTerminationSeconds(60);
taskExecutor.setThreadNamePrefix("BCarLogo-Thread-");
taskExecutor.initialize();
return taskExecutor;
}
内存里进行大数据量的统计 改为数据库进行统计 返回统计结果进行批量更新
内存统计优缺点:
优点:不需要频繁访问数据库和占用数据库连接资源
缺点:占用大量内存资源
数据库统计优缺点:
优点:不需要占用大量内存资源
缺点:需要频繁访问数据库、频繁IO、会占用数据库连接资源
五、线程池拓展-Hystrix资源隔离策略-线程池隔离
Hystrix的隔离策略有两种:分别是线程隔离和信号量隔离。
THREAD(线程隔离):使用该方式,HystrixCommand将会在单独的线程上执行,并发请求受线程池中线程数量的限制。
什么是线程池隔离
为每一个服务接口单独开辟一个线程池,保持与其他服务接口线程的隔离,提高该服务接口的独立性和高可用。
池隔离的优缺点
优点:
一个依赖调用可以给予一个线程池,这个依赖的异常不会影响其他的依赖。
使用线程可以完全隔离业务代码,请求线程可以快速返回。
可以完全模拟异步调用,方便异步编程。
缺点:
使用线程池的缺点主要是增加了计算的开销。每一个依赖调用都会涉及到队列,调度,上下文切换,而这些操作都有可能在不同的线程中执行。
线程池隔离流程