Java常用基础知识(三)

该内容都是基于我参加的 网易、阿里巴巴 面试所整理的:

1、你的项目用过线程池,你是怎么用线程池实现你的定时任务的?
大致把为什么用线程池(Timer不合适),线程池通常的几种创建方式说完,不同线程池的有什么不同作用介绍完。
线程池一般都是由并发包中的线程池工厂Executors创建的,其用方法有:
(1)、newCachedThreadPool() :创建一个可缓存机制的线程池,不需要指定大小,根据实际需求量来创建线程,并且当有之前创建过的线程可重用时,从缓存中获取出来继续使用,默认缓存时间为60秒,当60秒内没被重用的线程将会被注销,这种线程池不适用的情况下几乎不会占用资源。利用execute(Runnable)执行任务。(生成ThreadPoolExecutor)
(2)、newFixedThreadPool(int nThreads) :创建一个可重用的,固定线程数的线程池,其持有的线程数固定。利用execute(Runnable)方法执行任务。(生成ThreadPoolExecutor)
(3)、newScheduledThreadPool(int corePoolSize):创建一个可以做定时任务、延时任务的线程池,其线程数也是固定的。利用schedule(Runnable command, long delay, TimeUnit unit)或者scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)方法执行任务。(生成ScheduledThreadPoolExecutor)

除了上面的创建方式外还有newSingleThreadExecutor()和newSingleThreadScheduledExecutor()来创建对应的线程池,且指定线程数只有一。
TIP:
在使用Executors创建线程池时,上面每个方法都有一个重载的传入ThreadFactory的线程工厂的方法,用于自定义线程池内线程的创建。


3、线程池的内部结构是什么,都有哪些比较重要的参数?
比较重要的参数如下:
corePoolSize:池内线程初始值与最小值,就算是空闲状态,也会保持该数量线程。
maximumPoolSize:线程最大值,线程的增长始终不会超过该值。
keepAliveTime:当池内线程数高于corePoolSize时,经过多少时间多余的空闲线程才会被回收。回收前处于wait状态
unit:时间单位,可以使用TimeUnit的实例,如TimeUnit.MILLISECONDS 
workQueue:待入任务(Runnable)的等待场所,该参数主要影响调度策略,如公平与否,是否产生饿死(starving)
threadFactory:线程工厂类,有默认实现,如果有自定义的需要则需要自己实现ThreadFactory接口并作为参数传入。


4、corePoolSize和maximumPoolSize说一下是什么意思?
corePoolSize:池内线程初始值与最小值,就算是空闲状态,也会保持该数量线程。
maximumPoolSize:线程最大值,线程的增长始终不会超过该值。


5、说一下什么情况下线程池的线程数会超过corePoolSize的值?
线程池的基本大小,即在没有任务需要执行的时候线程池的大小,并且只有在工作队列满了的情况下才会创建超出这个数量的线程。
https://blog.csdn.net/lijunwyf/article/details/45000237


6、你们项目中有用到MQ队列,那说一下MQ队列在分布式的情况下,如何解决数据顺序问题?
http://blog.51cto.com/littledevil/2068718
https://segmentfault.com/a/1190000014512075


7、说一下你项目中的异常处理策略?
这个根据实际项目梳理。


8、说说你对JDK中异常和错误的理解?
主要就是Exception、Error、RuntimeException的理解。


9、对上面的错误类型都举个实际的例子吧?
Exception:IOException
Error:OutOfMemoryError、NoClassDefFoundError
RuntimeException:NullPointException、ClassCastException


10、你们用的数据库是Oracle,有哪些事务隔离级别?
按照隔离程度从低到高,MySQL 事务隔离级别分为四个不同层次:
读未提交(Read uncommitted),就是一个事务能够看到其他事务尚未提交的修改,这是最低的隔离水平,允许脏读出现。
读已提交(Read committed),事务能够看到的数据都是其他事务已经提交的修改,也就是保证不会看到任何中间性状态,当然脏读也不会出现。读已提交仍然是比较低级别的隔离,并不保证再次读取时能够获取同样的数据,也就是允许其他事务并发修改数据,允许不可重复读和幻象读(Phantom Read)出现。
可重复读(Repeatable reads),保证同一个事务中多次读取的数据是一致的,这是 MySQL InnoDB 引擎的默认隔离级别,但是和一些其他数据库实现不同的是,可以简单认为 MySQL 在可重复读级别不会出现幻象读。
串行化(Serializable),并发事务之间是串行化的,通常意味着读取需要获取共享读锁,更新需要获取排他写锁等,这是最高的隔离级别。


11、你知道什么是幻读吗?
(1)。脏读 :脏读就是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。
(2)。 不可重复读 :是指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的的数据可能是不一样的。
(3)。 幻读 : 是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样。


12、读已提交到可重复读,这之间是怎么实现的?
https://www.cnblogs.com/huanongying/p/7021555.html


13、串行化的乐观锁你一般是怎么样去实现的,举个例子吧?
一般可以通过在数据库字段加一个version字段。


14、说说Java的开发原则有哪些?
(1)、单一职责(Single Responsibility),类或者对象最好是只有单一职责,在程序设计中如果发现某个类承担着多种义务,可以考虑进行拆分。

(2)、开关原则(Open-Close, Open for extension, close for modification),设计要对扩展开放,对修改关闭。(对于相同模块的不同处理逻辑,尽量不要用if-else来处理,应该抽一个公共接口,然后使用不同的实现。)

(3)、里氏替换(Liskov Substitution),这是面向对象的基本要素之一,进行继承关系抽象时,凡是可以用父类或者基类的地方,都可以用子类替换。

(4)、接口分离(Interface Segregation),我们在进行类和接口设计时,如果在一个接口里定义了太多方法,其子类很可能面临两难,就是只有部分方法对它是有意义的,这就破坏了程序的内聚性。对于这种情况,可以通过拆分成功能单一的多个接口,将行为进行解耦。

(5)、依赖反转(Dependency Inversion),实体应该依赖于抽象而不是实现。也就是说高层次模块,不应该依赖于低层次模块,而是应该基于抽象。实践这一原则是保证产品代码之间适当耦合度的法宝。


15、单一职责是什么?


16、设计模式你了解吗?举个设计模式的例子,说明他们遵循那哪些OOP原则,不遵循哪些OOP原则?
简单工厂:一个具体工厂通过条件语句创建多个产品,产品的创建逻辑集中与一个工厂类。(将要创建的对象和当前对象解耦,但是工厂中带有判断逻辑,违背开放关闭原则)
工厂方法:一个工厂创建一个产品,所有的具体工厂继承自一个抽象工厂。(遵循了“开放—封闭”原则。但每增加一产品就要增加一个产品工厂的类,增加了额外的开发量。)
抽象工厂:一个具体工厂创建一个产品族,一个产品族是不同系列产品的组合,产品的创建的逻辑分在在每个具体工厂类中。所有的具体工厂继承自同一个抽象工厂。(符合“开闭原则”,)
从高层次来看,抽象工厂使用了组合,而工厂模式使用了继承。
https://blog.csdn.net/lingfengtengfei/article/details/12374469


17、工厂模式用起来有什么好处?
1、对调用者和实例创建过程进行解耦;
2、统一管理初始化过程,避免冗余代码;
3、可以对创建的过程命名;







1.spring aop的理解;

AOP:是一种面向切面编程的思想,其主要通过动态代理的模式来对程序进行横向拓展。常用于统一日记记录、事务管理等。
AOP的实现流程:首先编写一个切面,在切面中定义出所代理的目标类和对应的切入点,然后利用动态代理将切面织入到应用程序中。
既实际调用者调用的并不是真正的目标对象,而是代理对象,再由代理对象去执行真正的调用。


2.spring IOC的理解;

IOC既控制反转,是一种程序思想,主要是将传统的对象管理方式换成由容器管理对象的形式。传统模式管理类中的成员对象通常都是使用new 的方式进行实例化,而IOC的思想就是将
对象实例化后至于容器中管理,然后当需要的时候利用依赖注入来获取对象实例。
依赖注入的方式有:setter、构造器、字段注入三种方式。


3.spring中用到的设计模式;

IOC中包含了工厂模式(用户只需要传入参数,不需要知道具体实现,就能从工厂中获取到对应的实例)、单例模式—》BeanFactory
监听器:典型的观察者模式
AOP:代理模式
Spring实例化对象:策略模式(定义一系列的策略,然后根据实际情况创建对应的实例)
JDBCTemplate:模板方法模式(定义连接数据流操作的模板)


4.工厂模式使用方式;
1、简单工厂:既也称为静态工厂,既根据工厂类提供的静态方法创建实例,调用者只需要传入所要创建的实例信息,就可以获取到对应的实例。(工厂类需要维护所有实例创建的判断逻辑)
2、工厂方法:在简单工厂中,由工厂类进行所有的逻辑判断、实例创建。如果不想在工厂类中进行判断,可以为不同的产品提供不同的工厂,不同的工厂生产不同的产品。这样调用者可以根据自己的需求去调用具体的工厂。(但是又引发了新的问题,产品和工厂之间的耦合)
3、抽象工厂类:为了解决工厂方法的问题,可以再新增一个工厂类,该工厂类用于生成不同的工厂,这个类就叫做抽象工厂类


6.java线程池,几个重要的参数,分别对应什么使用场景;
线程池一般都是由并发包中的线程池工厂Executors创建的,其用方法有:
(1)、newCachedThreadPool() :创建一个可缓存机制的线程池,不需要指定大小,根据实际需求量来创建线程,并且当有之前创建过的线程可重用时,从缓存中获取出来继续使用,默认缓存时间为60秒,当60秒内没被重用的线程将会被注销,这种线程池不适用的情况下几乎不会占用资源。利用execute(Runnable)执行任务。(生成ThreadPoolExecutor)
(2)、newFixedThreadPool(int nThreads) :创建一个可重用的,固定线程数的线程池,其持有的线程数固定。利用execute(Runnable)方法执行任务。(生成ThreadPoolExecutor)
(3)、newScheduledThreadPool(int corePoolSize):创建一个可以做定时任务、延时任务的线程池,其线程数也是固定的。利用schedule(Runnable command, long delay, TimeUnit unit)或者scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)方法执行任务。(生成ScheduledThreadPoolExecutor)

除了上面的创建方式外还有newSingleThreadExecutor()和newSingleThreadScheduledExecutor()来创建对应的线程池,且指定线程数只有一。
TIP:
在使用Executors创建线程池时,上面每个方法都有一个重载的传入ThreadFactory的线程工厂的方法,用于自定义线程池内线程的创建。

其内部各个属性:
corePoolSize:池内线程初始值与最小值,就算是空闲状态,也会保持该数量线程。
maximumPoolSize:线程最大值,线程的增长始终不会超过该值。
keepAliveTime:当池内线程数高于corePoolSize时,经过多少时间多余的空闲线程才会被回收。回收前处于wait状态
unit:时间单位,可以使用TimeUnit的实例,如TimeUnit.MILLISECONDS 
workQueue:待入任务(Runnable)的等待场所,该参数主要影响调度策略,如公平与否,是否产生饿死(starving)
threadFactory:线程工厂类,有默认实现,如果有自定义的需要则需要自己实现ThreadFactory接口并作为参数传入。


8.java的锁机制,使用过哪些锁;
Java是锁机制指的是当多个线程要进入加锁的模块时,需要竞争,仅有一个线程能成功获取到锁,并且进入相应的执行模块,而其他线程会处于阻塞的状态。这是一种保证线程安全的手段。
一般同步锁借助synchronized关键字来实现,其具体实现并不在java的标准库中,而是由JVM实现的(C++),利用monitor entermonitor exit指令来 完成,当多个线程执行到加锁模块时,仅有一个线程能与monitor监视器建立联系并获取到锁。结束后会释放锁,由阻塞中的线程继续竞争。
除了synchronized外,jdk还提供了Lock方式的加锁,其基本能实现synchronized同步锁的所有功能,并且使用上会更加便利,比较常见的有:
(1)、 ReentrantLock :可以通过 lockunlock来手动控制加锁范围,并且还可以设置锁的公平性。(倾向于将访问权授予等待时间最长的线程)
(2)、ReentrantReadWriteLock.ReadLockReentrantReadWriteLock.WriteLock既为读写锁,因为不论是使用synchronized还是ReentrantLock都相对比较霸道,一个线程占用收,其他线程就无法使用这一块的资源,所以就有了读写锁。读锁支持并发读(共享锁),写锁(排它锁)。

另外加锁还有其他的实现方式:利用Semaphore信号量,在创建的时候设置许可数公平性,然后利用其acquire()获取许可,利用release()释放许可,当把许可数设置为1,其用法也就和锁机制起到一样的效果了。


9.hashmap是不是线程安全的,底层实现原理有没有看过,为什么不安全;jkd8之后的优化;
hashmap线程不安全,其内部为由数组和链表共同构成,由于没人任何锁机制的控制,所以可以被多个线程同时操作。所以其线程是不安全的,map接口下的线程安全类仅有Hashtable(利用synchronized同步锁)。
JDK8对HashMap做了优化,采用了红黑树的数据结构来做优化,使得HashMap存取速度更快。
对于hashmap这一类容器的源码有去研究过,后来发现JDK底层使用了大量的设计模式和算法,所以对于其源码可以说是看得云里雾里,目前制定了学习计划,先研究好设计模式和相关的排序算法,再去研究JDK的底层源码,这样的理解会更透彻。


10.concurrenthashmap为什么是线程安全的;
其主要利用了分段加锁(Segment,默认16个)的机制来保证其线程安全。缺点就是size计算是用重试计算机制。


11.java内存模型(JMM);
JVM内存主要可以分成JVM栈、本地方法栈、PC计数器(这三个是线程私有的)、堆(存放大部分对象)、方法区(常量池)。
其中堆在GC回收时还会分为新生代和老年代。新生代主要用于存放新创建的对象与存活时长小的对象,老年代则用于存放存活时间长的对象。


12.垃圾回收算法有没有了解过;
引用计数器:在每个引用都加一个计数器,引用+1 不引用-1 ,当计数器值为0时说明可以回收。(解决不了循环引用的问题)
跟搜索算法:利用GC Root根节点构建出引用链,找出引用不可达的对象,即为可回收对象。

标记-清除:标记出活跃的对象,清除不活跃的
标记-整理:标记出活跃的对象,将其压缩至内存另一端,然后清除不活跃的对象。(这样就不会产生大量不连续内存)
复制算法:将内存分为两块,每次只使用一块,GC时将活跃的对像移至另一块,清除当前块的内容。


13.YG和FG分别什么时候触发;


14.classLoader讲解一下,什么时候会破坏双亲委派机制;
一般来说,我们把 Java 的类加载过程分为三个主要步骤:加载、链接、初始化。
(1)、加载阶段(Loading),它是 Java 将字节码数据从不同的数据源读取到 JVM 中,并映射为 JVM 认可的数据结构(Class 对象);
(2)、链接(Linking),这是核心的步骤,把原始的类定义信息平滑地转化入 JVM 运行的过程中。这里可进一步细分为三个步骤:
验证(Verification),JVM 需要核验字节信息是符合 Java 虚拟机规范的;
准备(Preparation),创建类或接口中的静态变量,并初始化静态变量的初始值。但这里的“初始化”和下面的显式初始化阶段是有区别的,侧重点在于分配所需要的内存空间,不会去执行的 JVM 指令。
解析(Resolution),在这一步会将常量池中的符号引用(symbolic reference)替换为直接引用。
(3)、初始化阶段(initialization),这一步真正去执行类初始化的代码逻辑。

一般加载阶段都会遵循双亲委派机制,因为类加载器loadclass加载方法会先找寻是否有父类加载器,然后在执行加载。如果要破会该机制,可以自己创建类加载器,并且重写其loadclass方法即可。


15.redis的使用场景,redis可以存放什么样的数据类型;
由于开发项目中对数据实时准确性要求较高,所以都没有使用到redis等缓存机制,只有一些表皮的了解(分布式缓存机制、存放键值对、可以存放多种数据类型)。有的话也是使用的ehcache(利用cacheManager获取对应cahce对象,然后再调用其putget方法操作缓存),但是如果公司需要的话,我会在入职前学习和掌握该知识点。


17.分布式的一致性如何实现;
在分布式中有三个比较重要的特性。(CAP:强一致性、高可用性和分区容错性),由于之前使用的分布式都是利用springcloud实现的,遵循的是AP原则,所以对一致性较不理解。
高可用主要体现在Eureka的降级处理、服务熔断和自我保护机制。


18.spring cloud的理念,你的理解;
我认为的SpringCloud是微服务的一站式解决方案,主要是基本springboot的基础上,提供了大量的服务管理组件。


19.让你去将服务解耦,你的设计思路是什么样子的;
主要遵循的原则和OOP中的单一职责一样,单一服务单一职责,将不同业务模块分成不同的服务。


20.session和cookie的区别,深入讲解一下;
cookie机制采用的是在客户端保持状态的方案,而session机制采用的是在服务器端保持状态的方案。


22.jstack命令有没有用过;
jstack主要用于获取进行内的线程栈信息。利用jps获取进程pid,再利用jstack pid获取线程栈的信息。

23.jmap命令有没有用过;
Jmap是一个可以输出所有内存中对象的工具


24.说一下你用jstack解决过的实际问题
在程序出现死锁的时候可以利用jstack进行排查,先利用jps 获取对应进程,然后利用jstack获取线程栈的信息,比较容易发现的死锁就会自动被工具检测出来了。





1、BeanFactory和FactoryBean的区别?
BeanFactory,以Factory结尾,表示它是一个工厂类(接口),用于管理Bean的一个工厂。在Spring中,BeanFactory是IOC容器的核心接口,它的职责包括:实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。
FactoryBean,是一个Java Bean,但是它是一个能生产对象的工厂Bean,它的实现和工厂模式及修饰器模式很像。


2、springboot自动配置的过程?
主要利用xxxxAutoConfigurartion定义我们需要自定义配置的场景,xxxxAutoConfigurartion内部具体的一些自定义配置主要依赖于Springboot内部定义的xxxxProperties类来定义,最后配置出我们所需要的自动配置场景。
通常会将这些场景封装成一个个启动器,我们只需引入依赖信息就能使用该场景的配置了。


3、Spring IOC中的beanFactoy使用的是哪种工厂模式?
主要利用的是工厂方法。AbstractFactoryBean这个抽象类主要承担bean实例创建对象的前置判断工作,创建对象的工作则交给了一个抽象方法,并由其子类去实现各个实例创建的具体方式。


4、HashMap的结构?
简单来说,HashMap由数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的。数组(table)中主要存放的类型是Entry<K,V>对象,Entry<K,V>内部主要存放了一个键值对和当前key的哈希值(为了避免重复计算哈希值)。Entry<K,V>[] table = (Entry<K,V> []) EMPTY_TABLE ;
如果通过hashcode()定位到的数组位置不含链表(当前entry的next指向null),那么对于查找,添加等操作很快,仅需一次寻址即可;如果定位到的数组包含链表,对于添加操作,其时间复杂度为O(n),首先遍历链表,存在即覆盖,否则新增;对于查找操作来讲,仍需遍历链表,然后通过key对象的equals方法逐一比对查找。所以,性能考虑,HashMap中的链表出现越少,性能才会越好。

参考图片如下:

这里写图片描述


4、什么是HashMap的哈希冲突?如何解决?
当我们对某个元素进行哈希运算,得到一个存储地址,然后要进行插入的时候,发现已经被其他元素占用了,其实这就是所谓的哈希冲突,也叫哈希碰撞。

HashMap解决哈希冲突的方式是采用了链地址法,也就是数组+单向链表的方式。


5、TreeSet的结构是什么?
TreeSet的底层数据结构是二叉树。
保证元素唯一性的依据是利用compareTo方法return 0


5、LinkedHashMap?
LinkedHashMap继承与HashMap,与HashMap最大不同之处在于,HashMap中的散列表是一个单向链表,而LinkedHashMap中的散列表是双向链表,因此在散列表元素比较多的情况下, LinkedHashMap查找删除的效率更高。

猜你喜欢

转载自blog.csdn.net/qq_33404395/article/details/81839419