JVM在初始化阶段(即在Class被加载后,且被线程使用之前),会执行类的初始化。在初始化期间,JVM会去获取一个锁。这个锁可以同步多个线程对同一个类的初始化。
虚拟机会保证一个类的clinit()方法在多线程环境中被正确地加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的()方法,其他线程都需要阻塞等待,直到活动线程执行clinit()方法完毕。如果在一个类的clinit()方法中有耗时很长的操作,就可能造成多个进程阻塞(需要注意的是,其他线程虽然会被阻塞,但如果执行clinit()方法后,其他线程唤醒之后不会再次进入clinit()方法。同一个加载器下,一个类型只会初始化一次。
基于这个特性,可以使用这个方式实现线程安全的延迟初始化方案,这种方式的优点就是在外部类加载时并需要立即加载内部类,内部类不加载,则不会初始化INSTANCE,也就不会占用内存。同时这种初始化方案是线程安全的
双重检查锁定public class SingleInstanceTest {
private SingleInstanceTest() {
}
//volatile关键字
public static volatile SingleInstanceTest singleInstanceTest;
public static SingleInstanceTest getInstance() {
//第一次检查
if (singleInstanceTest == null) {
synchronized (SingleInstanceTest.class) {
//第二次检查
if (singleInstanceTest == null) {
//分配内存空间 memory = allocate()
//初始化对象 ctorInstance(memory)
//将singleInstanceTest指向刚分配的内存地址
singleInstanceTest = new SingleInstanceTest();
}
}
}
return singleInstanceTest;
}
}
这种方式有几个点需要注意一下:
1.第一次判空的目的:如果第一次检查instance不为null,那么就不需要执行下面的加锁和初始化操作,因此可以大幅降低synchronized带来的性能开销2.第二次判空的目的:想象一下这种情况,线程1、2同时判断第一次为空,在加锁的地方的阻塞了,如果没有第二次判空,那么线程1执行完毕后线程2就会再次执行,这样就初始化了两次。两次判空后,DCL就安全多了,一般不会存在问题。但当并发量特别大的时候,还是会存在风险的。也就是volatile改发挥作用了。3.volatile的作用:singleInstanceTest = new SingleInstanceTest();这行代码可以分解为 最后
希望大家能有一个好心态,想进什么样的公司要想清楚,并不一定是大公司,我选的也不是特大厂。当然如果你不知道选或是没有规划,那就选大公司!希望我们能先选好想去的公司再投或内推,而不是有一个公司要我我就去!还有就是不要害怕,也不要有压力,平常心对待就行,但准备要充足。最后希望大家都能拿到一份满意的 offer !如果目前有一份工作也请好好珍惜好好努力,找工作其实挺累挺辛苦的。
这里附上上述的面试题相关的几十套字节跳动,京东,小米,腾讯、头条、阿里、美团等公司19年的面试题。把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节。
由于篇幅有限,这里以图片的形式给大家展示一小部分。可以点击GitHub免费获取
md)
[外链图片转存中…(img-aS0mMHOz-1644051945658)]