没有合适的资源?快使用搜索试试~ 我知道了~
资源推荐
资源详情
资源评论
JAVA⾯试⼋股⽂
⽬录
Java基础
1. String 和StringBuffer和 StringBuilder的区别?
String 字符串常量
StringBuffer 字符串变量(线程安全)
StringBuilder 字符串变量(⾮线程安全)
2. sleep() 区间wait()区间有什么区别?
sleep 是 Thread 中的⽅法,线程暂停,让出CPU,但是不释放锁
wait() 是 Object 中的⽅法, 调⽤次⽅法必须让当前线程必须拥有此对象的monitor(即锁),执⾏之后 线程阻塞,让出CPU, 同时也释放
锁; 等待期间不配拥有CPU执⾏权, 必须调⽤ notify/notifyAll ⽅法唤醒,(notify是随机唤醒) 唤醒并不意味着⾥⾯就会执⾏,⽽是还是需
要等待分配到CPU才会执⾏;
3. Object 中有哪些⽅法?其中clone(),怎么实现⼀个对象的克隆,Java如何实现深度克隆?
clone 是浅拷贝;只克隆了⾃⾝对象和对象内实例变量的地址引⽤,使⽤它需要实现接⼝ Cloneable ;
使⽤ ObjectStream 进⾏深度克隆; 先将对象序列化;然后再反序列化;
public static <T extends Serializable> T deepClone(T t) throws CloneNotSupportedException {
//
保存对象为字节数组
try {
ByteArrayOutputStream bout = new ByteArrayOutputStream();
try(ObjectOutputStream out = new ObjectOutputStream(bout)) {
out.writeObject(t);
}
//
从字节数组中读取克隆对象
try(InputStream bin = new ByteArrayInputStream(bout.toByteArray())) {
ObjectInputStream in = new ObjectInputStream(bin);
return (T)(in.readObject());
}
}catch (IOException | ClassNotFoundException e){
CloneNotSupportedException cloneNotSupportedException = new CloneNotSupportedException();
e.initCause(cloneNotSupportedException);
throw cloneNotSupportedException;
}
}
ThreadLocal 相关
4. ThreadLocal作⽤和实现⽅式 ?
TL⽤于保存本地线程的值, 每个 Thread 都有⼀个 threadLocals 属性,它是⼀个 ThreadLocalMap 对象,本质上是⼀个 Entry 数组; Entry 是k-v
结构; 并且是 WeakReference 弱引⽤, K存的是 Thread 对象,Value是设置的值; 那么每个线程就可以读⾃⼰设置的值了;
ThreadLocal会不会发⽣内存泄漏?
会发⽣内存泄漏
ThreadLocalMap使⽤ThreadLocal的 弱引⽤作为key ,如果⼀个ThreadLocal没有外部强引⽤来引⽤它,那么系统 GC 的时候,这个
ThreadLocal势必会被回收,这样⼀来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的
value,如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会⼀直存在⼀条强引⽤链:Thread Ref -> Thread ->
ThreaLocalMap -> Entry -> value永远⽆法回收,造成内存泄漏。
其实,ThreadLocalMap的设计中已经考虑到这种情况,也加上了⼀些防护措施:在 ThreadLocal的get(),set(),remove()的时候都会清除线
程ThreadLocalMap⾥所有key为null的value。
1. 使⽤static的ThreadLocal,延长了ThreadLocal的⽣命周期,可能导致的内存泄漏
2. 分配使⽤了ThreadLocal⼜不再调⽤get(),set(),remove()⽅法,那么就会导致内存泄漏。
ThreadLocal为什么使⽤弱引⽤?
key是弱引⽤好⽍还可以 GC掉key的对象;强引⽤则不⾏
使⽤弱引⽤可以多⼀层保障:弱引⽤ThreadLocal不会内存泄漏,对应的value在下⼀次ThreadLocalMap调⽤set,get,remove的时
候会被清除。
5. InheritableThreadLocal作⽤和实现⽅式 ?
InheritableThreadLocal 基础 ThreadLocal ; 他跟 ThreadLocal 区别是 可以传递值给⼦线程; 每个 Thread 都有⼀
个 inheritableThreadLocals 属性, 创建⼦线程的时候,把把⽗线程的 Entry 数组 塞到⼦线程的Entry 数组 中; 所以就实现了⽗⼦线程的值传
递; 注意如果Value是⼀个⾮基本类型的对象, ⽗⼦线程指向的是相同的引⽤; ⼦线程如果修改了值,⽗线程也是会修改的;
6. InheritableThreadLocal所带来的问题?
线程不安全: 如果说线程本地变量是只读变量不会受到影响,但是如果是可写的,那么任意⼦线程针对本地变量的修改都会影响到主线
程的本地变量
线程池中可能失效: 在使⽤线程池的时候,ITL会完全失效,因为⽗线程的TLMap是通过Thread的 init ⽅法的时候进⾏赋值给⼦线程
的,⽽线程池在执⾏异步任务时可能不再需要创建新的线程了,因此也就不会再传递⽗线程的TLMap给⼦线程了
7. 如何解决线程池异步值传递问题 (transmittable-thread-local)?
阿⾥开源的 transmittable-thread-local 可以很好的解决 在线程池情况下,⽗⼦线程值传递问题; TransmittableThreadLocal 继承
了 InheritableThreadLocal , 简单的原理就是TTL 中的holder持有的是当前线程内的所有本地变量,被包装的run⽅法执⾏异步任务之前,
会使⽤replay进⾏设置⽗线程⾥的本地变量给当前⼦线程,任务执⾏完毕,会调⽤restore恢复该⼦线程原⽣的本地变量
HashMap ConcurrentHashMap相关
9. HashMap为什么线程不安全
1.在JDK1.7中,当并发执⾏扩容操作时会造成环形链和数据丢失的情况。(链表的头插法 造成环形链)
2.在JDK1.8中,在并发执⾏put操作时会发⽣数据覆盖的情况。(元素插⼊时使⽤的是尾插法)
HashMap在put的时候,插⼊的元素超过了容量(由负载因⼦决定)的范围就会触发扩容操作,就是rehash,这个会重新将原数组的
内容重新hash到新的扩容数组中,在多线程的环境下,存在同时其他的元素也在进⾏put操作,如果hash值相同,可能出现同时在同
⼀数组下⽤链表表⽰,造成闭环,导致在get时会出现死循环,所以HashMap是线程不安全的。
10. HashMap在jdk7和8中的区别
1. JDK1.7⽤的是头插法,⽽JDK1.8及之后使⽤的都是尾插法,那么他们为什么要这样做呢?因为JDK1.7是⽤单链表进⾏的纵向延伸,当采⽤头插
法就是能够提⾼插⼊的效率,但是也会容易出现逆序且环形链表死循环问题。但是在JDK1.8之后是因为加⼊了红⿊树使⽤尾插法,能够避免出现
逆序且链表死循环的问题。
2. 扩容后数据存储位置的计算⽅式也不⼀样:1. 在JDK1.7的时候是直接⽤hash值和需要扩容的⼆进制数进⾏&(这⾥就是为什么扩容的时候为啥⼀
定必须是2的多少次幂的原因所在,因为如果只有2的n次幂的情况时最后⼀位⼆进制数才⼀定是1,这样能最⼤程度减少hash碰撞)(hash值 &
length-1)
11. HashMap 为啥将链表改成红⿊树?
提⾼检索时间,在链表长度⼤于8的时候,将后⾯的数据存在红⿊树中,以加快检索速度。复杂度变成O(logn)
12. ConcurrentHashMap在jdk7和8中的区别?
可以看出JDK1.8版本的ConcurrentHashMap的数据结构已经接近HashMap,相对⽽⾔,ConcurrentHashMap只是增加了同步
的操作来控制并发,从JDK1.7版本的ReentrantLock+Segment+HashEntry,到JDK1.8版本中 synchronized+CAS+HashEntry+红⿊
树 ,相对⽽⾔
1. JDK1.8的实现降低锁的粒度,JDK1.7版本锁的粒度是基于Segment的,包含多个HashEntry,⽽JDK1.8锁的粒度就是HashEntry(⾸节点)
2. JDK1.8版本的数据结构变得更加简单,使得操作也更加清晰流畅,因为已经使⽤synchronized来进⾏同步,所以不需要分段锁的概念,也就不需
要Segment这种数据结构了,由于粒度的降低,实现的复杂度也增加了
3. JDK1.8使⽤红⿊树来优化链表,基于长度很长的链表的遍历是⼀个很漫长的过程,⽽红⿊树的遍历效率是很快的,代替⼀定阈值的链表,这样形
成⼀个最佳拍档
4. JDK1.8为什么使⽤内置锁synchronized来代替重⼊锁ReentrantLock; 因为粒度降低了
提到synchronized时候,顺便说⼀下javaSE1.6对锁的优化?
在JDK1.5中,synchronized是性能低效的。因为这是⼀个重量级操作,它对性能⼤的影响是阻塞的是实现,挂起 线程和恢复线程的
操作都需要转⼊内核态中完成,这些操作给系统的并发性带来了很⼤的压⼒
javaSE1.6引⼊了偏向锁,轻量级锁(⾃旋锁)后,synchronized和ReentrantLock两者的性能就差不多了
锁可以升级, 但不能降级. 即: ⽆锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁是单向的.
偏向锁
偏向锁 : HotSpot的作者经过研究发现,⼤多数情况下,锁不仅不存在多线程竞争,⽽且总是由同⼀线程多次获得; 偏向锁是四种状态
中最乐观的⼀种锁:从始⾄终只有⼀个线程请求某⼀把锁。
偏向锁的获取 : 当⼀个线程访问同步块并成功获取到锁时,会在对象头和栈帧中的锁记录字段⾥存储锁偏向的线程ID,以后该线程在进
⼊和退出同步块时不需要进⾏CAS操作来加锁和解锁,直接进⼊
偏性锁的撤销: 偏向锁使⽤了⼀种等待竞争出现才释放锁的机制,所以当其他线程竞争偏向锁时,持有偏向锁的线程才会释放偏向锁,
并将锁膨胀为轻量级锁(持有偏向锁的线程依然存活的时候)
轻量级锁
多个线程在不同的时间段请求同⼀把锁,也就是说没有锁竞争。
加锁: 线程在执⾏同步块之前,JVM会先在当前线程的栈桢中创建⽤于存储锁记录的空间,并将对象头中的Mark Word复制到锁记录
中,官⽅称为Displaced Mark Word。然后线程尝试使⽤CAS将对象头中的Mark Word替换为指向锁记录的指针。如果成功,当前
线程获得锁,如果失败,表⽰其他线程竞争锁,当前线程便尝试使⽤⾃旋来获取锁。
解锁: 轻量级锁解锁时, 会使⽤原⼦的CAS操作将当前线程的锁记录替换回到对象头, 如果成功, 表⽰没有竞争发⽣; 如果失败, 表⽰当前
锁存在竞争, 锁就会膨胀成重量级锁.
重量级锁
Java线程的阻塞以及唤醒,都是依靠操作系统来完成的,这些操作将涉及系统调⽤,需要从操作系统 的⽤户态切换⾄内核态,其开销⾮
常之⼤。
其他优化
锁粗化: 锁粗化就是将多次连接在⼀起的加锁、解锁操作合并为⼀次,将多个连续的锁扩展成为⼀个范围更⼤的锁
锁消除: 锁消除即删除不必要的加锁操作。根据代码逃逸技术,如果判断到⼀段代码中,堆上的数据不会逃逸出当前线程, 那么可以认
为这段代码是线程安全的,不必要加锁
ReentrantLock和synchronized的区别?
在HotSpot虚拟机中, 对象在内存中的布局分为三块区域: 对象头, ⽰例数据和对其填充.
对象头中包含两部分: MarkWord 和 类型指针.
多线程下synchronized的加锁就是对同⼀个对象的对象头中的MarkWord中的变量进⾏CAS操作
Synchronized
对于Synchronized来说,它是java语⾔的关键字,是原⽣语法层⾯的互斥,需要jvm实现,Synchronized的使⽤⽐较⽅便简洁,并且由编
译器去保证锁的加锁和释放
1. 代码块同步: 通过使⽤ monitorenter 和 monitorexit 指令实现的.
2. 同步⽅法: ACC_SYNCHRONIZED 修饰
ReentrantLock
ReenTrantLock的实现是⼀种⾃旋锁,通过循环调⽤CAS操作来实现加锁。它的性能⽐较好也是因为避免了使线程进⼊内核态的阻塞状
态。
1. 等待可中断,持有锁的线程长期不释放的时候,正在等待的线程可以选择放弃等待,这相当于Synchronized来说可以避免出现死锁的情况。通过
lock.lockInterruptibly()来实现这个机制。
2. 公平锁,多个线程等待同⼀个锁时,必须按照申请锁的时间顺序获得锁,Synchronized锁⾮公平锁,ReentrantLock默认的构造函数是创建的⾮
公平锁,可以通过参数true设为公平锁,但公平锁表现的性能不是很好。
3. 锁绑定多个条件,⼀个ReentrantLock对象可以同时绑定对个对象。ReenTrantLock提供了⼀个Condition(条件)类,⽤来实现分组唤醒需要
唤醒的线程们,⽽不是像synchronized要么随机唤醒⼀个线程要么唤醒全部线程。
13. 为什么重写equals时候被要求重写hashCode()?
如果两个对象相同(即:⽤ equals ⽐较返回true),那么它们的 hashCode 值⼀定要相同
如果两个对象的 hashCode 相同,它们并不⼀定相同(即:⽤ equals ⽐较返回 false
为了提供程序效率 通常会先进性 hashcode 的⽐较,如果不同,则就么有必要 equals ⽐较了;
14. 什么时候回发⽣内存泄露?让你写⼀段内存泄露的代码你会怎么写?
我们知道,对象都是有⽣命周期的,有的长,有的短,如果长⽣命周期的对象持有短⽣命周期的引⽤,就很可能会出现内存泄露
下⾯给出⼀个 Java 内存泄漏的典型例⼦,
Vector v = new Vector(10);
for (int i = 0; i < 100; i++) {
Object o = new Object();
v.add(o);
o = null;
}
在这个例⼦中,我们循环申请Object对象,并将所申请的对象放⼊⼀个 Vector 中,如果我们仅仅释放引⽤本⾝,那么 Vector 仍然引⽤该
对象,所以这个对象对 GC 来说是不可回收的。因此,如果对象加⼊到Vector 后,还必须从 Vector 中删除,最简单的⽅法就是将
Vector 对象设置为 null。
v = null
ThreadLocal使⽤不当也可能泄漏
在共享内存的并发模型⾥,线程之间共享程序的公共状态,线程之间通过写 - 读内存中的公共状态来隐式进⾏通信。Java 的并发采⽤
的是共享内存模型
剩余35页未读,继续阅读
资源评论
zz_ll9023
- 粉丝: 1068
- 资源: 5270
上传资源 快速赚钱
- 我的内容管理 展开
- 我的资源 快来上传第一个资源
- 我的收益 登录查看自己的收益
- 我的积分 登录查看自己的积分
- 我的C币 登录后查看C币余额
- 我的收藏
- 我的下载
- 下载帮助
安全验证
文档复制为VIP权益,开通VIP直接复制
信息提交成功