文章目录
-
- 1. 处女面,来自阿里的电话面试(2017/07/30,时长90分钟)
-
- 1.1 java中的反射Reflection
- 1.2 java中的动态代理
- 1.3 Hibernate中的缓存机制
- 1.4 Spring中对事物的管理
- 1.5 Spring中事务隔离的级别
- 1.6 数据库中的乐观锁(读)、悲观锁(写)
- 1.7 java中几种常用设计模式
- 7.4 适配器模式
- 1.8.JVM中的GC
- 1.9 Java中如何判断是不是同一个类
- 1.10 java中的类加载器ClassLoader
- 1.11 Spring中的依赖注入方式
- 1.12 java中常用的线程池有哪些
- 1.13 如何保证一个方法中的一小部分逻辑代码的线程安全,不用同步代码块、不用单独拎出来
- 1.14 java中的缓存,如何进行更新
- 1.15 java中继承和多态
- 1.16 java中覆盖和重载的概念
- 1.17 HashMap和Hashtable是否是线程安全的,他们内部机制是如何的
- 1.18 递归的概念以及使用场景
- 1.19 Java如何实现序列化的
- 2.广联达
- 3. 广州汇智通信
- 4. 南京万得
- 5.大华
- 6. 中兴
- 7. 烽火星空
- 8. 河狸家
- 9. 合合信息
- 10. 华三
- 11. 中科创达
- 12. 群硕
- 13. 努比亚
- 14. 上海银行
- 15. 唯品会金融
- 16. 怪兽充电
- 17. 百度
- 18. 携程
- 19. 京东
- 20. 腾讯
- 21. 蚂蚁金服
- 22. 平安壹钱包
- 23. 陆金所
- 24. 运满满/货车帮
1. 处女面,来自阿里的电话面试(2017/07/30,时长90分钟)
1.1 java中的反射Reflection
反射能动态地加载一个类、调用方法、访问属性,出发点在于JVM为每个类创建了一个java.lang.Class类的实例,通过这份对象可以获取这个类的信息,通过反射包下的API达到各种动态需求。
Java中的反射是一种强大的工具(其他语言中基本没有这个特性),可以创建灵活的代码,这些代码可以在运行时装配,无须在组件中进行链接。反射允许在编写与执行时,使程序代码能接触到装载到JVM中类的内部信息。Java中类的反射Reflectio是Java程序对自身进行检查,并且能直接操作程序内部的属性。代码示例:
Class<Person> clazz = Person.class;
//1.创建clazz对应的运行时类Person,用的是clazz.newInstance()
//这里的clazz.newInstance()实际上是调用的是Person类的无参构造器
Person p = clazz.newInstance();
System.out.println(p);
//2.然后在调用,调用的时候先用clazz.getField(“属性名”)的方法来获取public作用域的属性,然后再往对象里面设值
Field f1 = clazz.getField("name");
f1.set(p, "test2");
System.out.println(p);
//获取private作用域的属性值方法比一样,用上面的方法获取不到
//首先应该获取声明的属性
Field f2 = clazz.getDeclaredField("age");
//将访问权限改为true
f2.setAccessible(true);
f2.set(p, 20);
//3.通过反射调用运行类的方法,首先获取方法
Method m1 = clazz.getMethod("show");
//然后调用方法,没有形参的直接m1.invoke(p);
m1.invoke(p);
//调用有形参的方法,clazz.getMethod时需要指明参数的类型
Method m2 = clazz.getMethod("display", String.class);
//有形参的话m2.invoke(p,形参);传入形参
m2.invoke(p, "HK");
1.2 java中的动态代理
定义:为其他对象提供一种代理以控制对这个对象的访问。
每一个动态代理类都必须实现InvocationHandler接口,并且每个代理类的实例都关联到了一个handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的 invoke 方法来进行调用。这里主要是面向的切面思想。举个例子:
//定义了一个接口
public interface Subject {
public void rent();
public void hello(String str);
}
//实现类
public class RealSubject implements Subject {
@Override
public void rent()
{
System.out.println("I want to rent my house");
}
@Override
public void hello(String str)
{
System.out.println("hello: " + str);
}
}
//动态代理类
public class DynamicProxy implements InvocationHandler {
// 这个就是我们要代理的真实对象
private Object subject;
// 构造方法,给我们要代理的真实对象赋初值
public DynamicProxy(Object subject)
{
this.subject = subject;
}
@Override
public Object invoke(Object object, Method method, Object[] args) throws Throwable {
//在代理真实对象前我们可以添加一些自己的操作
System.out.println("before rent house");
System.out.println("Method:" + method);
//当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
method.invoke(subject, args);
//在代理真实对象后我们也可以添加一些自己的操作
System.out.println("after rent house");
return null;
}
}
//客户端
public class Client
{
public static void main(String[] args)
{
// 我们要代理的真实对象
Subject realSubject = new RealSubject();
// 我们要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法的
InvocationHandler handler = new DynamicProxy(realSubject);
/*
* 通过Proxy的newProxyInstance方法来创建我们的代理对象,我们来看看其三个参数
* 第一个参数 handler.getClass().getClassLoader() ,我们这里使用handler这个类的ClassLoader对象来加载我们的代理对象
* 第二个参数realSubject.getClass().getInterfaces(),我们这里为代理对象提供的接口是真实对象所实行的接口,表示我要代理的是该真实对象,这样我就能调用这组接口中的方法了
* 第三个参数handler, 我们这里将这个代理对象关联到了上方的 InvocationHandler 这个对象上
*/
Subject subject = (Subject)Proxy.newProxyInstance(handler.getClass().getClassLoader(), realSubject
.getClass().getInterfaces(), handler);
System.out.println(subject.getClass().getName());
subject.rent();
subject.hello("world");
}
}
1.3 Hibernate中的缓存机制
1.3.1 为什么使用缓存机制
Hibernate是一个持久层框架,经常访问物理数据库。为了降低应用程序对物理数据源访问的频次,从而提高应用程序的运行性能。缓存内的数据是对物理数据源中的数据的复制,应用程序在运行时从缓存读写数据,在特定的时刻或事件会同步缓存和物理数据源的数据。
1.3.2 hibernate的缓存原理
Hibernate缓存包括两大类:Hibernate一级缓存和Hibernate二级缓存。
- 一级缓存:又称Session的缓存,Session内置不能被卸载,Session的缓存是事务范围的缓存(Session对象的生命周期通常对应一个事务),一级缓存中,持久化类的每个实例都具有唯一的OID,Session的缓存是不可选的,必须要有的,不会出现并发的情况;
- 二级缓存:又称为SessionFactory的缓存,二级缓存的生命周期是进程范围或者集群范围的缓存,有可能出现并发问题,因此需要采用适当的并发访问策略,该策略为被缓存的数据提供了事务隔离级别,第二级缓存是可选的,是一个可配置的插件,默认下SessionFactory不会启用这个插件;
适合用二级缓存的数据:
- 很少被修改的数据 ;
- 不是很重要的数据,允许出现偶尔并发的数据 ;
- 不会被并发访问的数据;
- 常量数据;
适合存放到第二级缓存的数据:
- 经常被修改的数据 ;
- 绝对不允许出现并发访问的数据,如财务数据,绝对不允许出现并发 ;
- 与其他应用共享的数据;
1.3.3 Hibernate中的缓存是如何工作的
当Hibernate根据ID访问数据对象的时候,首先从Session一级缓存中查;查不到,如果配置了二级缓存,那么从二级缓存中查;如果都查不到,再查询数据库,把结果按照ID放入到缓存删除、更新、增加数据的时候,同时更新缓存。
Hibernate在性能提升上的几种方法:
- 懒加载:lazyLoad是常用提升性能的方法。不采用LazyLoad的情况下,Hibernate在获取PO(持久对象)时,将同时获取PO的属性,如果对象的关联结构深层次的话,可能会加载出大量数据,而实际中可能只需要几个属性,这样用懒加载的方式就会很棒,只有在调用这个属性的时候才会将这个属性从缓存中或数据库库中加载出来;
- 缓存:Cache是提升系统性能方面常用的方法。Hibernate中有一级缓存和二级缓存,一级缓存即Session缓存,是必须的,二级缓存即SessionFactory,是可选的,他可以明显的提升性能,同时也会更加消耗内存,可以指定上限从而避免过多的消耗内存;
1.4 Spring中对事物的管理
Spring配置事务管理器,这里需要注意的是Spring并不是直接管理事务的,而是将事务委托给JDBC、Hibernate或者JTA等持久化机制所提供的相关平台框架的事务来实现的。
Spring中事务传播的特性(所有的修饰词都是针对当前事务而言的):
- PROPAGATION_REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。
- PROPAGATION_SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行。
- PROPAGATION_MANDATORY(强制的):强制使用当前的事务,如果当前没有事务,就抛出异常。
- PROPAGATION_REQUIRES_NEW:新建事务(只用新事务,坚决不用别人的),如果当前存在事务,把当前事务挂起。
- PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
- PROPAGATION_NEVER:以非事务方式执行(不允许事务的出现),如果当前存在事务,则抛出异常。
- PROPAGATION_NESTED(嵌套):如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与 PROPAGATION_REQUIRED 类似的操作。
1.5 Spring中事务隔离的级别
数据库中事物的四大特性:原子性、一致性、隔离性、持久性。
这里主要讨论的是事物的隔离性,如果不考虑隔离性,那么将会发生下面这些情况:
- 脏读:一个事务读取另一个事务改写但还未提交的数据,如果这些数据被回滚,那么读到的数据是无效的;
- 不可重复读:是指在一个事务内,多次读同一数据但是结果不一样。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的的数据可能是不一样的。这样在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。在同一事务中,两次读取同一数据,得到内容不同,侧重点在于数据修改;
- 幻读(虚读):在一个事务中读取几行记录后,另一个事务插入一些记录,第一个事物就会发现有些原来没有的记录。同一事务中,用同样的操作读取两次,得到的记录数不相同,幻读的侧重点在于两次读取的纪录数量不一致;
下面找到一个不错的例子,仅供理解:
Read uncommitted 读未提交
公司发工资了,领导把5000元打到singo的账号上,但是该事务并未提交,而singo正好去查看账户,发现工资已经到账,是5000元整,非常高 兴。可是不幸的是,领导发现发给singo的工资金额不对,是2000元,于是迅速回滚了事务,修改金额后,将事务提交,最后singo实际的工资只有 2000元,singo空欢喜一场。出现上述情况,即我们所说的脏读 ,两个并发的事务,“事务A:领导给singo发工资”、“事务B:singo查询工资账户”,事务B读取了事务A尚未提交的数据。
当隔离级别设置为Read uncommitted 时,就可能出现脏读,如何避免脏读,请看下一个隔离级别。
Read committed 读提交
singo拿着工资卡去消费,系统读取到卡里确实有2000元,而此时她的老婆也正好在网上转账,把singo工资卡的2000元转到另一账户,并在 singo之前提交了事务,当singo扣款时,系统检查到singo的工资卡已经没有钱,扣款失败,singo十分纳闷,明明卡里有钱,为 何…
出现上述情况,即我们所说的不可重复读 ,两个并发的事务,“事务A:singo消费”、“事务B:singo的老婆网上转账”,事务A事先读取了数据,事务B紧接了更新了数据,并提交了事务,而事务A再次读取该数据时,数据已经发生了改变。当隔离级别设置为Read committed 时,避免了脏读,但是可能会造成不可重复读。
大多数数据库的默认级别就是Read committed,比如Sql Server , Oracle。如何解决不可重复读这一问题,请看下一个隔离级别。
Repeatable read 重复读(幻读侧重点是新插入了记录,不可重复读侧重的是修改)
当隔离级别设置为Repeatable read 时,可以避免不可重复读。当singo拿着工资卡去消费时,一旦系统开始读取工资卡信息(即事务开始),singo的老婆就不可能对该记录进行修改,也就是singo的老婆不能在此时转账。虽然Repeatable read避免了不可重复读,但还有可能出现幻读 。singo的老婆工作在银行部门,她时常通过银行内部系统查看singo的信用卡消费记录。有一天,她正在查询到singo当月信用卡的总消费金额 (select sum(amount) from transaction where month = 本月)为80元,而singo此时正好在外面胡吃海塞后在收银台买单,消费1000元,即新增了一条1000元的消费记录(insert transaction … ),并提交了事务,随后singo的老婆将singo当月信用卡消费的明细打印到A4纸上,却发现消费总额为1080元,singo的老婆很诧异,以为出 现了幻觉,幻读就这样产生了。
注:MySQL的默认隔离级别就是Repeatable read。
Serializable 序列化
Serializable 是最高的事务隔离级别,同时代价也花费最高,性能很低,一般很少使用,在该级别下,事务顺序执行,不仅可以避免脏读、不可重复读,还避免了幻像读。
1.脏读:
脏读就是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。
2.不可重复读:
是指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。(即不能读到相同的数据内容)
例如,一个编辑人员两次读取同一文档,但在两次读取之间,作者重写了该文档。当编辑人员第二次读取文档时,文档已更改。原始读取不可重复。如果只有在作者全部完成编写后编辑人员才可以读取文档,则可以避免该问题。
3.幻读:
是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样。
例如,一个编辑人员更改作者提交的文档,但当生产部门将其更改内容合并到该文档的主复本时,发现作者已将未编辑的新材料添加到该文档中。如果在编辑人员和生产部门完成对原始文档的处理之前,任何人都不能将新材料添加到文档中,则可以避免该问题。
1.6 数据库中的乐观锁(读)、悲观锁(写)
1.悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。
2.乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库如果提供类似于write_condition机制的其实都是提供的乐观锁。
适用场景:乐观锁适用于写比较少的情况下,即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果经常产生冲突,上层应用会不断的进行retry,这样反倒是降低了性能,所以这种情况下用悲观锁就比较合适,适合写操作比较多的情况。
1.7 java中几种常用设计模式
经典的设计模式一共23种,下面说几个常用的设计模式。其中在框架中的MVC模型视图控制器也是其中一种,模型表示业务逻辑,视图表示界面相关的部分,控制器则根据用户的输入,控制界面数据显示和更新model状态,MVC主要体现的是功能模块的分离。
1.7.1 单例模式(一个类只有一个实例)
单例模式主要是为了避免因为创建多个实例造成资源浪费,保证整个应用中有且只有一个实例,有这样几个要求:
- 只提供私有的构造方法;
- 一个类只允许有一个实例;
- 自行实例化;
- 对外提供一个静态的公有的函数用于创建或获取它本身的静态私有对象,里面又分懒汉(时间换空间,不推荐)和恶汉(空间换时间,可行);
//1.懒汉线程不安全
public class Singleton {
//构造方法私有化
private Singleton(){
}
//自己内部实例化,懒汉只有在使用的时候初始化
private static Singleton singleton;
//对外提供一个获取实例的方法
public static Singleton getSingleton() {
if(singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
//2.改进的懒汉单例,加同步锁,保证线程安全
public class Singleton {
//这里有的书上提供了私有的构造方法,有的没有,这里先加上
private Singleton(){
}
//初始化
private static Singleton instance;
public static synchronized Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
//3.饿汉单例
public class Singleton {
private Singleton(){
}
//饿汉,顾名思义,上来先搞一个实例化对象,并且声明为final,以后不在改变,天生线程安全
private static final Singleton singleton = new Singleton();
public static Singleton getSingleton() {