JAVA面试八股文-基础篇(基础及多线程)

冰冻三尺非一日之寒


JRE,JDK,JVM三者之间的关系

JDK开发工具,包含了JRE和java工具
JRE运行的环境,包含了JVM
JVM虚拟机

==和equals

==为堆的引用地址。
equals为判断值是否相等,
重写equals方法需要重新hashcode方法,保证对象hash一致。

final

编译时定义关键字。
1,标注在类上代表这个类不可以继承。
2,标注在方法上代表这个字不能被重写。
3,标记在属性上代表这个字不可以被修改。

内部类应用传递外部变量的时候为什么需要标记final

因为内部类的本质是2个class的文件,当外部类生命周期消亡后,内部类的传递本质是堆的引用地址,因此需要进行copy一份地址,如何保证值不被外部所修改?因此增加final保证

String,StringBuffer,StringBuilder的区别

Stirng不可变值类型
StringBuffer可变值类型,加了synchronize关键字,保证线程安全
StringBuilder可变值类型,非线程安全。

什么是可变值类型?Stirng修改值会在堆空间新建对象,栈指向新堆地址,原对象等待GC的回收,StringBuffer和StringBuilder则在原对象直接修改。

在单机情况下使用StringBuilder,非线程安全情况下使用StringBuffer,String用于不常修改值的属性地方。

重载和重写的区别

重载: 同一类,方法名相同,参数类型不同(个数不同,顺序不同)也是重载,方法返回值和修饰符可以不一样,不过在编译的时候就会报错,这个时候不是重载、

重写: 发生在父子类中,方法名和参数要相同,访问修饰符要小于或者等于父类

List和Set的区别

List: 有序可重复,内部是有序数组,可允许多个null元素。可以使用for(for循环),iterator(迭代器访问),get(i)(下标访问),List使用的时候最好指明使用的空间,因为List使用的是数组,数组本质是不可变的。当溢出原本数组空间的时候,会发生copy数组的情况,其长度为原来的一倍,消耗IO,因此我们使用List的时候最好指定长度大小,再采用尾插法,其效率会更高。

Set: 无序,不可重复,内部是链表结构,只能有一个null。只能使用iterator迭代器访问。

HashCode和Equals的区别

Equals: 如果不去重写,默认比较的是2个中的==应用地址类型。

HashCode: hashcode的作用是确定哈希表中的位置,哈希表理是存储栈的位置,哈希表负责维护。hashcode的值可能相同,当相同的时候就需要调用equals去验证是否相同。

JAVA因为这个原理规定了:
1)如果2个对象相等,则hashcode一定相等。
2)如果2个对象分别调用equals方法都返回true
3)2个对象hashcode相等,equals也不一定相等。

所以当我们重写equals方法的时候,hashcode也必须重写。

如何实现IOC容器

考察的就是我们对spring的理解

1.根据配置的路径文件加载路径下的.class文件

2.根据.class文件进行验证,解析,反射获取类对象

3.将类对象实例和对象名缓存起来。

Java类加载器有哪些

启动类加载(bootstarpclassloader),扩展类加载器(extclassloader),引用程序类加载器(appclassloader),自定义加载器(CustomClassLoader),。

启动类加载器负责加载JAVA_HOME/lib
父类加载器负责加载JAVA_HOME/lib/ext
引用程序类加载器(appclassloader)负责加载我们引入的Jar包之类的

双亲委派模型

JVM 通过双亲委派机制对类进行加载。双亲委派机制指一个类在收到类加载请求后,自己不会去尝试加载这个类,而是吧这个类加载请求向上委派给父类进行加载,父类收到这个类请求后会将请求委派给自己的父类,以此类推,这样所有类的加载请求都会加载到启动类加载器中。倘若父类加载不到就会向下请求给自己的子类。找到该类加载被成功。若找不到该类,则JVM会抛出ClassNotFound异常。

双亲委派机制的核心目的就是保证类的唯一性和安全性。

GC如何发现垃圾

引用计数器:即当对象被创建后在计数器里+1,当销毁时计数器里减1。为0时则GC进行回收。

问题:会产生循环引用GC无法剔除的问题。

可达性分析:即有一个GC roots对象,每建立一个对象,GCroots就会建立链接,倘若反射循环引用了,GCroots发现有链接了不会新建链接,当销毁时,与GCRoots的链接断开,当2次GC后发现还没有建立链接则代表发生了循环引用,GC清除对象。

GC roots是什么:
虚拟机栈中本地引用的的对象
方法区中的静态属性对象
方法区中常量引用对象
本地方法区中的Nactve标注的方法

线程的生命周期

创建,就绪,运行,阻塞,死亡

new thread:创建。
start:调用start线程进入就绪状态,等待cpu的调度,这里时间很短。
running:cpu运行。
blocking:线程因为某种原因放弃了cpu的使用权。进入等待调度阶段。
dead:线程消亡被回收。

sleep,wait,join,yield的区别

  • sleep会thread方法,wait是Object的方法。
  • sleep不会释放掉锁,wait会释放掉锁。
  • sleep不需要被唤醒,wait需要noticeall或者notice唤醒。
  • sleep会让线程强行重入,而wait则不一定,wait之后可能重新获得锁资源继续执行。

最后一点的解释:

sleep是让去cpu的使用权,让线程进入休眠状态,唤醒后会强制切换cpu的控制权,执行线程,wait之后会重新竞争锁,单线程情况下,继续cpu执行。

yield:让当前处于运行状态的线程退回到可运行状态,让出抢占资源的机会。

join:中断当前线程,让别的线程先执行,执行别的线程后再执行。

说说对线程安全的理解。

说线程安全其实本质就是内存安全。
在JVM内存模型中,堆是共享空间,栈是私有空间,对象建立在堆中,操作都在栈中执行。正因为栈的执向地址都是共享空间堆,多线程操作的话,单一时间内堆可能服务于其中的一个栈,其他线程操作无响应,造成了线程安全的隐患。

Thread和Runable的区别

Thread的本质也是通过Runable接口执行的。
Thread是继承单继承,Runable是接口多实线现的

谈谈守护线程

thread.setDemo(true)
thread.start()

守护线程:为所有的用户线程提供的服务线程。
守护线程的GC不可控,不知道什么时候执行,因此一般的业务处理不设置为守护线程。

经典守护线程GC:GC在cpu空闲时才会触发。

ThreadLocal

基本使用:

static final ThreadLocal sThreadLocal = new ThreadLocal();
sThreadLocal.set()
sThreadLocal.get()

ThreadLocal,线程副本变量。为每个线程创建一个独立的对象,这个对象只能为这个线程所用。

ThreadLocal的静态内部类ThreadLocalMap为每个Thread都维护了一个数组table,ThreadLocal确定了一个数组下标,而这个下标就是value存储的对应位置。

使用场景:
1)线程之间的数据隔离
2)session会话

并发、并行、串行

并发:多个线程操作同一资源
并行:多个线程操作多个资源
串行:多个线程操作顺序操作单个资源

并发的三大特性

可见性、有序性、原子性。

原子性:要么全部成功,要么全部失败。原子类atomic类。

有序性:指令重排

可见性:volatile

在JVM中,内存模型中将数据读取到工作内存中,每个线程中,工作内存不可见,线程A修改了值,线程B并不知道修改了值,最后刷会共享线程时,会发生ABA问题。

具体详情见文章:https://blog.csdn.net/qq_35059264/article/details/119563596

为什么使用线程池,线程池的参数

使用线程池是为了对我们线程进行核心的管理。保证线程的创建和销毁,减少因为new thread导致的线程IO开销。

线程池的核心参数有:

corepoolsize:核心线程数。

  • 如果是经常是IO操作,则一般是cpu核心数的2倍。
  • 如果是普通操作则和线程数量相等。

maximumPoolSize 最大线程数
keepAliveTime 超时时间,没人调用就会释放s
TimeUnit 超时单位
BlockingQueue 传入入的阻塞队列模型,当线程满了,存放位置的队列模型
ThreadFactory 线程工厂,创建线程的方法接口,可以设置名字等

RejectedExecutionHandler 拒绝策略

  • AbortPolicy:超过最大承载数了还有线程进来,不处理并报错
  • CallerRunsPolicy:哪来的去哪里,是main方法执行
  • DiscardPolicy:超过最大承载数了就会丢掉任务,不会抛出异常
  • DiscardOlderstPolicy:队列满了,尝试去和最早的竞争也不会抛出异常

线程池执行流程

线程池任务=>核心线程数满没有,没有则创建核心线程数=>阻塞任务队列是否满了,未满则放入队列,=>已满,零时线程数满没有,没满则创建临时线程=>满了则执行拒绝策略。

线程池为什么使用阻塞队列?线程池为什么是先放进队列,而不是先创建临时队列?

线程池为什么使用阻塞队列?

首先我们要搞明白阻塞队列是什么:
普通队列是有大小限制,当超出大小范围,则会抛出异常。

阻塞队列则不会抛出异常:

  • 既队列满了等待队列为空的时候进行写入,如果队列空了则等待队有数据后进行消费。

阻塞队列当没有任务的时候会自动进入wait状态,释放掉cpu资源,不会像thread需要手动的去wait或者sleep,并且阻塞队列自带唤醒功能,有数据了自带进入线程的功能

线程池为什么是先放进队列,而不是先创建临时队列?

因为在线程池中,创建线程的时候需要获取全局锁的,这个时候其他的线程都会阻塞,阻碍了其他其他线程,影响了效率,因此能使用固定线程就尽量固定线程。当线程队列满了,实在消耗不赢了,再满核运行。

线程池中复用原理

线程和任务进行了解耦,不像我们thread.start方法,任务和线程是绑定在一起的。

在线程池中,对thread进行了再次封装,直接循环遍历任务,有任务则直接执行run方法。通过这种方式只使用了固定的线程将所有的任务串联起来。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值