Java基础面试题笔记(三)


Java基础面试题笔记

面试题

51、类 ExampleA 继承 Exception,类 ExampleB 继承 ExampleA。

try {
  throw new ExampleB("b")
}
catch(ExampleA e){
  System.out.println("ExampleA");
}
catch(Exception e){
  System.out.println("Exception");
}

请问执行此段代码的输出是什么?

52、List、Set、Map 是否继承自 Collection 接口?

53、阐述 ArrayList、Vector、LinkedList 的存储性能和特性。

54、Collection 和 Collections 的区别?

55、List、Map、Set 三个接口存取元素时,各有什么特点?

56、TreeMap 和 TreeSet 在排序时如何比较元素?Collections 工具类中的 sort()方法如何比较元素?

57、Thread 类的 sleep()方法和对象的 wait()方法都可以让线程暂停执行,它们有什么区别?

58、线程的 sleep()方法和 yield()方法有什么区别?

59、当一个线程进入一个对象的 synchronized 方法 A 之后,其它线程是否可进入此对象 synchronized 方法 B?

60、请说出与线程同步以及线程调度相关的方法。

61、编写多线程程序有几种实现方式?

62、synchronized 关键字的用法?

63、举例说明同步和异步。

64、启动一个线程是调用 run()还是 start()方法?

65、什么是线程池(thread pool)?

66、线程的基本状态以及状态之间的关系?

67、简述 synchronized 和 java.util.concurrent.locks.Lock 的异同?


答案与解析

51、类 ExampleA 继承 Exception,类 ExampleB 继承 ExampleA。

try {
  throw new ExampleB("b")
}
catch(ExampleA e){
  System.out.println("ExampleA");
}
catch(Exception e){
  System.out.println("Exception");
}

请问执行此段代码的输出是什么?
输出:ExampleA
根据里氏代换原则[能使用父类型的地方一定能使用子类型

补充: 如下代码的输出结果是?

class Annoyance extends Exception {
}
class Sneeze extends Annoyance {
}
class Human {
	public static void main(String[] args) throws Exception {
		try {
			try {
				throw new Sneeze();
			} catch (Annoyance a) {
				System.out.println("Caught Annoyance");
				throw a;
			}
		} catch (Sneeze s) {
			System.out.println("Caught Sneeze");
			return;
		} finally {
			System.out.println("Hello World!");
		}
	}
}

输出是:

Caught Annoyance
Caught Sneeze
Hello World!

会奇怪打印第二行的童鞋看这里:
通过断点的加入可以看到,这是java多态的经典表现

52、List、Set、Map 是否继承自 Collection 接口?
List、Set 是 ,Map 不是。
List(列表):集合中的对象按照特定的方式排序,允许元素重复
Set(集):集合中的对象不按照特定方式排序,不允许元素重复
Map(映射):集合中的每个元素都包括一对key和value对象,不允许key重复,value可以重复

补充:java集合图
在这里插入图片描述

53、阐述 ArrayList、Vector、LinkedList 的存储性能和特性。
ArrayList 和 Vector 都是使用数组方式存储数据,它们都允许直接按序号索引元素,但是插入元素要涉及数组元素移动等内存操作,所以索引数据快而插入数据慢。
Vector是ArrayList的线程同步类,方法全部添加了 synchronized 修饰,因此 Vector 是线程安全的容器,但性能上较ArrayList 差。
LinkedList 使用双向链表(火车车厢)实现存储(将内存中零散的内存单元通过附加的引用关联起来,形成一个可以按序号索引的线性结构,这种链式存储方式与数组的连续存储方式相比,内存的利用率更高)
按序号索引数据需要进行前向或后向遍历,但是插入数据时只需要记录本项的前后项即可,所以插入速度较快。
Vector 属于遗留容器(Java 早期的版本中提供的容器,除此之外,Hashtable、Dictionary、BitSet、Stack、Properties都是遗留容器),已经不推荐使用,但是由于 ArrayList 和 LinkedListed 都是非线程安全的,如果遇到多个线程操作同一个容器的场景,则可以通过工具类Collections 中的 synchronizedList 方法将其转换成线程安全的容器后再使用(这是对装潢模式的应用,将已有对象传入另一个类的构造器中创建新的对象来增强实现)。

补充:Vector与ArrayList的区别
①:Vector所有方法都使用了synchronized ,而ArrayList没有
②:Vector扩容时是将容量扩大一倍,而AarrayList是只增加一半, ArrayList更省内存
从源码可以看出:
Vector:
在这里插入图片描述
AarrayList:
在这里插入图片描述
结论:即使在多线程环境下,我们也不要使用Vector类
ArrayList list = Collections.synchronizedList(new ArrayList()) ;

Hashtable 和 HashMap 区别

不同点HashtableHashMap
线程安全性线程安全线程不安全
是否允许null值key和value都不允许key允许且只能出现一次,value可以存在多个null值
遍历方式Iterator和EnumerationIterator
默认容量与扩容默认容量是11,扩容:old*2+1默认容量是16,扩容:old*2
hash值不同key的hashCode()重新计算hash值

补充:遗留容器中的 Properties 类和 Stack 类在设计上有严重的问题,Properties是一个键和值都是字符串的特殊的键值对映射,在设计上应该是关联一个Hashtable 并将其两个泛型参数设置为 String 类型,但是 Java API 中的Properties 直接继承了 Hashtable,这很明显是对继承的滥用。这里复用代码的方式应该是 Has-A 关系而不是 Is-A 关系,另一方面容器都属于工具类,继承工具类本身就是一个错误的做法,使用工具类最好的方式是 Has-A 关系(关联)或Use-A 关系(依赖)。同理,Stack 类继承 Vector 也是不正确的。Sun 公司的工程师们也会犯这种低级错误,让人唏嘘不已。

54、Collection 和 Collections 的区别?
Collection 是一个接口,它是 Set、List 等容器的父接口;
Collections 是个一个工具类,提供了一系列的静态方法来辅助容器操作,这些方法包括对容器的搜索、排序、线程安全化等等。

55、List、Map、Set 三个接口存取元素时,各有什么特点?
List(列表):集合中的对象按照特定的方式排序,允许元素重复
Set(集):集合中的对象不按照特定方式排序,不允许元素重复
Map(映射):集合中的每个元素都包括一对key和value对象,不允许key重复,value可以重复

56、TreeMap 和 TreeSet 在排序时如何比较元素?Collections 工具类中的 sort()方法如何比较元素?
TreeSet 要求存放的对象所属的类必须实现 Comparable 接口,该接口提供了比较元素的 compareTo() 方法,当插入元素时会回调该方法比较元素的大小
TreeMap 要求存放的键值对映射的键必须实现 Comparable 接口从而根据对元素进行排序
Collections 工具类的 sort 方法有两种重载的形式:
第一种要求传入的待排序容器中存放的对象比较实现 Comparable 接口以实现元素的比较;
第二种不强制性的要求容器中的元素必须可比较,但是要求传入第二个参数,参数是Comparator 接口的子类型(需要重写 compare 方法实现元素的比较),相当于一个临时定义的排序规则,其实就是通过接口注入比较元素大小的算法,也是对回调模式的应用(Java 中对函数式编程的支持)。

57、Thread 类的 sleep()方法和对象的 wait()方法都可以让线程暂停执行,它们有什么区别?
sleep()方法是计时等待,时间到了会自动进入就绪状态;
wait()方法是只有在其他线程调用此对象的 notify() 方法或 notifyAll() 方法后才能进入对象的等锁池去重新获取锁;

sleep()方法(休眠)是线程类(Thread)的静态方法,调用此方法会让当前线程暂停执行指定的时间,将执行机会(CPU)让给其他线程,但是对象的锁依然保持,因此休眠时间结束后会自动恢复(线程回到就绪状态,请参考第 66 题中的线程状态转换图)。

wait()是 Object 类的方法,调用对象的 wait()方法导致当前线程放弃对象的锁(线程暂停执行),进入对象的等待池(wait pool),只有调用对象的 notify()方法(或 notifyAll()方法)时才能唤醒等待池中的线程进入等锁池(lock pool),如果线程重新获得对象的锁就可以进入就绪状态。

补充:
并行:指两个或多个事件在同一时刻点发生
并发:指两个或多个事件在同一时间段发生

进程:是具有一定独立功能的程序关于某个数据集合上的一次运行活动,是操作系统进行资源调度和分配的一个独立单位,拥有独立的内存空间
线程:线程是进程的一个实体,是CPU调度和分派的一个基本单位,堆空间共享,栈空间独立,消耗的资源也比进程小,又称为轻型进程或进程元

**补充:线程状态:**线程可以处于下列状态之一:

NEW 新建状态 至今尚未启动的线程处于这种状态。
使用new创建一个线程对象,仅仅在堆内存中分配内存空间,线程压根没有启动
当调用start方法时,才进入runnable状态
线程的start方法只能调用一次,否则报 IllegalThreadStateException

RUNNABLE 可运行状态 正在 Java 虚拟机中执行的线程处于这种状态。其中还细分为以下两种状态
ready 就绪状态:线程调用start方法后,等待JVM的调度,此时线程还没有执行
running 运行状态:线程对象获得JVM调度,开始运行

BLOCKED 阻塞状态 受阻塞并等待某个监视器锁的线程处于这种状态。
正在运行的线程因为某些原因放弃CPU,暂时停止运行,进入阻塞状态
此时JVM不会给阻塞状态下的线程分配cpu,直到线程进入就绪状态,才有机会获得运行
导致阻塞状态的两种常见情况:
当线程A处于就绪状态,试图获取同步锁,却被B线程获取,此时JVM把线程A放到同步锁对象的锁池中
当线程处于运行状态,发出了io请求,此时线程被阻塞

WAITING 等待状态 无限期地等待另一个线程来执行某一特定操作的线程处于这种状态。
线程调用无参的wait方法后所处状态

TIMED_WAITING 计时等待 等待另一个线程来执行取决于指定等待时间的操作的线程处于这种状态。
调用有参的wait方法
调用sleep方法

TERMINATED 死亡状态 已退出的线程处于这种状态。
正常执行完run方法而退出
遇到异常而退出

58、线程的 sleep()方法和 yield()方法有什么区别?

区别sleepyield
优先级不考虑线程的优先级高优先级的线程有更大可能拿到锁(但不是绝对)
调用后状态进入阻塞状态(blocked)进入就绪状态(ready)
抛出异常会抛出InterruptedException不会抛出任何异常

(1) sleep()方法给其他线程运行机会时不考虑线程的优先级,因此会给低优先级的线程以运行的机会;yield()方法暂停当前正在执行的线程对象,并执行其他线程(但不排除当前线程重新拿到锁并开始执行)。

(2) 线程执行 sleep()方法后转入阻塞(blocked)状态,而执行 yield()方法后转入就绪(ready)状态;

(3)sleep()方法声明抛出 InterruptedException,而 yield()方法没有声明任何异常;

(4)sleep()方法比 yield()方法(跟操作系统 CPU 调度相关)具有更好的可移植性。

59、当一个线程进入一个对象的 synchronized 方法 A 之后,其它线程是否可进入此对象 synchronized 方法 B?
不能。其它线程只能访问该对象的非同步方法,同步方法则不能进入。因为非静态方法上的 synchronized 修饰符要求执行方法时要获得对象的锁,如果已经进入A 方法说明对象锁已经被取走,那么试图进入 B 方法的线程就只能在等锁池(注意不是等待池哦)中等待对象的锁。

60、请说出与线程同步以及线程调度相关的方法。
(1) wait():使一个线程处于等待(阻塞)状态,并且释放所持有的对象的锁;

(2)sleep():使一个正在运行的线程处于睡眠状态;

(3)notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由 JVM 确定唤醒哪个线程,而且与优先级无关;

(4)notityAll():唤醒所有处于等待状态的线程,该方法并不是将对象的锁给所有线程,而是让它们竞争,只有获得锁的线程才能进入就绪状态;

(5)join():强制执行调用该方法的线程

补充:Java 5 通过 Lock 接口提供了显式的锁机制(explicit lock),增强了灵活性以及对线程的协调。Lock 接口中定义了加锁(lock())和解锁(unlock())的方法,同时还提供了 newCondition()方法来产生用于线程之间通信的 Condition 对象;

private final Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();

此外,Java 5 还提供了信号量机制(semaphore),信号量可以用来限制对某个共享资源进行访问的线程的数量。在对资源进行访问之前,线程必须得到信号量的许可(调用 Semaphore 对象的 acquire()方法);在完成对资源的访问后,线程必须向信号量归还许可(调用 Semaphore 对象的 release()方法)。

61、编写多线程程序有几种实现方式?
①继承Thread类,实现run方法
A类 a = new A类(); a.start()
②实现Runable接口
Thread t = new Thread(new A()); t.start()
③实现Callable接口,并重写call方法
④创建线程池
线程池详解可以参考: https://www.cnblogs.com/dolphin0520/p/3932921.html.
创建简单的线程池实例可参考:
https://www.cnblogs.com/wihainan/p/4765862.html.
正确创建线程池对象可以参考:
https://blog.csdn.net/csdn_wangchen/article/details/84629088

62、synchronized 关键字的用法?
synchronized 关键字可以将对象或者方法标记为同步,以实现对对象和方法的互斥访问,可以用 synchronized(对象) { … }定义同步代码块,或者在声明方法时将 synchronized 作为方法的修饰符。

63、举例说明同步和异步。
同步:去饭馆吃饭
异步:点外卖
同步就是指阻塞式操作,而异步就是非阻塞式操作。

如果系统中存在临界资源(资源数量少于竞争资源的线程数量的资源),例如正在写的数据以后可能被另一个线程读到,或者正在读的数据可能已经被另一个线程写过了,那么这些数据就必须进行同步存取(数据库操作中的排他锁就是最好的例子)。当应用程序在对象上调用了一个需要花费很长时间来执行的方法,并且不希望让程序等待方法的返回时,就应该使用异步编程,在很多情况下采用异步途径往往更有效率。

64、启动一个线程是调用 run()还是 start()方法?
启动一个线程是调用 start()方法,使线程所代表的虚拟处理机处于可运行状态,这意味着它可以由 JVM 调度并执行,这并不意味着线程就会立即运行。run()方法是线程启动后要进行回调(callback)的方法。

65、什么是线程池(thread pool)?
可参考61题中的链接

在面向对象编程中,创建和销毁对象是很费时间的,因为创建一个对象要获取内存资源或者其它更多资源。在 Java 中更是如此,虚拟机将试图跟踪每一个对象,以便能够在对象销毁后进行垃圾回收。所以提高服务程序效率的一个手段就是尽可能减少创建和销毁对象的次数,特别是一些很耗资源的对象创建和销毁,这就是”池化资源”技术产生的原因。线程池顾名思义就是事先创建若干个可执行的线程放入一个池(容器)中,需要的时候从池中获取线程不用自行创建,使用完毕不需要销毁线程而是放回池中,从而减少创建和销毁线程对象的开销。Java 5+中的 Executor 接口定义一个执行线程的工具。它的子类型即线程池接口是 ExecutorService。要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,因此在工具类 Executors 面提供了一些静态工厂方法,生成一些常用的线程池,如下所示:

(1)newSingleThreadExecutor:创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。

(2)newFixedThreadPool:创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。

(3) newCachedThreadPool:创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60 秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说 JVM)能够创建的最大线程大小。

(4)newScheduledThreadPool:创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。

(5)newSingleThreadExecutor:创建一个单线程的线程池。此线程池支持定时以及周期性执行任务的需求。

66、线程的基本状态以及状态之间的关系?
57题中的补充:线程状态

67、简述 synchronized 和 java.util.concurrent.locks.Lock 的异同?
Lock 是 Java 5 以后引入的新的 API,和关键字 synchronized 相比
主要相同点:Lock 能完成 synchronized 所实现的所有功能;
主要不同点:Lock 有比synchronized 更精确的线程语义和更好的性能,而且不强制性的要求一定要获得锁。synchronized 会自动释放锁,而 Lock 一定要求程序员手工释放,并且最好在 finally 块中释放(这是释放外部资源的最好的地方)。
Lock锁更符合java中的面向对象思想。


面试题与解析来源“时代名猿”公众号,做了一些自己的笔记与理解~

2021-04-15

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值