面试宝典

相关概念

面向对象的三个特征

封装,继承,多态.这个应该是人人皆知.有时候也会加上抽象.java

多态的好处

容许不一样类对象对同一消息作出响应,即同一消息能够根据发送对象的不一样而采用多种不一样的行为方式(发送消息就是函数调用).主要有如下优势:c++

  1. 可替换性:多态对已存在代码具备可替换性.
  2. 可扩充性:增长新的子类不影响已经存在的类结构.
  3. 接口性:多态是超累经过方法签名,想子类提供一个公共接口,由子类来完善或者重写它来实现的.
  4. 灵活性:
  5. 简化性:

代码中如何实现多态

实现多态主要有如下三种方式: 1. 接口实现 2. 继承父类重写方法 3. 同一类中进行方法重载git

虚拟机是如何实现多态的

动态绑定技术(dynamic binding),执行期间判断所引用对象的实际类型,根据实际类型调用对应的方法.github

接口的意义

接口的意义用三个词就能够归纳:规范,扩展,回调.web

抽象类的意义

抽象类的意义能够用三句话来归纳:面试

  1. 为其余子类提供一个公共的类型
  2. 封装子类中重复定义的内容
  3. 定义抽象方法,子类虽然有不一样的实现,可是定义时一致的 ## 接口和抽象类的区别
比较 抽象类 接口
默认方法 抽象类能够有默认的方法实现 ,java 8以前,接口中不存在方法的实现.
实现方式 子类使用extends关键字来继承抽象类.若是子类不是抽象类,子类须要提供抽象类中所声明方法的实现. 子类使用implements来实现接口,须要提供接口中全部声明的实现.
构造器 抽象类中能够有构造器, 接口中不能
和正常类区别 抽象类不能被实例化 接口则是彻底不一样的类型
访问修饰符 抽象方法能够有public,protected和default等修饰 接口默认是public,不能使用其余修饰符
多继承 一个子类只能存在一个父类 一个子类能够存在多个接口
添加新方法 想抽象类中添加新方法,能够提供默认的实现,所以能够不修改子类现有的代码 若是往接口中添加新方法,则子类中须要实现该方法.
     

父类的静态方法可否被子类重写

不能.子类继承父类后,有相同的静态方法和非静态,这是非静态方法覆盖父类中的方法(即方法重写),父类的该静态方法被隐藏(若是对象是父类则调用该隐藏的方法),另外子类可集成父类的静态与非静态方法,至于方法重载我以为它其中一要素就是在同一类中,不能说父类中的什么方法与子类里的什么方法是方法重载的体现.算法

什么是不可变对象

不可变对象指对象一旦被建立,状态就不能再改变。任何修改都会建立一个新的对象,如 String、Integer及其它包装类。spring

可否建立一个包含可变对象的不可变对象?

固然能够建立一个包含可变对象的不可变对象的,你只须要谨慎一点,不要共享可变对象的引用就能够了,若是须要变化时,就返回原对象的一个拷贝。最多见的例子就是对象中包含一个日期对象的引用.编程

java 建立对象的几种方式

  1. 采用new
  2. 经过反射
  3. 采用clone
  4. 经过序列化机制

前2者都须要显式地调用构造方法. 形成耦合性最高的刚好是第一种,所以你发现不管什么框架,只要涉及到解耦必先减小new的使用.数组

switch中可否使用string作参数

在idk 1.7以前,switch只能支持byte,short,char,int或者其对应的封装类以及Enum类型。从idk 1.7以后switch开始支持String.

Object中有哪些公共方法?

  1. equals()
  2. clone()
  3. getClass()
  4. notify(),notifyAll(),wait()

java当中的四种引用

强引用,软引用,弱引用,虚引用.不一样的引用类型主要体如今GC上:

  1. 强引用:若是一个对象具备强引用,它就不会被垃圾回收器回收。即便当前内存空间不足,JVM也不会回收它,而是抛出 OutOfMemoryError 错误,使程序异常终止。若是想中断强引用和某个对象之间的关联,能够显式地将引用赋值为null,这样一来的话,JVM在合适的时间就会回收该对象
  2. 软引用:在使用软引用时,若是内存的空间足够,软引用就能继续被使用,而不会被垃圾回收器回收,只有在内存不足时,软引用才会被垃圾回收器回收。
  3. 弱引用:具备弱引用的对象拥有的生命周期更短暂。由于当 JVM 进行垃圾回收,一旦发现弱引用对象,不管当前内存空间是否充足,都会将弱引用回收。不过因为垃圾回收器是一个优先级较低的线程,因此并不必定能迅速发现弱引用对象
  4. 虚引用:顾名思义,就是形同虚设,若是一个对象仅持有虚引用,那么它至关于没有引用,在任什么时候候均可能被垃圾回收器回收。

更多了解参见深刻对象引用

WeakReference与SoftReference的区别?

这点在四种引用类型中已经作了解释,这里简单说明一下便可: 虽然 WeakReference 与 SoftReference 都有利于提升 GC 和 内存的效率,可是 WeakReference ,一旦失去最后一个强引用,就会被 GC 回收,而软引用虽然不能阻止被回收,可是能够延迟到 JVM 内存不足的时候。

为何要有不一样的引用类型

不像C语言,咱们能够控制内存的申请和释放,在Java中有时候咱们须要适当的控制对象被回收的时机,所以就诞生了不一样的引用类型,能够说不一样的引用类型实则是对GC回收时机不可控的妥协.有如下几个使用场景能够充分的说明:

  1. 利用软引用和弱引用解决OOM问题:用一个HashMap来保存图片的路径和相应图片对象关联的软引用之间的映射关系,在内存不足时,JVM会自动回收这些缓存图片对象所占用的空间,从而有效地避免了OOM的问题.
  2. 经过软引用实现Java对象的高速缓存:好比咱们建立了一Person的类,若是每次须要查询一我的的信息,哪怕是几秒中以前刚刚查询过的,都要从新构建一个实例,这将引发大量Person对象的消耗,而且因为这些对象的生命周期相对较短,会引发屡次GC影响性能。此时,经过软引用和 HashMap 的结合能够构建高速缓存,提供性能.

java中==和eqauls()的区别,equals()和`hashcode的区别

==是运算符,用于比较两个变量是否相等,而equals是Object类的方法,用于比较两个对象是否相等.默认Object类的equals方法是比较两个对象的地址,此时和==的结果同样.换句话说:基本类型比较用==,比较的是他们的值.默认下,对象用==比较时,比较的是内存地址,若是须要比较对象内容,须要重写equal方法

equals()hashcode()的联系

hashCode()是Object类的一个方法,返回一个哈希值.若是两个对象根据equal()方法比较相等,那么调用这两个对象中任意一个对象的hashCode()方法必须产生相同的哈希值. 若是两个对象根据eqaul()方法比较不相等,那么产生的哈希值不必定相等(碰撞的状况下仍是会相等的.)

a.hashCode()有什么用?与a.equals(b)有什么关系

hashCode() 方法是相应对象整型的 hash 值。它经常使用于基于 hash 的集合类,如 Hashtable、HashMap、LinkedHashMap等等。它与 equals() 方法关系特别紧密。根据 Java 规范,两个使用 equal() 方法来判断相等的对象,必须具备相同的 hashcode。

将对象放入到集合中时,首先判断要放入对象的hashcode是否已经在集合中存在,不存在则直接放入集合.若是hashcode相等,而后经过equal()方法判断要放入对象与集合中的任意对象是否相等:若是equal()判断不相等,直接将该元素放入集合中,不然不放入.

有没有可能两个不相等的对象有相同的hashcode

有可能,两个不相等的对象可能会有相同的 hashcode 值,这就是为何在 hashmap 中会有冲突。相等 hashcode 值的规定只是说若是两个对象相等,必须有相同的hashcode 值,可是没有关于不相等对象的任何规定。

能够在hashcode中使用随机数字吗?

不行,由于同一对象的 hashcode 值必须是相同的

“a==b”与a.equals(b)有什么区别

若是a 和b 都是对象,则 a==b 是比较两个对象的引用,只有当 a 和 b 指向的是堆中的同一个对象才会返回 true,而 a.equals(b) 是进行逻辑比较,因此一般须要重写该方法来提供逻辑一致性的比较。例如,String 类重写 equals() 方法,因此能够用于两个不一样对象,可是包含的字母相同的比较。

3*0.1==0.3返回值是什么

false,由于有些浮点数不能彻底精确的表示出来。

a=a+b与a+=b有什么区别吗?

隐式的将加操做的结果类型强制转换为持有结果的类型。若是两这个整型相加,如 byte、short 或者 int,首先会将它们提高到 int 类型,而后在执行加法操做。若是加法操做的结果比 a 的最大值要大,则 a+b 会出现编译错误,可是 a += b 没问题,以下: byte a = 127; byte b = 127; b = a + b; // error : cannot convert from int to byte b += a; // ok (译者注:这个地方应该表述的有误,其实不管 a+b 的值为多少,编译器都会报错,由于 a+b 操做会将 a、b 提高为 int 类型,因此将 int 类型赋值给 byte 就会编译出错)

内部类的做用

内部类能够用多个实例,每一个实例都有本身的状态信息,而且与其余外围对象的信息相互独立.在单个外围类当中,可让多个内部类以不一样的方式实现同一接口,或者继承同一个类.建立内部类对象的时刻病不依赖于外部类对象的建立.内部类并无使人疑惑的”is-a”关系,它就像是一个独立的实体.

内部类提供了更好的封装,除了该外围类,其余类都不能访问

final,finalize和finally的不一样之处

final 是一个修饰符,能够修饰变量、方法和类。若是 final 修饰变量,意味着该变量的值在初始化后不能被改变。finalize 方法是在对象被回收以前调用的方法,给对象本身最后一个复活的机会,可是何时调用 finalize 没有保证。finally 是一个关键字,与 try 和 catch 一块儿用于异常的处理。finally 块必定会被执行,不管在 try 块中是否有发生异常。

clone()是哪一个类型的方法?

java.lang.Cloneable 是一个标示性接口,不包含任何方法,clone 方法在 object 类中定义。而且须要知道 clone() 方法是一个本地方法,这意味着它是由 c 或 c++ 或 其余本地语言实现的。

深拷贝和浅拷贝的区别是什么?

浅拷贝:被复制对象的全部变量都含有与原来的对象相同的值,而全部的对其余对象的引用仍然指向原来的对象。换言之,浅拷贝仅仅复制所考虑的对象,而不复制它所引用的对象。

深拷贝:被复制对象的全部变量都含有与原来的对象相同的值,而那些引用其余对象的变量将指向被复制过的新对象,而再也不是原有的那些被引用的对象。换言之,深拷贝把要复制的对象所引用的对象都复制了一遍。

static都有哪些用法?

几乎全部的人都知道static关键字这两个基本的用法:静态变量和静态方法.也就是被static所修饰的变量/方法都属于类的静态资源,类实例所共享.

除了静态变量和静态方法以外,static也用于静态块,多用于初始化操做:

public calss PreCache{ static{ //执行相关操做 } }

此外static也多用于修饰内部类,此时称之为静态内部类.

最后一种用法就是静态导包,即import static.import static是在JDK 1.5以后引入的新特性,能够用来指定导入某个类中的静态资源,而且不须要使用类名.资源名,能够直接使用资源名,好比:

import static java.lang.Math.*; public class Test{ public static void main(String[] args){ //System.out.println(Math.sin(20));传统作法 System.out.println(sin(20)); } } 

final有哪些用法

final也是不少面试喜欢问的地方,能回答下如下三点就不错了: 1.被final修饰的类不能够被继承 2.被final修饰的方法不能够被重写 3.被final修饰的变量不能够被改变.若是修饰的引用,那么表示引用不可变,引用指向的内容可变. 4.被final修饰的方法,JVM会尝试将其内联,以提升运行效率 5.被final修饰的常量,在编译阶段会存入常量池中.

当前回答出编译器对final域要遵照的两个重排序规则更好: 1.在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操做之间不能重排序. 2.初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操做之间不能重排序.


数据类型相关

java中int char,long各占多少字节?

|类型|位数|字节数| |-|-|-| |short|2|16| |int|4|32| |long|8|64| |float|4|32 |double|8|64| |char|2|16|

64位的JVM当中,int的长度是多少?

Java 中,int 类型变量的长度是一个固定值,与平台无关,都是 32 位。意思就是说,在 32 位 和 64 位 的Java 虚拟机中,int 类型的长度是相同的。

java int和Integer的区别

Integer是int的包装类型,在拆箱和装箱中,而知自动转换.int是基本类型,直接存数值,而integer是对象,用一个引用指向这个对象.

int 和Integer谁占用的内存更多?

Integer 对象会占用更多的内存。Integer是一个对象,须要存储对象的元数据。可是 int 是一个原始类型的数据,因此占用的空间更少。

String,StringBuffer和StringBuilder区别

String是字符串常量,final修饰;StringBuffer字符串变量(线程安全); StringBuilder 字符串变量(线程不安全).

String和StringBuffer

String和StringBuffer主要区别是性能:String是不可变对象,每次对String类型进行操做都等同于产生了一个新的String对象,而后指向新的String对象.因此尽可能不在对String进行大量的拼接操做,不然会产生不少临时对象,致使GC开始工做,影响系统性能.

StringBuffer是对对象自己操做,而不是产生新的对象,所以在一般在有大量拼接的状况下咱们建议使用StringBuffer.

可是须要注意如今JVM会对String拼接作必定的优化: String s=“This is only ”+”simple”+”test”会被虚拟机直接优化成String s=“This is only simple test”,此时就不存在拼接过程.

StringBuffer和StringBuilder

StringBuffer是线程安全的可变字符串,其内部实现是可变数组.StringBuilder是java 5.0新增的,其功能和StringBuffer相似,可是非线程安全.所以,在没有多线程问题的前提下,使用StringBuilder会取得更好的性能.

什么是编译器常量?使用它有什么风险?

公共静态不可变(public static final )变量也就是咱们所说的编译期常量,这里的 public 可选的。实际上这些变量在编译时会被替换掉,由于编译器知道这些变量的值,而且知道这些变量在运行时不能改变。这种方式存在的一个问题是你使用了一个内部的或第三方库中的公有编译时常量,可是这个值后面被其余人改变了,可是你的客户端仍然在使用老的值,甚至你已经部署了一个新的jar。为了不这种状况,当你在更新依赖 JAR 文件时,确保从新编译你的程序。

java当中使用什么类型表示价格比较好?

若是不是特别关心内存和性能的话,使用BigDecimal,不然使用预约义精度的 double 类型。

如何将byte转为String

可使用 String 接收 byte[] 参数的构造器来进行转换,须要注意的点是要使用的正确的编码,不然会使用平台默认编码,这个编码可能跟原来的编码相同,也可能不一样。

能够将int强转为byte类型么?会产生什么问题?

咱们能够作强制转换,可是Java中int是32位的而byte是8 位的,因此,若是强制转化int类型的高24位将会被丢弃,byte 类型的范围是从-128.到128


关于垃圾回收

你知道哪些垃圾回收算法?

垃圾回收从理论上很是容易理解,具体的方法有如下几种:

  1. 标记-清除
  2. 标记-复制
  3. 标记-整理
  4. 分代回收 更详细的内容参见深刻理解垃圾回收算法

如何判断一个对象是否应该被回收

这就是所谓的对象存活性判断,经常使用的方法有两种:1.引用计数法;2:对象可达性分析.因为引用计数法存在互相引用致使没法进行GC的问题,因此目前JVM虚拟机多使用对象可达性分析算法.

简单的解释一下垃圾回收

Java 垃圾回收机制最基本的作法是分代回收。内存中的区域被划分红不一样的世代,对象根据其存活的时间被保存在对应世代的区域中。通常的实现是划分红3个世代:年轻、年老和永久。内存的分配是发生在年轻世代中的。当一个对象存活时间足够长的时候,它就会被复制到年老世代中。对于不一样的世代可使用不一样的垃圾回收算法。进行世代划分的出发点是对应用中对象存活时间进行研究以后得出的统计规律。通常来讲,一个应用中的大部分对象的存活时间都很短。好比局部变量的存活时间就只在方法的执行过程当中。基于这一点,对于年轻世代的垃圾回收算法就能够颇有针对性.

调用System.gc()会发生什么?

通知GC开始工做,可是GC真正开始的时间不肯定.


进程,线程相关

说说进程,线程,协程之间的区别

简而言之,进程是程序运行和资源分配的基本单位,一个程序至少有一个进程,一个进程至少有一个线程.进程在执行过程当中拥有独立的内存单元,而多个线程共享内存资源,减小切换次数,从而效率更高.线程是进程的一个实体,是cpu调度和分派的基本单位,是比程序更小的能独立运行的基本单位.同一进程中的多个线程之间能够并发执行.

你了解守护线程吗?它和非守护线程有什么区别

程序运行完毕,jvm会等待非守护线程完成后关闭,可是jvm不会等待守护线程.守护线程最典型的例子就是GC线程

什么是多线程上下文切换

多线程的上下文切换是指CPU控制权由一个已经正在运行的线程切换到另一个就绪并等待获取CPU执行权的线程的过程。

建立两种线程的方式?他们有什么区别?

经过实现java.lang.Runnable或者经过扩展java.lang.Thread类.相比扩展Thread,实现Runnable接口可能更优.缘由有二:

  1. Java不支持多继承.所以扩展Thread类就表明这个子类不能扩展其余类.而实现Runnable接口的类还可能扩展另外一个类.
  2. 类可能只要求可执行便可,所以集成整个Thread类的开销过大.

Runnable和Callable的区别

Runnable接口中的run()方法的返回值是void,它作的事情只是纯粹地去执行run()方法中的代码而已;Callable接口中的call()方法是有返回值的,是一个泛型,和Future、FutureTask配合能够用来获取异步执行的结果。 这实际上是颇有用的一个特性,由于多线程相比单线程更难、更复杂的一个重要缘由就是由于多线程充满着未知性,某条线程是否执行了?某条线程执行了多久?某条线程执行的时候咱们指望的数据是否已经赋值完毕?没法得知,咱们能作的只是等待这条多线程的任务执行完毕而已。而Callable+Future/FutureTask却能够获取多线程运行的结果,能够在等待时间太长没获取到须要的数据的状况下取消该线程的任务,真的是很是有用。

什么致使线程阻塞

阻塞指的是暂停一个线程的执行以等待某个条件发生(如某资源就绪),学过操做系统的同窗对它必定已经很熟悉了。Java 提供了大量方法来支持阻塞,下面让咱们逐一分析。

方法 说明
sleep() sleep() 容许 指定以毫秒为单位的一段时间做为参数,它使得线程在指定的时间内进入阻塞状态,不能获得CPU 时间,指定的时间一过,线程从新进入可执行状态。 典型地,sleep() 被用在等待某个资源就绪的情形:测试发现条件不知足后,让线程阻塞一段时间后从新测试,直到条件知足为止
suspend() 和 resume() 两个方法配套使用,suspend()使得线程进入阻塞状态,而且不会自动恢复,必须其对应的resume() 被调用,才能使得线程从新进入可执行状态。典型地,suspend() 和 resume() 被用在等待另外一个线程产生的结果的情形:测试发现结果尚未产生后,让线程阻塞,另外一个线程产生告终果后,调用 resume() 使其恢复。
yield() yield() 使得线程放弃当前分得的 CPU 时间,可是不使线程阻塞,即线程仍处于可执行状态,随时可能再次分得 CPU 时间。调用 yield() 的效果等价于调度程序认为该线程已执行了足够的时间从而转到另外一个线程
wait() 和 notify() 两个方法配套使用,wait() 使得线程进入阻塞状态,它有两种形式,一种容许 指定以毫秒为单位的一段时间做为参数,另外一种没有参数,前者当对应的 notify() 被调用或者超出指定时间时线程从新进入可执行状态,后者则必须对应的 notify() 被调用.

wait(),notify()和suspend(),resume()之间的区别

初看起来它们与 suspend() 和 resume() 方法对没有什么分别,可是事实上它们是大相径庭的。区别的核心在于,前面叙述的全部方法,阻塞时都不会释放占用的锁(若是占用了的话),而这一对方法则相反。上述的核心区别致使了一系列的细节上的区别。

首先,前面叙述的全部方法都隶属于 Thread 类,可是这一对却直接隶属于 Object 类,也就是说,全部对象都拥有这一对方法。初看起来这十分难以想象,可是实际上倒是很天然的,由于这一对方法阻塞时要释放占用的锁,而锁是任何对象都具备的,调用任意对象的 wait() 方法致使线程阻塞,而且该对象上的锁被释放。而调用 任意对象的notify()方法则致使因调用该对象的 wait() 方法而阻塞的线程中随机选择的一个解除阻塞(但要等到得到锁后才真正可执行)。

其次,前面叙述的全部方法均可在任何位置调用,可是这一对方法却必须在 synchronized 方法或块中调用,理由也很简单,只有在synchronized 方法或块中当前线程才占有锁,才有锁能够释放。一样的道理,调用这一对方法的对象上的锁必须为当前线程所拥有,这样才有锁能够释放。所以,这一对方法调用必须放置在这样的 synchronized 方法或块中,该方法或块的上锁对象就是调用这一对方法的对象。若不知足这一条件,则程序虽然仍能编译,但在运行时会出现IllegalMonitorStateException 异常。

wait() 和 notify() 方法的上述特性决定了它们常常和synchronized 方法或块一块儿使用,将它们和操做系统的进程间通讯机制做一个比较就会发现它们的类似性:synchronized方法或块提供了相似于操做系统原语的功能,它们的执行不会受到多线程机制的干扰,而这一对方法则至关于 block 和wakeup 原语(这一对方法均声明为 synchronized)。它们的结合使得咱们能够实现操做系统上一系列精妙的进程间通讯的算法(如信号量算法),并用于解决各类复杂的线程间通讯问题。

关于 wait() 和 notify() 方法最后再说明两点: 第一:调用 notify() 方法致使解除阻塞的线程是从因调用该对象的 wait() 方法而阻塞的线程中随机选取的,咱们没法预料哪个线程将会被选择,因此编程时要特别当心,避免因这种不肯定性而产生问题。

第二:除了 notify(),还有一个方法 notifyAll() 也可起到相似做用,惟一的区别在于,调用 notifyAll() 方法将把因调用该对象的 wait() 方法而阻塞的全部线程一次性所有解除阻塞。固然,只有得到锁的那一个线程才能进入可执行状态。

谈到阻塞,就不能不谈一谈死锁,略一分析就能发现,suspend() 方法和不指定超时期限的 wait() 方法的调用均可能产生死锁。遗憾的是,Java 并不在语言级别上支持死锁的避免,咱们在编程中必须当心地避免死锁。

以上咱们对 Java 中实现线程阻塞的各类方法做了一番分析,咱们重点分析了 wait() 和 notify() 方法,由于它们的功能最强大,使用也最灵活,可是这也致使了它们的效率较低,较容易出错。实际使用中咱们应该灵活使用各类方法,以便更好地达到咱们的目的。

为何wait()方法和notify()/notifyAll()方法要在同步块中被调用

这是JDK强制的,wait()方法和notify()/notifyAll()方法在调用前都必须先得到对象的锁

wait()方法和notify()/notifyAll()方法在放弃对象监视器时有什么区别

wait()方法和notify()/notifyAll()方法在放弃对象监视器的时候的区别在于:wait()方法当即释放对象监视器,notify()/notifyAll()方法则会等待线程剩余代码执行完毕才会放弃对象监视器。

wait()与sleep()的区别

关于这二者已经在上面进行详细的说明,这里就作个归纳好了:

  • sleep()来自Thread类,和wait()来自Object类.调用sleep()方法的过程当中,线程不会释放对象锁。而 调用 wait 方法线程会释放对象锁
  • sleep()睡眠后不出让系统资源,wait让其余线程能够占用CPU
  • sleep(milliseconds)须要指定一个睡眠时间,时间一到会自动唤醒.而wait()须要配合notify()或者notifyAll()使用

synchronized和ReentrantLock的区别

synchronized是和if、else、for、while同样的关键字,ReentrantLock是类,这是两者的本质区别。既然ReentrantLock是类,那么它就提供了比synchronized更多更灵活的特性,能够被继承、能够有方法、能够有各类各样的类变量,ReentrantLock比synchronized的扩展性体如今几点上: (1)ReentrantLock能够对获取锁的等待时间进行设置,这样就避免了死锁 (2)ReentrantLock能够获取各类锁的信息 (3)ReentrantLock能够灵活地实现多路通知 另外,两者的锁机制其实也是不同的:ReentrantLock底层调用的是Unsafe的park方法加锁,synchronized操做的应该是对象头中mark word.

FutureTask是什么

这个其实前面有提到过,FutureTask表示一个异步运算的任务。FutureTask里面能够传入一个Callable的具体实现类,能够对这个异步运算的任务的结果进行等待获取、判断是否已经完成、取消任务等操做。固然,因为FutureTask也是Runnable接口的实现类,因此FutureTask也能够放入线程池中。

一个线程若是出现了运行时异常怎么办?

若是这个异常没有被捕获的话,这个线程就中止执行了。另外重要的一点是:若是这个线程持有某个某个对象的监视器,那么这个对象监视器会被当即释放

如何在两个线程间共享数据

经过在线程之间共享对象就能够了,而后经过wait/notify/notifyAll、await/signal/signalAll进行唤起和等待,比方说阻塞队列BlockingQueue就是为线程之间共享数据而设计的

如何正确的使用wait()?使用if仍是while?

wait() 方法应该在循环调用,由于当线程获取到 CPU 开始执行的时候,其余条件可能尚未知足,因此在处理前,循环检测条件是否知足会更好。下面是一段标准的使用 wait 和 notify 方法的代码:

synchronized (obj) {
    while (condition does not hold)
      obj.wait(); // (Releases lock, and reacquires on wakeup)
      ... // Perform action appropriate to condition
 }

什么是线程局部变量

线程局部变量是局限于线程内部的变量,属于线程自身全部,不在多个线程间共享。Java提供ThreadLocal类来支持线程局部变量,是一种实现线程安全的方式。可是在管理环境下(如 web 服务器)使用线程局部变量的时候要特别当心,在这种状况下,工做线程的生命周期比任何应用变量的生命周期都要长。任何线程局部变量一旦在工做完成后没有释放,Java 应用就存在内存泄露的风险。

ThreadLoal的做用是什么?

简单说ThreadLocal就是一种以空间换时间的作法在每一个Thread里面维护了一个ThreadLocal.ThreadLocalMap把数据进行隔离,数据不共享,天然就没有线程安全方面的问题了.

生产者消费者模型的做用是什么?

(1)经过平衡生产者的生产能力和消费者的消费能力来提高整个系统的运行效率,这是生产者消费者模型最重要的做用 (2)解耦,这是生产者消费者模型附带的做用,解耦意味着生产者和消费者之间的联系少,联系越少越能够独自发展而不须要收到相互的制约

写一个生产者-消费者队列

能够经过阻塞队列实现,也能够经过wait-notify来实现.

使用阻塞队列来实现

//消费者
public class Producer implements Runnable{
    private final BlockingQueue<Integer> queue;

    public Producer(BlockingQueue q){
        this.queue=q;
    }

    @Override
    public void run() {
        try {
            while (true){
                Thread.sleep(1000);//模拟耗时
                queue.put(produce());
            }
        }catch (InterruptedException e){

        }
    }

    private int produce() {
        int n=new Random().nextInt(10000);
        System.out.println("Thread:" + Thread.currentThread().getId() + " produce:" + n);
        return n;
    }
}
//消费者
public class Consumer implements Runnable {
    private final BlockingQueue<Integer> queue;

    public Consumer(BlockingQueue q){
        this.queue=q;
    }

    @Override
    public void run() {
        while (true){
            try {
                Thread.sleep(2000);//模拟耗时
                consume(queue.take());
            }catch (InterruptedException e){

            }

        }
    }

    private void consume(Integer n) {
        System.out.println("Thread:" + Thread.currentThread().getId() + " consume:" + n);

    }
}
//测试
public class Main {

    public static void main(String[] args) {
        BlockingQueue<Integer> queue=new ArrayBlockingQueue<Integer>(100);
        Producer p=new Producer(queue);
        Consumer c1=new Consumer(queue);
        Consumer c2=new Consumer(queue);

        new Thread(p).start();
        new Thread(c1).start();
        new Thread(c2).start();
    }
}

使用wait-notify来实现

该种方式应该最经典,这里就不作说明了

ConcurrentHashMap的并发度是什么?

ConcurrentHashMap的并发度就是segment的大小,默认为16,这意味着最多同时能够有16条线程操做ConcurrentHashMap,这也是ConcurrentHashMap对Hashtable的最大优点,任何状况下,Hashtable能同时有两条线程获取Hashtable中的数据吗?

CyclicBarrier和CountDownLatch区别

这两个类很是相似,都在java.util.concurrent下,均可以用来表示代码运行到某个点上,两者的区别在于:

  • CyclicBarrier的某个线程运行到某个点上以后,该线程即中止运行,直到全部的线程都到达了这个点,全部线程才从新运行;CountDownLatch则不是,某线程运行到某个点上以后,只是给某个数值-1而已,该线程继续运行
  • CyclicBarrier只能唤起一个任务,CountDownLatch能够唤起多个任务
  • CyclicBarrier可重用,CountDownLatch不可重用,计数值为0该CountDownLatch就不可再用了

java中的++操做符线程安全么?

不是线程安全的操做。它涉及到多个指令,如读取变量值,增长,而后存储回内存,这个过程可能会出现多个线程交差

你有哪些多线程开发良好的实践?

  1. 给线程命名
  2. 最小化同步范围
  3. 优先使用volatile
  4. 尽量使用更高层次的并发工具而非wait和notify()来实现线程通讯,如BlockingQueue,Semeaphore
  5. 优先使用并发容器而非同步容器.
  6. 考虑使用线程池

关于volatile关键字

能够建立Volatile数组吗?

Java 中能够建立 volatile类型数组,不过只是一个指向数组的引用,而不是整个数组。若是改变引用指向的数组,将会受到volatile 的保护,可是若是多个线程同时改变数组的元素,volatile标示符就不能起到以前的保护做用了

volatile能使得一个非原子操做变成原子操做吗?

一个典型的例子是在类中有一个 long 类型的成员变量。若是你知道该成员变量会被多个线程访问,如计数器、价格等,你最好是将其设置为 volatile。为何?由于 Java 中读取 long 类型变量不是原子的,须要分红两步,若是一个线程正在修改该 long 变量的值,另外一个线程可能只能看到该值的一半(前 32 位)。可是对一个 volatile 型的 long 或 double 变量的读写是原子。

一种实践是用 volatile 修饰 long 和 double 变量,使其能按原子类型来读写。double 和 long 都是64位宽,所以对这两种类型的读是分为两部分的,第一次读取第一个 32 位,而后再读剩下的 32 位,这个过程不是原子的,但 Java 中 volatile 型的 long 或 double 变量的读写是原子的。volatile 修复符的另外一个做用是提供内存屏障(memory barrier),例如在分布式框架中的应用。简单的说,就是当你写一个 volatile 变量以前,Java 内存模型会插入一个写屏障(write barrier),读一个 volatile 变量以前,会插入一个读屏障(read barrier)。意思就是说,在你写一个 volatile 域时,能保证任何线程都能看到你写的值,同时,在写以前,也能保证任何数值的更新对全部线程是可见的,由于内存屏障会将其余全部写的值更新到缓存。

volatile类型变量提供什么保证?

volatile 主要有两方面的做用:1.避免指令重排2.可见性保证.例如,JVM 或者 JIT为了得到更好的性能会对语句重排序,可是 volatile 类型变量即便在没有同步块的状况下赋值也不会与其余语句重排序。 volatile 提供 happens-before 的保证,确保一个线程的修改能对其余线程是可见的。某些状况下,volatile 还能提供原子性,如读 64 位数据类型,像 long 和 double 都不是原子的(低32位和高32位),但 volatile 类型的 double 和 long 就是原子的.


关于集合

Java中的集合及其继承关系

关于集合的体系是每一个人都应该烂熟于心的,尤为是对咱们常用的List,Map的原理更该如此.这里咱们看这张图便可: 

更多内容可见集合类总结

poll()方法和remove()方法区别?

poll() 和 remove() 都是从队列中取出一个元素,可是 poll() 在获取元素失败的时候会返回空,可是 remove() 失败的时候会抛出异常。

LinkedHashMap和PriorityQueue的区别

PriorityQueue 是一个优先级队列,保证最高或者最低优先级的的元素老是在队列头部,可是 LinkedHashMap 维持的顺序是元素插入的顺序。当遍历一个 PriorityQueue 时,没有任何顺序保证,可是 LinkedHashMap 课保证遍历顺序是元素插入的顺序。

WeakHashMap与HashMap的区别是什么?

WeakHashMap 的工做与正常的 HashMap 相似,可是使用弱引用做为 key,意思就是当 key 对象没有任何引用时,key/value 将会被回收。

ArrayList和LinkedList的区别?

最明显的区别是 ArrrayList底层的数据结构是数组,支持随机访问,而 LinkedList 的底层数据结构是双向循环链表,不支持随机访问。使用下标访问一个元素,ArrayList 的时间复杂度是 O(1),而 LinkedList 是 O(n)。

ArrayList和HashMap默认大小?

在 Java 7 中,ArrayList 的默认大小是 10 个元素,HashMap 的默认大小是16个元素(必须是2的幂)。这就是 Java 7 中 ArrayList 和 HashMap 类的代码片断

private static final int DEFAULT_CAPACITY = 10;

 //from HashMap.java JDK 7
 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

Comparator和Comparable的区别?

Comparable 接口用于定义对象的天然顺序,而 comparator 一般用于定义用户定制的顺序。Comparable 老是只有一个,可是能够有多个 comparator 来定义对象的顺序。

如何实现集合排序?

你可使用有序集合,如 TreeSet 或 TreeMap,你也可使用有顺序的的集合,如 list,而后经过 Collections.sort() 来排序。

如何打印数组内容

你可使用 Arrays.toString() 和 Arrays.deepToString() 方法来打印数组。因为数组没有实现 toString() 方法,因此若是将数组传递给 System.out.println() 方法,将没法打印出数组的内容,可是 Arrays.toString() 能够打印每一个元素。

LinkedList的是单向链表仍是双向?

双向循环列表,具体实现自行查阅源码.

TreeMap是实现原理

采用红黑树实现,具体实现自行查阅源码.

遍历ArrayList时如何正确移除一个元素

该问题的关键在于面试者使用的是 ArrayList 的 remove() 仍是 Iterator 的 remove()方法。这有一段示例代码,是使用正确的方式来实如今遍历的过程当中移除元素,而不会出现 ConcurrentModificationException 异常的示例代码。

什么是ArrayMap?它和HashMap有什么区别?

ArrayMap是Android SDK中提供的,非Android开发者能够略过. ArrayMap是用两个数组来模拟map,更少的内存占用空间,更高的效率. 具体参考这篇文章:ArrayMap VS HashMap

HashMap的实现原理

1 HashMap概述: HashMap是基于哈希表的Map接口的非同步实现。此实现提供全部可选的映射操做,并容许使用null值和null键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。 2 HashMap的数据结构: 在java编程语言中,最基本的结构就是两种,一个是数组,另一个是模拟指针(引用),全部的数据结构均可以用这两个基本结构来构造的,HashMap也不例外。HashMap其实是一个“链表散列”的数据结构,即数组和链表的结合体。

当咱们往Hashmap中put元素时,首先根据key的hashcode从新计算hash值,根绝hash值获得这个元素在数组中的位置(下标),若是该数组在该位置上已经存放了其余元素,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最早加入的放入链尾.若是数组中该位置没有元素,就直接将该元素放到数组的该位置上.

你了解Fail-Fast机制吗

Fail-Fast即咱们常说的快速失败,更多内容参看fail-fast机制


关于日期

SimpleDateFormat是线程安全的吗?

很是不幸,DateFormat 的全部实现,包括 SimpleDateFormat 都不是线程安全的,所以你不该该在多线程序中使用,除非是在对外线程安全的环境中使用,如 将 SimpleDateFormat 限制在 ThreadLocal 中。若是你不这么作,在解析或者格式化日期的时候,可能会获取到一个不正确的结果。所以,从日期、时间处理的全部实践来讲,我强力推荐 joda-time 库。

如何格式化日期?

Java 中,可使用 SimpleDateFormat 类或者 joda-time 库来格式日期。DateFormat 类容许你使用多种流行的格式来格式化日期。参见答案中的示例代码,代码中演示了将日期格式化成不一样的格式,如 dd-MM-yyyy 或 ddMMyyyy。


关于异常

简单描述java异常体系

相比没有人不了解异常体系,关于异常体系的更多信息能够见:白话异常机制

什么是异常链

详情直接参见白话异常机制,不作解释了.

throw和throws的区别

throw用于主动抛出java.lang.Throwable 类的一个实例化对象,意思是说你能够经过关键字 throw 抛出一个 Error 或者 一个Exception,如:throw new IllegalArgumentException(“size must be multiple of 2″), 而throws 的做用是做为方法声明和签名的一部分,方法被抛出相应的异常以便调用者能处理。Java 中,任何未处理的受检查异常强制在 throws 子句中声明。


关于序列化

Java 中,Serializable 与 Externalizable 的区别

Serializable 接口是一个序列化 Java 类的接口,以便于它们能够在网络上传输或者能够将它们的状态保存在磁盘上,是 JVM 内嵌的默认序列化方式,成本高、脆弱并且不安全。Externalizable 容许你控制整个序列化过程,指定特定的二进制格式,增长安全机制。


关于JVM

JVM特性

平台无关性. Java语言的一个很是重要的特色就是与平台的无关性。而使用Java虚拟机是实现这一特色的关键。通常的高级语言若是要在不一样的平台上运行,至少须要编译成不一样的目标代码。而引入Java语言虚拟机后,Java语言在不一样平台上运行时不须要从新编译。Java语言使用模式Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码(字节码),就能够在多种平台上不加修改地运行。Java虚拟机在执行字节码时,把字节码解释成具体平台上的机器指令执行。

简单解释一下类加载器

有关类加载器通常会问你四种类加载器的应用场景以及双亲委派模型,更多的内容参看深刻理解JVM加载器

简述堆和栈的区别

VM 中堆和栈属于不一样的内存区域,使用目的也不一样。栈经常使用于保存方法帧和局部变量,而对象老是在堆上分配。栈一般都比堆小,也不会在多个线程之间共享,而堆被整个 JVM 的全部线程共享。

简述JVM内存分配

  1. 基本数据类型比变量和对象的引用都是在栈分配的
  2. 堆内存用来存放由new建立的对象和数组
  3. 类变量(static修饰的变量),程序在一加载的时候就在堆中为类变量分配内存,堆中的内存地址存放在栈中
  4. 实例变量:当你使用java关键字new的时候,系统在堆中开辟并不必定是连续的空间分配给变量,是根据零散的堆内存地址,经过哈希算法换算为一长串数字以表征这个变量在堆中的"物理位置”,实例变量的生命周期--当实例变量的引用丢失后,将被GC(垃圾回收器)列入可回收“名单”中,但并非立刻就释放堆中内存
  5. 局部变量: 由声明在某方法,或某代码段里(好比for循环),执行到它的时候在栈中开辟内存,当局部变量一但脱离做用域,内存当即释放

其余

java当中采用的是大端仍是小端?

XML解析的几种方式和特色

DOM,SAX,PULL三种解析方式:

  • DOM:消耗内存:先把xml文档都读到内存中,而后再用DOM API来访问树形结构,并获取数据。这个写起来很简单,可是很消耗内存。要是数据过大,手机不够牛逼,可能手机直接死机
  • SAX:解析效率高,占用内存少,基于事件驱动的:更加简单地说就是对文档进行顺序扫描,当扫描到文档(document)开始与结束、元素(element)开始与结束、文档(document)结束等地方时通知事件处理函数,由事件处理函数作相应动做,而后继续一样的扫描,直至文档结束。
  • PULL:与 SAX 相似,也是基于事件驱动,咱们能够调用它的next()方法,来获取下一个解析事件(就是开始文档,结束文档,开始标签,结束标签),当处于某个元素时能够调用XmlPullParser的getAttributte()方法来获取属性的值,也可调用它的nextText()获取本节点的值。

JDK 1.7特性

然 JDK 1.7 不像 JDK 5 和 8 同样的大版本,可是,仍是有不少新的特性,如 try-with-resource 语句,这样你在使用流或者资源的时候,就不须要手动关闭,Java 会自动关闭。Fork-Join 池某种程度上实现 Java 版的 Map-reduce。容许 Switch 中有 String 变量和文本。菱形操做符(<>)用于类型推断,再也不须要在变量声明的右边申明泛型,所以能够写出可读写更强、更简洁的代码

JDK 1.8特性

java 8 在 Java 历史上是一个开创新的版本,下面 JDK 8 中 5 个主要的特性: Lambda 表达式,容许像对象同样传递匿名函数 Stream API,充分利用现代多核 CPU,能够写出很简洁的代码 Date 与 Time API,最终,有一个稳定、简单的日期和时间库可供你使用 扩展方法,如今,接口中能够有静态、默认方法。 重复注解,如今你能够将相同的注解在同一类型上使用屡次。

Maven和ANT有什么区别?

虽然二者都是构建工具,都用于建立 Java 应用,可是 Maven 作的事情更多,在基于“约定优于配置”的概念下,提供标准的Java 项目结构,同时能为应用自动管理依赖(应用中所依赖的 JAR 文件),Maven 与 ANT 工具更多的不一样之处请参见答案。 这就是全部的面试题,如此之多,是否是?我能够保证,若是你能回答列表中的全部问题,你就能够很轻松的应付任何核心 Java 或者高级 Java 面试。虽然,这里没有涵盖 Servlet、JSP、JSF、JPA,JMS,EJB 及其它 Java EE 技术,也没有包含主流的框架如 spring MVC,Struts 2.0,hibernate,也没有包含 SOAP 和 RESTful web service,可是这份列表对作 Java 开发的、准备应聘 Java web 开发职位的人仍是一样有用的,由于全部的 Java 面试,开始的问题都是 Java 基础和 JDK API 相关的。若是你认为我这里有任何应该在这份列表中而被我遗漏了的 Java 流行的问题,你能够自由的给我建议。个人目的是从最近的面试中建立一份最新的、最优的 Java 面试问题列表。

JDBC最佳实践

  • 优先使用批量操做来插入和更新数据
  • 使用PreparedStatement来避免SQL漏洞
  • 使用数据链接池
  • 经过列名来获取结果集

IO操做最佳实践

  1. 使用有缓冲的IO类,不要单独读取字节或字符
  2. 使用NIO和NIO 2或者AIO,而非BIO
  3. 在finally中关闭流
  4. 使用内存映射文件获取更快的IO
相关文章
相关标签/搜索