冰冻三尺非一日之寒
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方法。通过这种方式只使用了固定的线程将所有的任务串联起来。