volatile关键字,他是如何保证可见性,有序性
- 对于加了voilatile关键字的成员成员变量,在对这个变量进行修改时,会直接将CPU高级缓存中的数据写回到主内存,对这个变量的读取也会直接从主内存中读取,从而保证了可见性
- 在对volatile修饰的成员变量进行读写时,会插入内存屏障,而内存屏障可以达到禁止重排的效果,从而保证有序性
蚂蚁一面:简述线程池原理,FixedTheadPool用的阻塞队列是什么?
线程池内部是通过队列和线程实现的,当我们利用线程池执行任务时:
- 如果此线程池中的数量小于corePoolSize,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务
- 如果此时线程池中的胡亮小于corePoolSize,但是队列缓冲workQueue未满,那么任务被放入缓冲队列
- 如果此时线程池中的数量大于等于corePoolSize,缓冲队列workQueue满,并且线程池中的数量小于maximumPoolSize,新建的线程来处理被添加的任务
- 如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量等于maximumPoolSize,那么通过handler所指定的策略来处理此任务
- 当线程池中的线程数量大于CorePoolSize时,如果某个线程空闲时间超过KeepAliveTime,线程将被终止,这样,线程池可以动态的调整线程池中的数量
FixedThreadPool代表定长线程池,底层用的LinkedBlockingQueue,表示无界的队列
如何理解Volatile关键字
在并发领域中,存在三大特性:原子性、可见性、有序性。volatile关键字修饰对象的属性,在并发环境下,可以保证这个属性的可见性,对于加了volatile关键字的属性,在对这个属性进行修改时,会直接将CPU高级缓存中的数据写回到主内存,对这个变量的读取也会直接从主内存中读取,从而保证了可见性。底层是通过操作系统的内存屏障来实现的,由于使用了内存屏障,所以会禁止指令重排,随意同时也就保证了有序性,在很多并发场景下,如果用好volatile关键字可以很好的提高执行效率。
对守护线程的理解
什么是守护线程
守护线程:为所有非守护线程提供服务的线程;任何一个守护线程都是整个JVM中所有非守护线程的保姆。
守护线程类似于整个进程的一个默默无闻的小喽啰,它的生死无关重要,它却依赖整个进程而运行,哪天其他线程结束了,没有要执行的了,程序就结束了,理都没理守护线程,就把他中断了。
守护线程的作用是什么
举例:GC垃圾回收线程,就是一个经典的守护线程,当我们的程序中不再有任何运行的Thread,程序就不会再产生垃圾, 垃圾回收器也就无事可做,所以当垃圾回收线城市JVM上仅剩的线程时,垃圾回收线程会自动离开,它始终再低级别的状态中运行,用于实时监控和管理系统中的可回收资源。
应用场景:
- 来为其他线程提供服务支持的情况
- 或者在任何情况下,程序结束时,这个线程必须正常且立刻关闭,就可以作为守护线程来理解,反之,如果一个正在执行某个操作的线程必须要正确地关闭掉否则就会出现不好的后果,那么这个线程就不能是守护线程 ,而是用户线程。通常都是这些关键的事务,比方说,数据库录入或者更新,这些操作都是不能被中断的
thread.setDaemon(true)必须在thread.start()之前设置,否则会抛出一个IllegalThreadStateException异常,你不能把正在运行的常规线程设置为守护线程。
在Daemon线程中产生的新线程也是Daemon的
守护线程不能用于去访问固有资源,比如读写操作或者计算操作,因为他会在任何时候甚至一个操作的中间发生中断。
java 自带的多线程框架,比如ExecutorService ,会将守护线程转换为用户线程,所以如果要使用后台线程就不能用Java的线程池。
为什么用线程池,解释下线程池参数
为什么使用线程池
- 降低资源的消耗;提高线程利用率,降低创建和销毁线程的消耗
- 提高响应速度,任务来了,直接有线程可用可执行,而不是先创建线程,再执行
- 提高线程的可管理性,线程是稀缺资源,使用线程池可以统一分配调优监控
线程池中的参数
- corePoolSize表示核心线程数,也就是正常情况下创建工作的线程数,这些线程创建后并不会消除,而是一种常驻线程
- maxinumPoolSize代表的是最大线程数,它与核心线程数相对应,表示最大允许被创建的线程数,比如当前任务比较多,将核心线程数都用完了,还无法满足需求时,此时就会创建新的线程,但是线程池内线程书不狐疑超过最大线程
- KeepAliveTime,unit表示超出核心线程数之外的线程的空闲存活时间,也就是核心线程不会消除,但是超出核心线程数的部分线程如果空闲一定的时间则会被消除,我们可以通过setKeepAliveTime来设置空闲时间
- workQueue用来存放带执行的任务,假设我们现在核心线程都已被使用,还有任务进来则群不放入队列,直到整个队列放满但任务还持续进入则会开始创建新的线程
- ThreadFactory实际上时一个线程工厂,用来生产线程执行任务,我们可以选择使用默认的创建工厂,产生的线程都再同一个组内,拥有相同的优先级,且都不是守护线程。当然我们也可以选择自定义线程工厂,一般我们会根据业务来定制不同的线程工厂
- Handler任务拒绝策略,有两种情况,第一种是我么不能调用shutdown等方法关闭线程池后,这时候即使线程池内部还有没有执行完的任务正在执行,但是由于线程池已经关闭,我们再继续像线程池提交任务就会遭到拒绝,另一种情况就是当达到最大线程数,线程池已经没有能力继续处理新提交的任务时,这也就是
线程池处理流程

线程池的底层工作原理
线程池内部是同归队列+线程实现的,当我们利用线程池执行任务时:
- 如果此时线程池中的线程数量小于corePoolSize,即线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务
- 如果此时线程池中的线程数量等于corePoolSize,但是缓冲队列workQueue未满,那么任务被放入缓冲队列
- 如果此时线程池中的线程数量大于等于corePoolSize,缓冲队列workQueue已满,并且线程池中的数量小于maxinumPoolSize,建新的线程来处理被添加的任务
- 如果此时线程池中的线程数量大于CorePoolSize,缓冲队列workQueue已满,并且线程池中的数量等于maximumPoolSize,那么通过Handler所 指定的策略来处理此任务
- 当线程池中的数量大于corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止,这样,线程池可以动态的调整池中的线程数。
线程池线程复用的原理
线程池将线程和任务进行解耦,线程是线程,任务是任务,摆脱了之前通过Thread创建线程时的一个线程必须对应一个任务的限制。
在线程池中,同一个线程可以从阻塞队列中不断获取新的任务来执行,其核心原理在于线程池对Thread进行了封装,并不是每次执行任务都会调用Thread.start()来创建新的线程,而是让每个线程去执行一个"循环任务",在这个"循环任务"中不停检查是否有任务需要被执行,如果有则直接执行,也就是调用任务中的run方法,将run方法当成一个普通的方法执行,通过这种方式只使用固定的线程九江所有任务的run方法串联起来。
线程池中阻塞队列的作用,为什么是先添加队列而不是先创建最大线程
-
一般的队列只能保证作为一个有线长度的缓冲器,如果超过了缓冲长度,就无法保留当前的任务了,阻塞队列通过阻塞可以保留朱 当前想要继续入队 的任务
阻塞队列可以保证任务队列中没有任务时阻塞获取任务的线程,使得线程进入wait状态,是否CPU资源
阻塞队列自带阻塞和唤醒功能,不要额外处理,无任务执行时,线程池利用阻塞队列的take方法挂起,从而维持核心线程的存货,不至于一直占用cpu资源
-
在创建新线程的时候,是要获取全局锁的,这个时候其他的就得阻塞,影响了整体效率
就好比一个企业里有10个(core)正式工的名额 ,最多招10个正式工,要是任务超过正式工人数(task>core)的情况下,工厂领导(线程池)不是首先扩招工人,还是这10个人,但是任务可以稍微积压一下,即先放到队列去(代价低)。10个正式工慢慢干,迟早护轨干完的,要是任务还在继续增加超过正式工的加班忍耐极限了(队列满了),就的招外包帮忙了(注意是临时工)要是正式工加上外包还是不能完成任务,那信赖的任务就会被领导拒绝了(线程池的拒绝策略)。
线程的生命周期及状态
线程通常有5中状态,创建、就绪、运行、阻塞和死亡状态
阻塞的情况分为三种:
- 等待阻塞:运行的线程执行wait 方法,该线程会释放占用的所有资源,JVM会把该线程放入"等待池"中,进入这个状态过后,是不能自动唤醒的,必须依靠其他线程调用notify或notifyAll方法才能被唤醒,wait是object类的方法
- 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入"锁池"中
- 其他阻塞:运行的线程执行sleep或join方法,或者发出来I/O请求时,JVM会把该线程置为阻塞状态,当 sleep状态超时,join等待线程终止或者超时时,或者I/O处理完毕时,线程重新转入就绪状态,sleep是Thread类的方法
线程的状态:
- 新建状态(New):新创建了一个线程对象
- 就绪状态(Runnable):线程对象创建后,其他线程调用了该线程的start方法,该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权
- 运行状态(Running):就绪状态的线程获取了CPU,执行程序代码
- 阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行,直到线程进入就绪状态,才有机会转到运行状态。