序号 | 链接地址 |
---|---|
1 | java面试题:Redis常见面试题(实际面试有被问到) |
2 | java面试题:Spring核心面试题(必须理解背诵) |
3 | java面试题:集合常见面试题 |
4 | java面试题:MQ、RabbitMQ面试题(面试必问,精华版) |
5 | java面试题:Mysql常见面试题 |
6 | java面试题:线程、线程池、锁常见的15道面试题 |
7 | java面试题:jvm常见的5道面试题 |
8 | java面试题:自定义注解常见面试题 |
9 | java面试题:单例模式5道常见面试题 |
10 | java面试题:Nacos常见面试题 |
11 | java面试题:Elasticsearch面试突击 |
12 | java面试题:springboot常见面试题(一) |
13 | Java面试题:SpringBoot常见面试题(二) |
14 | java面试题:HTTP的常见状态码(实际面试有被问到) |
15 | java面试题:常见项目真实面试题(实际面试被问到) |
16 | java面试题:项目常见面试题(单点登录、购物车) |
17 | java面试题:适用于2-5年后端开发面试(一) |
18 | java面试题:适用于2-5年后端开发面试(二) |
19 | java面试题:适用于2-5年后端开发面试(三) |
20 | java面试题:2-5年后端开发常见面试题(四) |
21 | java面试题:2-5年经验后端开发面试记录(五) |
... | 待更新 |
目录
1、创建线程有哪几种方式
1.继承Thread类
2.实现Runn
3.实现Callable接口
4.线程池方式
2、线程池的七大参数?
3、线程池的工作原理?
4、ThreadPoolExecutor 有哪些常用的方法?
5、说说submit()和 execute两个方法有什么区别?
6、shutdownNow() 和 shutdown() 两个方法有什么区别?
7、线程池中核心线程数量大小怎么设置?
8、线程池为什么需要使用(阻塞)队列?
9、线程池为什么要使用阻塞队列而不使用非阻塞队列?
10、了解线程池状态吗?
11、知道线程池中线程复用原理吗?
12、说说线程池创建需要的那几个核心参数的含义
13、说一说线程的生命周期
14、说一说synchronized的实现原理?
13、synchronized和Lock的区别是什么?
14、volatile与synchronized的区别
15、线程池拒绝策略有哪些?
Thread类本质上是实现了Runnable接口的一个实例,代表一个线程的实例。启动线程的唯一方法就是通过Thread类的start()实例方法。start(方法是一个native方法,它将启动一个新线程,并执行ru0方法。这种方式实现多线程很简单,通过自己创建的类直接extend Thread,并复写runO方法,就可以启动新线程并执行自己定义的runO方法:优点:代码简单。“
缺点:该类无法继承别的类
Java中的类属于单继承如果自己的类已经extends另一个类,就无法直接extends
Thread,,但是一个类继承一个类同时是可以实现多个接口的优点:继承其他类。统一实现该接口的实例可以共享资源。·缺点:代码复杂
实现Runnable和实现Callable接口的方式基本相同,不过Callable接口中的callo方法有返回值,Runnable接口中的runO方法无返回值
线程池,其实就是一个容纳多个线程的容器,其中的线程可以重复使用,省去了频繁创建线程对象的操作,因为反复创建线程是非常消耗资源的·优点:实现自动化装配,易于管理,循环利用资源。
corePoolSize:核心线程数
maximumPoolSize:最大线程数
keepAliveTime:空闲时间
unit:keepAliveTime的时间单位
threadFactory:创建线程的工厂类
workQueue:用于保存任务的队列
handler:拒绝策略
ThreadPoolExecutor有如下常用方法:
submit()/execute():执行线程池
shutdown()/shutdownNow():终止线程池
isShutdown():判断线程是否终止
getActiveCount():正在运行的线程数
getCorePoolSize():获取核心线程数
getMaximumPoolSize():获取最大线程数
getQueue():获取线程池中的任务队列
allowCoreThreadTimeOut(boolean):设置空闲时是否回收核心线程这些方法可以用来终止线程池、线程池监控等。
submit() 和 execute() 都是用来执行线程池的,只不过使用 execute() 执行线程池不能有返回方法,而使用 submit() 可以使用 Future 接收线程池执行的返回值。
shutdownNow() 和 shutdown() 都是用来终止线程池的,它们的区别是,
使用 shutdown() 程序不会报错,也不会立即终止线程,它会等待线程池中的缓存任务执行完之后再退出,执行了 shutdown() 之后就不能给线程池添加新任务了;
shutdownNow() 会试图立马停止任务,如果线程池中还有缓存任务正在执行,则会抛出 java.lang.InterruptedException: sleep interrupted 异常。
「CPU密集型任务」:比如像加解密,压缩、计算等一系列需要大量耗费 CPU 资源的任务,大部分场景下都是纯 CPU 计算。尽量使用较小的线程池,一般为CPU核心数+1。因为CPU密集型任务使得CPU使用率很高,若开过多的线程数,会造成CPU过度切换。
「IO密集型任务」:比如像 MySQL 数据库、文件的读写、网络通信等任务,这类任务不会特别消耗 CPU 资源,但是 IO 操作比较耗时,会占用比较多时间。可以使用稍大的线程池,一般为2*CPU核心数。IO密集型任务CPU使用率并不高,因此可以让CPU在等待IO的时候有其他线程去处理别的任务,充分利用CPU时间。
另外:线程的平均工作时间所占比例越高,就需要越少的线程;线程的平均等待时间所占比例越高,就需要越多的线程;
以上只是理论值,实际项目中建议在本地或者测试环境进行多次调优,找到相对理想的值大小。
主要有三点:
因为线程若是无限制的创建,可能会导致内存占用过多而产生OOM,并且会造成cpu过度切换。创建线程池的消耗较高。
阻塞队列可以保证任务队列中没有任务时阻塞获取任务的线程,使得线程进入wait状态,释放cpu资源。
当队列中有任务时才唤醒对应线程从队列中取出消息进行执行。
使得在线程不至于一直占用cpu资源。
(线程执行完任务后通过循环再次从任务队列中取出任务进行执行,代码片段如下
while (task != null || (task = getTask()) != null) {})。
不用阻塞队列也是可以的,不过实现起来比较麻烦而已,有好用的为啥不用呢?
通过获取线程池状态,可以判断线程池是否是运行状态、可否添加新的任务以及优雅地关闭线程池等。
RUNNING:线程池的初始化状态,可以添加待执行的任务。SHUTDOWN:线程池处于待关闭状态,不接收新任务仅处理已经接收的任务。
STOP:线程池立即关闭,不接收新的任务,放弃缓存队列中的任务并且中断正在处理的任务。TIDYING:线程池自主整理状态,调用 terminated() 方法进行线程池整理。TERMINATED:线程池终止状态。
线程池将线程和任务进行解耦,线程是线程,任务是任务,摆脱了之前通过 Thread 创建线程时的一个线程必须对应一个任务的限制。
在线程池中,同一个线程可以从阻塞队列中不断获取新任务来执行,其核心原理在于线程池对 Thread 进行了封装,并不是每次执行任务都会调用 Thread.start() 来创建新线程,而是让每个线程去执行一个“循环任务”,在这个“循环任务”中不停的检查是否有任务需要被执行,如果有则直接执行,也就是调用任务中的 run 方法,将 run 方法当成一个普通的方法执行,通过这种方式将只使用固定的线程就将所有任务的 run 方法串联起来。
ThreadPoolExecutor 最多包含以下七个参数:
corePoolSize:线程池中的核心线程数
maximumPoolSize:线程池中最大线程数
keepAliveTime:闲置超时时间
unit:keepAliveTime 超时时间的单位(时/分/秒等)
workQueue:线程池中的任务队列
threadFactory:为线程池提供创建新线程的线程工厂
rejectedExecutionHandler:线程池任务队列超过最大值之后的拒绝策略
线程的生命周期包含5个阶段,包括:新建、就绪、运行、阻塞、销毁。
新建:就是刚使用new方法,new出来的线程;
就绪:就是调用的线程的start()方法后,这时候线程处于等待CPU分配资源阶段,谁先抢的CPU资源,谁开始执行;
运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run方法定义了线程的操作和功能;
阻塞:在运行状态的时候,可能因为某些原因导致运行状态的线程变成了阻塞状态,比如sleep()、wait()之后线程就处于了阻塞状态,这个时候需要其他机制将处于阻塞状态的线程唤醒,比如调用notify或者notifyAll()方法。唤醒的线程不会立刻执行run方法,它们要再次等待CPU分配资源进入运行状态;
销毁:如果线程正常执行完毕后或线程被提前强制性的终止或出现异常导致结束,那么线程就要被销毁,释放资源;
synchronized关键字经过编译之后,会在同步代码块前后分别形成monitorenter和monitorexit字节码指令,在执行monitorenter指令的时候,首先尝试获取对象的锁,如果这个锁没有被锁定或者当前线程已经拥有了那个对象的锁,锁的计数器就加1,在执行monitorexit指令时会将锁的计数器减1,当减为0的时候就释放锁。如果获取对象锁一直失败,那当前线程就要阻塞等待,直到对象锁被另一个线程释放为止。
synchronized关键字最主要有以下3种应用方式,下面分别介绍
修饰实例方法,作用于当前实例加锁,进入同步代码前要获得当前实例的锁
修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁
修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。
1、lock是一个接口,而synchronized是java的一个关键字。
2、synchronized在发生异常时会自动释放占有的锁,因此不会出现死锁;而lock发生异常时,不会主动释放占有的锁,必须手动来释放锁,可能引起死锁的发生。
3、lock等待锁过程中可以用interrupt来中断等待,而synchronized只能等待锁的释放,不能响应中断;
4、Lock可以通过trylock来知道有没有获取锁,而synchronized不能;
5、synchronized是托管给JVM执行的,而lock是java写的控制锁的代码。
1、volatile关键字是线程同步的轻量级实现,所以volatile性能肯定比synchronized要好;volatile只能修饰变量,而synchronized可以修饰方法,代码块。随着JDK新版本的发布,synchronized的执行效率也有较大的提升,在开发中使用synchronized的比率还是很大的。
2、多线程访问volatile变量不会发生阻塞,而synchronized可能会阻塞。
3、volatile能保证数据的可见性,但是不能保证原子性;而synchronized可以保证原子性,也可以保证可见性。
4、关键字volatile解决的是变量在多个线程之间的可见性;synchronized关键字解决多个线程之间访问公共资源的同步性。
abortpolicy拒绝策略代码体现: