基础面试题(一)

61 阅读14分钟

final, finally, finalize 的区别

1. 在java中,final可以用来修饰类,方法和变量(成员变量或局部变量)。

2. finally作为异常处理的一部分,它只能用在try/catch语句中,并且附带一个语句块,表示这段语句最终一定会被执行(不管有没有抛出异常),经常被用在需要释放资源的情况下。

3. finalize()是在java.lang.Object里定义的,也就是说每一个对象都有这么个方法。这个方法在gc启动,该对象被回收的时候被调用。其实gc可以回收大部分的对象(凡是new出来的对象,gc都能搞定,一般情况下我们又不会用new以外的方式去创建对象),所以一般是不需要程序员去实现finalize的。特殊情况下,需要程序员实现finalize,当对象被回收的时候释放一些资源,比如:一个socket链接,在对象初始化时创建,整个生命周期内有效,那么就需要实现finalize,关闭这个链接。

int和Interger的区别

1. a == b 吗? 不相等。两个new出来的对象地址不一样。

2. c == d 吗? 都是基本数据类型的值肯定相等。

e == f 吗? g == h 吗?

答案是:e == f; g != h。为什么会出现这种情况?因为ava在进行编译时 Integer g = 130会被编译成 Integer.valueOf(130) ,这个可以通过反编译class文件看到。而通过Integer源码可以得出,Integer.valueOf() 方法会在数值-128~127之间会对Integer进行缓存,不会再重新new一个,所以 e==f ;当数值二大于127或者小于-128的时候则会重新new一个,所以g != h 。

3. c == e 吗, i == j 吗?

答案都是相等的。因为封装类和基本数据类型进行比较的时候,java会自动拆箱,然后比较数值是否相等。

重写与重载的区别

答:方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。重载发生在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同或者二者都不同)则视为重载;重写发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的参数列表,有兼容的返回类型,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常(里氏代换原则)。重载对返回类型没有特殊的要求,不能根据返回类型进行区分。

抽象类和接口有什么区别

1. 抽象类要被子类继承,接口要被类实现;

2. 接口只能做方法声明,抽象类中可以作方法声明,也可以做方法实现;

3. 接口里定义的变量只能是公共的静态的常量,抽象类中的变量是普通变量;

4. 接口是设计的结果,抽象类是重构的结果;

5. 抽象类和接口都是用来抽象具体对象的,但是接口的抽象级别最高;

6. 抽象类可以有具体的方法和属性,接口只能有抽象方法和不可变常量;

7. 抽象类主要用来抽象类别,接口主要用来抽象功能;

说说反射的用途

1. 在运行时判断任意一个对象所属的类

2. 在运行时构造任意一个类的对象

3. 在运行时判断任意一个类所具有的成员变量和方法(通过反射设置可以调用 private)

4. 在运行时调用一个对象的方法

说说自定义注解的场景及实现

登陆、权限拦截、日志处理,以及各种 Java 框架,如 Spring,Hibernate,JUnit 提到注解就不能不说反射,Java 自定义注解是通过运行时靠反射获取注解。

session 分布式处理

1. Session Replication 方式管理 (即session复制)

简介:将一台机器上的Session数据广播复制到集群中其余机器上

使用场景:机器较少,网络流量较小

优点:实现简单、配置较少、当网络中有机器Down掉时不影响用户访问

缺点:广播式复制到其余机器有一定廷时,带来一定网络开销

2. Session Sticky 方式管理

简介:即粘性Session、当用户访问集群中某台机器后,强制指定后续所有请求均落到此机器上

使用场景:机器数适中、对稳定性要求不是非常苛刻

优点:实现简单、配置方便、没有额外网络开销

缺点:网络中有机器Down掉时、用户Session会丢失、容易造成单点故障

3. 缓存集中式管理

简介:将Session存入分布式缓存集群中的某台机器上,当用户访问不同节点时先从缓存中拿Session信息使用场景:集群中机器数多、网络环境复杂

优点:可靠性好

缺点:实现复杂、稳定性依赖于缓存的稳定性、Session信息放入缓存时要有合理的策略写入

JDBC 流程

1. 注冊驱动 (仅仅做一次)

2. 建立连接(Connection)

3. 创建运行SQL的语句(Statement)

4. 运行语句

5. 处理运行结果(ResultSet)

6. 释放资源

List 和 Set 区别

1. list方法可以允许重复的对象,而set方法不允许重复对象

2. list可以插入多个null元素,而set只允许插入一个null元素

3. list是一个有序的容器,保持了每个元素的插入顺序。即输出顺序就是输入顺序,而set方法是无序容器,无法保证每个元素的存储顺序,TreeSet通过 Comparator 或者 Comparable 维护了一个排序顺序

Arraylist 与 LinkedList 区别

1. ArrayList是依靠数组来存放对象的;

2. LinkedList可以看做为一个双向链表,所有的操作都可以认为是一个双向链表的操作。并不是用普通的数组来存放数据的,而是使用结点来存放数据的,有一个指向链表头的结点first和一个指向链表尾的结点last;

3. 不同于ArrayList只能在数组末尾添加数据,LinkList可以很方便在链表头或者链表尾插入数据,或者在指定结点前后插入数据;

4. 对于数据频繁出入的情况下,并且要求操作要足够灵活,建议使用LinkedList;对于数组变动不大,主要是用来查询的情况下,可以使用ArrayList。

ArrayList 与 Vector 区别

1. Vector是线程安全的,源码中有很多的synchronized可以看出,而ArrayList不是。导致Vector效率无法和ArrayList相比;

2. ArrayList和Vector都采用线性连续存储空间,当存储空间不足的时候,ArrayList默认增加为原来的50%,Vector默认增加为原来的一倍;

3. Vector可以设置capacityIncrement,而ArrayList不可以,从字面理解就是capacity容量,Increment增加,容量增长的参数。

HashMap 和 Hashtable 的区别

1. Hashtable继承自Dictionary类,而HashMap继承自AbstractMap类。但二者都实现了Map接口;

2. Hashtable是线程安全的,而HashMap非线程安全;

3. HashMap把Hashtable的contains方法去掉了,改成containsValue和containsKey。Hashtable则保留了contains,containsValue和containsKey三个方法,其中contains和containsValue功能相同;

4. Hashtable中,key和value都不允许出现null值。HashMap中,null可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为null;

5. Hashtable、HashMap都使用了 Iterator。而由于历史原因,Hashtable还使用了Enumeration的方式 ;

6. HashTable直接使用对象的hashCode。而HashMap重新计算hash值;

7. HashTable在不指定容量的情况下的默认容量为11,而HashMap为16,Hashtable不要求底层数组的容量一定要为2的整数次幂,而HashMap则要求一定为2的整数次幂。Hashtable扩容时,将容量变为原来的2倍加1,而HashMap扩容时,将容量变为原来的2倍。

HashSet 和 HashMap 区别

HashMap 和 ConcurrentHashMap 的区别

1. ConcurrentHashMap对整个桶数组进行了分段,而HashMap则有;

2. ConcurrentHashMap在每一个分段上都用锁进行保护,从而让锁的粒度更精细一些,并发性能更好,而HashMap没有锁机制,不是线程安全的。

HashMap 的工作原理

1. 调用hashCode计算hash从而得到bucket位置;

2. HashMap会根据当前bucket的占用情况自动调整容量(超过Load Facotr则resize为原来的2倍);

3. 如果发生碰撞的时候,Hashmap通过链表将产生碰撞冲突的元素组织起来,在Java 8中,如果一个bucket中碰撞冲突的元素超过某个限制(默认是8),则使用红黑树来替换链表,从而提高速度。

ConcurrentHashMap 的工作原理

1. ConcurrentHashMap是Java1.5中引用的一个线程安全的支持高并发的HashMap集合类。

2. ConcurrentHashMap采用了非常精妙的"分段锁"策略(将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。),ConcurrentHashMap的主干是个Segment数组。Segment继承了ReentrantLock,所以它就是一种可重入锁(ReentrantLock)。在ConcurrentHashMap,一个Segment就是一个子哈希表,Segment里维护了一个HashEntry数组,并发环境下,对于不同Segment的数据进行操作是不用考虑锁竞争的;

sleep() 、join()、yield()有什么区别

1. sleep() 方法需要指定等待的时间,它可以让当前正在执行的线程在指定的时间内暂停执行,进入阻塞状态,该方法既可以让其他同优先级或者高优先级的线程得到执行的机会,也可以让低优先级的线程得到执行机会。但是 sleep() 方法不会释放“锁标志”,也就是说如果有 synchronized 同步块,其他线程仍然不能访问共享数据;

2. wait() 方法需要和 notify() 及 notifyAll() 两个方法一起介绍,这三个方法用于协调多个线程对共享数据的存取,所以必须在 synchronized 语句块内使用,也就是说,调用 wait(),notify() 和 notifyAll() 的任务在调用这些方法前必须拥有对象的锁。注意,它们都是 Object 类的方法,而不是 Thread 类的方法。wait() 方法与 sleep() 方法的不同之处在于,wait() 方法会释放对象的“锁标志”。当调用某一对象的 wait() 方法后,会使当前线程暂停执行,并将当前线程放入对象等待池中,直到调用了 notify() 方法后,将从对象等待池中移出任意一个线程并放入锁标志等待池中,只有锁标志等待池中的线程可以获取锁标志,它们随时准备争夺锁的拥有权。当调用了某个对象的 notifyAll() 方法,会将对象等待池中的所有线程都移动到该对象的锁标志等待池。除了使用 notify() 和 notifyAll() 方法,还可以使用带毫秒参数的 wait(long timeout) 方法,效果是在延迟 timeout 毫秒后,被暂停的线程将被恢复到锁标志等待池;

3. yield() 方法和 sleep() 方法类似,也不会释放“锁标志”,区别在于,它没有参数,即 yield() 方法只是使当前线程重新回到可执行状态,所以执行 yield() 的线程有可能在进入到可执行状态后马上又被执行,另外 yield() 方法只能使同优先级或者高优先级的线程得到执行机会,这也和 sleep() 方法不同;

4. join() 方法会使当前线程等待调用 join() 方法的线程结束后才能继续执行。

CountDownLatch

1. 解释一下CountDownLatch概念

a) CountDownLatch是在jdk1.5引入的,在java.util.concurrent包下,允许一个或多个线程等待直到其他线程完成操作;

b) CountDownLatch通过计数器来实现,初始值为线程的数量,每执行完一个线程 计数器减1,直到变成0,调用await方法的线程才能继续执行。

2. 给出一些CountDownLatch使用的例子

a) 核心服务检测

b) 模拟高并发

c) zook连接

3. CountDownLatch 类中主要的方法

a) await 当前线程等待直到计数器为0

b) countDown 计数器减1

CyclicBarrier原理

1. 线程达到屏障点时被阻塞,直到最后一个线程达到屏障点,屏障才会打开;

2. 构造方法为CyclicBarrier(int parties, Runnable barrierAction),barrierAction非必需,它将在给定数量的参与者(线程)处于等待状态时启动,并在启动barrier时执行给定的屏障操作,该操作由最后一个进入barrier的线程执行;

3. 调用await()方法使得线程进入等待状态,直到最后一个线程调用await()方法时,才会启动barrier;

4. 调用await(long timeout, TimeUnit unit)方法也可以使得线程进入等待状态,当等待超时后,该线程调用breakBarrier()方法终止CyclicBarrier,抛出TimeoutException 异常,而所有处于等待状态的线程将会抛出BrokenBarrierException 异常,其他线程调用await()方法时,也会抛出BrokenBarrierException异常,另外,这种情况下,最后一个线程也不会执行barrierAction。

Semaphore原理

1. Semaphore(信号量)是用来控制同时访问特定资源的线程数量,它通过协调各个线程,保证合理的使用公共资源;

2. 线程可以通过acquire()方法来获取信号量的许可,当信号量中没有可用的许可的时候,线程阻塞,直到有可用的许可为止。线程可以通过release()方法释放它持有的信号量的许可;

3. "公平信号量"和"非公平信号量"的释放信号量的机制是一样的!不同的是它们获取信号量的机制:线程在尝试获取信号量许可时,对于公平信号量而言,如果当前线程不在CLH队列的头部,则排队等候;而对于非公平信号量而言,无论当前线程是不是在CLH队列的头部,它都会直接获取信号量。该差异具体的体现在,它们的tryAcquireShared()函数的实现不同。

Exchanger 原理

1. Exchanger(交换者)是一个用于线程间协作的工具类。Exchanger用于进行线程间的数据交换。它提供一个同步点,在这个同步点两个线程可以交换彼此的数据。这两个线程通过exchange方法交换数据, 如果第一个线程先执行exchange方法,它会一直等待第二个线程也执行exchange,当两个线程都到达同步点时,这两个线程就可以交换数据,将本线程生产出来的数据传递给对方。因此使用Exchanger的重点是成对的线程使用exchange()方法,当有一对线程达到了同步点,就会进行交换数据。因此该工具类的线程对象是成对的;

2. Exchanger类提供了两个方法,String exchange(V x):用于交换,启动交换并等待另一个线程调用exchange;String exchange(V x,long timeout,TimeUnit unit):用于交换,启动交换并等待另一个线程调用exchange,并且设置最大等待时间,当等待时间超过timeout便停止等待。

ThreadLocal实现原理与使用场景

1. 实现原理:

每一个Thread对象维护一个ThreadLocalMap,它以ThreadLoal作为key值,Object作为value值。当调用ThreadLocal对象的set方法来设置值时,ThreadLocal先获取到当前Thread对象,然后获取到该Thread对象的ThreadLocalMap对象,通过ThreadLocalMap对象的set方法,以ThreadLocal对象作为key值,ThreadLocal对象set方法传入的Object对象作为value值,将数据加入到ThreadLocalMap对象中。

2. 使用场景:

a) 每个线程需要有自己单独的实例

b) 实例需要在多个方法中共享,但不希望被多线程共享