常见的Java开发面试题目
1.CGLIB 和 JDK生成动态代理类的区别。
JDK动态代理只能对实现了接口的类生成代理,而不能针对类
CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法
2.HashMap、HashTable和concurrentHashMap的区别,HashMap的底层实现。
1.HashTable的方法是同步的,HashMap未经同步,所以在多线程场合要手动同步HashMap这个区别就像Vector和ArrayList一样。
2.HashTable不允许null值(key和value都不可以),HashMap允许null值(key和value都可以)。
3.HashTable有一个contains(Object value),功能和containsValue(Object value)功能一样。
因为多线程环境下,使用HashMap进行put操作会引起死循环,导致CPU利用率接近100%,所以在并发情况下不能使用HashMap,如以下代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
final
HashMap<String, String> map =
new
HashMap<String, String>(
2
);
Thread t =
new
Thread(
new
Runnable() {
@Override
public
void
run() {
for
(
int
i =
0
; i <
10000
; i++) {
new
Thread(
new
Runnable() {
@Override
public
void
run() {
map.put(UUID.randomUUID().toString(),
""
);
}
},
"ftf"
+ i).start();
}
}
},
"ftf"
);
t.start();
t.join();
|
效率低下的HashTable容器
HashTable容器使用synchronized来保证线程安全,但在线程竞争激烈的情况下HashTable的效率非常低下。因为当一个线程访问HashTable的同步方法时,其他线程访问HashTable的同步方法时,可能会进入阻塞或轮询状态。如线程1使用put进行添加元素,线程2不但不能使用put方法添加元素,并且也不能使用get方法来获取元素,所以竞争越激烈效率越低。
锁分段技术
HashTable容器在竞争激烈的并发环境下表现出效率低下的原因是所有访问HashTable的线程都必须竞争同一把锁,那假如容器里有多把锁,每一把锁用于锁容器其中一部分数据,那么当多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效的提高并发访问效率,这就是ConcurrentHashMap所使用的锁分段技术,首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。
ConcurrentHashMap是由Segment数组结构和HashEntry数组结构组成。
Segment是一种可重入锁ReentrantLock,在ConcurrentHashMap里扮演锁的角色,HashEntry则用于存储键值对数据。
一个ConcurrentHashMap里包含一个Segment数组,Segment的结构和HashMap类似,是一种数组和链表结构, 一个Segment里包含一个HashEntry数组,每个HashEntry是一个链表结构的元素, 每个Segment守护着一个HashEntry数组里的元素,当对HashEntry数组的数据进行修改时,必须首先获得它对应的Segment锁。
3.Hibernate的工作原理,以及对象管理机制。
1.通过Configuration().configure();读取并解析hibernate.cfg.xml配置文件
2.由hibernate.cfg.xml中的<mapping resource="com/xx/User.hbm.xml"/>读取并解析映射信息
3.通过config.buildSessionFactory();//创建SessionFactory
4.sessionFactory.openSession();//打开Sesssion
5.session.beginTransaction();//创建事务Transation
6.persistent operate持久化操作
7.session.getTransaction().commit();//提交事务
8.关闭Session
9.关闭SesstionFactory
在hibernate中对象有三种状态:瞬时态或自由态(transient)、持久化态(persistent)、托管态或游离态(detached)
(1).三态简介:
◆transient:瞬态或者自由态 (例如new Person("张三")
1.该po通过new等方式创建
2.该po的实例和session没有关联,不在session中缓存
3.该po在数据库中没有对应的记录
◆persistent:持久化状态 例如通过get和load等得到的对象
1.该po通过get、load等方法从数据库中取出并转化成对象
2.该po与session中的实例关联,在session中缓存
3.该po在数据库中有对应的记录
◆detached:脱管状态或者游离态
1.该po通过session的close、clear或者evict(obj)方法从持久态变化得到
2.该po的实例和session没有关联,不在session中缓存
3.该po在数据库中有对应的记录(前提是没有其他session删除此记录)
4.通过delete(obj)但不能执行事务(在一个事务中),也可以得到游离态的po,因delete而变成游离态可以通过save或saveOrUpdate()变成持久态。
(2)hibernate常用方法简介
◆update方法
1.操作的对象:自由态或脱管状态(因session的关闭而处于脱管状态)的对象
2.作用:将对象变为持久态(更新到数据库中)
3.触发方式:手动调用
◆flush方法
1.操作的对象:持久态对象(po)
2.作用:将对象更新到数据库中
3.触发方式:
a.手动调用
b.session的关闭、SessionFactory关闭,自动调用
c.get()一个对象,把对象的属性进行改变,把资源关闭,自动调用
d.transaction commit的时候,自动调用
e.transaction commit的时候,自动调用
◆lock方法
1.操作的对象:没有更改过的 脱管状态的对象(针对的是因Session的关闭而处于脱管状态的po对象,不能针对因delete而处于脱管状态的po对象)
2.作用:将对象变为持久化状态,等待flush方法将其更新到数据库
3.触发方式:手动调用
◆clear方法
1.操作的对象:session缓存
2.作用:完整的清除session缓存
3.触发方式:手动调用
◆evcit(obj)方法
1.操作的对象:某个持久化对象
2.作用:把某个持久化对象从session的缓存中清空
3.触发方式:手动调用
注意:在实际业务中经常需要从数据库取到一个对象,并将其进行处理,编码或解码某些属性进行其他操作,这时如果对象处于持久态,会在session关闭时同步到数据库中,而我们不想更改数据库中数据的状态,这时有两个方法解决:
1.new一个自由态的对象,拷贝原对象的属性(主键id)除外,使用new处理的新对象进行操作。
2.将对象从session中清空(evict(obj)方法),使其变为detached托管态,再进行操作。
4.String,StringBuffer和StringBuilder的区别。
查看这篇 Stringbuffer与Stringbuilder源码学习和对比
5.流量控制需要用到什么算法。
流量控制就是让发送方的发送速率不要太快,让接收方来得及接受。利用滑动窗口机制可以很方便的在TCP连接上实现对发送方的流量控制。TCP的窗口单位是字节,不是报文段,发送方的发送窗口不能超过接收方给出的接收窗口的数值。
具体的算法有交互式数据流Nagle算法和慢启动与拥塞避免算法等,了解即可。
6.写一个死锁例子。
7.finally在任何情况下都会运行吗?如果不是在什么情况下不运行?
在程序中,应该确保占用的资源被释放,比如及时关闭数据库连接,关闭输入流,或者关闭输出流。finally代码块能保证特定的操作总是会被执行。
1
2
3
4
5
6
7
8
9
10
|
public
void
work()
throws
LeaveEarlyException {
try
{
便利店开门;
营业时间;
//可能会抛出Exception异常
}
catch
(Exception e){
//店员突发离店
throw
new
LeaveEarlyException();
}
finally
{
关门;
}
}
|
当你在捕获到异常的处理代码里加上:
System.exit();
这样的话finally的代码块是不会执行的。
8.Java为什么要使用内部类?
Java中的内部类和接口加在一起,可以的解决常被C++程序员抱怨Java中存在的一个问题——没有多继承。实际上,C++的多继承设计起来很复杂,而Java通过内部类加上接口,可以很好的实现多继承的效果。
每个内部类都能独立地继承自一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。如果没有内部类提供的可以继承多个具体的或抽象的类的能力,一些设计与编程问题就很难解决。从这个角度看,内部类使得多重继承的解决方案变得完整。接口解决了部分问题,而内部类有效地实现了“多重继承”。
9.Web开发中网站缓存有哪几种,有什么作用?
第一、DNS缓存。
这个是域名的缓存,比如说,我们的域名对应的IP是1.1.1.1换成2.2.2.2的时候,你的浏览器和你的路由器里边会保存半个小时左右的缓存。这时候就会出现一种情况是,有一部分人代开的是A服务器上的网站,有一部分人打开的是B服务器的网站。这二种奇怪的现象就是因为DNS的原因。等DNS缓存过去了之后就会正常。同时我们要说明的是,DNS缓存有两个重要的节点,一个是分布在整个网络的路由器上,另一个是因为本地电脑的原因,比如说同一个办公室的,你的打开是A服务器,而别人打开是B服务器,这就跟本地的DNS缓存有很大关系了。
第二、网站程序缓存。
这个一般我们可以在网站管理后台看到“更新缓存”的按钮,这就是网站程序的缓存。比如有的程序,特别是PHP的程序,他会将一些程序片段弄成缓存,比如将index.html经过处理放到一个地方变成index.php,方便随时调用。
第三、服务器缓存。
这种缓存是在服务器上,因为IIS等服务器的缓存作用,你的访问会有一些缓存。有的人不能区别网站缓存和服务器缓存。可以这样理解:网站的缓存系统就比如是一个漏洞,把油倒进漏斗里边,然后再流进水桶里边。漏洞就是网站程序缓存,水桶就是服务器缓存,哪一个地方没有刷新干净,都还会有油的香味在里边。服务器的缓存相当于第二道关。
第四、CDN加速缓存。
有的网站用到CDN加速,将你的网站分不到全国十几个服务器节点中。这就相当于把刚才我们讲到的油分发到很多油桶里边。这个时候如果不同地点的访问就会出现差异化。比如说360网站卫士,百度加速乐都有CDN加速功能。
第五、浏览器缓存。
很多主流的浏览器都有缓存,而360浏览器高速模式,搜狗浏览器高速模式缓存时间最多。这就是你经常看到同办公室的人打开网站是一个样,你打开这个网站又是一个样,这是因为有人经常访问,所以浏览器会缓存一些东西。
10.nio direct memory默认是多少?
在传统的文件IO操作中,我们都是调用操作系统提供的底层标准IO系统调用函数 read()、write()
,此时调用此函数的进程(在JAVA中即java进程)由当前的用户态切换到内核态,然后OS的内核代码负责将相应的文件数据读取到内核的IO缓冲区,然
后再把数据从内核IO缓冲区拷贝到进程的私有地址空间中去,这样便完成了一次IO操作。至于为什么要多此一举搞一个内核IO缓冲区把原本只需一次拷贝数据
的事情搞成需要2次数据拷贝呢?
我想学过操作系统或者计算机系统结构的人都知道,这么做是为了减少磁盘的IO操作,为了提高性能而考虑的,因为我们的程序访问一般都带有局部性,也就是所
谓的局部性原理,在这里主要是指的空间局部性,即我们访问了文件的某一段数据,那么接下去很可能还会访问接下去的一段数据,由于磁盘IO操作的速度比直接
访问内存慢了好几个数量级,所以OS根据局部性原理会在一次
read()系统调用过程中预读更多的文件数据缓存在内核IO缓冲区中,当继续访问的文件数据在缓冲区中时便直接拷贝数据到进程私有空间,避免了再次的低
效率磁盘IO操作。
11.如何理解NIO?
NIO是为提供I/O吞吐量而专门设计,其卓越的性能甚至可以与C媲美。NIO是通过Reactor模式的事件驱动机制来达到Non blocking的,那么什么是Reactor模式呢?Reactor翻译成中文是“反应器”,就是我们将事件注册到Reactor中,当有相应的事件发生时,Reactor便会告知我们有哪些事件发生了,我们再根据具体的事件去做相应的处理。
12.分布式如何实现单点登入?
单点登录的实现有很多,下面是CAS的实现原理:
用户尝试使用应用程序的 URL 访问应用程序。用户被重定向到 CAS 登录 URL,采用的是 HTTPS 连接,他请求的服务的名称作为参数传递。这时向用户显示一个用户名/密码对话框。
用户输入 ID 和密码,CAS 对他进行身份验证。如果身份验证失败,目标应用程序根本不会知道这个用户曾经试图访问它 —— 用户在 CAS 服务器上就被拦住了。
如果身份验证成功,CAS 就将用户重定向回目标应用程序,并在 URL 中附加一个称为 ticket 的参数。然后,CAS 尝试创建一个称为 ticket-granting cookie 的内存 cookie。这是为了以后进行自动的重新验证;如果存在这个 cookie,就表示这个用户已经成功地登录了,用户就不需要再次输入他的用户名和密码。
然后,应用程序要检查这个 ticket 是否正确,以及是否代表一个有效用户;检查的方法是,打开一个 HTTPS 连接来调用 CAS serviceValidate URL,并作为参数传递 ticket 和服务名称。CAS 检查这个 ticket 是否有效,以及是否与请求的服务相关联。如果检查成功,CAS 就将用户名返回给应用程序。
13.for(; ;)会如何运行?
死循环,for(;;) = while(true),
除非循环体中终止。