不管是老手媽媽還是新手媽媽都看這邊!我們都知道所有的產品裡,小朋友的商品應該是最難選的

小朋友長得又快,過了一個時期又瞬間抽高,怎麼選是一門很大的學問了,也可以避免買錯捶心肝~~1133283357.gif1133283357.gif

因為網路很發達,臉書又這麼普及媽咪買東西更是方便,尤其媽咪一定會到很多親子網站或是親子社團去了解產品的優缺點

不過看了這麼多網站真真假假的資料,【Doricare朵樂比】超Q彈巧拼地墊60x60cm歐風白大理石8入-附邊條是我在看到最多人推薦的好物

對於我這個精打細算的好媳婦好媽媽來說,真是太棒囉!1133283355.gif

通常有在關注相關婦幼產品的媽媽,不用考慮了,這款是我花有夠多時間才彙整出來的好物,不怕比較的啦

很多媽咪也都大推這款產品,真的很值得入手!

到貨速度也很快,光這一點就大推了!

所以我個人對【Doricare朵樂比】超Q彈巧拼地墊60x60cm歐風白大理石8入-附邊條的評比如下

質感:★★★★

使用爽感:★★★★☆

性能價格:★★★★☆

趁現在宅經濟發酵,大家又很保護小朋友不隨意出門,網購就變成媽咪們在家的興趣了~

而且廠商優惠只在這個時候~~1133283362.gif1133283362.gif

不然被掃光了也只能怪自己速度不夠快~下次記得手刀加速啊!

詳細介紹如下~參考一下吧

完整產品說明

 

Doricare朵樂比】超Q彈巧拼防護遊戲地墊

60x60cm歐風白大理石8

SAFE-安全無毒

ECO-環保可回收

CARE-極致呵護

 

 

 

就是要挑戰100%無味道! 我們做到了!

超Q彈防護遊戲墊來囉

超強防護力,生雞蛋掉落測試也摔不破喔!

 

 
 

 
 

八大特色:

1--超強保護

通過多次高度摔落測試,生雞蛋也不會破裂唷!測試高度約150cm

2--抗菌

經過SGS檢測數據,抗菌率達99.9%

3--好清洗

表面特殊處理,無論是油污還是液體,都可輕易擦除洗淨。

4--阻隔濕氣

針對台灣海島氣候設計的高密度組合膜,有效隔離地面濕氣及冰冷地板

5--台灣製造

台灣製造的好品質,邀您一起來支持!

6--吸音且輕巧

高密度彈性設計,能有效吸收重力所產生的音量,且重量僅有傳統地墊的1/2

7--底部止滑設計

底部特別加上防滑處理,讓地墊不容易移動,避免滑倒的發生,同時止滑面也可以當成素面地墊使用,圖案面與素面一次同時擁有!

8--100%無味

真的沒有味道!快來親自體驗

 



Doricare朵樂比】使用製程較複雜的交連技術,
讓每一個發泡的氣室組織都像一個獨立的泡泡一樣,
柔軟舒適卻擁有堅固的結構及彈性,使地墊氣室不坍塌破裂,
不僅大大提升地墊的耐用度,更提供小寶貝最佳的保護。







尺寸:60x60x厚度1.2cm 每片含邊條之後61.5x61.5cm 
入數:8片收縮模包裝 
材質:PE 
產地:台灣 
適用年齡:0歲以上 
注意事項:非食品,請勿咬食,請於室內使用,請遠離火源

品牌名稱

  •  

材質

  • PE聚乙烯

商品規格

  • 尺寸:60x60x1.2cm (含邊條後每片61.5x61.5cm)
    入數:8入
    材質:PE
    產地:台灣
    適用年齡:0歲以上
    注意事項:非食品,請勿咬食,請於室內使用,請遠離火源

 

非常推薦【Doricare朵樂比】超Q彈巧拼地墊60x60cm歐風白大理石8入-附邊條給大家

↓↓↓限量特惠的優惠按鈕↓↓↓

↓↓↓找不到適合的商品嗎,本月好物推薦一起來看吧↓↓↓

標籤註解:

PTT鄉民【Doricare朵樂比】超Q彈巧拼地墊60x60cm歐風白大理石8入-附邊條限量,團購,限時,週年慶,禮物,優惠,【Doricare朵樂比】超Q彈巧拼地墊60x60cm歐風白大理石8入-附邊條特價,開箱,比價,活動,好評,推薦

mobile01網友【Doricare朵樂比】超Q彈巧拼地墊60x60cm歐風白大理石8入-附邊條哪裡便宜,採購,優缺點,試用【Doricare朵樂比】超Q彈巧拼地墊60x60cm歐風白大理石8入-附邊條,好用,CP值,經驗,好康,集購,下殺,免比價,去哪買?,

名人推薦【Doricare朵樂比】超Q彈巧拼地墊60x60cm歐風白大理石8入-附邊條介紹,部落客,排行,【Doricare朵樂比】超Q彈巧拼地墊60x60cm歐風白大理石8入-附邊條,體驗,精選,限定,折扣,折價卷,dcard推薦,直播主推薦,網紅推薦熱賣款

熱點新知搶先報

 

1. 八種基本數據類型的大小,以及他們的封裝類。 (1)八種基本數據類型和封裝類 ... (2)自動裝箱和自動拆箱 什麼是自動裝箱拆箱 基本數據類型的自動裝箱(autoboxing)、拆箱(unboxing)是自J2SE 5.0開始提供的功能。 一般我們要創建一個類的對象實例的時候,我們會這樣: Class a = new Class(parameter); 當我們創建一個Integer對象時,卻可以這樣: Integer i = 100; (注意:不是 int i = 100; ) 實際上,執行上面那句代碼的時候,系統為我們執行了:Integer i = Integer.valueOf(100); 此即基本數據類型的自動裝箱功能。 基本數據類型與對象的差別 基本數據類型不是對象,也就是使用int、double、boolean等定義的變量、常量。 基本數據類型沒有可調用的方法。 eg: int t = 1; t. 後面是沒有方法滴。 Integer t =1; t. 後面就有很多方法可讓你調用了。 什麼時候自動裝箱 例如:Integer i = 100; 相當於編譯器自動為您作以下的語法編譯:Integer i = Integer.valueOf(100); 什麼時候自動拆箱 自動拆箱(unboxing),也就是將對象中的基本數據從對象中自動取出。如下可實現自動拆箱: Integer i = 10; //裝箱 int t = i; //拆箱,實際上執行了 int t = i.intValue();   在進行運算時,也可以進行拆箱。 Integer i = 10; System.out.println(i++); Integer的自動裝箱 //在-128~127 之外的數 Integer i1 =200; Integer i2 =200; System.out.println("i1==i2: "+(i1==i2)); // 在-128~127 之內的數 Integer i3 =100; Integer i4 =100; System.out.println("i3==i4: "+(i3==i4)); 輸出的結果是: i1==i2: falsei3==i4: true 說明: equals() 比較的是兩個對象的值(內容)是否相同。 "==" 比較的是兩個對象的引用(內存地址)是否相同,也用來比較兩個基本數據類型的變量值是否相等。 前面說過,int 的自動裝箱,是系統執行了 Integer.valueOf(int i),先看看Integer.java的源碼: public static Integer valueOf(int i) { if(i >= -128 && i <= IntegerCache.high) // 沒有設置的話,IngegerCache.high 默認是127 return IntegerCache.cache[i + 128]; else return new Integer(i); } 對於–128到127(默認是127)之間的值,Integer.valueOf(int i) 返回的是緩存的Integer對象!!!(並不是新建對象) 所以範例中,i3 與 i4實際上是指向同一個對象。 而其他值,執行Integer.valueOf(int i) 返回的是一個新建的 Integer對象,所以範例中,i1與i2 指向的是不同的對象。 當然,當不使用自動裝箱功能的時候,情況與普通類對象一樣,請看下例: Integer i3 =new Integer(100); Integer i4 =new Integer(100); System.out.println("i3==i4: "+(i3==i4));//顯示false 2. Switch能否用string做參數? 答案:在 Java 7之前,switch 只能支持 byte、short、char、int或者其對應的封裝類以及 Enum 類型。在 Java 7中,String支持被加上了。 3. equals與==的區別。 答案:equals是邏輯相等,==完全是對象是否是同一個(地址相等)。 4. Object有哪些公用方法? (1)clone 保護方法,實現對象的淺複製,只有實現了Cloneable接口才可以調用該方法,否則拋出CloneNotSupportedException異常 (2)equals 在Object中與==是一樣的,子類一般需要重寫該方法 (3)hashCode 該方法用於哈希查找,重寫了equals方法一般都要重寫hashCode方法。這個方法在一些具有哈希功能的Collection中用到 (4)getClass final方法,獲得運行時類型 (5)wait 使當前線程等待該對象的鎖,當前線程必須是該對象的擁有者,也就是具有該對象的鎖。wait()方法一直等待,直到獲得鎖或者被中斷。wait(long timeout)設定一個超時間隔,如果在規定時間內沒有獲得鎖就返回。 調用該方法後當前線程進入睡眠狀態,直到以下事件發生: 1. 其他線程調用了該對象的notify方法 2. 其他線程調用了該對象的notifyAll方法 3. 其他線程調用了interrupt中斷該線程 4. 時間間隔到了 此時該線程就可以被調度了,如果是被中斷的話就拋出一個InterruptedException異常 (6)notify 喚醒在該對象上等待的某個線程 (7)notifyAll 喚醒在該對象上等待的所有線程 (8)toString 轉換成字符串,一般子類都有重寫,否則列印句柄 5. Java的四種引用,強弱軟虛,用到的場景。 (1)強引用(StrongReference)強引用是使用最普遍的引用。如果一個對象具有強引用,那垃圾回收器絕不會回收它。 如下: Object o=new Object(); // 強引用 當內存空間不足,Java虛擬機寧願拋出OutOfMemoryError錯誤,使程序異常終止,也不會靠隨意回收具有強引用的對象來解決內存不足的問題。如果不使用時,要通過如下方式來弱化引用,如下: o=null; // 幫助垃圾收集器回收此對象 顯式地設置o為null,或超出對象的生命周期範圍,則gc認為該對象不存在引用,這時就可以回收這個對象。具體什麼時候收集這要取決於gc的算法。 舉例: public void test(){ Object o=new Object(); // 省略其他操作 } 在一個方法的內部有一個強引用,這個引用保存在棧中,而真正的引用內容(Object)保存在堆中。當這個方法運行完成後就會退出方法棧,則引用內容的引用不存在,這個Object會被回收。 但是如果這個o是全局的變量時,就需要在不用這個對象時賦值為null,因為強引用不會被垃圾回收。 強引用在實際中有非常重要的用處,舉個ArrayList的實現原始碼: private transient Object[] elementData; public void clear() { modCount++; // Let gc do its work for (int i = 0; i < size; i++) elementData[i] = null; size = 0; } 在ArrayList類中定義了一個私有的變量elementData數組,在調用方法清空數組時可以看到為每個數組內容賦值為null。不同於elementData=null,強引用仍然存在,避免在後續調用 add()等方法添加元素時進行重新的內存分配。使用如clear()方法中釋放內存的方法對數組中存放的引用類型特別適用,這樣就可以及時釋放內存。 (2)軟引用(SoftReference) 如果一個對象只具有軟引用,則內存空間足夠,垃圾回收器就不會回收它;如果內存空間不足了,就會回收這些對象的內存。只要垃圾回收器沒有回收它,該對象就可以被程序使用。軟引用可用來實現內存敏感的高速緩存。 String str=new String("abc"); // 強引用 SoftReference<String> softRef=new SoftReference<String>(str); // 軟引用 當內存不足時,等價於: If(JVM.內存不足()) { str = null; // 轉換為軟引用 System.gc(); // 垃圾回收器進行回收 } 軟引用在實際中有重要的應用,例如瀏覽器的後退按鈕。按後退時,這個後退時顯示的網頁內容是重新進行請求還是從緩存中取出呢?這就要看具體的實現策略了。 (1)如果一個網頁在瀏覽結束時就進行內容的回收,則按後退查看前面瀏覽過的頁面時,需要重新構建 (2)如果將瀏覽過的網頁存儲到內存中會造成內存的大量浪費,甚至會造成內存溢出 這時候就可以使用軟引用 Browser prev = new Browser(); // 獲取頁面進行瀏覽 SoftReference sr = new SoftReference(prev); // 瀏覽完畢後置為軟引用 if(sr.get()!=null){ rev = (Browser) sr.get(); // 還沒有被回收器回收,直接獲取 }else{ prev = new Browser(); // 由於內存吃緊,所以對軟引用的對象回收了 sr = new SoftReference(prev); // 重新構建 } 這樣就很好的解決了實際的問題。 軟引用可以和一個引用隊列(ReferenceQueue)聯合使用,如果軟引用所引用的對象被垃圾回收器回收,Java虛擬機就會把這個軟引用加入到與之關聯的引用隊列中。 3、弱引用 如果一個對象只具有弱引用,那就類似於可有可無的生活用品。弱引用與軟引用的區別在於:只具有弱引用的對象擁有更短暫的生命周期。在垃圾回收器線程掃描它所管轄的內存區域的過程中,一旦發現了只具有弱引用的對象,不管當前內存空間足夠與否,都會回收它的內存。不過,由於垃圾回收器是一個優先級很低的線程, 因此不一定會很快發現那些只具有弱引用的對象。弱引用可以和一個引用隊列(ReferenceQueue)聯合使用,如果弱引用所引用的對象被垃圾回收,Java虛擬機就會把這個弱引用加入到與之關聯的引用隊列中。當你想引用一個對象,但是這個對象有自己的生命周期,你不想介入這個對象的生命周期,這時候你就是用弱引用。這個引用不會在對象的垃圾回收判斷中產生任何附加的影響。比如說Thread中保存的ThreadLocal的全局映射,因為我們的Thread不想在ThreadLocal生命周期結束後還對其造成影響,所以應該使用弱引用,這個和緩存沒有關係,只是為了防止內存泄漏所做的特殊操作。 4、幽靈引用(虛引用) 虛引用主要用來跟蹤對象被垃圾回收器回收的活動。虛引用與軟引用和弱引用的一個區別在於:虛引用必須和引用隊列 (ReferenceQueue)聯合使用。當垃圾回收器準備回收一個對象時,如果發現它還有虛引用,就會在回收對象的內存後,把這個虛引用加入到與之關聯的引用隊列中。程序可以通過判斷引用隊列中是否已經加入了虛引用,來了解被引用的對象是否被垃圾回收。如果程序發現某個虛引用已經被加入到引用隊列,那麼就可以在所引用的對象的內存回收後採取必要的行動。由於Object.finalize()方法的不安全性、低效性,常常使用虛引用完成對象回收後的資源釋放工作。當你創建一個虛引用時要傳入一個引用隊列,如果引用隊列中出現了你的虛引用,說明它已經被回收,那麼你可以在其中做一些相關操作,主要是實現細粒度的內存控制。比如監視緩存,當緩存被回收後才申請新的緩存區。 6. hashcode的作用。 答案:hashCode用於返回對象的散列值,用於在散列函數中確定放置的桶的位置。 1、hashCode的存在主要是用於查找的快捷性,如Hashtable,HashMap等,hashCode是用來在散列存儲結構中確定對象的存儲地址的; 2、如果兩個對象相同,就是適用於equals(java.lang.Object) 方法,那麼這兩個對象的hashCode一定要相同; 3、如果對象的equals方法被重寫,那麼對象的hashCode也儘量重寫,並且產生hashCode使用的對象,一定要和equals方法中使用的一致,否則就會違反上面提到的第2點; 4、兩個對象的hashCode相同,並不一定表示兩個對象就相同,也就是不一定適用於equals(java.lang.Object) 方法,只能夠說明這兩個對象在散列存儲結構中,如Hashtable,他們「存放在同一個籃子裡」。 7. ArrayList、LinkedList、Vector的區別。 Arraylist和Vector是採用數組方式存儲數據,此數組元素數大於實際存儲的數據以便增加插入元素,都允許直接序號索引元素,但是插入數據要涉及到數組元素移動等內存操作,所以插入數據慢,查找有下標,所以查詢數據快,Vector由於使用了synchronized方法-線程安全,所以性能上比ArrayList要差,LinkedList使用雙向鍊表實現存儲,按序號索引數據需要進行向前或向後遍歷,但是插入數據時只需要記錄本項前後項即可,插入數據較快。 8. String、StringBuffer與StringBuilder的區別。 答案: String 字符串常量 StringBuffer 字符串變量(線程安全) StringBuilder 字符串變量(非線程安全) 簡要的說, String 類型和 StringBuffer 類型的主要性能區別其實在於 String 是不可變的對象, 因此在每次對 String 類型進行改變的時候其實都等同於生成了一個新的 String 對象,然後將指針指向新的 String 對象,所以經常改變內容的字符串最好不要用 String ,因為每次生成對象都會對系統性能產生影響,特別當內存中無引用對象多了以後, JVM 的 GC 就會開始工作,那速度是一定會相當慢的。 而如果是使用 StringBuffer 類則結果就不一樣了,每次結果都會對 StringBuffer 對象本身進行操作,而不是生成新的對象,再改變對象引用。所以在一般情況下我們推薦使用 StringBuffer ,特別是字符串對象經常改變的情況下。 String s1 = "aaaaa"; String s2 = "bbbbb"; String r = null; int i = 3694; r = s1 + i + s2; 通過查看字節碼可以發現,JVM內部使用的是創建了一個StringBuilder完成拼接工作 StringBufferJava.lang.StringBuffer線程安全的可變字符序列。一個類似於 String 的字符串緩衝區,但不能修改。雖然在任意時間點上它都包含某種特定的字符序列,但通過某些方法調用可以改變該序列的長度和內容。可將字符串緩衝區安全地用於多個線程。可以在必要時對這些方法進行同步,因此任意特定實例上的所有操作就好像是以串行順序發生的,該順序與所涉及的每個線程進行的方法調用順序一致。StringBuffer 上的主要操作是 append 和 insert 方法,可重載這些方法,以接受任意類型的數據。每個方法都能有效地將給定的數據轉換成字符串,然後將該字符串的字符追加或插入到字符串緩衝區中。append 方法始終將這些字符添加到緩衝區的末端;而 insert 方法則在指定的點添加字符。例如,如果 z 引用一個當前內容是「start」的字符串緩衝區對象,則此方法調用 z.append("le") 會使字符串緩衝區包含「startle」,而 z.insert(4, "le") 將更改字符串緩衝區,使之包含「starlet」。在大部分情況下 StringBuilder > StringBuffer java.lang.StringBuilderjava.lang.StringBuilder一個可變的字符序列是5.0新增的。此類提供一個與 StringBuffer 兼容的 API,但不保證同步。該類被設計用作 StringBuffer 的一個簡易替換,用在字符串緩衝區被單個線程使用的時候(這種情況很普遍)。如果可能,建議優先採用該類,因為在大多數實現中,它比 StringBuffer 要快。兩者的方法基本相同。 9. Map、Set、List、Queue、Stack的特點與用法。 10. HashMap和HashTable的區別。 答案:Hashtable和HashMap類有三個重要的不同之處。 1、第一個不同主要是歷史原因。Hashtable是基於陳舊的Dictionary類的,HashMap是Java 1.2引進的Map接口的一個實現。 2、也許最重要的不同是Hashtable的方法是同步的,但是這也是HashTable效率低下的原因,任何方法都是同步的,而HashMap的方法不是。這就意味著,雖然你可以不用採取任何特殊的行為就可以在一個多線程的應用程式中用一個Hashtable,但你必須同樣地為一個HashMap提供外同步。一個方便的方法就是利用Collections類的靜態的synchronizedMap()方法,它創建一個線程安全的Map對象,並把它作為一個封裝的對象來返回。這個對象的方法可以讓你同步訪問潛在的HashMap。這麼做的結果就是當你不需要同步時,你不能切斷Hashtable中的同步(比如在一個單線程的應用程式中),而且同步增加了很多處理費用。 3、第三點不同是,只有HashMap可以讓你將空值作為一個表的條目的key或value。HashMap中只有一條記錄可以是一個空的key,但任意數量的條目可以是空的value。這就是說,如果在表中沒有發現搜索鍵,或者如果發現了搜索鍵,但它是一個空的值,那麼get()將返回null。如果有必要,用containKey()方法來區別這兩種情況。 一些資料建議,當需要同步時,用Hashtable,反之用HashMap。但是,因為在需要時,HashMap可以被同步,HashMap的功能比Hashtable的功能更多,而且它不是基於一個陳舊的類的,所以有人認為,在各種情況下,HashMap都優先於Hashtable。 11. HashMap和ConcurrentHashMap的區別,HashMap的底層源碼。 (1)HashMap的源碼: 1、HashMap中的hash函數實現: 詳見:https://www.zhihu.com/question/20733617 2、HashMap源碼解讀 詳見:http://blog.csdn.net/ll530304349/article/details/53056346 (2)ConcurrentHashMap的源碼: 1、JDK1.7版本的實現 ConcurrentHashMap的鎖分段技術:假如容器里有多把鎖,每一把鎖用於鎖容器其中一部分數據,那麼當多線程訪問容器里不同數據段的數據時,線程間就不會存在鎖競爭,從而可以有效的提高並發訪問效率,這就是ConcurrentHashMap所使用的鎖分段技術。首先將數據分成一段一段的存儲,然後給每一段數據配一把鎖,當一個線程占用鎖訪問其中一個段數據的時候,其他段的數據也能被其他線程訪問。 ConcurrentHashMap不允許Key或者Value的值為NULL ... 第一:Segment類 Put 將一個HashEntry放入到該Segment中,使用自旋機制,減少了加鎖的可能性。 final V put(K key, int hash, V value, boolean onlyIfAbsent) { HashEntry<K,V> node = tryLock() ? null : scanAndLockForPut(key, hash, value); //如果加鎖失敗,則調用該方法 V oldValue; try { HashEntry<K,V>[] tab = table; int index = (tab.length - 1) & hash; //同hashMap相同的哈希定位方式 HashEntry<K,V> first = entryAt(tab, index); for (HashEntry<K,V> e = first;;) { if (e != null) { //若不為null,則持續查找,知道找到key和hash值相同的節點,將其value更新 K k; if ((k = e.key) == key || (e.hash == hash && key.equals(k))) { oldValue = e.value; if (!onlyIfAbsent) { e.value = value; ++modCount; } break; } e = e.next; } else { //若頭結點為null if (node != null) //在遍歷key對應節點鏈時沒有找到相應的節點 node.setNext(first); //當前修改並不需要讓其他線程知道,在鎖退出時修改自然會 //更新到內存中,可提升性能 else node = new HashEntry<K,V>(hash, key, value, first); int c = count + 1; if (c > threshold && tab.length < MAXIMUM_CAPACITY) rehash(node); //如果超過閾值,則進行rehash操作 else setEntryAt(tab, index, node); ++modCount; count = c; oldValue = null; break; } } } finally { unlock(); } return oldValue; } scanAndLockForPut 該操作持續查找key對應的節點鏈中是否已存在該節點,如果沒有找到已存在的節點,則預創建一個新節點,並且嘗試n次,直到嘗試次數超出限制,才真正進入等待狀態,即所謂的 自旋等待。 private HashEntry<K,V> scanAndLockForPut(K key, int hash, V value) { //根據hash值找到segment中的HashEntry節點 HashEntry<K,V> first = entryForHash(this, hash); //首先獲取頭結點 HashEntry<K,V> e = first; HashEntry<K,V> node = null; int retries = -1; // negative while locating node while (!tryLock()) { //持續遍歷該哈希鏈 HashEntry<K,V> f; // to recheck first below if (retries < 0) { if (e == null) { if (node == null) //若不存在要插入的節點,則創建一個新的節點 node = new HashEntry<K,V>(hash, key, value, null); retries = 0; } else if (key.equals(e.key)) retries = 0; else e = e.next; } else if (++retries > MAX_SCAN_RETRIES) { //嘗試次數超出限制,則進行自旋等待 lock(); break; } /*當在自旋過程中發現節點鏈的鏈頭髮生了變化,則更新節點鏈的鏈頭, 並重置retries值為-1,重新為嘗試獲取鎖而自旋遍歷*/ else if ((retries & 1) == 0 && (f = entryForHash(this, hash)) != first) { e = first = f; // re-traverse if entry changed retries = -1; } } return node; } remove 用於移除某個節點,返回移除的節點值。 final V remove(Object key, int hash, Object value) { if (!tryLock()) scanAndLock(key, hash); V oldValue = null; try { HashEntry<K,V>[] tab = table; int index = (tab.length - 1) & hash; //根據這種哈希定位方式來定位對應的HashEntry HashEntry<K,V> e = entryAt(tab, index); HashEntry<K,V> pred = null; while (e != null) { K k; HashEntry<K,V> next = e.next; if ((k = e.key) == key || (e.hash == hash && key.equals(k))) { V v = e.value; if (value == null || value == v || value.equals(v)) { if (pred == null) setEntryAt(tab, index, next); else pred.setNext(next); ++modCount; --count; oldValue = v; } break; } pred = e; e = next; } } finally { unlock(); } return oldValue; } Clear 要首先對整個segment加鎖,然後將每一個HashEntry都設置為null。 final void clear() { lock(); try { HashEntry<K,V>[] tab = table; for (int i = 0; i < tab.length ; i++) setEntryAt(tab, i, null); ++modCount; count = 0; } finally { unlock(); } } Put public V put(K key, V value) { Segment<K,V> s; if (value == null) throw new NullPointerException(); int hash = hash(key); //求出key的hash值 int j = (hash >>> segmentShift) & segmentMask; //求出key在segments數組中的哪一個segment中 if ((s = (Segment<K,V>)UNSAFE.getObject (segments, (j << SSHIFT) + SBASE)) == null) s = ensureSegment(j); //使用unsafe操作取出該segment return s.put(key, hash, value, false); //向segment中put元素 } Get public V get(Object key) { Segment<K,V> s; HashEntry<K,V>[] tab; int h = hash(key); //找出對應的segment的位置 long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE; if ((s = (Segment<K,V>)UNSAFE.getObjectVolatile(segments, u)) != null && (tab = s.table) != null) { //使用Unsafe獲取對應的Segmen for (HashEntry<K,V> e = (HashEntry<K,V>) UNSAFE.getObjectVolatile (tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE); e != null; e = e.next) { //找出對應的HashEntry,從頭開始遍歷 K k; if ((k = e.key) == key || (e.hash == h && key.equals(k))) return e.value; } } return null; } Size 求出所有的HashEntry的數目,先嘗試的遍歷查找、計算2遍,如果兩遍遍歷過程中整個Map沒有發生修改(即兩次所有Segment實例中modCount值的和一致),則可以認為整個查找、計算過程中Map沒有發生改變。否則,需要對所有segment實例進行加鎖、計算、解鎖,然後返回。 public int size() { final Segment<K,V>[] segments = this.segments; int size; boolean overflow; // true if size overflows 32 bits long sum; // sum of modCounts long last = 0L; // previous sum int retries = -1; // first iteration isn't retry try { for (;;) { if (retries++ == RETRIES_BEFORE_LOCK) { for (int j = 0; j < segments.length; ++j) ensureSegment(j).lock(); // force creation } sum = 0L; size = 0; overflow = false; for (int j = 0; j < segments.length; ++j) { Segment<K,V> seg = segmentAt(segments, j); if (seg != null) { sum += seg.modCount; int c = seg.count; if (c < 0 || (size += c) < 0) overflow = true; } } if (sum == last) break; last = sum; } } finally { if (retries > RETRIES_BEFORE_LOCK) { for (int j = 0; j < segments.length; ++j) segmentAt(segments, j).unlock(); } } return overflow ? Integer.MAX_VALUE : size; } (2)JDK1.8實現 在JDK1.8中對ConcurrentHashmap做了兩個改進: 取消segments欄位,直接採用transient volatile HashEntry<K,V>[] table保存數據,採用table數組元素作為鎖,從而實現了對每一行數據進行加鎖,進一步減少並發衝突的機率。 將原先 table數組+單向鍊表 的數據結構,變更為 table數組+單向鍊表+紅黑樹 的結構。對於hash表來說,最核心的能力在於將key hash之後能均勻的分布在數組中。如果hash之後散列的很均勻,那麼table數組中的每個隊列長度主要為0或者1。但實際情況並非總是如此理想,雖然ConcurrentHashMap類默認的加載因子為0.75,但是在數據量過大或者運氣不佳的情況下,還是會存在一些隊列長度過長的情況,如果還是採用單向列表方式,那麼查詢某個節點的時間複雜度為O(n);因此,對於個數超過8(默認值)的列表,jdk1.8中採用了紅黑樹的結構,那麼查詢的時間複雜度可以降低到O(logN),可以改進性能。 12. TreeMap、HashMap、LinkedHashMap的區別。 Map主要用於存儲健值對,根據鍵得到值,因此不允許鍵重複(重複了覆蓋了),但允許值 重複。Hashmap 是一個最常用的Map,它根據鍵的HashCode 值存儲數據,根據鍵可以直接獲取它的值,具有很快的訪問速度,遍歷時,取得數據的順序是完全隨機的。HashMap最多只允許一條記錄的鍵為Null;允許多條記錄的值為 Null;HashMap不支持線程的同步,即任一時刻可以有多個線程同時寫HashMap;可能會導致數據的不一致。如果需要同步,可以用 Collections的synchronizedMap方法使HashMap具有同步的能力,或者使用ConcurrentHashMap。 HashMap:線程不同步。根據key的hashcode進行存儲,內部使用靜態內部類Node的數組進行存儲,默認初始大小為16,每次擴大一倍。當發生Hash衝突時,採用拉鏈法(鍊表)。可以接受為null的鍵值(key)和值(value)。JDK 1.8中:當單個桶中元素個數大於等於8時,鍊表實現改為紅黑樹實現;當元素個數小於6時,變回鍊表實現。由此來防止hashCode攻擊。 LinkedHashMap:保存了記錄的插入順序,在用Iterator遍歷LinkedHashMap時,先得到的記錄肯定是先插入的. 也可以在構造時用帶參數,按照應用次數排序。在遍歷的時候會比HashMap慢,不過有種情況例外,當HashMap容量很大,實際數據較少時,遍歷起來可能會比LinkedHashMap慢,因為LinkedHashMap的遍歷速度只和實際數據有關,和容量無關,而HashMap的遍歷速度和他的容量有關。LinkedHashMap採用的hash算法和HashMap相同,但是它重新定義了數組中保存的元素Entry,該Entry除了保存當前對象的引用外,還保存了其上一個元素before和下一個元素after的引用,從而在哈希表的基礎上又構成了雙向連結列表。 TreeMap:線程不同步,基於 紅黑樹 (Red-Black tree)的NavigableMap 實現,能夠把它保存的記錄根據鍵排序,默認是按鍵值的升序排序,也可以指定排序的比較器,當用Iterator 遍歷TreeMap時,得到的記錄是排過序的。 HashTable:線程安全,HashMap的疊代器(Iterator)是fail-fast疊代器。HashTable不能存儲NULL的key和value。 13. Collection包結構,與Collections的區別。 (1)Collection 是單列集合 List 元素是有序的、可重複 有序的 collection,可以對列表中每個元素的插入位置進行精確地控制。 可以根據元素的整數索引(在列表中的位置)訪問元素,並搜索列表中的元素。 可存放重複元素,元素存取是有序的。 List接口中常用類 l Vector:線程安全,但速度慢,已被ArrayList替代。 底層數據結構是數組結構 l ArrayList:線程不安全,查詢速度快。 底層數據結構是數組結構 l LinkedList:線程不安全。增刪速度快。 底層數據結構是列表結構 Set(集) 元素無序的、不可重複。 取出元素的方法只有疊代器。不可以存放重複元素,元素存取是無序的。 Set接口中常用的類 l HashSet:線程不安全,存取速度快。 它是如何保證元素唯一性的呢?依賴的是元素的hashCode方法和euqals方法。 l TreeSet:線程不安全,可以對Set集合中的元素進行排序。 它的排序是如何進行的呢?通過compareTo或者compare方法中的來保證元素的唯一性。元素是以二叉樹的形式存放的。 (2)Map 是一個雙列集合 |--Hashtable:線程安全,速度快。底層是哈希表數據結構。是同步的。 不允許null作為鍵,null作為值。 |--Properties:用於配置文件的定義和操作,使用頻率非常高,同時鍵和值都是字符串。 是集合中可以和IO技術相結合的對象。(到了IO在學習它的特有和io相關的功能。) |--HashMap:線程不安全,速度慢。底層也是哈希表數據結構。是不同步的。 允許null作為鍵,null作為值。替代了Hashtable. |--LinkedHashMap: 可以保證HashMap集合有序。存入的順序和取出的順序一致。 |--TreeMap:可以用來對Map集合中的鍵進行排序. (3)Collection 和 Collections的區別 Collection是集合類的上級接口,子接口主要有Set 和List。 Collections是針對集合類的一個幫助類,提供了操作集合的工具方法:一系列靜態方法實現對各種集合的搜索、排序、線程安全化等操作。 14. try catch finally,try里有return,finally還執行麼? 答案:執行,並且finally的執行早於try裡面的return 結論: 1、不管有木有出現異常,finally塊中代碼都會執行; 2、當try和catch中有return時,finally仍然會執行; 3、finally是在return後面的表達式運算後執行的(此時並沒有返回運算後的值,而是先把要返回的值保存起來,管finally中的代碼怎麼樣,返回的值都不會改變,任然是之前保存的值),所以函數返回值是在finally執行前確定的; 4、finally中最好不要包含return,否則程序會提前退出,返回值不是try或catch中保存的返回值。 舉例: 情況1:try{} catch(){}finally{} return; 顯然程序按順序執行。 情況2:try{ return; }catch(){} finally{} return; 程序執行try塊中return之前(包括return語句中的表達式運算)代碼; 再執行finally塊,最後執行try中return; finally塊之後的語句return,因為程序在try中已經return所以不再執行。 情況3:try{ } catch(){return;} finally{} return; 程序先執行try,如果遇到異常執行catch塊, 有異常:則執行catch中return之前(包括return語句中的表達式運算)代碼,再執行finally語句中全部代碼, 最後執行catch塊中return. finally之後也就是4處的代碼不再執行。 無異常:執行完try再finally再return. 情況4:try{ return; }catch(){} finally{return;} 程序執行try塊中return之前(包括return語句中的表達式運算)代碼; 再執行finally塊,因為finally塊中有return所以提前退出。 情況5:try{} catch(){return;}finally{return;} 程序執行catch塊中return之前(包括return語句中的表達式運算)代碼; 再執行finally塊,因為finally塊中有return所以提前退出。 情況6:try{ return;}catch(){return;} finally{return;} 程序執行try塊中return之前(包括return語句中的表達式運算)代碼; 有異常:執行catch塊中return之前(包括return語句中的表達式運算)代碼; 則再執行finally塊,因為finally塊中有return所以提前退出。 無異常:則再執行finally塊,因為finally塊中有return所以提前退出。 最終結論:任何執行try 或者catch中的return語句之前,都會先執行finally語句,如果finally存在的話。 如果finally中有return語句,那麼程序就return了,所以finally中的return是一定會被return的,編譯器把finally中的return實現為一個warning。 15. Excption與Error包結構。OOM你遇到過哪些情況,SOF你遇到過哪些情況。 (1)Java異常 Java中有Error和Exception,它們都是繼承自Throwable類。 ... ... 二者的不同之處 Exception: 可以是可被控制(checked) 或不可控制的(unchecked)。 表示一個由程式設計師導致的錯誤。 應該在應用程式級被處理。 Error: 總是不可控制的(unchecked)。 經常用來用於表示系統錯誤或低層資源的錯誤。 如何可能的話,應該在系統級被捕捉。 異常的分類 Checked exception: 這類異常都是Exception的子類。異常的向上拋出機制進行處理,假如子類可能產生A異常,那麼在父類中也必須throws A異常。可能導致的問題:代碼效率低,耦合度過高。 Unchecked exception: 這類異常都是RuntimeException的子類,雖然RuntimeException同樣也是Exception的子類,但是它們是非凡的,它們不能通過client code來試圖解決,所以稱為Unchecked exception 。 16. Java面向對象的三個特徵與含義。 答案:繼承、多態、封裝 17. Override和Overload的含義與區別。 答案:見JVM靜態分派和動態分派 18. Interface與abstract類的區別。 答案:1.abstract class 在Java中表示的是一種繼承關係,一個類只能使用一次繼承關係。但是,一個類卻可以實現多個interface。2.在abstract class 中可以有自己的數據成員,也可以有非abstarct的方法,而在interface中,只能夠有靜態的不能被修改的數據成員(也就是必須是static final的,不過在 interface中一般不定義數據成員),所有的方法都是public abstract的。3.抽象類中的變量默認是 friendly 型,其值可以在子類中重新定義,也可以重新賦值。接口中定義的變量默認是public static final 型,且必須給其賦初值,所以實現類中不能重新定義,也不能改變其值。4.abstract class和interface所反映出的設計理念不同。其實abstract class表示的是"is-a"關係,interface表示的是"like-a"關係,門和報警的關係。 5.實現抽象類和接口的類必須實現其中的所有方法。抽象類中可以有非抽象方法。接口中則不能有實現方法。 abstract class 和 interface 是 Java語言中的兩種定義抽象類的方式,它們之間有很大的相似性。但是對於它們的選擇卻又往往反映出對於問題領域中的概念本質的理解、對於設計意圖的反映是否正確、合理,因為它們表現了概念間的不同的關係。 19. Static class 與non static class的區別。 答案: 比對如下: 靜態對象 非靜態對象 擁有屬性: 是類共同擁有的 是類各對象獨立擁有的內存分配: 內存空間上是固定的 空間在各個附屬類裡面分配 分配順序: 先分配靜態對象的空間 繼而再對非靜態對象分配空間,也就是初始化順序是先靜態再非靜態.Java靜態對象到底有什麼好處? A,靜態對象的數據在全局是唯一的,一改都改。如果你想要處理的東西是整個程序中唯一的,弄成靜態是個好方法。 非靜態的東西你修改以後只是修改了他自己的數據,但是不會影響其他同類對象的數據。 B,引用方便。直接用 類名.靜態方法名 或者 類名.靜態變量名就可引用並且直接可以修改其屬性值,不用get和set方法。C,保持數據的唯一性。此數據全局都是唯一的,修改他的任何一處地方,在程序所有使用到的地方都將會體現到這些數據的修改。有效減少多餘的浪費。 D,static final用來修飾成員變量和成員方法,可簡單理解為「全局常量」。對於變量,表示一旦給值就不可修改;對於方法,表示不可覆蓋。 20. java多態的實現原理。 答案:見JVM動態分派 21. 實現多線程的兩種方法:Thread與Runable。 22. volatile原理與線程同步的方法:sychronized、lock、reentrantLock等。 (1)Volatile原理 (一)計算機內存模型 計算機在執行程序時,每條指令都是在CPU中執行的,而執行指令過程中,勢必涉及到數據的讀取和寫入。由於程序運行過程中的臨時數據是存放在主存(物理內存)當中的,這時就存在一個問題,由於CPU執行速度很快,而從內存讀取數據和向內存寫入數據的過程跟CPU執行指令的速度比起來要慢的多,因此如果任何時候對數據的操作都要通過和內存的交互來進行,會大大降低指令執行的速度。因此在CPU裡面就有了高速緩存。當程序在運行過程中,會將運算需要的數據從主存複製一份到CPU的高速緩存當中,那麼CPU進行計算時就可以直接從它的高速緩存讀取數據和向其中寫入數據,當運算結束之後,再將高速緩存中的數據刷新到主存當中。舉個簡單的例子,比如下面的這段代碼: i = i + 1; 當線程執行這個語句時,會先從主存當中讀取i的值,然後複製一份到高速緩存當中,然後 CPU 執行指令對i進行加1操作,然後將數據寫入高速緩存,最後將高速緩存中i最新的值刷新到主存當中。 這個代碼在單線程中運行是沒有任何問題的,但是在多線程中運行就會有問題了。在多核 CPU 中,每條線程可能運行於不同的 CPU 中,因此 每個線程運行時有自己的高速緩存(對單核CPU來說,其實也會出現這種問題,只不過是以線程調度的形式來分別執行的)。比如同時有兩個線程執行這段代碼,假如初始時i的值為0,那麼我們希望兩個線程執行完之後i的值變為2。但是事實會是這樣嗎? 可能出現這種情況:初始時,兩個線程分別讀取i的值存入各自所在的 CPU 的高速緩存當中,然後 線程1 進行加1操作,然後把i的最新值1寫入到內存。此時線程2的高速緩存當中i的值還是0,進行加1操作之後,i的值為1,然後線程2把i的值寫入內存。最終結果i的值是1,而不是2。這就是著名的緩存一致性問題。通常稱這種被多個線程訪問的變量為共享變量。 為了解決緩存不一致性問題,通常來說有以下兩種解決方法: 通過在總線加LOCK#鎖的方式 通過 緩存一致性協議 這兩種方式都是硬體層面上提供的方式。 在早期的 CPU 當中,是通過在總線上加LOCK#鎖的形式來解決緩存不一致的問題。因為 CPU 和其他部件進行通信都是通過總線來進行的,如果對總線加LOCK#鎖的話,也就是說阻塞了其他 CPU 對其他部件訪問(如內存),從而使得只能有一個 CPU 能使用這個變量的內存。比如上面例子中 如果一個線程在執行 i = i +1,如果在執行這段代碼的過程中,在總線上發出了LCOK#鎖的信號,那麼只有等待這段代碼完全執行完畢之後,其他CPU才能從變量i所在的內存讀取變量,然後進行相應的操作。這樣就解決了緩存不一致的問題。但是上面的方式會有一個問題,由於在鎖住總線期間,其他CPU無法訪問內存,導致效率低下。 所以就出現了緩存一致性協議。最出名的就是 Intel 的MESI協議,MESI協議保證了每個緩存中使用的共享變量的副本是一致的。它核心的思想是:當CPU寫數據時,如果發現操作的變量是共享變量,即在其他CPU中也存在該變量的副本,會發出信號通知其他CPU將該變量的緩存行置為無效狀態,因此當其他CPU需要讀取這個變量時,發現自己緩存中緩存該變量的緩存行是無效的,那麼它就會從內存重新讀取。 ... (二)Java內存模型 在Java虛擬機規範中試圖定義一種Java內存模型(Java Memory Model,JMM)來屏蔽各個硬體平臺和作業系統的內存訪問差異,以實現讓Java程序在各種平臺下都能達到一致的內存訪問效果。那麼Java內存模型規定了程序中變量的訪問規則,往大一點說是定義了程序執行的次序。注意,為了獲得較好的執行性能,Java內存模型並沒有限制執行引擎使用處理器的寄存器或者高速緩存來提升指令執行速度,也沒有限制編譯器對指令進行重排序。也就是說,在java內存模型中,也會存在緩存一致性問題和指令重排序的問題。 Java內存模型規定所有的變量都是存在主存當中(類似於前面說的物理內存),每個線程都有自己的工作內存(類似於前面的高速緩存)。線程對變量的所有操作都必須在工作內存中進行,而不能直接對主存進行操作。並且每個線程不能訪問其他線程的工作內存。 在Java中,執行下面這個語句: i = 10; 執行線程必須先在自己的工作線程中對變量i所在的緩存行進行賦值操作,然後再寫入主存當中。而不是直接將數值10寫入主存當中。那麼Java語言本身對 原子性、可見性以及有序性提供了哪些保證呢? 原子性 即一個操作或者多個操作 要麼全部執行並且執行的過程不會被任何因素打斷,要麼就都不執行。 在Java中,對基本數據類型的變量的讀取和賦值操作是原子性操作,即這些操作是不可被中斷的,要麼執行,要麼不執行。上面一句話雖然看起來簡單,但是理解起來並不是那麼容易。看下面一個例子i:請分析以下哪些操作是原子性操作: x = 10; //語句1 y = x; //語句2 x++; //語句3 x = x + 1; //語句4 咋一看,有些朋友可能會說上面的4個語句中的操作都是原子性操作。其實只有語句1是原子性操作,其他三個語句都不是原子性操作。 語句1是直接將數值10賦值給x,也就是說線程執行這個語句的會直接將數值10寫入到工作內存中。 語句2實際上包含2個操作,它先要去讀取x的值,再將x的值寫入工作內存,雖然讀取x的值以及 將x的值寫入工作內存 這2個操作都是原子性操作,但是合起來就不是原子性操作了。 同樣的,x++和 x = x+1包括3個操作:讀取x的值,進行加1操作,寫入新的值。 也就是說,只有簡單的讀取、賦值(而且必須是將數字賦值給某個變量,變量之間的相互賦值不是原子操作)才是原子操作。不過這裡有一點需要注意:在32位平臺下,對64位數據的讀取和賦值是需要通過兩個操作來完成的,不能保證其原子性。但是好像在最新的JDK中,JVM已經保證對64位數據的讀取和賦值也是原子性操作了。 從上面可以看出,Java內存模型只保證了基本讀取和賦值是原子性操作,如果要實現更大範圍操作的原子性,可以通過synchronized和Lock來實現。由於synchronized和Lock能夠保證任一時刻只有一個線程執行該代碼塊,那麼自然就不存在原子性問題了,從而保證了原子性。 可見性 可見性是指當多個線程訪問同一個變量時,一個線程修改了這個變量的值,其他線程能夠立即看得到修改的值。 對於可見性,Java提供了volatile關鍵字來保證可見性。當一個共享變量被volatile修飾時,它會保證修改的值會立即被更新到主存,當有其他線程需要讀取時,它會去內存中讀取新值。而普通的共享變量不能保證可見性,因為普通共享變量被修改之後,什麼時候被寫入主存是不確定的,當其他線程去讀取時,此時內存中可能還是原來的舊值,因此無法保證可見性。 另外,通過synchronized和Lock也能夠保證可見性,synchronized和Lock能保證同一時刻只有一個線程獲取鎖然後執行同步代碼,並且在釋放鎖之前會將對變量的修改刷新到主存當中。因此可以保證可見性。 有序性 即程序執行的順序按照代碼的先後順序執行。 指令重排序,一般來說,處理器為了提高程序運行效率,可能會對輸入代碼進行優化,它不保證程序中各個語句的執行先後順序同代碼中的順序一致,但是它會保證程序最終執行結果和代碼順序執行的結果是一致的。 處理器在進行重排序時是會考慮指令之間的數據依賴性,如果一個指令Instruction 2必須用到Instruction 1的結果,那麼處理器會保證Instruction 1會在Instruction 2之前執行。 在Java內存模型中,允許編譯器和處理器對指令進行重排序,但是重排序過程不會影響到單線程程序的執行,卻會影響到多線程並發執行的正確性。 在Java裡面,可以通過volatile關鍵字來保證一定的「有序性」(具體原理在下一節講述)。另外可以通過synchronized和Lock來保證有序性,很顯然,synchronized和Lock保證每個時刻是有一個線程執行同步代碼,相當於是讓線程順序執行同步代碼,自然就保證了有序性。 另外,Java內存模型具備一些先天的「有序性」,即不需要通過任何手段就能夠得到保證的有序性,這個通常也稱為 happens-before 原則。如果兩個操作的執行次序無法從happens-before原則推導出來,那麼它們就不能保證它們的有序性,虛擬機可以隨意地對它們進行重排序。 下面就來具體介紹下happens-before原則(先行發生原則): 程序次序規則:一個線程內,按照代碼順序,書寫在前面的操作先行發生於書寫在後面的操作 鎖定規則:一個unLock操作先行發生於後面對同一個鎖額lock操作 volatile變量規則:對一個變量的寫操作先行發生於後面對這個變量的讀操作 傳遞規則:如果操作A先行發生於操作B,而操作B又先行發生於操作C,則可以得出操作A先行發生於操作C 線程啟動規則:Thread對象的start()方法先行發生於此線程的每個一個動作 線程中斷規則:對線程interrupt()方法的調用先行發生於被中斷線程的代碼檢測到中斷事件的發生 線程終結規則:線程中所有的操作都先行發生於線程的終止檢測,我們可以通過Thread.join()方法結束、Thread.isAlive()的返回值手段檢測到線程已經終止執行 對象終結規則:一個對象的初始化完成先行發生於他的finalize()方法的開始 對於程序次序規則來說,我的理解就是一段程序代碼的執行在單個線程中看起來是有序的。注意,雖然這條規則中提到「書寫在前面的操作先行發生於書寫在後面的操作」,這個應該是程序看起來執行的順序是按照代碼順序執行的,因為虛擬機可能會對程序代碼進行指令重排序。雖然進行重排序,但是最終執行的結果是與程序順序執行的結果一致的,它只會對不存在數據依賴性的指令進行重排序。因此,在單個線程中,程序執行看起來是有序執行的,這一點要注意理解。事實上,這個規則是用來保證程序在單線程中執行結果的正確性,但無法保證程序在多線程中執行的正確性。 第二條規則也比較容易理解,也就是說無論在單線程中還是多線程中,同一個鎖如果出於被鎖定的狀態,那麼必須先對鎖進行了釋放操作,後面才能繼續進行lock操作。 第三條規則是一條比較重要的規則,也是後文將要重點講述的內容。直觀地解釋就是,如果一個線程先去寫一個變量,然後一個線程去進行讀取,那麼寫入操作肯定會先行發生於讀操作。 第四條規則實際上就是體現happens-before原則具備傳遞性。 (三)深入剖析Volatile關鍵字 Volatile的語義 一旦一個共享變量(類的成員變量、類的靜態成員變量)被volatile修飾之後,那麼就具備了兩層語義: 保證了不同線程對這個變量進行操作時的可見性,即一個線程修改了某個變量的值,這新值對其他線程來說是立即可見的。   禁止進行指令重排序。 先看一段代碼,假如線程1先執行,線程2後執行: //線程1 boolean stop = false; while(!stop){ doSomething(); } //線程2 stop = true; 這段代碼是很典型的一段代碼,很多人在中斷線程時可能都會採用這種標記辦法。但是事實上,這段代碼會完全運行正確麼?即一定會將線程中斷麼?不一定,也許在大多數時候,這個代碼能夠把線程中斷,但是也有可能會導致無法中斷線程(雖然這個可能性很小,但是只要一旦發生這種情況就會造成死循環了)。 下面解釋一下這段代碼為何有可能導致無法中斷線程。在前面已經解釋過,每個線程在運行過程中都有自己的工作內存,那麼線程1在運行的時候,會將stop變量的值拷貝一份放在自己的工作內存當中。 那麼當線程2更改了stop變量的值之後,但是還沒來得及寫入主存當中,線程2轉去做其他事情了,那麼線程1由於不知道線程2對stop變量的更改,因此還會一直循環下去。 但是用volatile修飾之後就變得不一樣了:  - 使用volatile關鍵字會強制將修改的值立即寫入主存; 使用volatile關鍵字的話,當線程2進行修改時,會導致線程1的工作內存中緩存變量stop的緩存行無效(反映到硬體層的話,就是CPU的L1或者L2緩存中對應的緩存行無效); 由於線程1的工作內存中緩存變量stop的緩存行無效,所以線程1再次讀取變量stop的值時會去主存讀取。 那麼在線程2修改stop值時(當然這裡包括2個操作,修改線程2工作內存中的值,然後將修改後的值寫入內存),會使得線程1的工作內存中緩存變量stop的緩存行無效,然後線程1讀取時,發現自己的緩存行無效!!!!!!,它會等待緩存行對應的主存地址被更新之後,然後去對應的主存讀取最新的值。 那麼線程1讀取到的就是最新的正確的值。 Volatile與原子性 從上面知道volatile關鍵字保證了操作的可見性,但是volatile能保證對變量的操作是原子性嗎? 下面看一個例子: public class Test { public volatile int inc = 0; public void increase() { inc++; } public static void main(String[] args) { final Test test = new Test(); for(int i=0;i<10;i++){ new Thread(){ public void run() { for(int j=0;j<1000;j++) test.increase(); }; }.start(); } while(Thread.activeCount()>1) //保證前面的線程都執行完 Thread.yield(); System.out.println(test.inc); } } 大家想一下這段程序的輸出結果是多少?也許有些朋友認為是10000。但是事實上運行它會發現每次運行結果都不一致,都是一個小於10000的數字。可能有的朋友就會有疑問,不對啊,上面是對變量inc進行自增操作,由於volatile保證了可見性,那麼在每個線程中對inc自增完之後,在其他線程中都能看到修改後的值啊,所以有10個線程分別進行了1000次操作,那麼最終inc的值應該是1000*10=10000。 這裡面就有一個誤區了,volatile關鍵字能保證可見性沒有錯,但是上面的程序錯在沒能保證原子性。可見性只能保證每次讀取的是最新的值,但是volatile沒辦法保證對變量的操作的原子性。 在前面已經提到過,自增操作是不具備原子性的,它包括讀取變量的原始值、進行加1操作、寫入工作內存。那麼就是說自增操作的三個子操作可能會分割開執行,就有可能導致下面這種情況出現: 假如某個時刻變量inc的值為10,線程1對變量進行自增操作,線程1先讀取了變量inc的原始值,然後線程1被阻塞了; 然後線程2對變量進行自增操作,線程2也去讀取變量inc的原始值,由於線程1隻是對變量inc進行讀取操作,而沒有對變量進行修改操作,所以不會導致線程2的工作內存中緩存變量inc的緩存行無效,所以線程2會直接去主存讀取inc的值,發現inc的值時10,然後進行加1操作,並把11寫入工作內存,最後寫入主存。 然後線程1接著進行加1操作,由於已經讀取了inc的值,注意此時在線程1的工作內存中inc的值仍然為10,所以線程1對inc進行加1操作後inc的值為11,然後將11寫入工作內存,最後寫入主存。 那麼兩個線程分別進行了一次自增操作後,inc只增加了1。解釋到這裡,可能有朋友會有疑問,不對啊,前面不是保證一個變量在修改volatile變量時,會讓緩存行無效嗎?然後其他線程去讀就會讀到新的值,對,這個沒錯。這個就是上面的happens-before規則中的volatile變量規則,但是要注意,線程1對變量進行讀取操作之後,被阻塞了的話,並沒有對inc值進行修改。然後雖然volatile能保證線程2對變量inc的值讀取是從內存中讀取的,但是線程1沒有進行修改,所以線程2根本就不會看到修改的值。 根源就在這裡,自增操作不是原子性操作,而且volatile也無法保證對變量的任何操作都是原子性的。解決的方法也就是對提供原子性的自增操作即可。 在Java 1.5的java.util.concurrent.atomic包下提供了一些原子操作類,即對基本數據類型的 自增(加1操作),自減(減1操作)、以及加法操作(加一個數),減法操作(減一個數)進行了封裝,保證這些操作是原子性操作。atomic是利用CAS來實現原子性操作的(Compare And Swap),CAS實際上是利用處理器提供的CMPXCHG指令實現的,而處理器執行CMPXCHG指令是一個原子性操作。 Volatile與有序性 在前面提到volatile關鍵字能禁止指令重排序,所以volatile能在一定程度上保證有序性。volatile關鍵字禁止指令重排序有兩層意思: 當程序執行到volatile變量的讀操作或者寫操作時,在其前面的操作的更改肯定全部已經進行,且結果已經對後面的操作可見,在其後面的操作肯定還沒有進行; 在進行指令優化時,不能將在對volatile變量訪問的語句放在其後面執行,也不能把volatile變量後面的語句放到其前面執行。 可能上面說的比較繞,舉個簡單的例子: //x、y為非volatile變量 //flag為volatile變量 x = 2; //語句1 y = 0; //語句2 flag = true; //語句3 x = 4; //語句4 y = -1; //語句5 由於flag變量為volatile變量,那麼在進行指令重排序的過程的時候,不會將語句3放到語句1、語句2前面,也不會講語句3放到語句4、語句5後面。但是要注意語句1和語句2的順序、語句4和語句5的順序是不作任何保證的。 並且volatile關鍵字能保證,執行到語句3時,語句1和語句2必定是執行完畢了的,且語句1和語句2的執行結果對語句3、語句4、語句5是可見的。 Volatile的原理和實現機制 前面講述了源於volatile關鍵字的一些使用,下面我們來探討一下volatile到底如何保證可見性和禁止指令重排序的。下面這段話摘自《深入理解Java虛擬機》: 觀察加入volatile關鍵字和沒有加入volatile關鍵字時所生成的彙編代碼發現,加入volatile關鍵字時,會多出一個lock前綴指令 lock前綴指令實際上相當於一個 內存屏障(也成內存柵欄),內存屏障會提供3個功能: 它 確保指令重排序時不會把其後面的指令排到內存屏障之前的位置,也不會把前面的指令排到內存屏障的後面;即在執行到內存屏障這句指令時,在它前面的操作已經全部完成; 它會 強制將對緩存的修改操作立即寫入主存; 如果是寫操作,它會導致其他CPU中對應的緩存行無效。 23. 鎖的等級:方法鎖、對象鎖、類鎖。 (1)基礎 Java中的每一個對象都可以作為鎖。 對於同步方法,鎖是當前實例對象。 對於靜態同步方法,鎖是當前對象的Class對象。 對於同步方法塊,鎖是Synchonized括號里配置的對象。 當一個線程試圖訪問同步代碼塊時,它首先必須得到鎖,退出或拋出異常時必須釋放鎖。那麼鎖存在哪裡呢?鎖裡面會存儲什麼信息呢? (2)同步的原理 JVM規範規定JVM基於進入和退出 Monitor 對象來實現方法同步和代碼塊同步,但兩者的實現細節不一樣。代碼塊同步是使用monitorenter和monitorexit指令實現,而方法同步是使用另外一種方式實現的,細節在JVM規範里並沒有詳細說明,但是方法的同步同樣可以使用這兩個指令來實現。 monitorenter指令是在編譯後插入到同步代碼塊的開始位置,而monitorexit是插入到方法結束處和異常處,JVM要保證每個monitorenter必須有對應的monitorexit與之配對。任何對象都有一個monitor與之關聯,當且一個monitor被持有後,它將處於鎖定狀態。線程執行到 monitorenter 指令時,將會嘗試獲取對象所對應的 monitor 的所有權,即嘗試獲得對象的鎖。 2.1、Java對象頭 鎖存在Java對象頭裡。如果對象是數組類型,則虛擬機用3個Word(字寬)存儲對象頭,如果對象是非數組類型,則用2字寬存儲對象頭。在32位虛擬機中,一字寬等於四字節,即32bit。 | 長度 | 內容 | 說明 | | :------------- | :------------- | |32/64bit| Mark Word |存儲對象的hashCode或鎖信息等| |32/64bit| Class Metadata Address |存儲到對象類型數據的指針| |32/64bit| Array length |數組的長度(如果當前對象是數組)| Java對象頭裡的Mark Word里默認存儲對象的HashCode,分代年齡和鎖標記位。32位JVM的Mark Word的默認存儲結構如下: | | 25 bit |4bit|1bit是否是偏向鎖|2bit鎖標誌位| | :------------- | :------------- | |無鎖狀態| 對象的hashCode| 對象分代年齡| 0| 01| 在運行期間Mark Word里存儲的數據會隨著鎖標誌位的變化而變化。Mark Word可能變化為存儲以下4種數據: ... 2.2、鎖的升級 Java SE1.6為了減少獲得鎖和釋放鎖所帶來的性能消耗,引入了「偏向鎖」和「輕量級鎖」,所以在Java SE1.6里鎖一共有四種狀態,無鎖狀態,偏向鎖狀態,輕量級鎖狀態和重量級鎖狀態,它會隨著競爭情況逐漸升級。鎖可以升級但不能降級,意味著偏向鎖升級成輕量級鎖後不能降級成偏向鎖。這種鎖升級卻不能降級的策略,目的是為了提高獲得鎖和釋放鎖的效率。 24. 寫出生產者消費者模式。 25. ThreadLocal的設計理念與作用。 (1)ThreadLocal的介紹 ThreadLocal是一個線程的內部存儲類,可以在每個線程的內部存儲數據,當某個數據的作用域應該對應線程的時候就應該使用它;而是當某個很複雜的邏輯下的對象傳遞,需要在線程這個作用域內貫穿其中,用ThreadLocal可以避免這個創建多個靜態類。當你創建一個ThreadLocal對象後,注意,是一個對象,你在不同線程中去訪問它的值是可以不一樣的,並且是和線程相關聯的。它的實現原理其實比較簡單,每個線程中都會維護一個ThreadLocalMap,當在某個線程中訪問時,會取出這個線程自己的Map並且用當前ThreadLocal對象做索引來取出相對應的Value值,從而達到不同線程不同值的效果。 (2)實現原理  1、每個Thread對象內部都維護了一個ThreadLocalMap這樣一個ThreadLocal的Map,可以存放若干個ThreadLocal。 /* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null;  2、當我們在調用get()方法的時候,先獲取當前線程,然後獲取到當前線程的ThreadLocalMap對象,如果非空,那麼取出ThreadLocal的value,否則進行初始化,初始化就是將initialValue的值set到ThreadLocal中。 public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) return (T)e.value; } return setInitialValue(); }  3、當我們調用set()方法的時候,很常規,就是將值設置進ThreadLocal中。 4、ThreadLocalMap裡面存儲的Entry對象本質上是一個WeakReference<ThreadLocal>。也就是說,ThreadLocalMap裡面存儲的對象本質是一個對ThreadLocal對象的弱引用,該ThreadLocal隨時可能會被回收!即導致ThreadLocalMap裡面對應的Value的Key是null。我們需要把這樣的Entry給清除掉,不要讓它們占坑,避免內存泄漏。 那為什麼需要弱引用呢?因為線程的聲明周期是長於ThreadLocal對象的,當此對象不再需要的時候如果線程中還持有它的引用勢必也會產生內存泄漏的問題,所以自然應該是用弱引用來進行key的保存。 26. ThreadPool用法與優勢。 27. Concurrent包里的其他東西:ArrayBlockingQueue、CountDownLatch等等。 28. wait()和sleep()的區別。 答案: ① 這兩個方法來自不同的類分別是,sleep來自Thread類,和wait來自Object類。 sleep是Thread的靜態類方法,誰調用的誰去睡覺,即使在a線程里調用b的sleep方法,實際上還是a去睡覺,要讓b線程睡覺要在b的代碼中調用sleep。 ② 鎖: 最主要是sleep方法沒有釋放鎖,而wait方法釋放了鎖,使得其他線程可以使用同步控制塊或者方法。 sleep不出讓系統資源;wait是進入線程等待池等待,出讓系統資源,其他線程可以占用CPU。一般wait不會加時間限制,因為如果wait線程的運行資源不夠,再出來也沒用,要等待其他線程調用notify/notifyAll喚醒等待池中的所有線程,才會進入就緒隊列等待OS分配系統資源。sleep(milliseconds)可以用時間指定使它自動喚醒過來,如果時間不到只能調用interrupt()強行打斷。 Thread.sleep(0)的作用是「觸發作業系統立刻重新進行一次CPU競爭」。 ③ 使用範圍:wait,notify和notifyAll只能在同步控制方法或者同步控制塊裡面使用,而sleep可以在任何地方使用。 synchronized(x){ x.notify() //或者wait() } 29. foreach與正常for循環效率對比。 答案: 可以看出,循環ArrayList時,普通for循環比foreach循環花費的時間要少一點;循環LinkList時,普通for循環比foreach循環花費的時間要多很多。 當我將循環次數提升到一百萬次的時候,循環ArrayList,普通for循環還是比foreach要快一點;但是普通for循環在循環LinkList時,程序直接卡死。 結論:需要循環數組結構的數據時,建議使用普通for循環,因為for循環採用下標訪問,對於數組結構的數據來說,採用下標訪問比較好。 需要循環鍊表結構的數據時,一定不要使用普通for循環,這種做法很糟糕,數據量大的時候有可能會導致系統崩潰。 原因:foreach使用的是疊代器 30. Java IO與NIO。 31. 反射的作用與原理。 (1)概念 反射機制是在運行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個對象,都能夠調用它的任意一個方法和屬性;這種動態獲取的信息以及動態調用對象的方法的功能稱為java語言的反射機制。 (2)功能 反射機制主要提供了以下功能: 在運行時判斷任意一個對象所屬的類; 在運行時構造任意一個類的對象; 在運行時判斷任意一個類所具有的成員變量和方法; 在運行時調用任意一個對象的方法; 生成動態代理。 32. 泛型常用特點,List<String>能否轉為List<Object>。 (1)Java泛型 開發人員在使用泛型的時候,很容易根據自己的直覺而犯一些錯誤。比如一個方法如果接收List<Object>作為形式參數,那麼如果嘗試將一個List<String>的對象作為實際參數傳進去,卻發現無法通過編譯。雖然從直覺上來說,Object是String的父類,這種類型轉換應該是合理的。但是實際上這會產生隱含的類型轉換問題,因此編譯器直接就禁止這樣的行為。 (2)實現方式:類型擦除 Java中的泛型基本上都是在編譯器這個層次來實現的,在生成的Java字節代碼中是不包含泛型中的類型信息的。使用泛型的時候加上的類型參數,會被編譯器在編譯的時候去掉,這個過程就稱為類型擦除。如在代碼中定義的List<Object>和List<String>等類型,在編譯之後都會變成List。JVM看到的只是List,而由泛型附加的類型信息對JVM來說是不可見的。Java編譯器會在編譯時儘可能的發現可能出錯的地方,但是仍然無法避免在運行時刻出現類型轉換異常的情況。 很多泛型的奇怪特性都與這個類型擦除的存在有關,包括: 泛型類並沒有自己獨有的Class類對象。比如並不存在List<String>.class或是List<Integer>.class,而只有List.class。 靜態變量是被泛型類的所有實例所共享的。對於聲明為MyClass<T>的類,訪問其中的靜態變量的方法仍然是 MyClass.myStaticVar。不管是通過new MyClass<String>還是new MyClass<Integer>創建的對象,都是共享一個靜態變量。 泛型的類型參數不能用在Java異常處理的catch語句中。因為異常處理是由JVM在運行時刻來進行的。由於類型信息被擦除,JVM是無法區分兩個異常類型MyException<String>和MyException<Integer>的。對於JVM來說,它們都是 MyException類型的。也就無法執行與異常對應的catch語句。 類型擦除的基本過程也比較簡單,首先是找到用來替換類型參數的具體類。這個具體類一般是Object。如果指定了類型參數的上界的話,則使用這個上界。把代碼中的類型參數都替換成具體的類。同時去掉出現的類型聲明,即去掉<>的內容。比如T get()方法聲明就變成了Object get();List<String>就變成了List。接下來就可能需要生成一些橋接方法(bridge method)。這是由於擦除了類型之後的類可能缺少某些必須的方法。比如考慮下面的代碼: class MyString implements Comparable<String> { public int compareTo(String str) { return 0; } } 當類型信息被擦除之後,上述類的聲明變成了class MyString implements Comparable。但是這樣的話,類MyString就會有編譯錯誤,因為沒有實現接口Comparable聲明的int compareTo(Object)方法。這個時候就由編譯器來動態生成這個方法。 (3)泛型的檢測:不符合泛型T的檢測 在進行編譯之前就對所有泛型進行檢測,加入類型檢測和轉換的指令,比如返回泛型的結果實際上返回的是擦出後的類型,而虛擬機會多加一個類型轉換的指令。 (4)需要泛型之間的類型轉換怎麼做?通配符?與PECS 如果你是想遍歷collection,並對每一項元素操作時,此時這個集合時生產者(生產元素),應該使用 Collection<? extends E>,因為相對於你泛型E,能放進E中的(把Collection中的元素放入E類型中) ,應該是E的子類型。 如果你是想添加元素到collection中去,那麼此時集合時消費者(消費元素)應該使用Collection<? super E>,同理,你要將E類的元素放入Collection中去,那麼Collection應該存放的是E的父類型。 (5)泛型與多態的衝突 33. 解析XML的幾種方式的原理與特點:DOM、SAX、PULL。 34. Java與C++對比。 35. Java1.7與1.8新特性。 (1)1.8 一、接口的默認方法與靜態方法。也就是接口中可以有實現方法;並且接口也可以有靜態方法,工具類也可以使用接口來實現; 二、Lambda 表達式。簡化了代碼,實際上是函數式接口的簡化 函數式接口(functional interface 也叫功能性接口,其實是同一個東西)。簡單來說,函數式接口是只包含一個方法的接口。比如Java標準庫中的java.lang.Runnable和 java.util.Comparator都是典型的函數式接口。java 8提供 @FunctionalInterface作為註解,這個註解是非必須的,只要接口符合函數式接口的標準(即只包含一個方法的接口),虛擬機會自動判斷,但最好在接口上使用註解@FunctionalInterface進行聲明,以免團隊的其他人員錯誤地往接口中添加新的方法。 使用了函數式接口的匿名類可以使用Lambda來優化代碼。 三、方法引用 在學習lambda表達式之後,我們通常使用lambda表達式來創建匿名方法。然而,有時候我們僅僅是調用了一個已存在的方法。如下: Arrays.sort(stringsArray,(s1,s2)->s1.compareToIgnoreCase(s2)); 在Java8中,我們可以直接通過方法引用來簡寫lambda表達式中已經存在的方法。這種特性就叫方法引用: Arrays.sort(stringsArray, String::compareToIgnoreCase); (2)1.7 1,switch中可以使用字串了 2.運用List<String> tempList = new ArrayList<>(); 即泛型實例化類型自動推斷 3.語法上支持集合,而不一定是數組 final List<Integer> piDigits = [ 1,2,3,4,5,8 ]; 4.新增一些取環境信息的工具方法 File System.getJavaIoTempDir() // IO臨時文件夾 File System.getJavaHomeDir() // JRE的安裝目錄 File System.getUserHomeDir() // 當前用戶目錄 File System.getUserDir() // 啟動java進程時所在的目錄5 5.Boolean類型反轉,空指針安全,參與位運算 Boolean Booleans.negate(Boolean booleanObj) True => False , False => True, Null => Null boolean Booleans.and(boolean[] array) boolean Booleans.or(boolean[] array) boolean Booleans.xor(boolean[] array) boolean Booleans.and(Boolean[] array) boolean Booleans.or(Boolean[] array) boolean Booleans.xor(Boolean[] array) 6.兩個char間的equals boolean Character.equalsIgnoreCase(char ch1, char ch2)7.安全的加減乘除 int Math.safeToInt(long value) int Math.safeNegate(int value) long Math.safeSubtract(long value1, int value2) long Math.safeSubtract(long value1, long value2) int Math.safeMultiply(int value1, int value2) long Math.safeMultiply(long value1, int value2) long Math.safeMultiply(long value1, long value2) long Math.safeNegate(long value) int Math.safeAdd(int value1, int value2) long Math.safeAdd(long value1, int value2) long Math.safeAdd(long value1, long value2) int Math.safeSubtract(int value1, int value2) 8.map集合支持並發請求,且可以寫成 Map map = {name:"xxx",age:18}; 36. 設計模式:單例、工廠、適配器、責任鏈、觀察者等等。 37. JNI的使用。 38. 匿名內部類使用的參數為什麼要是final的 答案:因為你得到的參數實際上是拷貝而來的 (1)具體:https://www.zhihu.com/question/21395848/answer/91184967 (2)精簡:兩點 1.形參為什麼要拷貝一份: 不拷貝的話,匿名內部類中使用的引用與外部的引用是同一份引用,但是這個引用的生命周期與函數相同,若匿名內部類的生命周期超過這個函數的話(比如新開了一個線程),再去訪問這個引用會有空指針等問題。2.形參為什麼是final 從我們的角度來看,是看不到這個拷貝的過程。我們看到匿名內部類直接使用了這個引用,會造成一種錯覺:當在匿名內部類中修改這個引用的時候(指向別的實例),我們同時修改的是這個外面的引用,因為看起來他們是同一個嘛。然而並不是這樣的。為了防止這種錯覺,java索性將其設為final,即這個引用是無法修改的。 39. 單例模式的實現 最佳:靜態內部類 public class SingletonE { private static class SingletonHolder { /** * 單例對象實例 */ static final SingletonE instance = new SingletonE(); } public static SingletonE getInstance() { return SingletonHolder.instance; } } 更多技術學習資料獲取方式:轉發文章+關注私信【資料】即可獲得

 

↓↓↓限量特惠的優惠按鈕↓↓↓

↓↓↓更多嬰幼兒產品一起來看吧↓↓↓

 

WWW456TTVVV45TYGQ

 

 

文章來源取自於:

 

 

每日頭條 https://kknews.cc/code/r64a56v.html

MOMO購物網 https://www.momoshop.com.tw/goods/GoodsDetail.jsp?i_code=6820228&memid=6000007380&cid=apuad&oid=1&osm=league

如有侵權,請來信告知,我們會立刻下架。

DMCA:dmca(at)kubonews.com

聯絡我們:contact(at)kubonews.com


【最新開箱文】【很多人都入手,值得考慮】【使用感想】
【這一款Dcard上口碑還不錯】 【開箱實測比較,想買的看這邊】 【media 媚點】光透柔焦蜜粉2入組【產品即將出清】 【使用這個有訣竅,看看開箱心得吧】 【ACTS 維詩彩妝】好氣色腮紅餅 粉紅極光7305L【百貨週年慶線上買最便宜】 【Dcard網友推薦開箱】 【maNara 曼娜麗】毛孔無瑕礦泥洗顏粉45g(1)【Dcard新手提問好用嗎】 【momo購物節這款有優惠】 【Disney 迪士尼】Mickey 經典米奇香氛泡泡浴(350ml)【富邦卡現金回饋5%】 【Dcard開箱推薦開箱】 【Dr.Douxi 朵璽】極淨保濕魔幻水 500ml(送 125ml2瓶)

arrow
arrow
    全站熱搜

    搶買這個產品 發表在 痞客邦 留言(0) 人氣()