线程的 start 方法跟 run 方法的区别?
- start() 方法来启动线程,真正实现了多线程运行。这时无需等待 run() 方法体代码执行完毕,可以直接继续执行下面的代码。
- 通过调用 Thread 类的 start() 方法来启动一个线程, 这时此线程是处于就绪状态, 并没有运行。
- 方法 run() 称为线程体,它包含了要执行的这个线程的内容;当线程进入了运行状态,就开始运行 run() 函数当中的代码;当 run() 方法运行结束,此线程终止,然后 CPU 再调度其它线程。
list 和 map 的数据结构
List 属于单列集合;存储的是有序,可重复的元素。ArrayList、Vector、LinkedList 都实现了 list。ArrayList 和 Vector 底层使用了数组,查询快,增删慢;LinkedList 底层使用的是双向链表,查询慢,增删快。ArrayList 和 LinkedList 线程不安全,效率高;Vector 线程安全,效率低。
Map 是双列集合;存储的是键值对形式的元素,键唯一,值可重复;其数据结构仅仅对键有效,与值无关。HashMap、LinkedHashMap、Hashtable、TreeMap 都实现了 Map。HashMap 和 Hashtable 底层是哈希表;LinkedHashMap 底层是基于哈希表增加了双向链表记录迭代顺序;TreeMap 底层是红黑树。
同步机制 Sychronized 怎么实现同步的
Java 中每一个对象都可以作为锁,这是 synchronized 实现同步的基础。当一个线程访问同步代码块时,它首先是需要得到锁才能执行同步代码,当退出或者抛出异常时必须要释放锁。
Java 中同步代码块是使用 monitorenter 和 monitorexit 指令实现。同步方法依靠的是方法修饰符上的 ACC_SYNCHRONIZED 实现。
同步代码块:使用 MonitorEnter 和 MoniterExit 指令实现的,在编译时,monitorenter 指令插入到同步代码块的开始位置,monitorexit 指令插入到同步代码块的结束位置。JVM 需要保证每一个 monitorenter 都有一个 monitorexit 与之相对应。任何对象都有一个 monitor 与之相关联,当且一个 monitor 被持有之后,它将处于锁定状态。线程执行到 monitorenter 指令时,将会尝试获取对象所对应的 monitor 所有权,即尝试获取对象的锁。
同步方法:依赖 flags 标志 ACC_SYNCHRONIZED 实现,字节码中没有具体的逻辑,需要查看 JVM 的底层实现(同步方法也可以通过 Monitor 指令实现)。ACC_SYNCHRONIZED 标志表示方法为同步方法,如果为非静态方法(没有 ACC_STATIC 标志),使用调用该方法的对象作为锁对象;如果为静态方法(有 ACC_STATIC 标志),则使用该方法所属的 Class 类在 JVM 的内部对象表示 Klass 作为锁对象。
Java 内存垃圾回收前,如何判定某一对象是垃圾?
- 引用计数法:在 Java 中,引用和对象是有关联的。如果要操作对象则必须用引用进行。因此,可以通过引用计数来判断一个对象是否可以回收。简单说,即一个对象如果没有任何与之关联的引用,则说明对象不太可能再被用到,那么这个对象就是可回收对象。
- 可达性分析:为了解决引用计数法的循环引用问题,Java 使用了可达性分析的方法。通过一系列的 “GC roots” 对象作为起点搜索。如果在 “GC roots” 和一个对象之间没有可达路径,则称该对象是不可达的。要注意的是,不可达对象不等价于可回收对象,不可达对象变为可回收对象至少要经过两次标记过程。两次标记后仍然是可回收对象,则将面临回收。
HashMap 在 Java 7 跟 Java 8 中实现的不同点有哪些?
JAVA 7 实现:
大方向上,HashMap 里面是一个数组,然后数组中每个元素是一个单向链表。如图,每个绿色的实体是嵌套类 Entry 的实例,Entry 包含四个属性:key, value, hash 值和用于单向链表的 next。
- capacity:当前数组容量,始终保持 2^n,可以扩容,扩容后数组大小为当前的 2 倍。
- loadFactor:负载因子,默认为 0.75。
- threshold:扩容的阈值,等于 capacity * loadFactor。
JAVA 8 实现:
- 对 HashMap 进行了一些修改,最大的不同就是利用了红黑树,所以其由“数组+链表+红黑树”组成,如下图所示。
- 在 Java 7 HashMap 查找的时候,根据 hash 值能够快速定位到数组的具体下标,但是之后的话,需要顺着链表一个个比较下去才能找到需要的,时间复杂度为 O(n) 取决于链表的长度。为了降低这部分的开销,在 Java 8 中,当链表中的元素超过了 8 个以后,会将链表转换为红黑树,在这些位置进行查找的时候可以降低时间复杂度为 O(logn)。
Java 中的检查型异常和非检查型异常有什么区别?
Error 错误:虚拟机内部的错误,通过代码无法解决。
Exception 异常:通过代码可以解决。
- 运行时异常, 又叫非检查型异常,指的是 RuntimeException 和 RuntimeException 的子类;运行的时候出现的问题,可以处理,也可以不处理。
- 非运行时异常,又叫检查型异常,指的是 Exception 和 Exception 的子类(除了 RuntimeException);在编译期间就必须处理。
检查型异常和非检查型异常的主要区别在于其处理方式。检查型异常都需要使用 try,catch 和 finally 关键字在编译器进行处理,否则会出现编译器报错;而非检查型异常则不需要这样做。
获取 class 对象的常用方式有哪些?
- 调用某个对象的 getClass() 方法,如 Person p = new Person(); Class c = p.getClass();
- 调用某个类的 class 属性来获取该类对应的 Class 对象,如 Class c = Person.class;
- 使用 Class 类中的 forName() 静态方法,这是最安全且性能最好的,也是最常用的,如 Class c = Class.forName("类的全路径");
利用反射创建对象的两种方式是什么?
- 使用 Class 对象 newInstance() 方法来创建该 Class 对象对应类的实例,但是这种方法要求该 Class 对象对应的类有默认的空构造器,若是没有,则会报异常。
- 调用 Constructor 对象的 newInstance()。先使用 Class 对象的 getConstructor() 方法来获取指定的 Constructor 对象,再调用 Constructor 对象的 newInstance() 方法来创建 Class 对象对应类的实例,通过这种方法可以选定构造方法来创建实例。
下面的代码输出结果是什么?
public static synchronized void main(String[] a){
Thread t = new Thread(){
public void run(){
SoGood();
}
};
t.run();
System.out.print("Hello");
}
static synchronized void SoGood(){
System.out.print("SoGood");
}
输出为 SoGoodHello。
因为这里没有开启一个线程,run 只是调用 Thread 类的方法,而 start 才是开启一个线程。
多态常用的应用场景有哪些
- 通过方法的参数传递形成多态。
public static void draw(Shape s){
s.show();
}
draw(new Rect(1, 2, 3, 4));
- 在方法体中使用抽象类的引用指向子类对象时形成多态。
Account acc = new FixedAccount();
- 通过返回值类型形成多态。
Calender getInstance(){
return new GregorianCalendar(zone, aLocale);
}
想了解更多,欢迎关注我的微信公众号:Renda_Zhang