携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第31天,点击查看活动详情
线程池的底层工作原理
线程池内部是通过队列+线程实现的,当我们利用线程执行任务时:
1、如果此时线程池中的线程数量小于corePoolSize,既是线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务
2、如果此时线程池中的线程数量等于corePoolSize,但是缓冲队列workQueue未满,那么任务被放入缓冲队列
3、如果此时线程池中的线程数量大于等于corePoolSize,缓冲队列workQueue满,并且线程池中额数量小于maximumPoolSize,建新的线程来处理被添加的任务
4、如果此时线程池中的线程数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量等于maximumPoolSize,那么通过handler所指定的策略来处理任务
5、当线程池中的线程数量大于corePoolSIze,如果某线程空闲时间超过keepAliveTime,线程将被终止,这样,线程池可以动态的调整池中的线程数
为什么用线程池?说说线程池的核心配置参数都是干什么的
1、降低资源消耗;提高线程利用率,降低创建和销毁线程的消耗
2、提高响应速度;任务来了,直接有线程可用可执行,而不是先创建线程,再执行
3、提高线程的可管理行;线程是稀缺资源,使用线程池可以统一分配调优监控
·corePoolSize 代表的是核心线程数,也就是正常情况下创建工作的线程数,这些线程创建后并不会消除,而是一种常驻线程
·maxinumPoolSize 代表的是最大线程数,他与核心线程数相对应,表示最大允许被创建的线程数,比如当前任务较多,讲核心线程数都用完了,还无法满足需求时,此时就会创建新的线程,但是线程池内线程总数不会超过最大线程数
·keepAliveTime、unit 表示超出核心线程之外的线程的空闲存活时间,也就是核心线程不会消除,但是超过核心线程数的部分线程如果空闲一定的时间则会被消除
·workQueue 用来存放待执行的任务,假设我们现在核心线程都已被使用,还有任务进来则全部放入队列,直到整个队列被放满但任务还在持续进入则会开始创建新的线程
·ThreadFactory 实际上是一个线程工程,用来生产线程执行任务。我们可以选择使用默认的创建工厂,产生的线程都在同一个组内,拥有相同的优先级,且都不是守护线程。当然我们也可以选择自定义线程工厂,一般我们会根据业务来制定不同的线程工厂
·Handler 任务拒绝策略,有两种情况,第一种是当我们调用shutdown等方法关闭线程池后,这时候既是线程内部还有没执行完的任务正在执行,但是由于线程池已经关闭,我们在继续向线程池提交任务就会遭到拒绝。另一种情况就是当达到最大线程数,线程池已经没有能力继续处理新提交的任务时,也是拒绝
如果线程池的队列满了之后,会发生什么事情
1、如果使用的是无界队列,那么可以继续提交任务时没关系
2、如果使用的是有界队列,提交任务时,如果队列满了,如果核心线程数没有达到上线,那么增加核心线程数,如果线程数已经达到了最大值,则使用拒绝策略
sleep、wait、join、yield的作用
1、锁池
所有需要竞争同步锁的线程都会放在锁池当中,比如当前对象的锁已经被其中一个线程得到,则其他线程需要在这个锁池进行等待,当前的线程释放同步锁后锁池中的线程去竞争同步锁,当某个线程得到后会进入就绪队列进行等待cpu资源分配。
2、等待池
当我们调用wait()方法后,线程会放到等待池中,等待池的线程是不会去竞争同步锁。只有调用了notify或notifyAll方法后等待池的线程才会开始去竞争锁,notify是随机从等待池选出一个线程放到锁池,而notifyAll是将等待池的所有线程放到锁池当中
1、sleep是Thread类的静态本地方法,wait则是Object类的本地方法
2、sleep方法不会释放lock,但是wait会释放,而且会加入到等待队列中
3、sleep方法不依赖与同步器synchronize,但是wait需要依赖synchronize关键字
4、sleep不需要被唤醒,但是wait需要
5、sleep一般用于当前线程休眠,或者轮循暂停操作,wait则多用于多线程之间的通信
6、sleep会让出CPU执行时间且强制上下文切换,而wait则不一定,wait后可能还是有机会重新竞争到锁继续执行的
yield()执行后线程直接进入就绪状态,马上释放了cpu的执行权,但是依旧保留了cpu的执行资格,所以有可能cpu下次进入线程调度还会让这个线程获取到执行权继续执行
join()执行后线程进入阻塞状态
Synchronize的自旋锁、偏向锁、轻量级锁、重量级锁
1、偏向锁:在锁对象的对象头中记录一下当前获取到该锁的线程id,该线程下次如果又来获取该锁就可以直接获取到了
2、轻量级锁:由偏向锁升级而来,当一个线程获取到锁后,此时这把锁是偏向锁,此时如果又第二个线程来竞争,偏向锁会升级为轻量级锁,之所以叫轻量级锁,是为了和重量级锁区分开来,轻量级锁底层通过自旋来实现的,并不会阻塞线程
3、如果自旋次数过多仍然没有获取到锁,则会升级为重量级锁,重量级锁会导致线程阻塞
4、自旋锁:自旋锁就是线程获取锁的过程中,不回去阻塞线程,也就无所谓唤醒线程,阻塞和唤醒这两个步骤都是需要操作系统去进行的,比较小号时间,自旋锁是线程通过cas获取预期的一个标记,如果没有获取到,则继续循环获取,如果获取到了则表示获取了锁,这个过程线程一直在运行中,相对而言没有使用太多的操作系统资源,比较轻量