JAVA基础知识点

1)面向对象的特性有哪些?

答:封装继承多态

  • 封装 : 封装是指将对象的实现细节隐藏起来,然后通过公共的方法来向外暴露出该对象的功能。 使用封装不仅仅安全,更可以简化操作
  • 继承 : 继承是面向对象实现软件复用的重要手段,当子类继承父类后,子类是一种特殊的父类,能直接或间接获得父类里的成员。
      继承的缺点:1)继承是一种强耦合关系,父类变子类也必须变;2)继承破坏了封装,对于父类而言,它的实现细节对子类来说都是透明的。
  • 多态简而言之就是同一个行为具有多个不同表现形式或形态的能力。

多态的条件:1)继承;2)重写;3)向上转型。
多态的好处:当把不同的子类对象都当作父类类型来看,可以屏蔽不同子类对象之间的实现差异,从而写出通用的代码达到通用编程,以适应需求的不断变化。

正是这三个特性让面向对象拥有了各种设计模式。

2)面向对象和面向过程的区别?

答:
面向过程就像是用泥巴制造一个机器,上面怎么捏,中间怎么捏,下面怎么捏,最后机器捏好了,但是问题就是
 1)机器不好修,坏了就要用胶水粘(难以维护)
 2)再想改机器的就不太容易(可扩展性差
 3)一次性很难以把机器捏好(设计不够直观)
面向对象就不一样了,它也是制作一个机器,但是他把机器分为各个模块,各个零件,然后最后组装起来,这样的话就有几个优点了:
 1)机器好修(容易维护)
 2)机器可以很好的加新的功能(可扩展性好)
 3)机器的零件(比如螺丝)还可以在其他地方应用(可复用性强)
 4)机器可以很好地制作(设计直观)

3)JDK 和 JRE 的区别是什么?

答:Java 运行时环境(JRE-Java Runtime Environment),它包括 Java 虚拟机、Java 核心类库和支持文件,但并不包含开发工具(JDK-Java Development Kit)——编译器、调试器和其他工具。

Java 开发工具包(JDK)是完整的 Java 软件开发包,包含了 JRE,编译器和其他的工具(比如 JavaDoc, Java 调试器),可以让开发者开发、编译、执行 Java 应用程序。

4)Java 中覆盖和重载是什么意思?

答:

  • 覆盖(Override)是指子类对父类方法的一种重写,只能比父类抛出更少的异常访问权限不能比父类的小被覆盖的方法不能是 private 的否则只是在子类中重新定义了一个新方法。(因为大家知道,由于多态,父类引用会引用子类的对象,如果抛出更多的异常或者访问权限比父类小的话,就会在编程中出现一些问题)
  • 重载(Overload)表示同一个类中可以有多个名称相同的方法,但这些方法的参数列表各不相同。
    面试官: 那么构成重载的条件有哪些?
    答:参数类型不同、参数个数不同、参数顺序不同。
    面试官: 函数的返回值不同可以构成重载吗?为什么?
    答:不可以,因为 Java 中调用函数并不需要强制赋值。Java中如果方法名相同,但返回参数类型不同则会报错。
5)抽象类和接口的区别有哪些?

答:

  • 抽象类中可以没有抽象方法;接口中的方法必须是抽象方法;
  • 抽象类中可以有普通的成员变量;接口中的变量必须是 static final 类型的,必须被初始化,
  • 接口中只有常量,没有变量。
  • 抽象类只能单继承,接口可以继承多个父接口;
  • Java 8 中接口中会有 default 方法,即方法可以被实现。
    在这里插入图片描述

面试官:抽象类和接口如何选择?
答:

  • 如果要创建不带任何方法定义和成员变量的基类,那么就应该选择接口而不是抽象类。

  • 如果知道某个类应该是基类,那么第一个选择的应该是让它成为一个接口,只有在必须要有方法定义和成员变量的时候,才应该选择抽象类。因为抽象类中允许存在一个或多个被具体实现的方法,只要方法没有被全部实现该类就仍是抽象类。

6)Java 和 C++ 的区别:

答:

  • 都是面向对象的语言,都支持封装、继承和多态

  • 指针:Java不提供指针来直接访问内存,程序更加安全(最大的区别,也是JAVA被广泛应用的原因)

  • 继承: Java的类是单继承的,C++支持多重继承;Java通过一个类实现多个接口来实现C++中的多重继承; Java中类不可以多继承,但是!!!接口可以多继承

  • 内存: Java有自动内存管理机制,不需要程序员手动释放无用内存。

7)“static” 关键字是什么意思?

答:“static”(静态的) 关键字表明一个成员变量或者是成员方法可以在没有所属的类的实例变量情况下访问

静态这个词用在JAVA里面可是很精髓的,我们可以这样理解,就是只要是静态的那就是在编译时期就决定的,因为是在编译时期就解析的。也就是说是放在方法区的。

面试官:Java中是否可以覆盖(override)一个 private 或者是 static 的方法?
答:Java 中 static 方法不能被覆盖,因为方法覆盖是基于运行时动态绑定的,而 static 方法是编译时静态绑定的。static 方法跟类的任何实例都不相关,所以概念上不适用。

Java 中也不可以覆盖 private 的方法,因为 private 修饰的变量和方法只能在当前类中使用,如果是其他的类继承当前类是不能访问到 private 变量或方法的,当然也不能覆盖。

8)静态绑定和动态绑定的区别:

在Java中,当你调用一个方法时,可能会在编译时期(compile time)解析(resolve),也可能实在运行时期(runtime)解析,这完全取决于到底是一个静态方法(static method)还是一个虚方法(virtual method)。如果是在编译时期解析,那么就称之为静态绑定(static binding),如果方法的调用是在运行时期解析,那就是动态绑定(dynamic binding)或者延迟绑定(late binding)。Java是一门面向对象的编程语言,优势就在于支持多态(Polymorphism)。多态使得父类型的引用变量可以引用子类型的对象。如果调用子类型对象的一个虚方法(非private,final or static),编译器将无法找到真正需要调用的方法,因为它可能是定义在父类型中的方法,也可能是在子类型中被重写(override)的方法,这种情形,只能在运行时进行解析,因为只有在运行时期,才能明确具体的对象到底是什么。这也是我们俗称的运行时或动态绑定(runtime or dynamic binding)。另一方面,private, static和final方法将在编译时解析,因为编译器知道它们不能被重写,所有可能的方法都被定义在了一个类中,这些方法只能通过此类的引用变量进行调用。这叫做静态绑定或编译时绑定(static or compile time binding)。所有的private,static和final方法都通过静态绑定进行解析。 这两个概念的关系, 与“方法重载”(overloading,静态绑定)和“方法重写”(overriding,动态绑定)类似。动态绑定只有在重写可能存在时才会用到,而重载的方法在编译时期即可确定(这是因为它们总是定义在同一个类里面)

总而言之,其区别如下:

静态绑定在编译时期动态绑定在运行时期

静态绑定只用到类型信息,方法的解析根据引用变量的类型决定,而动态绑定则根据实际引用的的对象决定

③在java中,private static 和 final 方法都是静态绑定,只有虚方法才是动态绑定

多态是通过动态绑定实现的

9)动态绑定是如何实现的?

一个对象的多态方法的地址将被存储在该对象的方法表(method table)里面。在运行时期,调用多态方法的时候JVM会在此表中搜索方法的名字,从而获取方法的地址。 方法表里包含方法的名字和对应的地址(注意,这个地址是动态绑定的)。 这个方法表对所有属于这个类的对象而言,都是一样的,所以它会存储在Class对象中(这里对象类型以Integer为例)(在其他的语言中,这样的表又叫做vtables,虚函数表)。 需要说明的是,java语言中,如果没有添加任何关键字,则方法默认就是虚方法,任何子类都可以重写它。
方法表并不属于语言的一部分,但是会有很多种不同的实现(不同的JVM提供商可以自由选择实现的细节,只要结果保证一致就ok)。其中,Sun公司的JVM实现,则选择了将方法表入口放在对象的常量池(constant pool)里,你可以使用命令java -verbose foo来查看。(所有的属于同一个类型的对象都将拥有同一个方法表,JVM也可以将其放在别的地方)

下面,将通过一个图表实例来展示,对于某些类(这里以Integer为例)而言,是如何一步步构建方法表的。初始时,表都是空的。运行时,方法表将从最远的祖先类开始,逐步加入这些多态方法。通常,这个最远的祖先是Object类。

方法名地址注释
Object.toString111Object.toString method address
10个其他的方法

接下来,这个表中将加入第二远的祖先类的多态方法,如果已经存在,就修改其地址值。此例中,第二远的类是Number类。如果你查看了javadoc,你就会发现Number类并没有重写任何方法,只是额外多了六个方法,因而,将这六个多的方法加入表中。此时,toString项并没有被改变,方法表如下:

方法名地址注释
Object.toString111Object.toString method address
Number.intValue222Number.intValue method address
15个其他的方法

这个过程一直持续下去,直到所有的父类的多态方法都被合并进这个表里。最后,方法表会被Integer类的多态方法所更新,此时,toString方法会被重写:

方法名地址注释
Object.toString333Integer.toString method address
Number.intValue444interger.intValue method address
Integer.parseInt555Number.longValue method address
其他的一些方法

方法表中的方法名这一项,只包含最初始的类名,所谓重写,只是修改了地址栏下的值,不会改变方法名的值。所以,如果用javap指令查看多态方法名,只会显示Object.toString,而不是toString或者Integer.toString。

需要说明的是,此处,假如有Number num = new Integer(10),即便方法表里面有了Integer.parseInt方法,我们仍不能通过num来调用parseInt。也就是说,num变量引用了Integer对象,并且与上述方法表关联,但在编译时期时,编译器会根据语法规则,实行访问控制,num不能调用和访问独属于Integer的类方法,只能访问自己拥有访问权限的类方法,而这些方法中的某些方法,在运行过程中,名字未变,映射地址却发生了变化,因而调用的是所引用的子类Integer的实现。这点要弄清楚!

10)Java 是值传递还是引用传递?

一般认为,Java 内的传递都是值传递.,Java 中实例对象的传递是引用传递,Java 是值传递的!

11)JDK 中常用的包有哪些?

答:java.lang、
在这里插入图片描述
java.util [包含集合框架、遗留的 collection 类、事件模型、日期和时间设施、国际化和各种实用工具类(字符串标记生成器、随机数生成器和位数组、日期Date类、堆栈Stack类、向量Vector类等)。集合类、时间处理模式、日期时间工具等各类常用工具包]
java.iojava.net、java.sql。

12)Integer 的缓存机制

在这里插入图片描述

在这里插入图片描述

13)下述两种方法分别创建了几个 Sring 对象?
// 第一种:直接赋一个字面量
String str1 = "ABCD";
// 第二种:通过构造器创建
String str2 = new String("ABCD");

解析:考察的是对 String 对象和 JVM 内存划分的知识。

答:String str1 = “ABCD”;最多创建一个String对象,最少不创建String对象.如果常量池中,存在”ABCD”,那么str1直接引用,此时不创建String对象.否则,先在常量池先创建”ABCD”内存空间,再引用.

String str2 = new String(“ABCD”);最多创建两个String对象,至少创建一个String对象。new关键字绝对会在堆空间创建一块新的内存区域,所以至少创建一个String对象。
在这里插入图片描述

  • 当执行第一句话的时候,会在常量池中添加一个新的ABCD字符,str1指向常量池的ABCD
  • 当执行第二句话的时候,因为有new操作符,所以会在堆空间新开辟一块空间用来存储新的String对象,因为此时常量池中已经有了ABCD字符,所以堆中的String对象指向常量池中的ABCD,而str2则指向堆空间中的String对象。

String 对象是一个特殊的存在,需要注意的知识点也比较多,这里给一个之前写的 String 详解的文章链接:传送门其中包含的问题大概有:
1)“+” 怎么连接字符串;(编译器自动引入了一个java.lang.StringBuilder类。虽然我们在源代码中并没有使用StringBuilder类,但是编译器却自作主张地使用了它,因为它更高效。)
2)字符串的比较:
在这里插入图片描述
3)StringBuilder/StringBuffer/String 的区别;(String不可变,StringBuilder(JDK 1.5之后引入)线程不安全,StringBuffer 线程安全)

  • 单独使用""引号创建的字符串都是直接量,编译期就已经确定存储到常量池中;
  • 使用new String("")创建的对象会存储到堆内存中,是运行期才创建;
  • 使用只包含直接量的字符串连接符如"aa" + "bb"创建的也是直接量编译期就能确定,已经确定存储到常量池中(str2和str3);
  • 使用包含String直接量(无final修饰符)的字符串表达式(如"aa" + s1)创建的对象是运行期才创建的,存储在堆中;
  • 通过变量/调用方法去连接字符串,都只能在运行时期才能确定变量的值和方法的返回值,不存在编译优化操作.
    在这里插入图片描述
14)使用增值后的变量来运算:

在这里插入图片描述

15) 交换变量的三种方式:

答:

  • 第一种:通过第三个变量:
public class Test{
    public static void main(String[] args) {
        int x = 5;
        int y = 10;
        swap(x,y);
        System.out.println(x);
        System.out.println(y);

        Value v = new Value(5,10);
        swap(v);
        System.out.println(v.x);
        System.out.println(v.y);
    }

    // 无效的交换:形参的改变无法反作用于实参
    public static void swap(int x,int y) {
        int temp = x;
        x = y;
        y = temp;
    }

    // 有效的交换:通过引用(变量指向一个对象)来修改成员变量
    public static void swap(Value value) {
        int temp = value.x;
        value.x = value.y;
        value.y = temp;
    }
}

class Value{
    int x;
    int y;

    public Value(int x,int y) {
        this.x = x;
        this.y = y;
    }
}
  • 第二种:通过通过相加的方式(相同的 Value 类不再重复展示)
public class Test{
    public static void main(String[] args) {
        Value v1 = new Value(5,10);
        swap(v1);
        System.out.println("v1交换之后的结果为:");
        System.out.println(v1.x);
        System.out.println(v1.y);
    }

    public static void swap(Value v) {
        v.x = v.x + v.y;
        v.y = v.x - v.y;
        v.x = v.x - v.y;
    }
}
  • 第三种:通过异或的方式:
    位异或运算符(^)有这样的一个性质,就是两个整型的数据x与y,有:
    (x ^ y ^ y) == x这说明,如果一个变量x异或另外一个变量y两次,结果为x。通过这一点,可以实现交换两个变量的值:
public class Test{
    public static void main(String[] args) {
        Value v1 = new Value(5,10);
        swap(v1);
        System.out.println("v1交换之后的结果为:");
        System.out.println(v1.x);
        System.out.println(v1.y);
    }

    public static void swap(Value v) {
        v.x = v.x ^ v.y;
        v.y = v.x ^ v.y;
        v.x = v.x ^ v.y;
    }
}
16)Java 对象初始化顺序?

答:
初始化,顾名思义就是要把这个对象实例化的过程,那么就要将这个类里面的基本成员变量之类的东西都加载到内存中,当然还有它父类的成员变量。那么第一步就是先调用该类的父类的构造函数(可以通过super关键字指定父类的构造函数,否则默认调用无参的构造函数,并且需要在子类的构造函数的第一行调用)之后静态成员变量的初始化函数和静态初始化块则按照在代码当中的顺序执行,成员变量如果没有指定值的话则赋予默认值,即基本数据类型为0或false等,对象则为null;最后调用自身构造函数。

17)true、false 与 null 是关键字吗?

答:不是。true、false 是布尔类型的字面常量,null 是引用类型的字面常量。

面试官:那 goto 与 const 呢?

答:是。goto 与 const 均是 Java 语言保留的关键字,即没有任何语法应用。

18)java的异常层次结构:

在这里插入图片描述
  我们可以看到Throwable类是异常层级中的基类。

  Error类表示内部错误(比如内存溢出),这类错误使我们无法控制的;
  Exception表示异常,RuntimeException(顾名思义,运行时异常,在运行时才会出现,所以编译时我们不会知道哪里有问题)所以它的子类属于未检查异常,这类异常包括ArrayIndexOutOfBoundsException、NullPointerException等,我们应该通过条件判断等方式语句避免未检查异常的发生。 IOException及其子类属于已检查异常,编译器会检查我们是否为所有可能抛出的已检查异常提供了异常处理器,若没有则会报错。 对于未检查异常,我们无需捕获(当然Java也允许我们捕获,但我们应该做的事避免未检查异常的发生)

19)Java中的原始数据类型都有哪些,它们的大小及对应的封装类是什么?
  • byte——1 byte——Byte
  • short——2 bytes——Short
  • int——4 bytes——Integer
  • long——8 bytes——Long
  • float——4 bytes——Float
  • double——8 bytes——Double
  • char——2 bytes——Character
  • boolean——————Boolean

boolean数据类型非true即false。
这个数据类型表示1 bit,但是它的大小并没有精确定义。
《Java虚拟机规范》中如是说:“虽然定义了boolean这种数据类型,但是只对它提供了非常有限的支持。在Java虚拟机中没有任何供boolean值专用的字节码指令,Java语言表达式所操作的boolean值,在编译之后都使用Java虚拟机中的int数据类型来代替,而boolean数组将会被编码成Java虚拟机的byte数组,每个元素boolean元素占8 bit(位)”。这样我们可以得出boolean类型单独使用是4个字节,在数组中又是1个字节。
那虚拟机为什么要用int来代替boolean呢?为什么不用byte或short,这样不是更节省内存空间吗?
实际上,使用int的原因是,对于当下32位的CPU来说,一次进行32位的数据交换更加高效。
综上,我们可以知道:官方文档对boolean类型没有给出精确的定义,《Java虚拟机规范》给出了“单独时使用4个字节,boolean数组时1个字节”的定义,具体还要看虚拟机实现是否按照规范来,所以1个字节、4个字节都是有可能的。这其实是一种时空权衡。 boolean类型的封装类是Boolean。

20)谈一谈”==“与”equals()"的区别。
  • “==”判断的是两个对象的地址是否相同。
  • equals()是一个方法,源自Object类,Object类对它的实现也是比较地址是否相同,但是,是方法就可以被重写,所以很多类都对equals()方法进行了重写,比如String,Date类等。
21)Java中的四种引用及其应用场景是什么?

java中存在四种引用机制,分别是强引用,软引用,弱引用,虚引用

  • 强引用:

一般情况下我们用new方式创建的引用就是强引用,比如:

 Client client = new Client();

jvm进行GC的时候是不会回收存在强引用的对象的,比如:

Server server = new Server();
Client client = new Client();

当在第二行时jvm内存耗尽,jvm会报内存溢出的错误,也不会去回收第一行的对象。

只有当存在强引用的方法块执行完毕或者手动将强引用设置为null,这样才有可能被垃圾回收器回收掉。

应用的地方很多,但我竟一时举不出例子。

  • 软引用

如果一个对象具有软引用,内存空间足够,垃圾回收器就不会回收它;

如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。

软引用可用来实现内存敏感的高速缓存,比如网页缓存、图片缓存等。使用软引用能防止内存泄露,增强程序的健壮性。

我们使用SoftReference实例来保存一个对象的软引用,比如:

MyObject aRef = new  MyObject();  
SoftReference aSoftRef=new SoftReference(aRef);

aSoftRef就是aRef的软引用,如果我们将:

aRef==null;

此时当内存不足jvm进行GC时就会将aSoftRef清除掉,使用:

MyObject anotherRef=(MyObject)aSoftRef.get();

重新获得强引用对象如果软引用对象已经被GC清楚,则返回null,我们可以利用软引用实现一些缓存功能

第一次从数据库加载数据的时候将对象设置一个软引用当对象使用完毕GC还没有清理的时候再次加载对象时,

可以先从软引用队列查找是否存在跟对象相关的软应用,如果存在则调用get方法获取该对象的强引用,否则再去数据库加载

说到软引用队列ReferenceQueue,往往是结合软引用使用,当软引用的对象被清除以后往往软引用本身的引用也就没有存在的价值了,此时需要一个队列来记录所有软引用,然后轮询这清除这些软引用,此时会选择将软引用加入软引用队列,然后如果软引用的get方法返回Null时则清除改软引用。

可用于图片缓存中,内存不足时系统会自动回收不再使用的Bitmap

  • 弱引用

弱引用与软引用相似,不同的是jvm 在GC时会直接将存在弱引用的对象清除,而不是在内存不足时才开始清除,所以相比软引用被GC的几率更大。
同样可用于图片缓存中,这时候只要Bitmap不再使用就会被回收

  • 虚引用

需引用往往是发生在finalize之后,与前几种引用不同的是,具有虚引用的对象是无法转换为强引用被调用的,它更像是一个finalize方法的记录,可以通过查找虚引用判断对象是否要被GC回收,以及相关资源是否在finalize的时候被关闭等

22)Object中定义了哪些方法?
  • clone():由于clone()方法是protected修饰的,因此需要实现Cloneable接口才能调用,同时需要覆写clone()方法才能调用。

分为浅拷贝和深拷贝:

  • 浅拷贝:
    clone()方法的内容只是return super.clone();,将导致,虽然对象和克隆对象之间的地址不一样,但两个对象内部的对象地址一样。只是浅浅的克隆了表象,所以叫做浅拷贝。
  • 深拷贝:
    clone()的内容:
 	 Person person = (Person)super.clone();
     //手动对address属性进行clone,并赋值给新的person对象
     person.address = (Address) address.clone();
     return person;

嗯,对,我想说的就是如大家所见,它被"深深"的克隆了。和浅拷贝相反。大概就是这个样子。

  • equals():前面我解释过了,所以在这里就不给大家再解释了。
  • hashCode(): 哈希值,散列表中有用。
  • toString():嗯,对的,你懂,变成String类型,方便打印啊什么啊之类的。
  • notify():
    notify方法只唤醒一个等待(对象的)线程并使该线程开始执行。所以如果有多个线程等待一个对象,这个方法只会唤醒其中一个线程,选择哪个线程取决于操作系统对多线程管理的实现。
  • notifyAll():
    会唤醒所有等待(对象的)线程,尽管哪一个线程将会第一个处理取决于操作系统的实现。如果当前情况下有多个线程需要被唤醒,推荐使用notifyAll 方法。
  • wait():
    当线程执行wait()方法时候,会释放当前的锁,然后让出CPU,进入等待状态。
  • finalize():
    子类可以覆盖该方法以实现资源清理工作,GC在回收对象之前调用该方法。
  • getClass():
    getClass() 返回此 Object 的运行时类。
23)hashCode的作用是什么?

在散列表内部,我们使用桶(bucket)来保存键值对,我们前面所说的数组索引即为桶号,决定了给定的键存于散列表的哪个桶中。散列表所拥有的桶数被称为散列表的容量(capacity)由hashCode获取桶号

24)对于“try-catch-finally”,若try语句块中包含“return”语句,finally语句块会执行吗?

会执行。只有两种情况finally块中的语句不会被执行:

  • 调用了System.exit()方法;
  • JVM“崩溃”了。
25)静态内部类与非静态内部类的区别

静态内部类不会持有外围类的引用,而非静态内部类会隐式持有外围类的一个引用。

26)Java中多态的实现原理

所谓多态,指的就是父类引用指向子类对象,调用方法时会调用子类的实现而不是父类的实现。多态的实现的关键在于“动态绑定”。详细见链接:

JAVA动态绑定的内部实现机制
在这里插入图片描述

27)简述Java中创建新线程的两种方法
  • 继承Thread类(假设子类为MyThread),并重写run()方法,然后new一个MyThread对象并对其调用start()即可启动新线程。

  • 实现Runnable接口(假设实现类为MyRunnable),而后将MyRunnable对象作为参数传入Thread构造器,在得到的Thread对象上调用start()方法即可。

28)简述Java中进行线程同步的方法
  • volatile: Java Memory Model保证了对同一个volatile变量的写happens before对它的读

1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。(只是可见性,而无法保证原子性。)(通过synchronized和Lock也能够保证可见性,synchronized和Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中。因此可以保证可见性。)
2)禁止进行指令重排序。只能保证一定程度上的有序性。
补充:synchronized关键字是防止多个线程同时执行一段代码,那么就会很影响程序执行效率,而volatile关键字在某些情况下性能要优于synchronized,但是要注意volatile关键字是无法替代synchronized关键字的,因为volatile关键字无法保证操作的原子性。

  • synchronized:可以来对一个代码块或是对一个方法上锁,被“锁住”的地方称为临界区,进入临界区的线程会获取对象的monitor,这样其他尝试进入临界区的线程会因无法获取monitor而被阻塞。由于等待另一个线程释放monitor而被阻塞的线程无法被中断。
  • ReentrantLock(重入锁): 尝试获取锁的线程可以被中断可以设置超时参数。
  • lock1.lockInterruptibly(); // 以可以响应中断的方式加锁
    如果这个时候调用了t1.interrupt();方法,则持有重入锁lock2的线程t2会响应中断,并不再继续等待lock1,同时释放了其原本持有的lock2,这样t1获取到了lock2,正常执行完成。t2也会退出,但只是释放了资源并没有完成工作。
  • 锁申请等待限时:
    可以使用 tryLock()或者tryLock(long timeout, TimeUtil unit) 方法进行一次限时的锁等待。
    前者不带参数,这时线程尝试获取锁,如果获取到锁则继续执行,如果锁被其他线程持有,则立即返回 false ,也就是不会使当前线程等待,所以不会产生死锁。
    后者带有参数,表示在指定时长内获取到锁则继续执行,如果等待指定时长后还没有获取到锁则返回false。
  • Interrupt():**线程的thread.interrupt()方法是中断线程,将会设置该线程的中断状态位,即设置为true,中断的结果线程是死亡、还是等待新的任务或是继续运行至下一步,就取决于这个程序本身。**线程会不时地检测这个中断标示位,以判断线程是否应该被中断(中断标示值是否为true)。它并不像stop方法那样会中断一个正在运行的线程。
29)简述Java中具有哪几种粒度的锁

Java中可以对类、对象、方法或是代码块上锁。

  • 非静态方法:
    给对象加锁(可以理解为给这个对象的内存上锁,注意 只是这块内存,其他同类对象都会有各自的内存锁),这时候,在其他一个以上线程中执行该对象的这个同步方法(注意:是该对象)就会产生互斥。
  • 静态方法:
    相当于在类上加锁(*.class 位于代码区,静态方法位于静态区域,这个类产生的对象公用这个静态方法,所以这块内存,N个对象来竞争), 这时候,只要是这个类产生的对象,在调用这个静态方法时都会产生互斥
30)给出“生产者-消费者”问题的一种解决方案

使用阻塞队列:

多余消费者进入队列,当生产者有剩余时,从队列中拿出一个消费者。

31)ThreadLocal的设计理念与作用

ThreadLocal的作用是提供线程内的局部变量,在**多线程环境下访问时能保证各个线程内的ThreadLocal变量各自独立。**也就是说,每个线程的ThreadLocal变量是自己专用的,其他线程是访问不到的。ThreadLocal最常用于以下这个场景:多线程环境下存在对非线程安全对象的并发访问,而且该对象不需要在线程间共享,但是我们不想加锁,这时候可以使用ThreadLocal来使得每个线程都持有一个该对象的副本。

32)wait(),sleep() 的区别:
  • wait():Object类中定义的实例方法。在**指定对象上调用wait方法会让当前线程进入等待状态(前提是当前线程持有该对象的monitor),此时当前线程会释放相应对象的monitor,**这样一来其它线程便有机会获取这个对象的monitor了。当其它线程获取了这个对象的monitor并进行了所需操作时,便可以调用notify方法唤醒之前进入等待状态的线程。
  • sleep():Thread类中的静态方法,**作用是让当前线程进入休眠状态,以便让其他线程有机会执行。**进入休眠状态的线程不会释放它所持有的锁。
33)线程池的用法与优势:
  • 优势: 实现对线程的复用,避免了反复创建及销毁线程的开销;使用线程池统一管理线程可以减少并发线程的数目,而线程数过多往往会在线程上下文切换上以及线程同步上浪费过多时间。

  • 用法: 我们可以调用ThreadPoolExecutor的某个构造方法来自己创建一个线程池。但通常情况下我们可以使用Executors类提供给我们的静态工厂方法来更方便的创建一个线程池对象。创建了线程池对象后,我们就可以调用submit方法(可以通过Future来获取返回数据)或者execute()方法(不能接收返回的数据)提交任务到线程池中去执行了;线程池使用完毕后我们要记得调用shutdown方法(启动一次顺序关闭,执行以前提交的任务,但不接受新任务。如果已经关闭,则调用没有其他作用)来关闭它

34)简述Java IO与NIO的区别:
  • Java IO是面向流的,这意味着我们需要每次从流中读取一个或多个字节,直到读取完所有字节;NIO是面向缓冲的,也就是说会把数据读取到一个缓冲区中,然后对缓冲区中的数据进行相应处理。

Java IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方。此外,它不能前后移动流中的数据。如果需要前后移动从流中读取的数据,需要先将它缓存到一个缓冲区。 Java NIO的缓冲导向方法略有不同。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动。这就增加了处理过程中的灵活性。但是,还需要检查是否该缓冲区中包含所有您需要处理的数据。而且,需确保当更多的数据读入缓冲区时,不要覆盖缓冲区里尚未处理的数据。

  • Java IO是阻塞IO,而NIO是非阻塞IO

  • Java NIO中存在一个称为选择器(selector)的东西,它允许你把多个通道(channel)注册到一个选择器上,然后使用一个线程来监视这些通道:若这些通道里有某个准备好可以开始进行读或写操作了,则开始对相应的通道进行读写。而在等待某通道变为可读/写期间,请求对通道进行读写操作的线程可以去干别的事情

  • 总结
    NIO允许你用一个单独的线程或几个线程管理很多个channels(网络的或者文件的),代价是程序的处理和处理IO相比更加复杂。
    如果你需要同时管理成千上万的连接,但是每个连接只发送少量数据,例如一个聊天服务器,用NIO实现会更好一些,相似的,如果你需要保持很多个到其他电脑的连接,例如P2P网络,用一个单独的线程来管理所有出口连接是比较合适的
    如果你只有少量的连接但是每个连接都占有很高的带宽,同时发送很多数据,传统的IO会更适合

35)反射的作用与原理

反射的作用概括地说是运行时获取类的各种定义信息,比如定义了哪些属性与方法。原理是通过类的class对象来获取它的各种信息。

36)Java中的泛型机制

从Java 5开始,ArrayList在使用时可以加上一个类型参数(type parameter),这个类型参数用来指明ArrayList中的元素类型。

  • 泛型类:
    所谓泛型类(generic class)就是具有一个或多个类型参数的类。
  • 泛型方法:
    所谓泛型方法,就是带有类型参数的方法,它既可以定义在泛型类中,`也可以定义在普通类中。

形如”<? extends BoundingType>"的代码叫做通配符的子类型限定(set会出错,编译器不知道set的具体是什么类别的类,但是get是对的)。与之对应的还有通配符的超类型限定,格式是这样的:<? super BoundingType>。(和上面相反)
无限定的通配符<?>,set和get都会出错。

37)动态代理:

十分钟了解动态代理

38)ConcurrentHashMap的内部结构:

ConcurrentHashMap为了提高本身的并发能力,在内部采用了一个叫做Segment的结构,一个Segment其实就是一个类HashTable的结构,Segment内部维护了一个链表数组,我们用下面这一幅图来看下ConcurrentHashMap的内部结构:
  在这里插入图片描述
  从上面的结构我们可以了解到,ConcurrentHashMap定位一个元素的过程需要进行两次Hash操作第一次Hash定位到Segment,第**二次Hash定位到元素所在的链表的头部,**因此,这一种结构的带来的副作用是Hash的过程要比普通的HashMap要长,但是带来的好处是写操作的时候可以只对元素所在的Segment进行加锁即可,不会影响到其他的Segment,这样,在最理想的情况下,ConcurrentHashMap可以最高同时支持Segment数量大小的写操作(刚好这些写操作都非常平均地分布在所有的Segment上)所以,通过这一种结构,ConcurrentHashMap的并发能力可以大大的提高。

参考链接:
Java 基础知识点
java 中的四种引用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值