Java面试常问的知识点

HashMap、LinkedHashMap、ConcurrentHashMap、ArrayList、LinkedList的底层实现。

答:HashMap是基于哈希表的Map接口的非同步实现,它允许null键和null值,且HashMap依托于它的数据结构的设计,存储效率特别高,这是我用它的原因 HashMap是基于hash算法实现的,通过put(key,value)存储对象到HashMap中,也可以通过get(key)从HashMap中获取对象。当我们使用put的时候,首先HashMap会对key的hashCode()的值进行hash计算,根据hash值得到这个元素在数组中的位置,将元素存储在该位置的链表上。当我们使用get的时候,首先HashMap会对key的hashCode()的值进行hash计算,根据hash值得到这个元素在数组中的位置,将元素从该位置上的链表中取出


HashMap和Hashtable的区别。

Hashtable方法是同步的 。HashMap方法是非同步的 Hashtable基于Dictionary类 HashMap基于AbstractMap,而AbstractMap基于Map接口的实现 Hashtable中key和value都不允许为null,遇到null,直接返回 NullPointerException HashMap中key和value都允许为null,遇到key为null的时候,调用putForNullKey方法进行处理,而对value没有处理 Hashtable中hash数组默认大小是11,扩充方式是old*2+1 HashMap中hash数组的默认大小是16,而且一定是2的指数。

ArrayList、LinkedList、Vector的区别。

LinkedList底层是双向链表 ArrayList底层是可变数组 LinkedList不允许随机访问,即查询效率低 ArrayList允许随机访问,即查询效率高 LinkedList插入和删除效率快 ArrayList插入和删除效率低。
Vector是基于可变数组的List接口的同步实现 Vector是有序的 Vector允许null键和null值 Vector已经不建议使用了。

LinkedHashMap与HashMap的区别

LinkedHashMap有序的,有插入顺序和访问顺序 HashMap无序的 LinkedHashMap内部维护着一个运行于所有条目的双向链表 HashMap内部维护着一个单链表

HashMap和ConcurrentHashMap的区别

ConcurrentHashMap基于双数组和链表的Map接口的同步实现 ConcurrentHashMap中元素的key是唯一的、value值可重复 ConcurrentHashMap不允许使用null值和null键 ConcurrentHashMap是无序的线程安全的

HashMap和LinkedHashMap的区别

LinkedHashMap有序的,有插入顺序和访问顺序 HashMap无序的 LinkedHashMap内部维护着一个运行于所有条目的双向链表 HashMap内部维护着一个单链表

LinkedHashMap底层使用哈希表与双向链表来保存所有元素,它维护着一个运行于所有条目的双向链表(如果学过双向链表的同学会更好的理解它的源代码),此链表定义了迭代顺序,该迭代顺序可以是插入顺序或者是访问顺序 1.按插入顺序的链表:在LinkedHashMap调用get方法后,输出的顺序和输入时的相同,这就是按插入顺序的链表,默认是按插入顺序排序 2.按访问顺序的链表:在LinkedHashMap调用get方法后,会将这次访问的元素移至链表尾部,不断访问可以形成按访问顺序排序的链表。简单的说,按最近最少访问的元素进行排序(类似LRU算法)

HashMap是线程安全的吗。

不是线程安全的,方法一:通过Collections.synchronizedMap()返回一个新的Map,这个新的map就是线程安全的. 这个要求大家习惯基于接口编程,因为返回的并不是HashMap,而是一个Map的实现. 方法二:重新改写了HashMap,具体的可以查看java.util.concurrent.ConcurrentHashMap. 这个方法比方法一有了很大的改进. 方法一使用的是的synchronized方法,是一种悲观锁.在进入之前需要获得锁,确保独享当前对象,然后做相应的修改/读取. 方法二使用的是乐观锁,只有在需要修改对象时,比较和之前的值是否被人修改了,如果被其他线程修改了,那么就会返回失败.锁的实现,使用的是NonfairSync. 这个特性要确保修改的原子性,互斥性,无法在JDK这个级别得到解决,JDK在此次需要调用JNI方法,而JNI则调用CAS指令来确保原子性与互斥性. 当如果多个线程恰好操作到ConcurrentHashMap同一个segment上面,那么只会有一个线程得到运行,其他的线程会被LockSupport.park(),稍后执行完成后,会自动挑选一个线程来执行LockSupport.unpark(). 如何得到/释放锁 得到锁: 方法一:在Hashmap上面,synchronized锁住的是对象(不是Class),所以第一个申请的得到锁,其他线程将进入阻塞,等待唤醒. 方法二:检查AbstractQueuedSynchronizer.state,如果为0,则得到锁,或者申请者已经得到锁,则也能再辞得到锁,并且state也加1. 释放锁:都是得到锁的逆操作,并且使用正确,二种方法都是自动选取一个队列中的线程得到锁可以获得CPU资源. 优缺点 方法一:优点:代码实现十分简单,一看就懂. 缺点:从锁的角度来看,方法一直接使用了锁住方法,基本上是锁住了尽可能大的代码块.性能会比较差. 方法二:优点:需要互斥的代码段比较少,性能会比较好. ConcurrentHashMap把整个Map切分成了多个块,发生锁碰撞的几率大大降低,性能会比较好. 缺点:代码实现稍稍复杂些.

ConcurrentHashMap是怎么实现线程安全的。

ConcurrentHashMap的数据结构为一个Segment数组,Segment的数据结构为HashEntry的数组,而HashEntry存的是我们的键值对,可以构成链表。可以简单的理解为数组里装的是HashMap ConcurrentHashMap定位一个元素的过程需要进行两次Hash操作,第一次Hash定位到Segment,第二次Hash定位到元素所在的链表的头部,因此,这一种结构的带来的副作用是Hash的过程要比普通的HashMap要长,但是带来的好处是写操作的时候可以只对元素所在的Segment进行加锁即可,不会影响到其他的Segment。正是因为其内部的结构以及机制,ConcurrentHashMap在并发访问的性能上要比Hashtable和同步包装之后的HashMap的性能提高很多。在理想状态下,ConcurrentHashMap 可以支持 16 个线程执行并发写操作(如果并发级别设置为 16),及任意数量线程的读操作

HashMap 的长度为什么是2的幂次方

因为HashMap的数据结构是数组和链表的结合,其中里面数组的默认长度是16,HashMap的长度为2的幂次方的原因是为了减少Hash碰撞,尽量使Hash算法的结果均匀分布。

多线程并发相关问题(必问)

创建线程的3种方式。 一、继承Thread类创建线程类 (1)定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行体。 (2)创建Thread子类的实例,即创建了线程对象。 (3)调用线程对象的start()方法来启动该线程。 二、通过Runnable接口创建线程类 (1)定义runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。 (2)创建 Runnable实现类的实例,并依此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。 (3)调用线程对象的start()方法来启动该线程。 三、通过Callable和Future创建线程 (1)创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。 (2)创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。 (3)使用FutureTask对象作为Thread对象的target创建并启动新线程。 (4)调用FutureTask对象的get()方法来获得子线程执行结束后的返回值

二、创建线程的三种方式的对比 采用实现Runnable、Callable接口的方式创见多线程时,优势是: 线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。 在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。

劣势是: 编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法。 使用继承Thread类的方式创建多线程时优势是: 编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this即可获得当前线程。

劣势是: 线程类已经继承了Thread类,所以不能再继承其他父类。

什么是线程安全。

线程安全就是在多线程环境下也不会出现数据不一致,而非线程安全就有可能出现数据不一致的情况。
Runnable接口和Callable接口的区别。

不同之处: 1.Callable可以返回一个类型,而Runnable不可以

2.Callable能够抛出checked exception,而Runnable不可以。

3.Runnable是自从java1.1就有了,而Callable是1.5之后才加上去的

4.Callable和Runnable都可以应用于executors。而Thread类只支持Runnable.


wait方法和sleep方法的区别。

sleep()方法必须传入参数,参数就是休眠时间,时间到了就会自动醒来 wait()方法可以传入参数也可以不传入参数,传入参数就是在参数结束的时间后开始等待,不穿如参数就是直接等待。 sleep方法必须要捕获异常,而wait方法不需要捕获异常。
synchronized、Lock、ReentrantLock、ReadWriteLock。 synchronized: 是JVM实现的一种锁, 用于同步方法和代码块,执行完后自动释放锁。其中锁的获取和释放分别是monitorenter和monitorexit指令,该锁在实现上分为了偏向锁、轻量级锁和重量级锁,其中偏向锁在1.6是默认开启的,轻量级锁在多线程竞争的情况下会膨胀成重量级锁,有关锁的数据都保存在对象头中。 Lock:Lock是一个锁的接口,提供获取锁和解锁的方法(lock,trylock,unlock) ReentrantLock:ReentrantLock是k是Lock接口的实现类,基于AQS实现的,在AQS内部会保存一个状态变量state,通过CAS修改该变量的值,修改成功的线程表示获取到该锁,没有修改成功,或者发现状态state已经是加锁状态,则通过一个Waiter对象封装线程,添加到等待队列中,并挂起等待被唤醒。 ReadWriteLock :可以实现读写锁,当读取的时候线程会获得read锁,其他线程也可以获 得read锁同时并发的去读取,但是写程序运行获取到write锁的时候,其他线程是不能进行操作的,因为write是排它锁,而上面介绍的两种不管你是read还是write没有抢到锁的线程都会被阻塞或者中断,它也是个接口,里面定义了两种方法readLock()和writeLock(),他的一个实现类是ReentrantReadWriteLock。
介绍下CAS(无锁技术)。

CAS(Compare and Swap),即比较并替换,实现并发算法时常用到的一种技术,CAS是通过unsafe类的compareAndSwap方法实现的。CAS的思想很简单:三个参数,一个当前内存值V、旧的预期值A、即将更新的值B,当且仅当预期值A和内存值V相同时,将内存值修改为B并返回true,否则什么都不做,并返回false。 缺点:CAS存在一个很明显的问题,即ABA问题。 问题:如果变量V初次读取的时候是A,并且在准备赋值的时候检查到它仍然是A,那能说明它的值没有被其他线程修改过了吗? 如果在这段期间曾经被改成B,然后又改回A,那CAS操作就会误认为它从来没有被修改过。针对这种情况,java并发包中提供了一个带有标记的原子引用类AtomicStampedReference,它可以通过控制变量值的版本来保证CAS的正确性。


volatile关键字的作用和原理

作用:保持内存可见性(内存可见性(Memory Visibility):所有线程都能看到共享内存的最新状态。)

原理:观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令,lock前缀指令实际上相当于一个内存屏障(也成内存栅栏)它会强制将对缓存的修改操作立即写入主存如果是写操作,它会导致其他CPU中对应的缓存行无效


什么是ThreadLocal。

线程变量,一个以ThreadLocal对象为键、任意对象为值的存储结构,每个ThreadLocal可以放一个线程级别的变量,但是它本事可以被多个线程共享使用,而且又可以达到线程安全的目的,且绝对线程安全。 常用的3个方法:set()、get()、remove()。都是线程安全的。


创建线程池的4种方式。

1.newSingleThreadExecutor 创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。 2.newFixedThreadPool 创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。

3.newCachedThreadPool 创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程, 那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。

4.newScheduledThreadPool 创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。
ThreadPoolExecutor的内部工作原理


分布式环境下,怎么保证线程安全

向线程池提交一个任务后,它的主要处理流程如下图所示 一个线程从被提交(submit)到执行共经历以下流程: 线程池判断核心线程池里是的线程是否都在执行任务,如果不是,则创建一个新的工作线程来执行任务。如果核心线程池里的线程都在执行任务,则进入下一个流程 线程池判断工作队列是否已满。如果工作队列没有满,则将新提交的任务储存在这个工作队列里。如果工作队列满了,则进入下一个流程。 线程池判断其内部线程是否都处于工作状态。如果没有,则创建一个新的工作线程来执行任务。如果已满了,则交给饱和策略来处理这个任务。

JVM相关问题

介绍下垃圾收集机制(在什么时候,对什么,做了什么)。

标记-清除算法、复制算法、标记-整理算法、分代收集算法 主要是对一些静态对象做清理和整理的工作

垃圾收集有哪些算法,各自的特点。

标记-清除算法、复制算法、标记-整理算法、分代收集算法

类加载的过程。

整个生命周期包括:加载、验证、准备、解析、初始化、使用和卸载七个阶段。


双亲委派模型。 双亲委托模型的工作过程是:

如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委托给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父类加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需要加载的类)时,子加载器才会尝试自己去加载。 使用双亲委托机制的好处是:能够有效确保一个类的全局唯一性,当程序中出现多个限定名相同的类时,类加载器在执行加载时,始终只会加载其中的某一个类。

有哪些类加载器。

分别是 bootstrapClassLoader (主要加载java核心api) , ExtClassLoaders是扩展类的类加载器,AppClassLoader 程序类加载器,还有一个是用户继承ClassLoader重写的类加载器。

能不能自己写一个类叫java.lang.String。

可以,但是即使你写了这个类,也没有用。 这个问题涉及到加载器的委托机制,在类加载器的结构图(在下面)中, BootStrap是顶层父类,ExtClassLoader是BootStrap类的子类,ExtClassLoader又是AppClassLoader的父类 这里以java.lang.String为例,当我是使用到这个类时,Java虚拟机会将java.lang.String类的字节码加载到内存中。


数据库相关问题,针对MySQL(必问)

1.给题目让你手写SQL。
2.有没有SQL优化经验。
3.MySQL索引的数据结构。
4.SQL怎么进行优化。
5. SQL关键字的执行顺序。 from->on->join->where->group by->having->select->distinct->union
6.有哪几种索引。 普通索引:仅加速查询 唯一索引:加速查询 + 列值唯一(可以有null) 主键索引:加速查询 + 列值唯一(不可以有null)+ 表中只有一个 组合索引:多列值组成一个索引,专门用于组合搜索,其效率大于索引合并 全文索引:对文本的内容进行分词,进行搜索
7. 什么时候该(不该)建索引。 一般来说,在WHERE和JOIN中出现的列需要建立索引,但也不完全如此,因为MySQL只对<,<=,=,>,>=,BETWEEN,IN,以及某些时候的LIKE才会使用索引。
8. Explain包含哪些列。 expain出来的信息有10列,分别是id、select_type、table、type、possible_keys、key、key_len、ref、rows、Extra,

框架相关问题

Hibernate和Mybatis的区别。

Spring MVC和Struts2的区别。

Spring用了哪些设计模式

1.工厂模式,这个很明显,在各种BeanFactory以及ApplicationContext创建中都用到了; 2.模版模式,这个也很明显,在各种BeanFactory以及ApplicationContext实现中也都用到了;

3.代理模式,在Aop实现中用到了JDK的动态代理;

4.单例模式,这个比如在创建bean的时候。

5.策略模式在Java中的应用,这个太明显了,因为Comparator这个接口简直就是为策略模式而生的。Comparable和Comparator的区别一文中,详细讲了Comparator的使用。比方说Collections里面有一个sort方法,因为集合里面的元素有可能是复合对象,复合对象并不像基本数据类型,可以根据大小排序,复合对象怎么排序呢?基于这个问题考虑,Java要求如果定义的复合对象要有排序的功能,就自行实现Comparable接口或Comparator接口. 6.原型模式:使用原型模式创建对象比直接new一个对象在性能上好得多,因为Object类的clone()方法是一个native方法,它直接操作内存中的二进制流,特别是复制大对象时,性能的差别非常明显。

7.迭代器模式:Iterable接口和Iterator接口 这两个都是迭代相关的接口,可以这么认为,实现了Iterable接口,则表示某个对象是可被迭代的;Iterator接口相当于是一个迭代器,实现了Iterator接口,等于具体定义了这个可被迭代的对象时如何进行迭代的

Spring中AOP主要用来做什么

1.Spring声明式事务管理配置。

2.Controller层的参数校验。

3.使用Spring AOP实现MySQL数据库读写分离案例分析

4.在执行方法前,判断是否具有权限。

5.对部分函数的调用进行日志记录。监控部分重要函数,若抛出指定的异常,可以以短信或邮件方式通知相关人员。

6.信息过滤,页面转发等等功能,博主一个人的力量有限,只能列举这么多,欢迎评论区对文章做补充。

Spring注入bean的方式。

使用属性的setter方法注入 这是最常用的方式; 使用构造器注入; 使用Filed注入(接口注入).

什么是IOC,什么是依赖注入。

inverse of controll IOC所谓控制反转就是把创建对象(bean)和维护对象(bean)的关系的权利从程序中转移到spring的容器文件(就是spring的配置文件),程序不再维护 依赖注入实在编译阶段商未知所需的功能来自哪个类的情况下,将其他的对象所依赖的功能对象实例化的模式。这就需要一种极致来激活相应的组件已提供特定的功能,所依赖注入是控制反转的基础。否则如果在组件不受框架控制的情况下,框架有怎么知道要创建那个组件?

Spring是单例还是多例,怎么修改。

spring默认是单例模式(就每个请求都是用的同一对象,对于dao层肯定是棒棒的),但是有的时候,我们需要每个请求都 产生一个新的对象,就如做微信小程序,用scoket、不可能一直都用一个来接收的,因为需要分配房间,所以需要使用到多例。 可以修改为多例,在此bean节点中添加一个属性,scope=“prototype",例如

Spring事务隔离级别和传播性。

介绍下Mybatis/Hibernate的缓存机制。

Mybatis的mapper文件中#和$的区别。

Mybatis的mapper文件中resultType和resultMap的区别。

发布了33 篇原创文章 · 获赞 2 · 访问量 575

猜你喜欢

转载自blog.csdn.net/weixin_46062001/article/details/104018942