java面经查缺补漏之四十六天

1.分布式ID生成策略知道哪些?

参考:https://www.cnblogs.com/chengxy-nds/p/12315917.html

 

什么是分布式ID?

在我们业务数据量不大的时候,单库单表完全可以支撑现有业务,数据再大一点搞个MySQL主从同步读写分离也能对付。

但随着数据日渐增长,主从同步也扛不住了,就需要对数据库进行分库分表,但分库分表后需要有一个唯一ID来标识一条数据,数据库的自增ID显然不能满足需求;特别一点的如订单、优惠券也都需要有唯一ID做标识。此时一个能够生成全局唯一ID的系统是非常必要的。那么这个全局唯一ID就叫分布式ID

简单来说就是分布式系统的一个全局唯一的ID。

(1)基于UUID

UUID的生成简单到只有一行代码,太长耗时而且这个ID没有任何意义,所以不推荐用作分布式ID

(2)基于数据库自增ID

当我们需要一个ID的时候,向表中插入一条记录返回主键ID,但这种方式有一个比较致命的缺点,访问量激增时MySQL本身就是系统的瓶颈,用它来实现分布式服务风险比较大,不推荐!单点存在宕机风险.

(3)基于数据库的集群模式

解决了上面单点宕机的风险,但是压力还是在数据库那里,还是无法满足高并发的需求。

(4)基于redis的实现

原理就是利用redisincr命令实现ID的原子性自增。

RDB会定时打一个快照进行持久化,假如连续自增但redis没及时持久化,而这会Redis挂掉了,重启Redis后会出现ID重复的情况。

AOF会对每条写命令进行持久化,即使Redis挂掉了也不会出现ID重复的情况,但由于incr命令的特殊性,会导致Redis重启恢复的数据时间过长。

(5)基于雪花算法(Snowflake)模式

在这里插入图片描述

(6)百度(uid-generator)

(7)美团(Leaf)

(8)滴滴(Tinyid)

2.hash扰动是如何实现的?为何要做扰动?

下面是源码中的扰动函数:

static final int hash(Object key) {
    int h;
    // key.hashCode():返回散列值也就是hashcode
    // ^ :按位异或
    // >>>:无符号右移,忽略符号位,空位都以0补齐
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

使用扰动函数之后可以减少碰撞 

3.类的生命周期了解吗?

加载,验证,准备,解析,初始化,使用,卸载。

4.Spring IoC中Bean的生命周期?谁来管理Bean的生命周期?

参考:https://blog.csdn.net/w_linux/article/details/80086950?depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2&utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2

5.@Autowired和@Resource的区别? 

@Resource是Java自己的注解,@Resource有两个属性是比较重要的,分是name和type;Spring将@Resource注解的name属性解析为bean的名字,而type属性则解析为bean的类型。所以如果使用name属性,则使用byName的自动注入策略,而使用type属性时则使用byType自动注入策略。如果既不指定name也不指定type属性,这时将通过反射机制使用byName自动注入策略。
    @Autowired是spring的注解,是spring2.5版本引入的,Autowired只根据type进行注入,不会去匹配name。如果涉及到type无法辨别注入对象时,那需要依赖@Qualifier或@Primary注解一起来修饰。

 

byName:被注入bean的id名必须与set方法后半截匹配,并且id名称的第一个单词首字母必须小写,这一点与手动set注入有点不同。

byType:查找所有的set方法,将符合符合参数类型的bean注入。

6.线程池有哪些?线程池怎么创建怎么用?

参考:https://www.cnblogs.com/HigginCui/p/8686653.html

线程池通过Executors就可以直接创建

Executors.newFixedThreadPool(60);

线程池的种类

(1)newFixedThreadPool()方法

该方法返回一个固定线程数量的线程池。该线程池中的线程数量始终不变,当有一个新的任务提交时,线程池中若有空闲线程,则立即处理。

若没有空闲线程,则新的任务会被暂存在一个任务队列中,待有线程空闲时,便处理在任务队列中的任务。

(2)newSingleThreadExecutor()方法

只有一条线程来执行任务,适用于有顺序的任务的应用场景。若多多余一个任务被提交到该线程池,任务会被保存在一个任务队列中,待线程空闲,按先入先出顺序执行任务。

(3)newCachedThreadPool()方法

该线程池中没有核心线程,非核心线程的数量为Integer.max_value,就是无限大,当有需要时创建线程来执行任务,没有需要时回收线程,适用于耗时少,任务量大的情况。

(4)newScheduleThreadPool()方法

有核心线程,但也有非核心线程,非核心线程的大小也为无限大。适用于执行周期性的任务。

7.GC Roots有哪些?

(1)虚拟机栈引用的对象

(2)方法区中的类静态属性引用的对象

(3)方法区中的常量引用的对象 

8.MySQL行锁是否会有死锁的情况?

两个单条的sql语句涉及到的加锁数据相同,但是加锁顺序不同,导致了死锁。

9.ArrayList和LinkedList区别?

Arraylist:底层是基于动态数组,根据下标随机访问数组元素的效率高,向数组尾部添加元素的效率高;但是,删除数组中的数据以及向数组中间添加数据效率低,因为需要移动数组。

Linkedlist基于链表的动态数组,数据添加删除效率高,只需要改变指针指向即可,但是访问数据的平均效率低,需要对链表进行遍历。

10.熟悉string的底层实现吗?

下面是string的一段源码

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];

    /** Cache the hash code for the string */
    private int hash; // Default to 0
    ...
}

(1)String类被final关键字修饰,意味着String类不能被继承,并且它的成员方法都默认为final方法;字符串一旦创建就不能再修改。

(2)String实例的值是通过字符数组实现字符串存储的。

(3)字符串对象可以使用“+”连接其他对象。其中字符串连接是通过 StringBuilder(或 StringBuffer)类及其append 方法实现的,对象转换为字符串是通过 toString 方法实现的 

(4)JVM为了提高性能和减少内存的开销,在实例化字符串的时候进行了一些优化:使用字符串常量池。每当创建字符串常量时,JVM会首先检查字符串常量池,如果该字符串已经存在常量池中,那么就直接返回常量池中的实例引用。如果字符串不存在常量池中,就会实例化该字符串并且将其放到常量池中。由于String字符串的不可变性,常量池中一定不存在两个相同的字符串。

(5)例子

/**
 * 运行结果为true false
 */
String s1 = "AB";
String s2 = "AB";
String s3 = new String("AB");
System.out.println(s1 == s2);
System.out.println(s1 == s3);

 

由于常量池中不存在两个相同的对象,所以s1和s2都是指向JVM字符串常量池中的"AB"对象。new关键字一定会产生一个对象,并且这个对象存储在堆中。所以String s3 = new String(“AB”);产生了两个对象:保存在栈中的s3和保存堆中的String对象。

这里写图片描述

当执行String s1 = "AB"时,JVM首先会去字符串常量池中检查是否存在"AB"对象,如果不存在,则在字符串常量池中创建"AB"对象,并将"AB"对象的地址返回给s1;如果存在,则不创建任何对象,直接将字符串常量池中"AB"对象的地址返回给s1。

11.如何实现异步编程?

(1)回调函数,这种方式回调比较简单,但是这种方式来回回调,会使得这种递归很深。

(2)事件监听。

(3)观察者模式,消息订阅发布。

12.创建线程有什么开销?

对操作系统来说,创建一个线程的代价是十分昂贵的, 需要给它分配内存、列入调度,直接向系统申请资源,也很耗时。

正是因为这个原因,并不是所有情况下创建线程来执行任务,都是高效的。比如有T1,T2,T3,T1和T3是创建线程和销毁线程的时间,T2是执行任务的时间,T2比较大的时候,这样效率才很高,如果T2很小,花费就都是创建和销毁的时间了,没有那么大的意义。

13.JAVA面向对象的理解?

参考:https://blog.csdn.net/weixin_40066829/article/details/78111476

在我理解,面向对象是向现实世界模型的自然延伸,这是一种“万物皆对象”的编程思想。在现实生活中的任何物体都可以归为一类事物,而每一个个体都是一类事物的实例。面向对象的编程是以对象为中心,以消息为驱动,所以程序=对象+消息。


面向对象有三大特性,封装、继承和多态。


封装就是将一类事物的属性和行为抽象成一个类,使其属性私有化,行为公开化,提高了数据的隐秘性的同时,使代码模块化。这样做使得代码的复用性更高。


继承则是进一步将一类事物共有的属性和行为抽象成一个父类,而每一个子类是一个特殊的父类--有父类的行为和属性,也有自己特有的行为和属性。这样做扩展了已存在的代码块,进一步提高了代码的复用性。


如果说封装和继承是为了使代码重用,那么多态则是为了实现接口重用。多态的一大作用就是为了解耦--为了解除父子类继承的耦合度。如果说继承中父子类的关系式IS-A的关系,那么接口和实现类之之间的关系式HAS-A。简单来说,多态就是允许父类引用(或接口)指向子类(或实现类)对象。很多的设计模式都是基于面向对象的多态性设计的。


总结一下,如果说封装和继承是面向对象的基础,那么多态则是面向对象最精髓的理论。掌握多态必先了解接口,只有充分理解接口才能更好的应用多态。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章