概述
2022年同花顺Java面试
前言
最近疫情期间抽空面试了一家互联网公司,很多问题都很常见,但是很长时间没有看这方面的知识了,所以回答的确实很不好,于是周末抽空把涉及的问题整理一下,希望对大家有用。
正文
Java基础
1.一个java文件中有几个类,编译后有几个class文件?
在一个源文件中用class
关键字定义了几个类,编译的时候就会产生几个字节码文件
2. Java是否可以同时使用abstract和fInal声明方法?
final
的四种用法:
final
修饰的是引用不可修改,但是引用地址指向的数据可以改变。final
可以修饰方法 方法不可被覆盖 (继承类之间的关系)final
修饰类 表示该类不能被继承final
修饰成员变量,该成员变量不可被修改
方法声明了abstract
,则需要重写abstract
方法,所以我们在方法不能同时使用abstract
和fInal
声明方法.
其他相关问题:
-a.Java是否可以同时使用abstract和static声明方法?
static
与abstract
不能同时使用, 用static
声明方法表明这个方法在不生成类的实例时可直接被类调用, 而abstract
方法不能被调用,两者矛盾。
b. Java是否可以同时使用abstract和native声明方法?
native
与abstract
不能同时使用,因为 native
暗示这些方法是有实现体的,只不过这些实现体是非java
的,但是abstract
却显然的指明这些方法无实现体。
c.Java是否可以同时使用abstract和 synchronized声明方法?
synchronize
与abstract
不能同时使用,synchronize
(经常结合static
)声明方法,我们称作类锁,用来确保多线程下的线程安全。- 用
synchronized
的前提是该方法可以被直接调用,所以不能和abstract
一起使用.
3.使用线程的方式
- 使用
Thread
,重写run()
方法 - 实现
Runnable
接口,重写run()
方法 - 实现
Callable
接口,相对于Runnable
是有返回值,重写call()
方法 - 使用
Executor
框架来创建线程池
Future
Future
通常与Callable
结合使用,Future
可以对Callable
任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过get
方法获取执行结果,该方法会阻塞直到任务返回结果。- 但是
Future
只是一个接口,所以是无法直接用来创建对象使用的,因此就有了FutureTask
。 - 在
JDK1.8
中提供了CompletableFuture
,提供了非常强大的Future
的扩展功能,可以帮助我们简化异步编程的复杂性,提供了函数式编程的能力,可以通过回调的方式处理计算结果,并且提供了转换和组合CompletableFuture
的方法。
4.实现线程安全方式
实现线程同步的6种方式:
- 使用
Synchronized
标记临界区 - 对变量使用
volatile
进行标记,可以实现原子操作的线程安全 - 使用锁机制
- 使用局部变量
ThreadLocal
实现线程同步 - 使用阻塞队列
SyschronousQueue
实现线程同步 - 使用原子变量实现线程同步
5.synchronized是不是公平锁?公平锁的性能高还是非公平锁的性能高?
- 公平锁和非公公平锁的区别在于公平锁会保证先来的线程会先获取资源(其内部实现时
AQS原理
),而非公平不能保证。 - 公平锁的实现是通过
FIFO先进先出的队列
来实现的,非公平锁会由JVM
就近安排线程获取资源的顺序,所以非公平锁的性能是优于公平锁的。 sychronized
是非公平锁,ReenterLock
(默认也是非公平锁)可以实现公平锁。
6.ReetrantLocK的原理
ReentrantLock
主要利用CAS+AQS队列
来实现的:
- 先通过
CAS
尝试获取锁, 如果此时已经有线程占据了锁,那就加入AQS队列
并且被挂起; - 当锁被释放之后, 公平锁的情况下:排在队首的线程会被唤醒
CAS
再次尝试获取锁,非公平锁下由JVM
就近决定哪个线程获取资源。
基于AQS原理
AQS
是将每一条请求共享资源的线程封装成一个CLH锁队列
的一个结点(Node
),来实现锁的分配。AQS
就是基于CLH队列
,用volatile
修饰共享变量state
,线程通过CAS
去改变状态符,成功则获取锁成功,失败则进入等待队列,等待被唤醒。CLH队列
是一个虚拟的双向队列(FIFO先进先出
),虚拟的双向队列即不存在队列实例,仅存在节点之间的关联关系。
7.线程池的核心参数
public ThreadPoolExecutor(
int corePoolSize, #核心线程数
int maxinmumPoolSize, #线程总数 非核心数=总数-核心数
long keepAliveTime, #当前线程数大于核心线程数时 线程的等待新任务的等待时间(核心线程也会面临死亡)
TimeUnit unit, #时间单位
BlockingQueue<Runnable> workQueue #任务队列
RejectedExecutionHandler #(选填) 拒绝处理器
)
8.当一个新的任务进来,线程池怎么处理
线程池处理线程的过程:
- 当前运行线程数 小于
corePoolSize
任务直接交给核心线程进行执行 - 当前运行线程数 大于或等于
corePoolSize
任务且满足队列未满,那么任务将进入任务队列进行等待,并且任务队列都具有阻塞性,所以只有当核心线程数的任务执行完了,才会从任务队列中获取任务。 - 当前运行线程数 大于或等于
corePoolSize
任务且队列已满,那么 任务进入非核心线程。 - 当核心线程、等待队列、非核心线程都被占用的时候线程会被拒绝器处理。
9.线程池拒绝策略有哪些?
适用:那些既不能进入核心线程、等待队列,也无法使用非核心线程来处理,或者线程异常的线程:
- CallerRunsPolicy:直接运行该任务的
run
方法,但不是在线程池内部,适合处理业务比较重要且数量不多的场景。 - AbortPolicy:
RejectedExecutionException
异常抛出。适用对业务非常重要的完全不能不执行的场景。(默认) - DiscardPolicy:不会做任何处理。适合处理丢失对业务影响不大的场景。
- DiscardOldestPolicy:检查等待队列 强行取出队列头部任务(并抛弃该头部任务)后再进行执行该任务。适合新数据比旧数据重要的场景。
10.线程A、B如何实现线程A完成后,再执行线程B.
方式一:使用join
Thread
类中的join
方法的主要作用就是同步,它可以使得线程之间的并行执行变为串行执行。
方式二:CountDownLatch(线程计数器)+Semaphore信号量
CountDownLatch
用于主线程等待其他子线程任务都执行完毕后再执行;Semaphore
信号量是java.util.concurrent
包下用来:限制线程并发的数量的工具类,基于AQS
实现的,在构造的时候会设置一个值,代表着资源数量。
方式三:使用CyclicBarrier(回环栅栏)
CyclicBarrier
用于一组线程相互等待大家都达到某个状态后,再同时执行;
11.Java的引用类型,并且举例说明其使用场景
- 强引用:
A a=new A()
只要引用a存在,垃圾回收器不会回收。 - 软引用:
SoftReference
类似于缓存的方式,不影响垃圾回收,可以提升速度,节省内存。若对象被回收,此时可以重新new,主要是用来缓存服务器中间计算结果以及不需要实时保存的用户行为。通常放在用在对缓存比较敏感的应用中。 - 弱引用:
WeakReference
用于监控对象是否被垃圾回收器回收。 - 虚引用:
PhantomReference
,每次垃圾回收的时候都会被回收。主要用于判断对象是否已经从内存中删除。
Spring部分
1.Spring Bean的作用域以及Spring Bean是不线程安全的
- 单例模式(默认):
Spring
创建Bean
的原则是不等bean
创建完成就将beanFactory
提早放到缓存中,如果其他bean
依赖这个bean
可以直接使用,这种三级缓存的机制很好地解决了循环依赖的问题。 - 多例模式(原型模式):每次使用时都会创建新的
Bean
实例,Bean
调用setAllowCircularReferences(false)
来禁止循环依赖,否则出现循环依赖会直接抛异常。 - Request模式:一次
request
一个实例, 当前Http
请求结束,该bean
实例也将会被销毁. - Session模式:在一次
Http Session
中,容器会返回该 Bean 的同一实例。而对不同的Session
请求则会创建新的实例,该bean
实例仅在当前Session
内有效。 - global Session模式(不常使用):在一个全局的
Http Session
中,容器会返回该Bean
的同一个实例,仅在 使用portlet context
时有效。
在xml
配置文件中添加配置属性scope= prototype
使用,或者使用注解@Scope("prototype")
单例模式和多例模式的线程问题
多例模式:每次创建一个新对象,也就是线程之间并不存在Bean
共享,自然是不会有线程安全的问题。
单例模式下的线程安全问题:
-
对于类似于
DAO
这种只有查询的无状态Bean
在单例情况下是可以保证线程安全 -
在
@Controller/@Service
等容器中,默认单例模式是线程不安全的。 -
即使
@Controller/@Service
使用多列模式下, 如果使用静态变量,它也不是线程安全的 -
一定要定义变量的话,用
ThreadLocal
来封装,这个是线程安全的
2.说一说Spring循环依赖
循环依赖指的是Spring Bean
之间相互依赖的情况。
解决循环依赖的前置条件:
- 出现循环依赖的
Bean
必须要是单例,都是多例模会直接报错 - 依赖注入的方式不能全是构造器注入的方式
Spring
在创建Bean
的时候默认是按照自然排序来进行创建的,IOC
容器先创建A,再创建B:
- A、B均采用
setter
方式相互注入 - A采用
setter
方式获取B,B采用构造器方式获取A
以上两种情况的循环依赖时可以解决循环依赖,其他情况都会异常抛出。
解决循环依赖的流程:
- 当A完成了实例化并添加进了
Bean
的缓存池(一级缓存)中。 - 为A进行属性注入了,在注入时发现A依赖了B,会去实例化B。
- 在创建B的时候,因为A已经放在
Bean
的缓存池(一级缓存)当中了,所以无论B采用的setter
方式还是构造器方式都可以获取A。
这里需要注意的是:
-
如果A采用的是构造器方式,创建A时发现依赖于B,于是会先去创建B,但是B又依赖于A,并且缓存没有A,所以会直接因为循环依赖,导致启动异常。
-
@Autowired、@Resources
实际上都是setter
方式注入依赖。
AOP在解决循环依赖的问题上,会使用三级缓存的方式去完成
- singletonObject:一级缓存,这里的
bean
是已经创建完成的,一旦进行getBean
操作时,我们第一时间就会寻找一级缓存 - earlySingletonObjects:二级缓存,该缓存所获取到的
bean
是提前曝光出来的,是还没创建完成的。 - singletonFactories:三级缓存为早期曝光对象工厂,这个工厂的目的在于延迟对实例化阶段生成的对象的代理,只有真正发生循环依赖的时候,才去提前生成代理对象。
知识点:
- 添加单例缓存,当
bean
被创建完以后进行的操作。这时候说明bean
已经创建完,删除二三级缓存,直接留下一级缓存,并且注册该bean
。 - 二级缓存可以提前曝光被其他
Bean
所引用,它可以解決循环依赖。但是二级缓存无法解决AOP+循环依赖
的问题,因为不可能每次执行singleFactory.getObject()
方法都给我产生一个新的代理对象,所以还要借助另外一个缓存来保存产生的代理对象。 - 三级缓存是用来来保存产生的代理对象,但它并没有所谓性能上的“提升”。
3.DispatcherServlet前端控制器的工作流程
用户发送请求和返回响应的流程:
- 发送请求至
DispatcherServlet
- 映射处理器获取处理器映射至
DispatcherServet
HandlerAdapter
进行处理器适配- 调用处理器相应功能处理方法(获得
View
和model
至DispatcherServlet
) ViewResolver
接收View
进行视图解析Model
加入到View
中进行视图渲染DispatcherServlet
返回响应
其他部分
单例模式
使用场景:资源共享的情况
描述:单例模式自行实例化且实例化一次 构造函数必须是私有的
懒汉单例模式和饥汉单例模式
- 懒汉单例模式:在调用资源时,如果没有进行实例化的话,就进行一次实例化。
- 饥汉单例模式:在没有调用时就已经自行实例化了。
多线程下使用懒汉单例模式
使用synchronized方法(类锁)+ synchronized块(对象锁)
外部加了一个判空的逻辑,确保线程安全;
synchronized两种锁:
- 对象锁:用
sychronized
修饰代码块 (手动去指定锁对象) - 类锁:用
sychronized
修饰普通方法(类锁只能在同一时刻被一个对象拥有 通过方法的ACC_SYNCHRONIZED
标志符是否被设置,这里会隐形调用monitorenter
、monitorexit
这两个指令 )
用单例模式下都是同一个实例对象,所以类锁无法保证数据安全,需要再结合对象锁来进行双重校验。
singleton属性被volatile修饰,其作用:
- 可见性:线程A实例化属性之后,
Volatile
刷入主存 - 防止指令重排: 防止多线程下指令重排,进而实例化两次或者获取一个null
- volatile变量规则:对
volatile
修饰的变量的写操作 先行发生于后面对这个变量的读操作;
/**
* 单例模式-双重校验锁
*
* @author szekinwin
*/
public class SingleTon3 {
//私有化构造方法
private SingleTon3() {
}
//通过volatile,实现添加内存屏障,防止指令重排序
private static volatile SingleTon3 singleTon = null;
public synchronized static SingleTon3 getInstance() {
//第一次校验
if (singleTon == null) {
synchronized (SingleTon3.class) {
//第二次校验
if (singleTon == null) {
singleTon = new SingleTon3();
}
}
}
return singleTon;
}
}
总结
很多时候我都觉得自己挺菜的,真的有很多东西需要去学,上面很多的知识很多其实都学过,可是真的面试的时候总是不能完整清晰的回答出来,学习真是一直习惯,不能荒废。
最后
以上就是怕孤单心情为你收集整理的2022年同花顺Java面试2022年同花顺Java面试的全部内容,希望文章能够帮你解决2022年同花顺Java面试2022年同花顺Java面试所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复