26-30
并发、并行、串行
串行在时间上不可能发生重叠,前一个任务没有完成,下一个人就只能等待
并行在时间上重叠,两个任务在同一时刻互不干扰的情况下同时执行
并发允许两个任务在彼此不干扰的情况下,同一个时间点,只有一个任务进行,两个任务交替执行
并发的三大特性
原子性、可见性、有序性(枷锁都可以解决)
原子性(枷锁)
cpu在执行某个方法是要么不做要么就全部做完,不能方法执行一半的时候发生切换,就不做了。是一个一次性操作也叫做原子性操作。
i++
1: 将i从主存读到工作内存的副本之中
2:进行+1操作
3:将结果写入工作内存
4:将工作内存的值刷到主存之中(有操作系统决定,不确定什么时候进行)
如果1234不是原子性一起的话那么就会不安全,所以要保证原子性操作
可见性(枷锁,volatile):当多个线程访问同一个变量时,一个线程对数据进行访问时,其他线程可以知道
mesi和总线锁定(总线lock),基于这两个可以保证t1中改变了i,那么在t2中i的值就会失效
有序性(枷锁,volatile,final):虚拟机中进行代码编译时,对于那些改变了顺序之后并不影响结果的代码,虚拟机并不一定会按照我们的顺序执行,可能做指令重排,单线程没有问题但是多线程会有影响。
在多线程中:如果第二个方法及其依赖上一个方法的执行顺序时就会出现问题。
解决方法加锁,volatile(new一个对象时)
为什么要使用线程池
线程池的三大优势
1.降低资源的线程池:提高线程的利用率,降低创建和销毁线程的消耗
2.提高响应速度,任务来了可以直接有线程执行,而不是先创建线程在执行
3.提高了线程的客观理性,线程时稀缺资源,使用线程池可以统一分配调优监控
corepoolsize核心线程数,代表正常情况下创建工作的线程数,这些线程创建之后并不会被消除,是一种常驻线程
maxsize最大线程数,表示最多允许被创建的线程数有多少,核心线程数创建完了还需要继续创建线程时,也会创建新的线程,但是最大的线程数量不会超过最大线程数。
keepalivetime、unit表示超出核心线程数之外的线程的空闲存活时间,也就是说核心线程不会销毁,但是超出的线程空闲一定时间就会被销毁,可以通过setalivetime来设置最大空闲时间。
workqueue用来存放待执行的任务,假设我们现在的核心线程都已被使用,还有其他任务进来则全部放进队列,直到整个队列放满但任务还在持续进入则会开始创建新的线程。
handler任务拒绝策来,有两种策略,第一种当我们调用shutdown等方法关闭线程池之后,即使线程池里面还有没有执行完的任务在执行时候,但由于线程池已经关闭了,所以我们在向线程池提交任务时,就会遭到拒绝。另一种情况就是达到了最大线程数,没有能力继续处理新的线程了也会拒绝。
threadfactory线程工厂,用来生产线程执行任务,我们可以选择默认的创建工厂,创建的线程都会在同一个组内,相同的优先级并且都不是守护线程。当然我们自己也可以自定义线程工厂,根据不同的需要指定不同的线程工厂。
简述线程池的处理流程
线程池执行任务,判断核心线程数是不是满了,如果满了就创建核心线程执行
如果满了就判断任务队列满了吗,没满就放进等待队列中
如果满了就判断最大线程数满了吗,没有满就创建临时线程执行(临时线程有存活时间)
如果最大线程满了就拒绝执行
线程池中阻塞队列的作用?为什么时先添加队列而不是先创建最大线程?
一个队列只能保证作为一个有线长度的缓冲区,如果超出了缓冲长度,就无法保留当前任务,阻塞队列会通过阻塞保留想要继续入队的任务。
阻塞队列可以保证任务队列中没有任务时阻塞获取任务的线程,使线程进入wait状态,释放cpu资源。
阻塞队列自带阻塞和唤醒功能,不需要额外的处理,无任务执行时,线程池利用阻塞队列的take方法挂起,从而维持核心线程的存活,不至于一直占用cpu资源
在创建新线程时需要全局锁,这个时候其他的线程就得阻塞,影响整体效率,也会带来线程频繁创建和销毁,和使用线程池的初衷不符合。
就好比一个工厂有10个正式员工的名额,当任务超过了正式工的名额时,领导肯定首先考虑让10个正式工去加班而不是考虑区找新人来做,10个人慢慢总会昨晚的,如果任务一直增加超过了最大处理的极限,那么才会找新人,如果外包满了,任务还是处理不了,那么领导就选择不接这个项目。