微信公衆号:慕容千語的架構筆記。歡迎關注一起進步。
1. 如何用數組實作隊列?
用數組實作隊列時要注意 溢出 現象,這時我們可以采用循環數組的方式來解決,即将數組收尾相接。使用front指針指向隊列首位,tail指針指向隊列末位。
2. 内部類通路局部變量的時候,為什麼變量必須加上final修飾?
因為生命周期不同。局部變量在方法結束後就會被銷毀,但内部類對象并不一定,這樣就會導緻内部類引用了一個不存在的變量。
是以編譯器會在内部類中生成一個局部變量的拷貝,這個拷貝的生命周期和内部類對象相同,就不會出現上述問題。
但這樣就導緻了其中一個變量被修改,兩個變量值可能不同的問題。為了解決這個問題,編譯器就要求局部變量需要被final修飾,以保證兩個變量值相同。
在JDK8之後,編譯器不要求内部類通路的局部變量必須被final修飾,但局部變量值不能被修改(無論是方法中還是内部類中),否則會報編譯錯誤。利用javap檢視編譯後的位元組碼可以發現,編譯器已經加上了final。
3. long s = 499999999 * 499999999 在上面的代碼中,s的值是多少?
根據代碼的計算結果,s的值應該是-1371654655,這是由于Java中右側值的計算預設是int類型。
4. NIO相關,Channels、Buffers、Selectors
NIO(Non-blocking IO)為所有的原始類型提供(Buffer)緩存支援,字元集編碼解碼解決方案。 Channel :一個新的原始I/O 抽象。 支援鎖和記憶體映射檔案的檔案通路接口。提供多路(non-bloking) 非阻塞式的高伸縮性網絡I/O 。
IO | NIO |
---|---|
面向流 | 面向緩沖 |
阻塞IO | 非阻塞IO |
無 | 選擇器 |
流與緩沖
Java NIO和IO之間第一個最大的差別是,IO是面向流的,NIO是面向緩沖區的。 Java IO面向流意味着每次從流中讀一個或多個位元組,直至讀取所有位元組,它們沒有被緩存在任何地方。此外,它不能前後移動流中的資料。如果需要前後移動從流中讀取的資料,需要先将它緩存到一個緩沖區。
Java NIO的緩沖導向方法略有不同。資料讀取到一個它稍後處理的緩沖區,需要時可在緩沖區中前後移動。這就增加了處理過程中的靈活性。但是,還需要檢查是否該緩沖區中包含所有您需要處理的資料。而且,需確定當更多的資料讀入緩沖區時,不要覆寫緩沖區裡尚未處理的資料。
阻塞與非阻塞IO
Java IO的各種流是阻塞的。這意味着,當一個線程調用read() 或 write()時,該線程被阻塞,直到有一些資料被讀取,或資料完全寫入。該線程在此期間不能再幹任何事情了。 Java NIO的非阻塞模式,是線程向某通道發送請求讀取資料,僅能得到目前可用的資料,如果目前沒有資料可用時,就什麼都不會擷取,當然它不會保持線程阻塞。是以直至資料變的可以讀取之前,該線程可以繼續做其他的事情。 非阻塞寫也是如此。是以一個單獨的線程現在可以管理多個輸入和輸出通道。
選擇器(Selectors)
Java NIO 的 選擇器允許一個單獨的線程來監視多個輸入通道,你可以注冊多個通道使用一個選擇器,然後使用一個單獨的線程來“選擇”通道:這些通道裡已經有可以處理的輸入,或者選擇已準備寫入的通道。這種選擇機制,使得一個單獨的線程很容易來管理多個通道。
5. 反射的用途
Java反射機制可以讓我們在編譯期(Compile Time)之外的運作期(Runtime)檢查類,接口,變量以及方法的資訊。反射還可以讓我們在運作期執行個體化對象,調用方法,通過調用get/set方法擷取變量的值。同時我們也可以通過反射來擷取泛型資訊,以及注解。還有更進階的應用–動态代理和動态類加載(ClassLoader.loadclass())。
下面列舉一些比較重要的方法:
- getFields:擷取所有 public 的變量。
- getDeclaredFields:擷取所有包括 private , protected 權限的變量。
- setAccessible:設定為 true 可以跳過Java權限檢查,進而通路private權限的變量。
- getAnnotations:擷取注解,可以用在類和方法上。
擷取方法的泛型參數:
method = Myclass.class.getMethod("setStringList", List.class);
Type[] genericParameterTypes = method.getGenericParameterTypes();
for(Type genericParameterType : genericParameterTypes){
if(genericParameterType instanceof ParameterizedType){
ParameterizedType aType = (ParameterizedType) genericParameterType;
Type[] parameterArgTypes = aType.getActualTypeArguments();
for(Type parameterArgType : parameterArgTypes){
Class parameterArgClass = (Class) parameterArgType;
System.out.println("parameterArgClass = " + parameterArgClass);
}
}
}
動态代理:
//Main.java
public static void main(String[] args) {
HelloWorld helloWorld=new HelloWorldImpl();
InvocationHandler handler=new HelloWorldHandler(helloWorld);
//建立動态代理對象
HelloWorld proxy=(HelloWorld)Proxy.newProxyInstance(
helloWorld.getClass().getClassLoader(),
helloWorld.getClass().getInterfaces(),
handler);
proxy.sayHelloWorld();
}
//HelloWorldHandler.java
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
//調用之前
doBefore();
//調用原始對象的方法
result=method.invoke(obj, args);
//調用之後
doAfter();
return result;
}
通過反射擷取方法注解的參數:
Class aClass = TheClass.class;
Annotation[] annotations = aClass.getAnnotations();
for(Annotation annotation : annotations){
if(annotation instanceof MyAnnotation){
MyAnnotation myAnnotation = (MyAnnotation) annotation;
System.out.println("name: " + myAnnotation.name());
System.out.println("value: " + myAnnotation.value());
}
}
非靜态内部類能定義靜态方法嗎?
public class OuterClass{
private static float f = 1.0f;
class InnerClass{
public static float func(){return f;}
}
}
以上代碼會出現編譯錯誤,因為隻有靜态内部類才能定義靜态方法。
6. Lock 和 Synchronized 有什麼差別?
使用方法的差別
- Synchronized:在需要同步的對象中加入此控制,synchronized可以加在方法上,也可以加在特定代碼塊中,括号中表示需要鎖的對象。
- Lock:需要顯示指定起始位置和終止位置。一般使用ReentrantLock類做為鎖,多個線程中必須要使用一個ReentrantLock類做為對象才能保證鎖的生效。且在加鎖和解鎖處需要通過lock()和unlock()顯示指出。是以一般會在finally塊中寫unlock()以防死鎖。
性能的差別
synchronized是托管給JVM執行的,而lock是java寫的控制鎖的代碼。在Java1.5中,synchronize是性能低效的。因為這是一個重量級操作,需要調用操作接口,導緻有可能加鎖消耗的系統時間比加鎖以外的操作還多。相比之下使用Java提供的Lock對象,性能更高一些。但是到了Java1.6,發生了變化。synchronize在語義上很清晰,可以進行很多優化,有适應自旋,鎖消除,鎖粗化,輕量級鎖,偏向鎖等等。導緻在Java1.6上synchronize的性能并不比Lock差。
- Synchronized:采用的是CPU悲觀鎖機制,即線程獲得的是獨占鎖。獨占鎖意味着 其他線程隻能依靠阻塞來等待線程釋放鎖。而在CPU轉換線程阻塞時會引起線程上下文切換,當有很多線程競争鎖的時候,會引起CPU頻繁的上下文切換導緻效率很低。
- Lock:用的是樂觀鎖方式。所謂樂觀鎖就是,每次不加鎖而是假設沒有沖突而去完成某項操作,如果因為沖突失敗就重試,直到成功為止。樂觀鎖實作的機制就是CAS操作。我們可以進一步研究ReentrantLock的源代碼,會發現其中比較重要的獲得鎖的一個方法是compareAndSetState。這裡其實就是調用的CPU提供的特殊指令。
ReentrantLock:具有更好的可伸縮性:比如時間鎖等候、可中斷鎖等候、無塊結構鎖、多個條件變量或者鎖投票。
7. float 變量如何與 0 比較?
folat類型的還有double類型的,這些小數類型在趨近于0的時候直接等于0的可能性很小,一般都是無限趨近于0,是以不能用==來判斷。應該用|x-0|
//用程式表示就是
fabs(x) < 0.00001f
8. 如何建立非靜态内部類?
内部類在聲明的時候必須是 Outer.Inner a,就像int a 一樣,至于靜态内部類和非靜态内部類new的時候有點差別:
- Outer.Inner a = new Outer().new Inner()(非靜态,先有Outer對象才能 new 内部類)
- Outer.Inner a = new Outer.Inner()(靜态内部類)
9. Java辨別符命名規則
可以包含:字母、數字、$、_(下劃線),不可用數字開頭,不能是 Java 的關鍵字和保留字。
11. 你知道哪些JDK中用到的設計模式?
- 裝飾模式:java.io
- 單例模式:Runtime類
- 簡單工廠模式:Integer.valueOf方法
- 享元模式:String常量池、Integer.valueOf(int i)、Character.valueOf(char c)
- 疊代器模式:Iterator
- 職責鍊模式:ClassLoader的雙親委派模型
- 解釋器模式:正規表達式java.util.regex.Pattern
12. ConcurrentHashMap如何保證線程安全
JDK 1.7及以前:
ConcurrentHashMap允許多個修改操作并發進行,其關鍵在于使用了鎖分離技術。它使用了多個鎖來控制對hash表的不同部分進行的修改。ConcurrentHashMap内部使用段(Segment)來表示這些不同的部分,每個段其實就是一個小的hash table,它們有自己的鎖。隻要多個修改操作發生在不同的段上,它們就可以并發進行。
JDK 1.8:
Segment雖保留,但已經簡化屬性,僅僅是為了相容舊版本。
插入時使用CAS算法:unsafe.compareAndSwapInt(this, valueOffset, expect, update)。 CAS(Compare And Swap)意思是如果valueOffset位置包含的值與expect值相同,則更新valueOffset位置的值為update,并傳回true,否則不更新,傳回false。插入時不允許key或value為null
與Java8的HashMap有相通之處,底層依然由“數組”+連結清單+紅黑樹;
底層結構存放的是TreeBin對象,而不是TreeNode對象;
CAS作為知名無鎖算法,那ConcurrentHashMap就沒用鎖了麼?當然不是,當hash值與連結清單的頭結點相同還是會synchronized上鎖,鎖連結清單。
13. i++在多線程環境下是否存在問題,怎麼解決?
雖然遞增操作++i是一種緊湊的文法,使其看上去隻是一個操作,但這個操作并非原子的,因而它并不會作為一個不可分割的操作來執行。實際上,它包含了三個獨立的操作:讀取count的值,将值加1,然後将計算結果寫入count。這是一個“讀取 - 修改 - 寫入”的操作序列,并且其結果狀态依賴于之前的狀态。是以在多線程環境下存在問題。
要解決自增操作在多線程環境下線程不安全的問題,可以選擇使用Java提供的原子類,如AtomicInteger或者使用synchronized同步方法。
14. new與newInstance()的差別
new是一個關鍵字,它是調用new指令建立一個對象,然後調用構造方法來初始化這個對象,可以使用帶參數的構造器
newInstance()是Class的一個方法,在這個過程中,是先取了這個類的不帶參數的構造器Constructor,然後調用構造器的newInstance方法來建立對象。
Class.newInstance不能帶參數,如果要帶參數需要取得對應的構造器,然後調用該構造器的Constructor.newInstance(Object … initargs)方法
15. 你了解哪些JDK1.8的新特性?
- 接口的預設方法和靜态方法,JDK8允許我們給接口添加一個非抽象的方法實作,隻需要使用default關鍵字即可。也可以定義被static修飾的靜态方法。
- 對HashMap進行了改進,當單個桶的元素個數大于6時就會将實作改為紅黑樹實作,以避免構造重複的hashCode的攻擊
- 多并發進行了優化。如ConcurrentHashMap實作由分段加鎖、鎖分離改為CAS實作。
- JDK8拓寬了注解的應用場景,注解幾乎可以使用在任何元素上,并且允許在同一個地方多次使用同一個注解
- Lambda表達式
16. 你用過哪些JVM參數?
- Xms 堆最小值
- Xmx 堆最大值
- Xmn: 新生代容量
- XX:SurvivorRatio 新生代中Eden與Surivor空間比例
- Xss 棧容量
- XX:PermSize 方法區初始容量
- XX:MaxPermSize 方法區最大容量
- XX:+PrintGCDetails 收集器日志參數
17. 如何打破 ClassLoader 雙親委托?
重寫loadClass()方法。
18. hashCode() && equals()
hashcode() 傳回該對象的哈希碼值,支援該方法是為哈希表提供一些優點,例如,java.util.Hashtable 提供的哈希表。
在 Java 應用程式執行期間,在同一對象上多次調用 hashCode 方法時,必須一緻地傳回相同的整數,前提是對象上 equals 比較中所用的資訊沒有被修改(equals預設傳回對象位址是否相等)。如果根據 equals(Object)方法,兩個對象是相等的,那麼在兩個對象中的每個對象上調用 hashCode 方法都必須生成相同的整數結果。
以下情況不是必需的:如果根據 equals(java.lang.Object) 方法,兩個對象不相等,那麼在兩個對象中的任一對象上調用 hashCode 方法必定會生成不同的整數結果。但是,程式員應該知道,為不相等的對象生成不同整數結果可以提高哈希表的性能。
實際上,由 Object 類定義的 hashCode 方法确實會針對不同的對象傳回不同的整數。(這一般是通過将該對象的内部位址轉換成一個整數來實作的,但是 JavaTM 程式設計語言不需要這種實作技巧I。)
hashCode的存在主要是用于查找的快捷性,如 Hashtable,HashMap等,hashCode 是用來在散列存儲結構中确定對象的存儲位址的;
如果兩個對象相同,就是适用于 equals(java.lang.Object) 方法,那麼這兩個對象的 hashCode 一定要相同;
如果對象的 equals 方法被重寫,那麼對象的 hashCode 也盡量重寫,并且産生 hashCode 使用的對象,一定要和 equals 方法中使用的一緻,否則就會違反上面提到的第2點;
兩個對象的hashCode相同,并不一定表示兩個對象就相同,也就是不一定适用于equals(java.lang.Object) 方法,隻能夠說明這兩個對象在散列存儲結構中,如Hashtable,他們“存放在同一個籃子裡”。
19. Thread.sleep() & Thread.yield()
sleep()和yield()都會釋放CPU。
sleep()使目前線程進入停滞狀态,是以執行sleep()的線程在指定的時間内肯定不會執行;yield()隻是使目前線程重新回到可執行狀态,是以執行yield()的線程有可能在進入到可執行狀态後馬上又被執行。
sleep()可使優先級低的線程得到執行的機會,當然也可以讓同優先級和高優先級的線程有執行的機會;yield()隻能使同優先級的線程有執行的機會。
20. #{}和${}的差別是什麼?
{}是預編譯處理,${}是字元串替換。
Mybatis在處理#{}時,會将sql中的#{}替換為?号,調用PreparedStatement的set方法來指派;
Mybatis在處理${}時,就是把${}替換成變量的值。
使用#{}可以有效的防止SQL注入,提高系統安全性。
21. 通常一個Xml映射檔案,都會寫一個Dao接口與之對應,請問,這個Dao接口的工作原理是什麼?Dao接口裡的方法,參數不同時,方法能重載嗎?
Dao接口,就是人們常說的Mapper接口,接口的全限名,就是映射檔案中的namespace的值,接口的方法名,就是映射檔案中MappedStatement的id值,接口方法内的參數,就是傳遞給sql的參數。Mapper接口是沒有實作類的,當調用接口方法時,接口全限名+方法名拼接字元串作為key值,可唯一定位一個MappedStatement,舉例:com.mybatis3.mappers.StudentDao.findStudentById,可以唯一找到namespace為com.mybatis3.mappers.StudentDao下面id = findStudentById的MappedStatement。在Mybatis中,每一個、、、标簽,都會被解析為一個MappedStatement對象。
Dao接口裡的方法,是不能重載的,因為是全限名+方法名的儲存和尋找政策。
Dao接口的工作原理是JDK動态代理,Mybatis運作時會使用JDK動态代理為Dao接口生成代理proxy對象,代理對象proxy會攔截接口方法,轉而執行MappedStatement所代表的sql,然後将sql執行結果傳回。
歡迎關注微信公衆号:慕容千語的架構筆記 一起學習提升