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。簡單來說,多態就是允許父類引用(或接口)指向子類(或實現類)對象。很多的設計模式都是基於面向對象的多態性設計的。


總結一下,如果說封裝和繼承是面向對象的基礎,那麼多態則是面向對象最精髓的理論。掌握多態必先了解接口,只有充分理解接口才能更好的應用多態。

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