资讯详情

就在刚刚这份java八股文成功让我进入字节,拿到了人生第一个18k

一、java基础

对象的三个基本特征是:包装、继承和多态。

继承:使某一类型的对象获得另一类型对象属性的方法。继承是子类继承父类的特征和行为,使子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使子类具有与父类相同的行为。

包装:隐藏部分对象的属性和实现细节,数据访问只能通过公共接口进行。这样,对象就可以为内部数据提供不同级别的保护,以防止对象的私有部分在程序中发生意外变化或错误使用。

多态性:不同的子类对象对于同一行为有不同的表现形式。多态性存在的三个条件:1)继承;2)重写;3)父类引用指向子类对象。

举个简单的例子:我们按下英雄联盟 Q 键此动作:

对亚索来说,是斩钢闪光

对提莫来说,是致盲吹箭

对剑圣来说,是阿尔法的突袭

同一事件发生在不同的情况下 的对象上会产生不同的结果。

我再举一个简单的例子来帮助你理解。这个例子可能不完全准确,但我认为它有利于理解。

public class Animal  {  // 动物     public void sleep()  {         System.out.println("躺着睡");     } } class Horse extends Animal  {  // 马 是一种动物     public void sleep()  {         System.out.println("站着睡");     } } class Cat extends Animal  {  // 猫 是一种动物     private int age;     public int getAge()  {         return age   1;     }     @Override     public void sleep()  {         System.out.println("四脚朝天睡觉");     } } 

例如:

House 和 Cat 都是 Animal,所以他们都继承了 Animal,同时也从 Animal 继承了 sleep 这个行为。

但是针对 sleep 这个行为,House 和 Cat 重写,有不同的表现形式(实现),我们称之为多态。

在 Cat 里,将 age 属性定义为 private,外界不能直接访问,需要获得 Cat 的 age 只能通过信息 getAge 方法,从而隐藏在外面 age 属性,这叫封装。当然,这里 age 只是一个例子,在实际使用中可能是一个复杂的对象。

image.png

// 代码块1short s1 = 1; s1 = s1   1;// 代码块2short s1 = 1; s1  = 1; 

编译错误的原因是:不兼容的类型: 从int转换到short可能会有损失。

正常编译和执行代码块2。

编译代码块2,字节码如下:

public class com.joonwhee.open.demo.Convert {public com.joonwhee.open.demo.Convert();Code:0: aload_01: invokespecial #1 // Method java/lang/Object."<init>":()V4: returnpublic static void main(java.lang.String[]);Code:0: iconst_1 // 将int类型值1入(操作数)栈1: istore_1 // 将栈顶int将类型值保存到局部变量1中2: iload_1 // 装载在局部变量1中int类型值3: iconst_1 // 将int类型值1进栈4: iadd // 将栈顶两int类型数相加,结果入栈5: i2s // 将栈顶int类型值截断成short类型值,后带符号扩展到int类型值入栈。6: istore_1 // 将栈顶int将类型值保存到局部变量1中7: return} 

可见字节码中包含了字节码 i2s 该指令用于将军 int 转成 short。i2s 是 int to short 的缩写。

其实,s1 = 1 相当于 s1 = (short)(s1 1)如果你感兴趣,你可以编译这两行代码的字节码,你会发现它们是一样的。

public static void main(String[] args) {Integer a = 128, b = 128, c = 127, d = 127;System.out.println(a == b);System.out.println(c == d);} 

答案是:false,true。

执行 Integer a = 相当于执行:Integer a = Integer.valueOf(128)将基本类型自动转换为包装类的过程称为自动包装(autoboxing)。

public static Integer valueOf(int i) {if (i >= IntegerCache.low && i <= IntegerCache.high)return IntegerCache.cache[i   (-IntegerCache.low)];return new Integer(i);} 

在 Integer 中引入了 IntegerCache 缓存一定范围的值,IntegerCache 默认情况下,范围为-128~127。

本题中的 127 命中了 IntegerCache,所以 c 和 d 和 128 没有命中,所以 a 和 b 是不同的对象。

但是这个缓存范围时可以修改的,可能有些人不知道。可以通过JVM启动参数:-XX:AutoBoxCacheMax= 修改上限值,如下图所示:

图片上传失败…(image-c42f3b-1656755130127)]

2 << 3。

高级:通常可以认为位置操作是性能最高的。然而,事实上,编译器现在非常聪明,许多指令编译器可以自己优化。因此,在实践中,我们不需要特别追求实用的位置操作,这不仅会导致代码可读性差,而且一些自我智能优化会误导编译器,使编译器无法更好地优化。

&&:逻辑和操作符。操作符左右两侧的表达式为 true,才返回 true。同时,如果第一个表达式是短路的话 false,则直接返回 false。

&:逻辑与运算符,按位与运算符。

按位和计算符:用于二进制计算,只有对应的两个二进位为1,结果位为1 ,否则为0。

逻辑与操作符:& 逻辑与时,和 && 区别在于没有短路。逻辑和操作符通常用于使用 &&,而 & 更适合于位操作。

答:不是。Java 只有8种基本数据类型:byte、short、int、long、float、double、char、boolean;除基本类型外(primitive type),其余的都是引用类型(reference type)。

基本数据类型:数据直接存储在栈上

引用数据类型的差异:数据存储在堆上,引用地址只存储在堆上

不行。String 类使用 final 修饰,不能继承。

String:String 值创建后不能修改,任何对 String 修改会导致新的 String 对象的生成。

StringBuffer:跟 String 类似,但是值可以被修改,使用 synchronized 确保线程安全。

StringBuilder:StringBuffer 非线程安全版本未使用 synchronized,性能更高,建议优先使用。

一两个。如果字符串常量池已经存在xyz,是一个;否则,两个。

没有字符创常量池 “xyz”,此时会创建如下两个对象:

一是字符串字面量 “xyz” 相应的,停留(intern)在全球共享的字符串常量池中,这个例子也在堆中,字符串常量池只被引用。

另一个是通 new String() 创建并初始化的,内容与"xyz"相同的实例,也是在堆中。

两个语句都会先去字符串常量池中检查是否已经存在 “xyz”,如果有则直接使用,如果没有则会在常量池中创建 “xyz” 对象。

另外,String s = new String(“xyz”) 还会通过 new String() 在堆里创建一个内容与 “xyz” 相同的对象实例。

所以前者其实理解为被后者的所包含。

==:运算符,用于比较基础类型变量和引用类型变量。

对于基础类型变量,比较的变量保存的值是否相同,类型不一定要相同。

short s1 = 1; long l1 = 1;// 结果:true。类型不同,但是值相同System.out.println(s1 == l1);

对于引用类型变量,比较的是两个对象的地址是否相同。

Integer i1 = new Integer(1);Integer i2 = new Integer(1);// 结果:false。通过new创建,在内存中指向两个不同的对象System.out.println(i1 == i2);

equals:Object 类中定义的方法,通常用于比较两个对象的值是否相等。

equals 在 Object 方法中其实等同于 ==,但是在实际的使用中,equals 通常被重写用于比较两个对象的值是否相同。

Integer i1 = new Integer(1);Integer i2 = new Integer(1);// 结果:true。两个不同的对象,但是具有相同的值System.out.println(i1.equals(i2)); // Integer的equals重写方法public boolean equals(Object obj) {    if (obj instanceof Integer) {        // 比较对象中保存的值是否相同        return value == ((Integer)obj).intValue();    }    return false;}

不对。hashCode() 和 equals() 之间的关系如下:

当有 a.equals(b) == true 时,则 a.hashCode() == b.hashCode() 必然成立,

反过来,当 a.hashCode() == b.hashCode() 时,a.equals(b) 不一定为 true。

反射是指在运行状态中,对于任意一个类都能够知道这个类所有的属性和方法;并且对于任意一个对象,都能够调用它的任意一个方法;这种动态获取信息以及动态调用对象方法的功能称为反射机制。

数据分为基本数据类型和引用数据类型。基本数据类型:数据直接存储在栈中;引用数据类型:存储在栈中的是对象的引用地址,真实的对象数据存放在堆内存里。

浅拷贝:对于基础数据类型:直接复制数据值;对于引用数据类型:只是复制了对象的引用地址,新旧对象指向同一个内存地址,修改其中一个对象的值,另一个对象的值随之改变。

深拷贝:对于基础数据类型:直接复制数据值;对于引用数据类型:开辟新的内存空间,在新的内存空间里复制一个一模一样的对象,新老对象不共享内存,修改其中一个对象的值,不会影响另一个对象。

深拷贝相比于浅拷贝速度较慢并且花销较大。

并发:两个或多个事件在同一时间间隔发生。

并行:两个或者多个事件在同一时刻发生。

并行是真正意义上,同一时刻做多件事情,而并发在同一时刻只会做一件事件,只是可以将时间切碎,交替做多件事情。

网上有个例子挺形象的:

你吃饭吃到一半,电话来了,你一直到吃完了以后才去接,这就说明你不支持并发也不支持并行。

你吃饭吃到一半,电话来了,你停了下来接了电话,接完后继续吃饭,这说明你支持并发。

你吃饭吃到一半,电话来了,你一边打电话一边吃饭,这说明你支持并行。

Constructor 不能被 override(重写),但是可以 overload(重载),所以你可以看到⼀个类中有多个构造函数的情况。

值传递。Java 中只有值传递,对于对象参数,值的内容是对象的引用。

public class Demo {    /**     * 静态变量:又称类变量,static修饰     */    public static String STATIC_VARIABLE = "静态变量";    /**     * 实例变量:又称成员变量,没有static修饰     */    public String INSTANCE_VARIABLE = "实例变量";}

成员变量存在于堆内存中。静态变量存在于方法区中。

成员变量与对象共存亡,随着对象创建而存在,随着对象被回收而释放。静态变量与类共存亡,随着类的加载而存在,随着类的消失而消失。

成员变量所属于对象,所以也称为实例变量。静态变量所属于类,所以也称为类变量。

成员变量只能被对象所调用 。静态变量可以被对象调用,也可以被类名调用。

区分两种情况,发出调用时是否显示创建了对象实例。

1)没有显示创建对象实例:不可以发起调用,非静态方法只能被对象所调用,静态方法可以通过对象调用,也可以通过类名调用,所以静态方法被调用时,可能还没有创建任何实例对象。因此通过静态方法内部发出对非静态方法的调用,此时可能无法知道非静态方法属于哪个对象。

public class Demo {    public static void staticMethod() {        // 直接调用非静态方法:编译报错        instanceMethod();    }    public void instanceMethod() {        System.out.println("非静态方法");    }}

2)显示创建对象实例:可以发起调用,在静态方法中显示的创建对象实例,则可以正常的调用。

public class Demo {    public static void staticMethod() {        // 先创建实例对象,再调用非静态方法:成功执行        Demo demo = new Demo();        demo.instanceMethod();    }    public void instanceMethod() {        System.out.println("非静态方法");    }}

public class InitialTest {    public static void main(String[] args) {        A ab = new B();        ab = new B();    }}class A {    static { // 父类静态代码块        System.out.print("A");    }    public A() { // 父类构造器        System.out.print("a");    }}class B extends A {    static { // 子类静态代码块        System.out.print("B");    }    public B() { // 子类构造器        System.out.print("b");    }}

执行结果:ABabab,两个考察点:

1)静态变量只会初始化(执行)一次。

2)当有父类时,完整的初始化顺序为:父类静态变量(静态代码块)->子类静态变量(静态代码块)->父类非静态变量(非静态代码块)->父类构造器 ->子类非静态变量(非静态代码块)->子类构造器 。

方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。

重载:一个类中有多个同名的方法,但是具有有不同的参数列表(参数类型不同、参数个数不同或者二者都不同)。

重写:发生在子类与父类之间,子类对父类的方法进行重写,参数都不能改变,返回值类型可以不相同,但是必须是父类返回值的派生类。即外壳不变,核心重写!重写的好处在于子类可以根据需要,定义特定于自己的行为。

如果我们有两个方法如下,当我们调用:test(1) 时,编译器无法确认要调用的是哪个。

// 方法1int test(int a);// 方法2long test(int a);

方法的返回值只是作为方法运行之后的一个“状态”,但是并不是所有调用都关注返回值,所以不能将返回值作为重载的唯一区分条件。

抽象类只能单继承,接口可以多实现。

抽象类可以有构造方法,接口中不能有构造方法。

抽象类中可以有成员变量,接口中没有成员变量,只能有常量(默认就是 public static final)

抽象类中可以包含非抽象的方法,在 Java 7 之前接口中的所有方法都是抽象的,在 Java 8 之后,接口支持非抽象方法:default 方法、静态方法等。Java 9 支持私有方法、私有静态方法。

抽象类中的抽象方法类型可以是任意修饰符,Java 8 之前接口中的方法只能是 public 类型,Java 9 支持 private 类型。

设计思想的区别:

接口是自上而下的抽象过程,接口规范了某些行为,是对某一行为的抽象。我需要这个行为,我就去实现某个接口,但是具体这个行为怎么实现,完全由自己决定。

抽象类是自下而上的抽象过程,抽象类提供了通用实现,是对某一类事物的抽象。我们在写实现类的时候,发现某些实现类具有几乎相同的实现,因此我们将这些相同的实现抽取出来成为抽象类,然后如果有一些差异点,则可以提供抽象方法来支持自定义实现。

我在网上看到有个说法,挺形象的:

普通类像亲爹 ,他有啥都是你的。

抽象类像叔伯,有一部分会给你,还能指导你做事的方法。

接口像干爹,可以给你指引方法,但是做成啥样得你自己努力实现。

Error 和 Exception 都是 Throwable 的子类,用于表示程序出现了不正常的情况。区别在于:

Error 表示系统级的错误和程序不必处理的异常,是恢复不是不可能但很困难的情况下的一种严重问题,比如内存溢出,不可能指望程序能处理这样的情况。

Exception 表示需要捕捉或者需要程序进行处理的异常,是一种设计或实现问题,也就是说,它表示如果程序运行正常,从不会发生的情况。

修饰类:该类不能再派生出新的子类,不能作为父类被继承。因此,一个类不能同时被声明为abstract 和 final。

修饰方法:该方法不能被子类重写。

修饰变量:该变量必须在声明时给定初值,而在以后只能读取,不可修改。 如果变量是对象,则指的是引用不可修改,但是对象的属性还是可以修改的。

public class FinalDemo {    // 不可再修改该变量的值    public static final int FINAL_VARIABLE = 0;    // 不可再修改该变量的引用,但是可以直接修改属性值    public static final User USER = new User();    public static void main(String[] args) {        // 输出:User(id=0, name=null, age=0)        System.out.println(USER);        // 直接修改属性值        USER.setName("test");        // 输出:User(id=0, name=test, age=0)        System.out.println(USER);    }}

27、阐述 final、finally、finalize 的区别。

其实是三个完全不相关的东西,只是长的有点像。。

final 如上所示。

finally:finally 是对 Java 异常处理机制的最佳补充,通常配合 try、catch 使用,用于存放那些无论是否出现异常都一定会执行的代码。在实际使用中,通常用于释放、数据库连接等资源,把资源释放方法放到 finally 中,可以大大降低程序出错的几率。

finalize:Object 中的方法,在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。finalize()方法仅作为了解即可,在 Java 9 中该方法已经被标记为废弃,并添加新的 java.lang.ref.Cleaner,提供了更灵活和有效的方法来释放资源。这也侧面说明了,这个方法的设计是失败的,因此更加不能去使用它。

public class TryDemo {    public static void main(String[] args) {        System.out.println(test());    }    public static int test() {        try {            return 1;        } catch (Exception e) {            return 2;        } finally {            System.out.print("3");        }    }}

执行结果:31。

相信很多同学应该都做对了,try、catch。finally 的基础用法,在 return 前会先执行 finally 语句块,所以是先输出 finally 里的 3,再输出 return 的 1。

public class TryDemo {    public static void main(String[] args) {        System.out.println(test1());    }    public static int test1() {        try {            return 2;        } finally {            return 3;        }    }}

执行结果:3。

这题有点先将,但也不难,try 返回前先执行 finally,结果 finally 里不按套路出牌,直接 return 了,自然也就走不到 try 里面的 return 了。

finally 里面使用 return 仅存在于面试题中,实际开发中千万不要这么用。

public class TryDemo {    public static void main(String[] args) {        System.out.println(test1());    }    public static int test1() {        int i = 0;        try {            i = 2;            return i;        } finally {            i = 3;        }    }}

执行结果:2。

这边估计有不少同学会以为结果应该是 3,因为我们知道在 return 前会执行 finally,而 i 在 finally 中被修改为 3 了,那最终返回 i 不是应该为 3 吗?确实很容易这么想,我最初也是这么想的,当初的自己还是太年轻了啊。

这边的根本原因是,在执行 finally 之前,JVM 会先将 i 的结果暂存起来,然后 finally 执行完毕后,会返回之前暂存的结果,而不是返回 i,所以即使这边 i 已经被修改为 3,最终返回的还是之前暂存起来的结果 2。

这边其实根据字节码可以很容易看出来,在进入 finally 之前,JVM 会使用 iload、istore 两个指令,将结果暂存,在最终返回时在通过 iload、ireturn 指令返回暂存的结果。

java基础一共120道,篇幅有限目前先展现30道。完整题集和答案都在我的(java八股文面试宝典)文档里了,获取方式在文末了!

二、java多线程

线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。程序员可以通过它进行多处理器编程,你可以使用多线程对 运算密集型任务提速。比如,如果一个线程完成一个任务要100毫秒,那么用十个线程完成改任务只需10毫秒。Java在语言层面对多线程提供了卓越的支 持,它也是一个很好的卖点。

线程是进程的子集,一个进程可以有很多线程,每条线程并行执行不同的任务。不同的进程使用不同的内存空间,而所有的线程共享一片相同的内存空间。别把它和栈内存搞混,每个线程都拥有单独的栈内存用来存储本地数据。

在语言层面有两种方式。java.lang.Thread 类的实例就是一个线程但是它需要调用java.lang.Runnable接口来执行,由于线程类本身就是调用的Runnable接口所以你可以继承 java.lang.Thread 类或者直接调用Runnable接口来重写run()方法实现线程。

这个问题是上题的后续,大家都知道我们可以通过继承Thread类或者调用Runnable接口来实现线程,问题是,那个方法更好呢?什么情况下使 用它?这个问题很容易回答,如果你知道Java不支持类的多重继承,但允许你调用多个接口。所以如果你要继承其他类,当然是调用Runnable接口好 了。

这个问题经常被问到,但还是能从此区分出面试者对Java线程模型的理解程度。start()方法被用来启动新创建的线程,而且start()内部 调用了run()方法,这和直接调用run()方法的效果不一样。当你调用run()方法的时候,只会是在原来的线程中调用,没有新的线程启 动,start()方法才会启动新线程。

Runnable和Callable都代表那些要在不同的线程中执行的任务。Runnable从JDK1.0开始就有了,Callable是在 JDK1.5增加的。它们的主要区别是Callable的 call() 方法可以返回值和抛出异常,而Runnable的run()方法没有这些功能。Callable可以返回装载有计算结果的Future对象。

CyclicBarrier 和 CountDownLatch 都可以用来让一组线程等待其它线程。与 CyclicBarrier 不同的是,CountdownLatch 不能重新使用。

Java内存模型规定和指引Java程序在不同的内存架构、CPU和操作系统间有确定性地行为。它在多线程的情况下尤其重要。Java内存模型对一 个线程所做的变动能被其它线程可见提供了保证,它们之间是先行发生关系。这个关系定义了一些规则让程序员在并发编程时思路更清晰。比如,先行发生关系确保 了:

  • 线程内的代码能够按先后顺序执行,这被称为程序次序规则。
  • 对于同一个锁,一个解锁操作一定要发生在时间上后发生的另一个锁定操作之前,也叫做管程锁定规则。
  • 前一个对volatile的写操作在后一个volatile的读操作之前,也叫volatile变量规则。
  • 一个线程内的任何操作必需在这个线程的start()调用之后,也叫作线程启动规则。
  • 一个线程的所有操作都会在线程终止之前,线程终止规则。
  • 一个对象的终结操作必需在这个对象构造完成之后,也叫对象终结规则。
  • 可传递性

我强烈建议大家阅读《Java并发编程实践》第十六章来加深对Java内存模型的理解。

volatile是一个特殊的修饰符,只有成员变量才能使用它。在Java并发程序缺少同步类的情况下,多线程对成员变量的操作对其它线程是透明的。volatile变量可以保证下一个读取操作会在前一个写操作之后发生,就是上一题的volatile变量规则。

如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量 的值也和预期的是一样的,就是线程安全的。一个线程安全的计数器类的同一个实例对象在被多个线程使用的情况下也不会出现计算失误。很显然你可以将集合类分 成两组,线程安全和非线程安全的。Vector 是用同步方法来实现线程安全的, 而和它相似的ArrayList不是线程安全的。

竞态条件会导致程序在并发情况下出现一些bugs。多线程对一些资源的竞争的时候就会产生竞态条件,如果首先要执行的程序竞争失败排到后面执行了, 那么整个程序就会出现一些不确定的bugs。这种bugs很难发现而且会重复出现,因为线程间的随机竞争。

Java提供了很丰富的API但没有为停止线程提供API。JDK 1.0本来有一些像stop(), suspend() 和 resume()的控制方法但是由于潜在的死锁威胁因此在后续的JDK版本中他们被弃用了,之后Java API的设计者就没有提供一个兼容且线程安全的方法来停止一个线程。当run() 或者 call() 方法执行完的时候线程会自动结束,如果要手动结束一个线程,你可以用volatile 布尔变量来退出run()方法的循环或者是取消任务来中断线程。

这是我在一次面试中遇到的一个很刁钻的Java面试题, 简单的说,如果异常没有被捕获该线程将会停止执行。Thread.UncaughtExceptionHandler是用于处理未捕获异常造成线程突然中 断情况的一个内嵌接口。当一个未捕获异常将造成线程中断的时候JVM会使用Thread.getUncaughtExceptionHandler()来 查询线程的UncaughtExceptionHandler并将线程和异常作为参数传递给handler的uncaughtException()方法 进行处理。

你可以通过共享对象来实现这个目的,或者是使用像阻塞队列这样并发的数据结构。这篇教程《Java线程间通信》(涉及到在两个线程间共享对象)用wait和notify方法实现了生产者消费者模型。

这又是一个刁钻的问题,因为多线程可以等待单监控锁,Java API 的设计人员提供了一些方法当等待条件改变的时候通知它们,但是这些方法没有完全实现。notify()方法不能唤醒某个具体的线程,所以只有一个线程在等 待的时候它才有用武之地。而notifyAll()唤醒所有线程并允许他们争夺锁确保了至少有一个线程能继续运行。

这是个设计相关的问题,它考察的是面试者对现有系统和一些普遍存在但看起来不合理的事物的看法。回答这些问题的时候,你要说明为什么把这些方法放在 Object类里是有意义的,还有不把它放在Thread类里的原因。一个很明显的原因是JAVA提供的锁是对象级的而不是线程级的,每个对象都有锁,通 过线程获得。如果线程需要等待某些锁那么调用对象中的wait()方法就有意义了。如果wait()方法定义在Thread类中,线程正在等待的是哪个锁 就不明显了。简单的说,由于wait,notify和notifyAll都是锁级别的操作,所以把他们定义在Object类中因为锁属于对象。

ThreadLocal是Java里一种特殊的变量。每个线程都有一个ThreadLocal就是每个线程都拥有了自己独立的一个变量,竞争条件被 彻底消除了。它是为创建代价高昂的对象获取线程安全的好方法,比如你可以用ThreadLocal让SimpleDateFormat变成线程安全的,因 为那个类创建代价高昂且每次调用都需要创建不同的实例所以不值得在局部范围使用它,如果为每个线程提供一个自己独有的变量拷贝,将大大提高效率。首先,通 过复用减少了代价高昂的对象的创建个数。其次,你在没有使用高代价的同步或者不变性的情况下获得了线程安全。线程局部变量的另一个不错的例子是 ThreadLocalRandom类,它在多线程环境中减少了创建代价高昂的Random对象的个数。

在Java并发程序中FutureTask表示一个可以取消的异步运算。它有启动和取消运算、查询运算是否完成和取回运算结果等方法。只有当运算完 成的时候结果才能取回,如果运算尚未完成get方法将会阻塞。一个FutureTask对象可以对调用了Callable和Runnable的对象进行包 装,由于FutureTask也是调用了Runnable接口所以它可以提交给Executor来执行。

interrupted() 和 isInterrupted()的主要区别是前者会将中断状态清除而后者不会。Java多线程的中断机制是用内部标识来实现的,调用Thread.interrupt()来中断一个线程就会设置中断标识为true。当中断线程调用静态方法Thread.interrupted()来 检查中断状态时,中断状态会被清零。而非静态方法isInterrupted()用来查询其它线程的中断状态且不会改变中断状态标识。简单的说就是任何抛 出InterruptedException异常的方法都会将中断状态清零。无论如何,一个线程的中断状态有有可能被其它线程调用中断来改变。

主要是因为Java API强制要求这样做,如果你不这么做,你的代码会抛出IllegalMonitorStateException异常。还有一个原因是为了避免wait和notify之间产生竞态条件。

处于等待状态的线程可能会收到错误警报和伪唤醒,如果不在循环中检查等待条件,程序就会在没有满足结束条件的情况下退出。因此,当一个等待线程醒来 时,不能认为它原来的等待状态仍然是有效的,在notify()方法调用之后和等待线程醒来之前这段时间它可能会改变。这就是在循环中使用wait()方 法效果更好的原因,你可以在Eclipse中创建模板调用wait和notify试一试。如果你想了解更多关于这个问题的内容,我推荐你阅读《Effective Java》这本书中的线程和同步章节。

同步集合与并发集合都为多线程和并发提供了合适的线程安全的集合,不过并发集合的可扩展性更高。在Java1.5之前程序员们只有同步集合来用且在 多线程并发的时候会导致争用,阻碍了系统的扩展性。Java5介绍了并发集合像ConcurrentHashMap,不仅提供线程安全还用锁分离和内部分 区等现代技术提高了可扩展性。

为什么把这个问题归类在多线程和并发面试题里?因为栈是一块和线程紧密相关的内存区域。每个线程都有自己的栈内存,用于存储本地变量,方法参数和栈 调用,一个线程中存储的变量对其它线程是不可见的。而堆是所有线程共享的一片公用内存区域。对象都在堆里创建,为了提升效率线程会从堆中弄一个缓存到自己 的栈,如果多个线程使用该变量就可能引发问题,这时volatile 变量就可以发挥作用了,它要求线程从主存中读取变量的值。

创建线程要花费昂贵的资源和时间,如果任务来了才创建线程那么响应时间会变长,而且一个进程能创建的线程数有限。为了避免这些问题,在程序启动的时 候就创建若干线程来响应处理,它们被称为线程池,里面的线程叫工作线程。从JDK1.5开始,Java API提供了Executor框架让你可以创建不同的线程池。比如单线程池,每次处理一个任务;数目固定的线程池或者是缓存线程池(一个适合很多生存期短 的任务的程序的可扩展线程池)。

java多线程一共66道,篇幅有限目前先展现24道。完整题集和答案都在我的(java八股文面试宝典)文档里了,获取方式在文末了!

三、Mysql

(1) 如果表的类型是 MyISAM,那么是 18

因为 MyISAM 表会把自增主键的最大 ID 记录到数据文件里,重启 MySQL 自增主键的最大ID 也不会丢失

(2)如果表的类型是 InnoDB,那么是 15

InnoDB 表只是把自增主键的最大 ID 记录到内存中,所以重启数据库或者是对表进OPTIMIZE 操作,都会导致最大 ID 丢失

Mysql 数据库软件是一个客户端或服务器系统,其中包括:支持各种客户端程序和库的多线程 SQL 服务器、不同的后端、广泛的应用程序编程接口和管理工具。

HEAP 表存在于内存中,用于临时高速存储。

BLOB 或 TEXT 字段是不允许的

只能使用比较运算符=,<,>,=>,= <

HEAP 表不支持 AUTO_INCREMENT

索引不可为 NULL

Mysql 服务器的默认端口是 3306。

Mysql 是开源软件,随时可用,无需付费。

Mysql 是便携式的

带有命令提示符的 GUI。

使用 Mysql 查询浏览器支持管理

以下是 FLOAT 和 DOUBLE 的区别:

1)浮点数以 8 位精度存储在 FLOAT 中,并且有四个字节。

2)浮点数存储在 DOUBLE 中,精度为 18 位,有八个字节。

CHAR_LENGTH 是字符数,而 LENGTH 是字节数。Latin 字符的这两个数据是相同的,但是对于 Unicode 和其他编码,它们是不同的。

SQL 标准定义的四个隔离级别为:

  • read uncommited :读到未提交数据
  • read committed:脏读,不可重复读
  • repeatable read:可重读
  • serializable :串行事物

ENUM 是一个字符串对象,用于指定一组预定义的值,并在创建表时使用

Create table size(name ENUM('Smail,‘Medium’,‘Large’);

REGEXP 是模式匹配,其中匹配模式在搜索值的任何位置。

以下是 CHAR 和 VARCHAR 的区别:

CHAR 和 VARCHAR 类型在存储和检索方面有所不同

CHAR 列长度固定为创建表时声明的长度,长度值范围是 1 到 255

当 CHAR 值被存储时,它们被用空格填充到特定长度,检索 CHAR 值时需删除尾随空格。

字符串类型是:

  • SET
  • BLOB
  • ENUM
  • CHAR
  • TEXT
  • VARCHAR

SELECT VERSION();用于获取当前 Mysql 的版本。

存储引擎称为表类型,数据使用各种技术存储在文件中。

技术涉及:

  • Storage mechanism
  • Locking levels
  • Indexing
  • Capabilities and functions.

以下是 Mysql 中可用的驱动程序:

  • PHP 驱动程序
  • JDBC 驱动程序
  • ODBC 驱动程序
  • CWRAPPER
  • PYTHON 驱动程序
  • PERL 驱动程序
  • RUBY 驱动程序
  • CAP11PHP 驱动程序
  • Ado.net5.mxj

创建表时 TIMESTAMP 列用 Zero 更新。只要表中的其他字段发生更改,UPDATE CURRENT_TIMESTAMP 修饰符就将时间戳字段更新为当前时间。

表格的每一行都由主键唯一标识,一个表只有一个主键。

主键也是候选键。按照惯例,候选键可以被指定为主键,并且可以用于任何外键引用。

我们可以通过以下命令登录:

[mysql dir]/bin/mysql -h hostname -u

它用来压缩 MyISAM 表,这减少了磁盘或内存使用。

Heal 表的大小可通过称为 max_heap_table_size 的 Mysql 配置变量来控制。

在 MyISAM Static 上的所有字段有固定宽度。动态 MyISAM 表将具有像 TEXT,BLOB 等字段,以适应不同长度的数据类型。

MyISAM Static 在受损情况下更容易恢复。

federated 表,允许访问位于其他服务器数据库上的表。

每当行被更改时,时间戳字段将获取当前时间戳。

它会停止递增,任何进一步的插入都将产生错误,因为密钥已被使用。

LAST_INSERT_ID 将返回由 Auto_increment 分配的最后一个值,并且不需要指定表名称。

索引是通过以下方式为表格定义的:SHOW INDEX FROM

%对应于 0 个或更多字符,_只是 LIKE 语句中的一个字符。

UNIX_TIMESTAMP 是从 Mysql 时间戳转换为 Unix 时间戳的命令

FROM_UNIXTIME 是从 Unix 时间戳转换为 Mysql 时间戳的命令

在 SELECT 语句的列比较中使用=,<>,<=,<,> =,>,<<,>>,<=>,AND,OR 或 LIKE 运算符。

Mysql一共55道,篇幅有限目前先展现30道。完整题集和答案都在我的(java八股文面试宝典)文档里了,获取方式在文末了!

四、java微服务

在微服务中,SpringCloud是一个提供与外部系统集成的系统。它是一个敏捷的框架,可以短平快构建应用程序。与有限数量的数据处理相关联,它在微服务体系结构中起着非常重要的作用。

以下为 Spring Cloud 的核心特性:

Spring boot是微服务面试问题的主要话题。

随着新功能的加入,Spring变得越来越复杂。无论何时启动新项目,都必须添加新的构建路径或Maven依赖项。简而言之,你需要从头开始做每件事。Spring Boot是一种帮助您避免所有代码配置的解决方案。

这可以通过在application.properties文件中指定属性来完成。

例如,在Spring MVC应用程序中,您必须指定后缀和前缀。这可以通过在application.properties文件中输入下面提到的属性来完成。

对于后缀 - spring.mvc.view.suffix: .jsp

对于前缀 - spring.mvc.view.prefix: /WEB-INF/

它是最重要的功能之一,可帮助您访问在生产环境中运行的应用程序的当前状态。有多个指标可用于检查当前状态。它们还为RESTful Web服务提供端点,可以简单地用于检查不同的度量标准。

实施需要最少的配置。您需要做的就是spring-boot-starter-security在pom.xml文件中添加starter。您还需要创建一个Spring配置类,它将覆盖所需的方法,同时扩展 WebSecurityConfigurerAdapter 应用程序中的安全性。这是一些示例代码:

package com.gkatzioura.security.securityendpoints.config;import org.springframework.context.annotation.Configuration;import org.springframework.security.config.annotation.web.builders.HttpSecurity;import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;@Configurationpublic class SecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/welcome").permitAll().anyRequest().authenticated().and().formLogin().permitAll().and().logout().permitAll();}}

无论何时创建Java应用程序,都可以通过两种方法进行部署:

  • 使用外部的应用程序容器。
  • 将容器嵌入jar文件中。
  • Spring Boot包含Jetty,Tomcat和Undertow服务器,所有服务器都是嵌入式的。
  • Jetty - 用于大量项目,Eclipse Jetty可以嵌入到框架,应用程序服务器,工具和集群中。
  • Tomcat - Apache Tomcat是一个开源JavaServer Pages实现,可以很好地与嵌入式系统配合使用。
  • Undertow - 一个灵活而突出的Web服务器,它使用小型单一处理程序来开发Web服务器。

端到端测试 验证工作流中的所有流程,以检查一切是否按预期工作。它还确保系统以统一的方式工作,从而满足业务需求。

它结合了对整个应用程序的监控以及自动化测试。语义监控的主要好处是找出对您的业务更有利可图的因素。

从业务角度来看,语义监控以及服务层监控可以监控微服务。一旦检测到问题,它们就可以实现更快的隔离和 错误分类,从而减少修复所需的主要时间。它对服务层和事务层进行分类,以确定受可用性或性能不佳影响的事务。

有多种方法可以设置服务发现。我将选择我认为效率最高的那个,Netflix的Eureka。这是一个简单的程序,不会对应用程序造成太大影响。此外,它支持多种类型的Web应用程序。

Eureka配置包括两个步骤 - 客户端配置和服务器配置。

使用属性文件可以轻松完成客户端配置。在clas spath中,Eureka搜索一个eureka-client.properties文件。它还搜索由特定于环境的属性文件中的环境引起的覆盖。

对于服务器配置,您必须首先配置客户端。完成后,服务器启动一个客户端,该客户端用于查找其他服务器。。默认情况下,Eureka服务器使用客户端配置来查找对等服务器。

这是一个非常常见的微服务面试问题,你应该准备好了!微服务架构提供了许多优点。这里有几个:

  • 微服务可以轻松适应其他框架或技术。
  • 单个进程的失败不会影响整个系统。
  • 为大企业和小型团队提供支持。
  • 可以在相对较短的时间内独立部署。

报告和仪表板主要用于监视和维护微服务。有多种工具可以帮助实现此目的。报告 和仪表板可用于:

  • 找出哪些微服务公开了哪些资源。
  • 找出组件发生变化时受影响的服务。
  • 提供一个简单的点,只要需要文档,就可以访问它。
  • 部署的组件的版本。

我见过许多开发者在这个问题上摸索。毕竟,在面试微服务架构师角色时,他们会被问到这个问题,所以承认它的缺点可能有点棘手。以下是一些很好的答案:

它们需要大量协作 - 微服务需要大量的合作。不同的微服务模块,可能分散在不同的团队,团队之间需要始终保持良好的同步。

他们需要建立繁重的架构 - 系统是分布式的,架构涉及很多。

他们需要过多的计划来处理操作开销 - 如果您计划使用微服务架构,则需要为操作开销做好准备。

需要熟练的专业人员,他们可以支持异构分布的微服务。

PACT是一个开源工具。它有助于测试消费者和服务提供商之间的互动。消费者服务开发人员首先编写一个测试,该测试定义了与服务提供者的交互模式。测试包括提供者的状态,请求正文和预期的响应。基于此,PACT创建了一个针对其执行测试的存根。输出存储在JSON文件中。

主要关注核心领域逻辑。基于领域的模型检测复杂设计。这涉及与公司层面领域方面的专家定期合作,以解决与领域相关的问题并改进应用程序的模型。在回答这个微服务面试问题时,您还需要提及DDD的核心基础知识。他们是:

DDD主要关注领域逻辑和领域本身。

复杂的设计完全基于领域的模型。

为了改进模型的设计并解决任何新出现的问题,DDD不断与公司领域方面的专家合作。

组件之间依赖关系强度的度量被认为是耦合。一个好的设计总是被认为具有高内聚力和低耦合性。

面试官经常会问起凝聚力。它也是另一个测量单位。更像是一个模块内部的元素保持结合的程度。

必须记住,设计微服务的一个重要关键是低耦合和高内聚的组合。当低耦合时,服务对其他服务的依赖很少。这样可以保持服务的完整性。在高内聚性中,将所有相关逻辑保存在服务中成为可能。否则,服务将尝试彼此通信,从而影响整体性能。

开放授权协议,这允许通过在HTTP服务上启用客户端应用程序(例如第三方提供商Facebook,GitHub等)来访问资源所有者的资源。因此,您可以在不使用其凭据的情况下与另一个站点共享存储在一个站点上的资源。

OAuth允许像Facebook这样的第三方使用最终用户的帐户信息,同时保证其安全(不使用或暴露用户的密码)。它更像是代表用户的中介,同时为服务器提供访问所需信息的令牌。

要管理基于微服务的应用程序,容器是最简单的选择。它帮助用户单独部署和开发。您还可以使用Docker将微服务封装到容器的镜像中。没有任何额外的依赖或工作,微服务可以使用这些元素。

另一个经常被问到的微服务面试问题是如何访问RESTful微服务?你可以通过两种方法做到这一点:

使用负载平衡的REST模板。

使用多个微服务。

说到缺点,这里是另一个微服务面试问题,将围绕测试微服务时面临的挑战。

在开始编写集成测试的测试用例之前,测试人员应该全面了解对所有入站和出站过程。当独立的团队正在开发不同的功能时,协作可能会被证明是一项非常困难的任务。很难找到空闲时间窗口来执行完整的回归测试。随着微服务数量的增加,系统的复杂性也随之增加。在从单片架构过渡期间,测试人员必须确保组件之间的内部通信没有中断。

不仅在开发上,而且在方面流程也经常发生错误。一些常见错误是:

  • 通常开发人员无法概述当前的挑战。
  • 重写已经存在的程序。
  • 职责、时间线和界限没有明确定义。
  • 未能从一开始就实施和确定自动化的范围。

微服务一共38道,篇幅有限目前先展现20道。完整题集和答案都在我的(java八股文面试宝典)文档里了,获取方式在文末了!

五、jvm

1. 这里涉及到 -XX:TargetSurvivorRatio 参数,Survivor 区的目标使用率默认 50,即 Survivor 区对象目标使用率为 50%。

2. Survivor 区相同年龄所有对象大小的总和 (Survivor 区内存大小 * 这个目标使用率)时,大于或等于该年龄的对象直接进入老年代。

3. 当然,这里还需要考虑参数 -XX:MaxTenuringThreshold 晋升年龄最大阈值

遇到 new、getstatic、putstatic 或 invokestatic 字节码指令时,还未初始化。典型场景包括 new 实例化对象、读取或设置静态字段、调用静态方法。

对类反射调用时,还未初始化。

初始化类时,父类还未初始化。

虚拟机启动时,会先初始化包含 main 方法的主类。使用 JDK7 的动态语言支持时,如果 MethodHandle 实例的解析结果为指定类型的方法句柄且句柄对应的类还未初始化。

口定义了默认方法,如果接口的实现类初始化,接口要在其之前初始化。其余所有引用类型的方式都不会触发初始化,称为被动引用。被动引用实例:① 子类使用父类的静态字段时,只有父类被初始化。② 通过数组定义使用类。③ 常量在编译期会存入调用类的常量池,不会初始化定义常量的类。

接口和类加载过程的区别:初始化类时如果父类没有初始化需要初始化父类,但接口初始化时不要求父接口初始化,只有在真正使用父接口时(如引用接口中定义的常量)才会初始化。

GC 是垃 圾收 集的 意思 ,内存 处理 是编 程人 员容 易出 现问 题的 地方 ,忘记 或者 错误的内 存回 收会 导致 程序 或系 统的 不稳 定甚 至崩 溃, Java 提供 的 GC 功能 可以 自动监测 对象 是否 超过 作用 域从 而达 到自 动回 收内 存的 目的 ,Java 语言 没有 提供 释放已分配内存的 显示 操作 方法 。Java 程序 员不 用担 心内 存管 理, 因为 垃圾 收集 器会自动 进行 管理 。要 请求 垃圾 收集 ,可 以调 用下 面的 方法 之一 :System.gc() 或Runtime.getRuntime().gc() ,但 JVM 可以 屏蔽 掉线 示的 垃圾 回收 调用 。

垃圾回收可以有效的防止内存泄露,有效的使用可以使用的内存。垃圾回收器通常是作为一个单独的低优先级的线程运行,不可预知的情况下对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收,程序员不能实时的调用垃圾回收器对某个对象或所有对象进行垃圾回收。在 Java 诞生初期,垃圾回收是 Java最大的亮点之一,因为服务器端的编程需要有效的防止内存泄露问题,然而时过境迁,如今 Java 的垃圾回收机制已经成为被诟病的东。移动智能终端用户通常觉得 iOS 的系统比 Android 系统有更好的用户体验,其中一个深层次的原因就在于 Android 系统中垃圾回收的不可预知性。

MinorGC在年轻代空间不足的时候发生,MajorGC指的是老年代的GC,出现MajorGC一般经常伴有MinorGC。

FullGC有三种情况。

1、 当老年代无法再分配内存的时候

2、 元空间不足的时候

3、 显示调用System.gc的时候。另外,像CMS一类的垃圾回收器,在MinorGC出现promotion failure的时候也会发生FullGC

它的意思是,除了顶层的启动类加载器以外,其余的类加载器,在加载之前,都会委派给它的父加载器进行加载。这样一层层向上传递,直到祖先们都无法胜任,它才会真正的加载。

Java默认是这种行为。当然Java中也有很多打破双亲行为的骚操作,比如SPI(JDBC驱动加载),OSGI等。

当对象对当前使用这个对象的应用程序变得不可触及的时候,这个对象就可以被回收了。

1、 Tomcat可以加载自己目录下的class文件,并不会传递给父类的加载器。

2、 Java的SPI,发起者是 BootstrapClassLoader, BootstrapClassLoader已经是最上层的了。它直接获取了 AppClassLoader进行驱动加载,和双亲委派是相反的。。

1、 部分对象会在From和To区域中复制来复制去,如此交换15次(由JVM参数MaxTenuringThreshold决定,这个参数默认是15),最终如果还是存活,就存入到老年代。

2、 如果对象的大小大于Eden的二分之一会直接分配在old,如果old也分配不下,会做一次majorGC,如果小于eden的一半但是没有足够的空间,就进行minorgc也就是新生代GC。

3、 minor gc后,survivor仍然放不下,则放到老年代

4、 动态年龄判断 ,大于等于某个年龄的对象超过了survivor空间一半 ,大于等于某个年龄的对象直接进入老年代

除了数据运行区,其他区域均有可能造成 OOM 的情况。

堆溢出: java.lang.OutOfMemoryError: Java heap space

栈溢出: java.lang.StackOverflowError

永久代溢出: java.lang.OutOfMemoryError: PermGen space

JVM 类加载机制分为五个部分:加载,验证,准备,解析,初始化。

加载

加载是类加载过程中的一个阶段, 这个阶段会在内存中生成一个代表这个类的 java.lang.Class 对象, 作为方法区这个类的各种数据的入口。注意这里不一定非得要从一个 Class 文件获取,这里既可以从 ZIP 包中读取(比如从 jar 包和 war 包中读取),也可以在运行时计算生成(动态代理),也可以由其它文件生成(比如将 JSP 文件转换成对应的 Class 类)。

验证

这一阶段的主要目的是为了确保 Class 文件的字节流中包含的信息是否符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。

准备

准备阶段是正式为类变量分配内存并设置类变量的初始值阶段,即在方法区中分配这些变量所使用的内存空间。注意这里所说的初始值概念,比如一个类变量定义为:

实际上变量 v 在准备阶段过后的初始值为 0 而不是 8080, 将 v 赋值为 8080 的 put static 指令是程序被编译后, 存放于类构造器方法之中。

但是注意如果声明为:

public static final int v = 8080;

在编译阶段会为 v 生成 ConstantValue 属性,在准备阶段虚拟机会根据 ConstantValue 属性将 v赋值为 8080。

解析

解析阶段是指虚拟机将常量池中的符号引用替换为直接引用的过程。符号引用就是 class 文件中的

public static int v = 8080;

实际上变量 v 在准备阶段过后的初始值为 0 而不是 8080, 将 v 赋值为 8080 的 put static 指令是程序被编译后, 存放于类构造器方法之中。但是注意如果声明为:

在编译阶段会为 v 生成 ConstantValue 属性,在准备阶段虚拟机会根据 ConstantValue 属性将 v赋值为 8080。

解析

解析阶段是指虚拟机将常量池中的符号引用替换为直接引用的过程。符号引用就是 class 文件中的

public static final int v = 8080;

在编译阶段会为 v 生成 ConstantValue 属性,在准备阶段虚拟机会根据 ConstantValue 属性将 v赋值为 8080。

解析

解析阶段是指虚拟机将常量池中的符号引用替换为直接引用的过程。符号引用就是 class 文件中的:

1、 CONSTANT_Class_info

2、 CONSTANT_Field_info

3、 CONSTANT_Method_info

等类型的常量。

符号引用

符号引用与虚拟机实现的布局无关, 引用的目标并不一定要已经加载到内存中。各种虚拟机实现的内存布局可以各不相同,但是它们能接受的符号引用必须是一致的,因为符号引用的字面量形式明确定义在 Java 虚拟机规范的 Class 文件格式中。

直接引用

直接引用可以是指向目标的指针,相对偏移量或是一个能间接定位到目标的句柄。如果有了直接引用,那引用的目标必定已经在内存中存在。

初始化

初始化阶段是类加载最后一个阶段,前面的类加载阶段之后,除了在加载阶段可以自定义类加载器以外,其它操作都由 JVM 主导。到了初始阶段,才开始真正执行类中定义的 Java 程序代码。

标签: 紧凑型高速连接器

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台