java面试题(一)

java基础

1.string和stringbuffer和stringbuilder的区别?

string是字符串常量,每次操作会重新开辟新的内存空间;
stringbuffer是字符串变量,每次操作使用初次开辟的内存空间(类似于java的string,存在一个字符串缓冲区;同时它是线程安全的)
stringbuilde是字符串变量,使用方法和buffer类似,但是非线程安全。

2.sleep() 区间wait()区间有什么区别?

sleep属于thread,不释放锁;wait属于object,释放锁和次线程持有的资源,其他线程可以使用同步控制块和方法;wait需要使用notify或者notifyAll唤醒

3. Object 中有哪些方法?其中clone(),怎么实现一个对象的克隆,Java如何实现深度克隆?

(1)clone 是浅拷贝,当一个类中的成员不仅有基本数据类型,而且还有引用数据类型的时候,如果该类在重写父类的clone方法时,仅仅只是调用父类的clone方法,那此时拷贝的仅仅只是该类,而该类中的引用数据类型还是原来的那一份,最直接的体现就是,当我们修改了该引用数据类型,那么本体和克隆体都会被修改,这就成为浅拷贝

(2)如果该类在重写父类的clone方法时,再调用完父类的方法后,也把dog拷贝了,那就称为深拷贝

ThreadLocal 相关

4. ThreadLocal作用和实现方式 ?

定义:是一个线程内部的存储类,可以为指定线程存储数据。各线程间互不干扰
实现方式:
每一个线程都会维护一个ThreadLocalMap对象,其实就是一个map,索引为ThreadLocal的hash值。
详解如下

5.ThreadLocal是否存在内存泄露?

存在,因为ThreadLocalMap使用threadlocal的弱引用作为key,如果threadlocal不存在外部强引用时,会被gc回收,结果会导致map中key为null,如果线程不结束,这个null的key会一直存在。
为什么使用弱引用,不会影响gc回收对象,强引用则会影响gc回收。
详解如下

6.InheritableThreadLocal作用和实现方式 ?

首先要理解 为什么 在 新线程中得不到值,是因为我们其实是根据 Thread.currentThread(),拿到该线程的 threadlocals,从而进一步得到我们之前预先 set 好的值。那么如果我们新开一个线程,这个时候,由于 Thread.currentThread() 已经变了,从而导致获得的 threadlocals 不一样,我们之前并没有在这个新的线程的 threadlocals 中放入值,那么我就再通过 threadlocal.get()方法 是不可能拿到值的。
那么解决办法就是 我们在新线程中,要把父线程的 threadlocals 的值 给复制到 新线程中的 threadlocals 中来。这样,我们在新线程中得到的 threadlocals 才会有东西,再通过 threadlocal.get() 中的 threadlocal,就会得到值。
详解如下

7.InheritableThreadLocal所带来的问题?

线程不安全:子线程可以对主线程的变量随意修改
线程池失效:在线程池中执行异步任务可能不会新建线程,也就不会继承父线程的tlmap
如何解决:使用alibaba开源的transmittable-thread-local,将值传递从子线程创建时修改为子线程任务执行时
详解如下

HashMap ConcurrentHashMap相关

8.HashMap为什么线程不安全

1.在JDK1.7中,当并发执行扩容操作时会造成环形链和数据丢失的情况。(链表的头插法 造成环形链)
2.在JDK1.8中,在并发执行put操作时会发生数据覆盖的情况。(元素插入时使用的是尾插法)
HashMap在put的时候,插入的元素超过了容量(由负载因子决定)的范围就会触发扩容操作,就是rehash,这个会重新将原数组的内容重新hash到新的扩容数组中,在多线程的环境下,存在同时其他的元素也在进行put操作,如果hash值相同,可能出现同时在同一数组下用链表表示,造成闭环,导致在get时会出现死循环,所以HashMap是线程不安全的。
JDK1.7和JDK1.8中HashMap为什么是线程不安全的

9.HashMap在jdk7和8中的区别

JDK1.7用的是头插法,而JDK1.8及之后使用的都是尾插法,那么他们为什么要这样做呢?因为JDK1.7是用单链表进行的纵向延伸,当采用头插法就是能够提高插入的效率,但是也会容易出现逆序且环形链表死循环问题。但是在JDK1.8之后是因为加入了红黑树使用尾插法,能够避免出现逆序且链表死循环的问题。
扩容后数据存储位置的计算方式也不一样:1. 在JDK1.7的时候是直接用hash值和需要扩容的二进制数进行&(这里就是为什么扩容的时候为啥一定必须是2的多少次幂的原因所在,因为如果只有2的n次幂的情况时最后一位二进制数才一定是1,这样能最大程度减少hash碰撞)(hash值 & length-1)

10.HashMap 为啥将链表改成红黑树?

提高检索时间,在链表长度大于8的时候,将后面的数据存在红黑树中,以加快检索速度。复杂度变成O(logn)

11.ConcurrentHashMap在jdk7和8中的区别?

可以看出JDK1.8版本的ConcurrentHashMap的数据结构已经接近HashMap,相对而言,ConcurrentHashMap只是增加了同步的操作来控制并发,从JDK1.7版本的ReentrantLock+Segment+HashEntry,到JDK1.8版本中synchronized+CAS+HashEntry+红黑树,相对而言
JDK1.8的实现降低锁的粒度,JDK1.7版本锁的粒度是基于Segment的,包含多个HashEntry,而JDK1.8锁的粒度就是HashEntry(首节点)
JDK1.8版本的数据结构变得更加简单,使得操作也更加清晰流畅,因为已经使用synchronized来进行同步,所以不需要分段锁的概念,也就不需要Segment这种数据结构了,由于粒度的降低,实现的复杂度也增加了
JDK1.8使用红黑树来优化链表,基于长度很长的链表的遍历是一个很漫长的过程,而红黑树的遍历效率是很快的,代替一定阈值的链表,这样形成一个最佳拍档
JDK1.8为什么使用内置锁synchronized来代替重入锁ReentrantLock; 因为粒度降低了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值