1、Java異常以及常用工具類體系。異常處理機(jī)制主要回答了三個問題? 答:1)、第一個是異常類型回答了什么被拋出。 2)、第二個是異常堆棧跟蹤回答了在哪里拋出。 3)、第三個是異常信息回答了為什么被拋出。Throwable是所有異常體系的頂級父類,包含了Error類和Exception類。從概念角度分析Java的異常處理機(jī)制。 2、Java的異常體系,Error和Exception的區(qū)別? 答:1)、Error,程序無法處理的系統(tǒng)錯誤,編譯器不做檢查。表示系統(tǒng)致命的錯誤,程序無法處理這些錯誤,Error類一般是指與JVM相關(guān)的問題,如果系統(tǒng)奔潰、虛擬機(jī)錯誤、內(nèi)存空間不足、方法調(diào)用棧溢出等等錯誤。 3、Exception主要包含兩類,一類是RuntimeException、另一類是非RuntimeException。 答:1)、RuntimeException(運(yùn)行時異常)異常表示不可預(yù)知的,程序應(yīng)當(dāng)自行避免,例如數(shù)組下標(biāo)越界,訪問空指針異常等等。 4、Java的異常體系,從責(zé)任角度來看。 答:Error屬于JVM需要負(fù)擔(dān)的責(zé)任。RuntimeException是程序應(yīng)該負(fù)擔(dān)的責(zé)任。Checked Exception可檢查異常是Java編譯器應(yīng)該負(fù)擔(dān)的責(zé)任。 5、常見Error以及Exception。RuntimeException運(yùn)行時異常。 答:1)、第一種,NullPointerException空指針引用異常。 6、非RuntimeException非運(yùn)行時異常。 答:1)、第一種,ClassNotFoundException,找不到指定的class的異常。 7、Error錯誤異常。 答:1)、第一種,NoClassDefFoundError,找不到class定義的異常。造成的原因包含,類依賴的class或者jar包不存在。類文件存在,但是存在不同的域中。大小寫問題,javac編譯的時候無視大小寫的,很有可能編譯出來的class文件就與想要的不一樣。 8、Java的異常處理機(jī)制,Exception的處理機(jī)制。 答:1)、第一步、拋出異常,創(chuàng)建異常對象,交由運(yùn)行時系統(tǒng)處理。當(dāng)一個方法出現(xiàn)錯誤引發(fā)異常的時候,方法創(chuàng)建異常對象,并交付給運(yùn)行時系統(tǒng),系統(tǒng)對象中包含了異常類型,異常出現(xiàn)時的程序狀態(tài)等異常信息,運(yùn)行時系統(tǒng)負(fù)責(zé)尋找處置異常的代碼并執(zhí)行?! ?/span> 2)、第二步、捕獲異常,尋找合適的異常處理器處理異常,否則終止運(yùn)行。方法拋出異常以后,運(yùn)行時系統(tǒng)將轉(zhuǎn)為尋找合適的異常處理器,即ExceptionHandle。潛在的異常處理是異常發(fā)生時依次存留在調(diào)用棧方法的集合,當(dāng)異常處理器所能處理的異常類型與拋出的異常類型相符的時候,即為合適的異常處理器,運(yùn)行時系統(tǒng)從發(fā)生異常的方法開始依次回查調(diào)用棧中的方法直至找到含有異常處理器的方法并執(zhí)行。當(dāng)運(yùn)行時系統(tǒng)遍歷了調(diào)用棧都沒有找到合適的異常處理器,則運(yùn)行時系統(tǒng)終止,java程序終止。 9、Java異常的處理規(guī)則。 答:具體明確,拋出的異常應(yīng)能通過異常類名和message準(zhǔn)確說明異常的類型和產(chǎn)生異常的原因。 10、try-catch的性能問題。Java異常處理消耗性能的地方。 答:第一點(diǎn)、try-catch塊影響JVM的優(yōu)化。 11、集合之List和Set的區(qū)別,如下所示。 11.1、備注:線程安全和線程不安全的集合: Vector、HashTable、Properties是線程安全的。 1)、注意:為了保證集合線程是安全的,效率就比較低;線程不安全的集合效率相對會高一些。 2)、如果需要保證集合既是安全的而且效率高,可以使用Collections為我們提出了解決方案,把這些集合包裝成線程安全的集合。 3)、Collections的工具類,將自己創(chuàng)建的集合類實(shí)例傳入進(jìn)去,便可以包裝成一個線程安全的集合類實(shí)例。因?yàn)镾ynchronizedMap有一個Object mutex互斥對象成員,對里面的公共方法使用synchronized對mutex進(jìn)行加鎖操作。相比SynchronizedMap使用的synchronized對mutex進(jìn)行加鎖操作,hashtable線程安全的原因,是在公有的方法都加入了synchronized修飾符,此時獲取的是方法調(diào)用者的鎖。SynchronizedMap和hashtable的原理幾乎相同,唯一的區(qū)別就是鎖定的對象不同,因此這兩者在多線程環(huán)境下,由于都是串行執(zhí)行的,效率比較低下,此時可以學(xué)習(xí)ConcurrentHashMap。 4)、無論是Hashtable還是Collections的工具類SynchronizedMap,當(dāng)多線程并發(fā)的情況下,都要競爭同一把鎖,導(dǎo)致效率極其低下,而在jdk1.5之后,為了改進(jìn)hashTable的痛點(diǎn),ConcurrentHashMap應(yīng)運(yùn)而生, 11.2、Tree的核心在于排序,保證元素排序。 答:1)、自然排序,讓對象所屬的類去實(shí)現(xiàn)comparable接口,無參構(gòu)造,基于元素對象自身實(shí)現(xiàn)的comparable接口的自然排序。 自然排序代碼實(shí)現(xiàn),如下所示: 1 package com.thread;
2
3 import java.util.Set;
4 import java.util.TreeSet;
5
6 /**
7 * 自然排序,實(shí)現(xiàn)了Comparable自然排序的接口,實(shí)現(xiàn)了該接口就要實(shí)現(xiàn)equals和hashcode方法和compareTo方法。
8 * <p>
9 * <p>
10 * <p>
11 * 為了使Customer類在添加了treeSet之后可以正確排序,要求Customer類里面的equals方法和compareTo方法按照相同的規(guī)則
12 * 來比較兩個對象是否相等。
13 */
14 public class Customer implements Comparable {
15
16 private String name;
17 private int age;
18
19 public Customer(String name, int age) {
20 this.name = name;
21 this.age = age;
22 }
23
24 public String getName() {
25 return name;
26 }
27
28 public void setName(String name) {
29 this.name = name;
30 }
31
32 public int getAge() {
33 return age;
34 }
35
36 public void setAge(int age) {
37 this.age = age;
38 }
39
40 @Override
41 public boolean equals(Object obj) {
42 // 兩個對象相等返回true
43 if (this == obj) {
44 return true;
45 }
46 if (!(obj instanceof Customer)) {
47 return false;
48 }
49 final Customer other = (Customer) obj;
50
51 if (this.name.equals(other.name) && this.age == other.getAge()) {
52 return true;
53 } else {
54 return false;
55 }
56 }
57
58 @Override
59 public int hashCode() {
60 // 重新了equals方法,就要重新hashcode方法
61 int result;
62 result = (name == null) ? 0 : name.hashCode();
63 result = 29 * result + age;
64 // 如果兩個對象是相等的,那么hashcode返回值必須是相等的
65 return result;
66 }
67
68 @Override
69 public int compareTo(Object o) {
70 // compareTo返回值大于0,說明Customer1大于Customer2,反之Customer1小于Customer2
71 // 如果等于0,Customer1等于Customer2
72 Customer other = (Customer) o;
73 // 先按照name屬性排序
74 if (this.name.compareTo(other.getName()) > 0) {
75 return 1;
76 }
77 if (this.name.compareTo(other.getName()) < 0) {
78 return -1;
79 }
80
81 // 再按照age屬性排序
82 if (this.age > other.getAge()) {
83 return 1;
84 }
85 if (this.age < other.getAge()) {
86 return -1;
87 }
88 return 0;
89 }
90
91 public static void main(String[] args) {
92 Set<Customer> set = new TreeSet<>();
93 Customer customer1 = new Customer('tom', 16);
94 Customer customer2 = new Customer('tom', 19);
95 Customer customer3 = new Customer('tom', 20);
96 set.add(customer1);
97 set.add(customer2);
98 set.add(customer3);
99
100 // 循環(huán)遍歷
101 for (Customer c : set) {
102 System.out.println(c.getName() + ' , ' + c.getAge());
103 }
104 }
105 }
客戶化排序,實(shí)現(xiàn)Comparator,然后實(shí)現(xiàn)compare方法: 1 package com.thread; 2 3 import java.util.*; 4 5 /** 6 * 客戶化排序,實(shí)現(xiàn)Comparator,然后實(shí)現(xiàn)compare方法 7 */ 8 public class CustomerComparator implements Comparator<Customer> { 9 10 @Override 11 public int compare(Customer c1, Customer c2) { 12 // 對姓名進(jìn)行排序 13 if (c1.getName().compareTo(c2.getName()) > 0) { 14 return -1; 15 } 16 if (c1.getName().compareTo(c2.getName()) < 0) { 17 return 1; 18 } 19 20 // 對年齡進(jìn)行排序 21 if (c1.getAge() - c2.getAge() > 0) { 22 return -1; 23 } 24 if (c1.getAge() - c2.getAge() < 0) { 25 return 1; 26 } 27 return 0; 28 } 29 30 public static void main(String[] args) { 31 // 此時既使用了自然排序,也使用了客戶化排序, 32 // 在客戶化排序和自然排序共存的情況下,最終結(jié)果以客戶化排序優(yōu)先。 33 // 可以查看TreeMap源碼的get(Object key) -> getEntry(key)方法。 34 // 可以看到先使用客戶化排序得到的結(jié)果。 35 Set<Customer> set = new TreeSet<>(new CustomerComparator()); 36 Customer customer1 = new Customer('張三三', 16); 37 Customer customer2 = new Customer('李四四', 19); 38 Customer customer3 = new Customer('王五五', 20); 39 set.add(customer1); 40 set.add(customer2); 41 set.add(customer3); 42 Iterator<Customer> iterator = set.iterator(); 43 while (iterator.hasNext()) { 44 Customer next = iterator.next(); 45 System.out.println(next.getName() + ' ' + next.getAge()); 46 } 47 } 48 49 } 12、Map集合。 答:Map集合用于保存具有映射關(guān)系的數(shù)據(jù),Map保存的數(shù)據(jù)都是key-value對的形式的,也就是key-value組成的鍵值對形式的,Map里面的key是不可以重復(fù)的,key是用于標(biāo)示集合里面的每項(xiàng)數(shù)據(jù)的,Map里面的value則是可以重復(fù)的。 13、Hashtable、HashMap、ConcurrentHashMap的區(qū)別,如下所示: 答:1)、HashMap,存儲特點(diǎn)是鍵值對映射,在Java8以前,是數(shù)組+鏈表的組成,HashMap結(jié)合了數(shù)組和鏈表的優(yōu)勢進(jìn)行編寫的。數(shù)組的特點(diǎn)是查詢快,增刪慢,而鏈表的特點(diǎn)是查詢慢,增刪快。HashMap是非Synchronized,所以是線程不安全的,但是效率高。HashMap是由數(shù)組和鏈表組成的,HashMap的數(shù)組長度在未賦初始值的時候,默認(rèn)長度是16的,一個長度為16的數(shù)組中,每個元素存儲的就是鏈表的頭節(jié)點(diǎn),通過類似于hash(key.hashCode) % len,哈希函數(shù)取模的操作獲得要添加的元素所要存放的數(shù)組的位置,實(shí)際上,HashMap的哈希算法是通過位運(yùn)算來進(jìn)行的,相對于取模運(yùn)算呢,效率更高。這里面有一個極端的情況,如果添加到哈希表里面的不同的值的鍵位來通過哈希散列運(yùn)算,總是得出相同的值即分配到同一個桶中,這樣會是某個桶中鏈表的長度變得很長,由于鏈表查詢需要從頭部開始遍歷,因此,在最壞的情況下呢,HashMap性能惡化,從O(1)變成了O(n)。 HashMap,存儲特點(diǎn)是鍵值對映射,在Java8以后,HashMap采用了數(shù)組 + 鏈表 + 紅黑樹的組成。Java8以后使用常量TREEIFY_THRESHOLD來控制是否將鏈表轉(zhuǎn)換為紅黑樹,來存儲數(shù)據(jù),這意味著,即使在最壞的情況下,HashMap的性能從O(n)提高到O(logn)。 HashMap的成員變量,Node<K,V>[] table可以看作是Node<K,V>這個數(shù)組和鏈表組成的復(fù)合結(jié)構(gòu),數(shù)組被分為一個個的bucket桶,通過hash值決定了鍵值對在這個數(shù)組的尋址,hash值相同的鍵值對則以鏈表的形式來存儲,而鏈表的大小超過TREEIFY_THRESHOLD =8這個值的時候,就會被改造成紅黑樹,而當(dāng)某個桶上面的元素總數(shù)因?yàn)閯h除變得低于閾值UNTREEIFY_THRESHOLD =6之后,紅黑樹又被轉(zhuǎn)換為鏈表,以保證更高的性能。 2)、Hashtable是線程安全的,是因?yàn)樵诜椒ǘ技恿藄ynchronized關(guān)鍵字,和Collections.synchronizedMap(map)效果一樣,都是串行執(zhí)行的,效率比較低,唯一的區(qū)別就是鎖定的對象不同而已。為了提升多線程下的執(zhí)行性能,引入了ConcurrentHashMap。 4)、如何優(yōu)化Hashtable呢?如何設(shè)計(jì)ConcurrentHashMap呢? a)、通過鎖細(xì)粒度化,將整鎖拆解成多個鎖進(jìn)行優(yōu)化。對象鎖之間是不相互制約的,因此,我們可以將原本一個鎖的行為拆分多個鎖,早期的ConcurrentHashMap也是這樣做的,ConcurrentHashMap早期使用的是分段鎖技術(shù)(由數(shù)組和鏈表組成),通過分段鎖Segment來實(shí)現(xiàn),將鎖一段一段的進(jìn)行存儲,然后給每一段數(shù)據(jù)配一把鎖即Segment,當(dāng)一個線程占用一把鎖即Segment的時候,然后訪問其中一段數(shù)據(jù)的時候呢,位于其他Segment的數(shù)據(jù)也能被其他線程同時訪問,默認(rèn)是分配16個Segment,理論上比Hashtable效率提升了16倍,相比于早期的HashMap,就是將hashMap的table數(shù)組邏輯上拆分成多個子數(shù)組,每個子數(shù)組配置一把鎖,線程在獲取到某把分段鎖的時候,比如,獲取到編號為8的Segment之后呢,才能操作這個子數(shù)組,而其他線程想要操作該子數(shù)組的時候,只能被阻塞,但是如果其他線程操作的是其他未被占用的Segment所管轄的子數(shù)組,那么是不會被阻塞的。此時呢,可以將分段鎖拆分的更細(xì),或者不使用分段鎖,而是table里面的每個bucket都用一把不同的鎖進(jìn)行管理,ConcurrentHashMap的效率就得到了更好的提高。 5)、Hashtable、HashMap、ConcurrentHashMap的區(qū)別,面試回答: a)、HashMap線程不安全的,底層是通過數(shù)組 + 鏈表 + 紅黑樹。鍵值對key-value均可以為null,但是hashtable,ConcurrentHashMap兩個類都不支持。 d)、HashMap的key、value均可以為null,而其它的兩個類Hashtable、ConcurrentHashMap不支持的。 14、HashMap中的put方法的邏輯,如下所示: 1)、如果HashMap未被初始化過,則進(jìn)行初始化操作。 1 /**
2 * The default initial capacity - MUST be a power of two.
3 */
4 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
5
6 /**
7 * The maximum capacity, used if a higher value is implicitly specified
8 * by either of the constructors with arguments.
9 * MUST be a power of two <= 1<<30.
10 */
11 //
12 static final int MAXIMUM_CAPACITY = 1 << 30;
13
14 /**
15 * The load factor used when none specified in constructor.
16 */
17 // 默認(rèn)的擴(kuò)容因子DEFAULT_LOAD_FACTOR = 0.75f
18 static final float DEFAULT_LOAD_FACTOR = 0.75f;
19
20 /**
21 * The bin count threshold for using a tree rather than list for a
22 * bin. Bins are converted to trees when adding an element to a
23 * bin with at least this many nodes. The value must be greater
24 * than 2 and should be at least 8 to mesh with assumptions in
25 * tree removal about conversion back to plain bins upon
26 * shrinkage.
27 */
28 // 如果鏈表的長度超過TREEIFY_THRESHOLD = 8的時候呢,就會被改造成紅黑樹。
29 static final int TREEIFY_THRESHOLD = 8;
30
31 /**
32 * The bin count threshold for untreeifying a (split) bin during a
33 * resize operation. Should be less than TREEIFY_THRESHOLD, and at
34 * most 6 to mesh with shrinkage detection under removal.
35 */
36 // 如果某個桶上面元素的總數(shù),因?yàn)閯h除而低于閾值之后呢,即低于UNTREEIFY_THRESHOLD = 6的時候,紅黑樹又被轉(zhuǎn)換成了鏈表以保證更高的性能。
37 static final int UNTREEIFY_THRESHOLD = 6;
38
39 /**
40 * The smallest table capacity for which bins may be treeified.
41 * (Otherwise the table is resized if too many nodes in a bin.)
42 * Should be at least 4 * TREEIFY_THRESHOLD to avoid conflicts
43 * between resizing and treeification thresholds.
44 */
45 //
46 static final int MIN_TREEIFY_CAPACITY = 64;
47
48
49
50 /**
51 * Constructs an empty <tt>HashMap</tt> with the default initial capacity
52 * (16) and the default load factor (0.75).
53 */
54 // 無參構(gòu)造函數(shù),table的數(shù)組并沒有在構(gòu)造函數(shù)中進(jìn)行初始化,而是僅僅給了一些成員變量賦初始值。HashMap是按照LazyLoad的原則,在首次使用的時候才會被初始化的。
55 public HashMap() {
56 this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
57 }
58
59
60
61
62 ......
63
64
65
66
67
68 /**
69 * Basic hash bin node, used for most entries. (See below for
70 * TreeNode subclass, and in LinkedHashMap for its Entry subclass.)
71 */
72 static class Node<K,V> implements Map.Entry<K,V> {
73 final int hash; // hash值
74 final K key; //鍵值key
75 V value; //鍵值value
76 Node<K,V> next; //指向下一個節(jié)點(diǎn)的next
77
78 Node(int hash, K key, V value, Node<K,V> next) {
79 this.hash = hash;
80 this.key = key;
81 this.value = value;
82 this.next = next;
83 }
84
85 public final K getKey() { return key; }
86 public final V getValue() { return value; }
87 public final String toString() { return key + '=' + value; }
88
89 public final int hashCode() {
90 return Objects.hashCode(key) ^ Objects.hashCode(value);
91 }
92
93 public final V setValue(V newValue) {
94 V oldValue = value;
95 value = newValue;
96 return oldValue;
97 }
98
99 public final boolean equals(Object o) {
100 if (o == this)
101 return true;
102 if (o instanceof Map.Entry) {
103 Map.Entry<?,?> e = (Map.Entry<?,?>)o;
104 if (Objects.equals(key, e.getKey()) &&
105 Objects.equals(value, e.getValue()))
106 return true;
107 }
108 return false;
109 }
110 }
111
112
113
114
115
116
117 /**
118 * The table, initialized on first use, and resized as
119 * necessary. When allocated, length is always a power of two.
120 * (We also tolerate length zero in some operations to allow
121 * bootstrapping mechanics that are currently not needed.)
122 */
123 // HashMap的內(nèi)部結(jié)構(gòu),HashMap可以看作是通過數(shù)組Node<K,V>[] table,和鏈表組合而成的復(fù)合結(jié)構(gòu),數(shù)組被分為一個個的bucket,通過hash值決定了鍵值對在這個數(shù)組的尋址,hash值相同的鍵值對則以鏈表的形式來存儲。
124 transient Node<K,V>[] table;
125
126
127
128
129 ......
130
131
132
133
134
135 /**
136 * Associates the specified value with the specified key in this map.
137 * If the map previously contained a mapping for the key, the old
138 * value is replaced.
139 *
140 * @param key key with which the specified value is to be associated
141 * @param value value to be associated with the specified key
142 * @return the previous value associated with <tt>key</tt>, or
143 * <tt>null</tt> if there was no mapping for <tt>key</tt>.
144 * (A <tt>null</tt> return can also indicate that the map
145 * previously associated <tt>null</tt> with <tt>key</tt>.)
146 */
147 public V put(K key, V value) {
148 return putVal(hash(key), key, value, false, true);
149 }
150
151
152 /**
153 * Implements Map.put and related methods
154 *
155 * @param hash hash for key
156 * @param key the key
157 * @param value the value to put
158 * @param onlyIfAbsent if true, don't change existing value
159 * @param evict if false, the table is in creation mode.
160 * @return previous value, or null if none
161 */
162 final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
163 boolean evict) {
164 Node<K,V>[] tab; Node<K,V> p; int n, i;
165 // 如果table為null的時候,或者table的長度為0的時候
166 if ((tab = table) == null || (n = tab.length) == 0)
167 // 調(diào)用resize()方法初始化table,resize()方法的作用是進(jìn)行初始化和擴(kuò)容的功能。
168 n = (tab = resize()).length;
169 // 做hash運(yùn)算,算出鍵值對在table里面的具體位置。hash哈希值并不是key本身的hashCode的,而是來自于hash與或產(chǎn)生的結(jié)果的。
170 if ((p = tab[i = (n - 1) & hash]) == null)
171 // 如果通過hash運(yùn)算得到的位置還沒有元素存儲到里面的時候,則會直接new該鍵值對的Node,放到該數(shù)組的位置當(dāng)中tab[i]。
172 tab[i] = newNode(hash, key, value, null);
173 // 否則就繼續(xù)向下走。
174 else {
175 Node<K,V> e; K k;
176 // 如果發(fā)現(xiàn)同樣的位置,已經(jīng)存在鍵值對的時候,且鍵和傳進(jìn)來的鍵是一致的,
177 if (p.hash == hash &&
178 ((k = p.key) == key || (key != null && key.equals(k))))
179 // 則直接替換數(shù)組里面的元素值
180 e = p;
181 // 否則,如果當(dāng)前數(shù)組位置存儲的是否已經(jīng)是樹化后的節(jié)點(diǎn)
182 else if (p instanceof TreeNode)
183 // 如果是樹化了,就按照樹的方式嘗試存儲鍵值對
184 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
185 else {
186 // 如果未樹化,則按照鏈表的插入方式往鏈表后面添加元素
187 for (int binCount = 0; ; ++binCount) {
188 if ((e = p.next) == null) {
189 p.next = newNode(hash, key, value, null);
190 // 判斷鏈表元素的總數(shù),一旦超過了TREEIFY_THRESHOLD,則將鏈表進(jìn)行樹化操作。
191 if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
192 // 樹化操作哦
193 treeifyBin(tab, hash);
194 break;
195 }
196 //
197 if (e.hash == hash &&
198 ((k = e.key) == key || (key != null && key.equals(k))))
199 break;
200 p = e;
201 }
202 }
203 // 如果插入的鍵位存在于hashMap中的時候,則對對應(yīng)的鍵位進(jìn)行值的更新操作。
204 if (e != null) { // existing mapping for key
205 V oldValue = e.value;
206 if (!onlyIfAbsent || oldValue == null)
207 e.value = value;
208 afterNodeAccess(e);
209 return oldValue;
210 }
211 }
212 ++modCount;
213 // 當(dāng)hashMap里面的szie大于閾值的時候呢,就對HashMap進(jìn)行擴(kuò)容
214 if (++size > threshold)
215 resize();
216 afterNodeInsertion(evict);
217 return null;
218 }
15、HashMap中的get方法的邏輯,如下所示: 1 /** 2 * Returns the value to which the specified key is mapped, 3 * or {@code null} if this map contains no mapping for the key. 4 * 5 * <p>More formally, if this map contains a mapping from a key 6 * {@code k} to a value {@code v} such that {@code (key==null ? k==null : 7 * key.equals(k))}, then this method returns {@code v}; otherwise 8 * it returns {@code null}. (There can be at most one such mapping.) 9 * 10 * <p>A return value of {@code null} does not <i>necessarily</i> 11 * indicate that the map contains no mapping for the key; it's also 12 * possible that the map explicitly maps the key to {@code null}. 13 * The {@link #containsKey containsKey} operation may be used to 14 * distinguish these two cases. 15 * 16 * @see #put(Object, Object) 17 */ 18 public V get(Object key) { 19 Node<K,V> e; 20 // 通過傳入的key值進(jìn)行調(diào)用getNode()方法 21 return (e = getNode(hash(key), key)) == null ? null : e.value; 22 } 23 24 25 26 27 ...... 28 29 30 31 32 33 /** 34 * Implements Map.get and related methods 35 * 36 * @param hash hash for key 37 * @param key the key 38 * @return the node, or null if none 39 */ 40 final Node<K,V> getNode(int hash, Object key) { 41 Node<K,V>[] tab; Node<K,V> first, e; int n; K k; 42 // 鍵對象的hashcode,通過哈希算法,找到bucket的位置 43 if ((tab = table) != null && (n = tab.length) > 0 && 44 (first = tab[(n - 1) & hash]) != null) { 45 // 找到Bucket的位置以后,調(diào)用key.equals(k))方法找到鏈表中正確的節(jié)點(diǎn),最終找到要找的值 46 if (first.hash == hash && // always check first node 47 ((k = first.key) == key || (key != null && key.equals(k)))) 48 return first; 49 if ((e = first.next) != null) { 50 if (first instanceof TreeNode) 51 return ((TreeNode<K,V>)first).getTreeNode(hash, key); 52 do { 53 if (e.hash == hash && 54 ((k = e.key) == key || (key != null && key.equals(k)))) 55 return e; 56 } while ((e = e.next) != null); 57 } 58 } 59 return null; 60 } 16、HashMap,如何有效減少碰撞? 答:樹化這種被動的方式可以提升性能,哈希運(yùn)算也是可以提升性能的關(guān)鍵。 1)、擾動函數(shù),促使元素位置分布均勻,減少碰撞的機(jī)率。原理就是如果兩個不相等的對象返回不同的hashcode的話,或者說元素位置盡量的分布均勻些,那么碰撞的機(jī)率就會小些,意味著有些元素就可以通過數(shù)組來直接去獲取了,這樣可以提升hashMap的性能的。哈希算法的內(nèi)部實(shí)現(xiàn),是讓不同對象返回不同的hashcode值。 1 /**
2 * Computes key.hashCode() and spreads (XORs) higher bits of hash
3 * to lower. Because the table uses power-of-two masking, sets of
4 * hashes that vary only in bits above the current mask will
5 * always collide. (Among known examples are sets of Float keys
6 * holding consecutive whole numbers in small tables.) So we
7 * apply a transform that spreads the impact of higher bits
8 * downward. There is a tradeoff between speed, utility, and
9 * quality of bit-spreading. Because many common sets of hashes
10 * are already reasonably distributed (so don't benefit from
11 * spreading), and because we use trees to handle large sets of
12 * collisions in bins, we just XOR some shifted bits in the
13 * cheapest possible way to reduce systematic lossage, as well as
14 * to incorporate impact of the highest bits that would otherwise
15 * never be used in index calculations because of table bounds.
16 */
17 static final int hash(Object key) {
18 int h;
19 // 即先獲取key.hashCode(),hashCode方法返回值是int類型的,是32位的,然后再將高位數(shù)移位到低位,移動16位,最后進(jìn)行異或運(yùn)算。
20 return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
21 }
16.1、HashMap,從獲取hash到散列的過程。 1)、不使用hashcode()方法獲取的值,是因?yàn)閗ey.hashCode();方法返回的是int類型的散列值,如果直接使用這個散列值作為下標(biāo)去訪問hashMap數(shù)組的話呢,考慮到二進(jìn)制的32位帶符號的int值的范圍呢-2147483648——2147483647,前后區(qū)間大概有40億的映射空間,只要哈希函數(shù)映射的均勻松散,一般應(yīng)用是很難出現(xiàn)碰撞的,但是40億長度的數(shù)組在內(nèi)存中是放不下的,況且,HashMap在擴(kuò)容之前數(shù)組默認(rèn)大小才是16,所以直接拿這個散列值使用不現(xiàn)實(shí)的。 17、hashMap含參的構(gòu)造器,可以傳入初始化的hashMap的初始化大小的,根據(jù)傳入的初始化值,換算成2的n次方,轉(zhuǎn)換成最接近的2的倍數(shù)的值,這樣做,就是為了通過哈希運(yùn)算定位桶的時候呢,能實(shí)現(xiàn)用與操作來代替取模進(jìn)而獲得更好的效果。 1 /** 2 * Constructs an empty <tt>HashMap</tt> with the specified initial 3 * capacity and the default load factor (0.75). 4 * 5 * @param initialCapacity the initial capacity. 6 * @throws IllegalArgumentException if the initial capacity is negative. 7 */ 8 // hashMap含參的構(gòu)造器,可以傳入初始化的hashMap的初始化大小的 9 public HashMap(int initialCapacity) { 10 this(initialCapacity, DEFAULT_LOAD_FACTOR); 11 } 12 13 14 15 /** 16 * Constructs an empty <tt>HashMap</tt> with the specified initial 17 * capacity and load factor. 18 * 19 * @param initialCapacity the initial capacity 20 * @param loadFactor the load factor 21 * @throws IllegalArgumentException if the initial capacity is negative 22 * or the load factor is nonpositive 23 */ 24 // hashMap含參的構(gòu)造器,調(diào)用該構(gòu)造器。 25 public HashMap(int initialCapacity, float loadFactor) { 26 if (initialCapacity < 0) 27 throw new IllegalArgumentException('Illegal initial capacity: ' + 28 initialCapacity); 29 if (initialCapacity > MAXIMUM_CAPACITY) 30 initialCapacity = MAXIMUM_CAPACITY; 31 if (loadFactor <= 0 || Float.isNaN(loadFactor)) 32 throw new IllegalArgumentException('Illegal load factor: ' + 33 loadFactor); 34 this.loadFactor = loadFactor; 35 // 根據(jù)傳入的hashMap的初始化值,并不是傳入的初始化值多大,就是多大的 36 this.threshold = tableSizeFor(initialCapacity); 37 } 38 39 40 41 42 ....... 43 44 45 46 47 /** 48 * Returns a power of two size for the given target capacity. 49 */ 50 // 根據(jù)傳入的初始化值,換算成2的n次方,轉(zhuǎn)換成最接近的2的倍數(shù)的值,這樣做,就是為了通過哈希運(yùn)算定位桶的時候呢,能實(shí)現(xiàn)用與操作來代替取模進(jìn)而獲得更好的效果。 51 static final int tableSizeFor(int cap) { 52 int n = cap - 1; 53 n |= n >>> 1; 54 n |= n >>> 2; 55 n |= n >>> 4; 56 n |= n >>> 8; 57 n |= n >>> 16; 58 return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; 59 } 18 、hashMap的擴(kuò)容resize? 1)、hashMap的擴(kuò)容,就是重新計(jì)算容量,向hashMap對象中不停的添加元素,而hashMap對象內(nèi)部的數(shù)組無法裝載更多的元素的時候,對象就需要擴(kuò)大數(shù)組的長度了,才能裝入更多的元素。java中的數(shù)組是無法進(jìn)行自動擴(kuò)容的,hashMap的擴(kuò)容,是使用新的大的數(shù)組替換小的數(shù)組。 2)、hashMap的默認(rèn)負(fù)載因子是0.75f,當(dāng)hashMap填滿了75%的bucket的時候呢,就會創(chuàng)建原來hashMap大小2倍的bucket數(shù)組,來重新調(diào)整map的大小,并將原來的對象放入的新的bucket數(shù)組中。 3)、HashMap擴(kuò)容的問題,多線程環(huán)境下,調(diào)整大小會存在條件競爭,容易造成死鎖。rehashing是一個比較耗時的過程,由于需要將原先的hashMap中的鍵值對重新移動的新的hashMap中去,是一個比較耗時的過程。 1 /**
2 * The load factor used when none specified in constructor.
3 */
4 // 默認(rèn)的負(fù)載因子
5 static final float DEFAULT_LOAD_FACTOR = 0.75f;
6
7
8
9
10 ......
11
12
13
14
15
16 /**
17 * Initializes or doubles table size. If null, allocates in
18 * accord with initial capacity target held in field threshold.
19 * Otherwise, because we are using power-of-two expansion, the
20 * elements from each bin must either stay at same index, or move
21 * with a power of two offset in the new table.
22 *
23 * @return the table
24 */
25 // hashMap的擴(kuò)容resize,就是重新計(jì)算容量,向hashMap對象中不停的添加元素,而hashMap對象內(nèi)部的數(shù)組無法裝載更多的元素的時候,對象就需要擴(kuò)大數(shù)組的長度了,才能裝入更多的元素。java中的數(shù)組是無法進(jìn)行自動擴(kuò)容的,hashMap的擴(kuò)容,是使用新的大的數(shù)組替換小的數(shù)組。
26
27 // hashMap的默認(rèn)負(fù)載因子是0.75f,當(dāng)hashMap填滿了75%的bucket的時候呢,就會創(chuàng)建原來hashMap大小2倍的bucket數(shù)組,來重新調(diào)整map的大小,并將原來的對象放入的新的bucket數(shù)組中。
28 final Node<K,V>[] resize() {
29 Node<K,V>[] oldTab = table;
30 int oldCap = (oldTab == null) ? 0 : oldTab.length;
31 int oldThr = threshold;
32 int newCap, newThr = 0;
33 if (oldCap > 0) {
34 if (oldCap >= MAXIMUM_CAPACITY) {
35 threshold = Integer.MAX_VALUE;
36 return oldTab;
37 }
38 else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
39 oldCap >= DEFAULT_INITIAL_CAPACITY)
40 newThr = oldThr << 1; // double threshold
41 }
42 else if (oldThr > 0) // initial capacity was placed in threshold
43 newCap = oldThr;
44 else { // zero initial threshold signifies using defaults
45 newCap = DEFAULT_INITIAL_CAPACITY;
46 newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
47 }
48 if (newThr == 0) {
49 float ft = (float)newCap * loadFactor;
50 newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
51 (int)ft : Integer.MAX_VALUE);
52 }
53 threshold = newThr;
54 @SuppressWarnings({'rawtypes','unchecked'})
55 Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
56 table = newTab;
57 if (oldTab != null) {
58 for (int j = 0; j < oldCap; ++j) {
59 Node<K,V> e;
60 if ((e = oldTab[j]) != null) {
61 oldTab[j] = null;
62 if (e.next == null)
63 newTab[e.hash & (newCap - 1)] = e;
64 else if (e instanceof TreeNode)
65 ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
66 else { // preserve order
67 Node<K,V> loHead = null, loTail = null;
68 Node<K,V> hiHead = null, hiTail = null;
69 Node<K,V> next;
70 do {
71 next = e.next;
72 if ((e.hash & oldCap) == 0) {
73 if (loTail == null)
74 loHead = e;
75 else
76 loTail.next = e;
77 loTail = e;
78 }
79 else {
80 if (hiTail == null)
81 hiHead = e;
82 else
83 hiTail.next = e;
84 hiTail = e;
85 }
86 } while ((e = next) != null);
87 if (loTail != null) {
88 loTail.next = null;
89 newTab[j] = loHead;
90 }
91 if (hiTail != null) {
92 hiTail.next = null;
93 newTab[j + oldCap] = hiHead;
94 }
95 }
96 }
97 }
98 }
99 return newTab;
100 }
19、 ConcurrentHashMap是出自于JUC包的,ConcurrentHashMap有很多地方和hashMap類似的,包含屬性參數(shù)之類的。ConcurrentHashMap使用的CAS + synchronized進(jìn)行高效的同步更新數(shù)據(jù)的。 ConcurrentHashMap總結(jié),jdk1.8的實(shí)現(xiàn),也是鎖分離的思想,比起Segment,鎖拆的更細(xì),只要哈希不沖突,就不會出現(xiàn)并發(fā)或者鎖的情況。 1)、首先使用無鎖操作CAS插入頭節(jié)點(diǎn),失敗則循環(huán)重試,如果插入失敗,則說明有別的線程插入頭節(jié)點(diǎn)了,需要再次循環(huán)進(jìn)行操作。 20、ConcurrentHashMap的put方法的邏輯。 1)、判斷Node[]數(shù)組是否初始化,沒有則進(jìn)行初始化操作。 1 private static final int MAXIMUM_CAPACITY = 1 << 30; 2 3 /** 4 * The default initial table capacity. Must be a power of 2 5 * (i.e., at least 1) and at most MAXIMUM_CAPACITY. 6 */ 7 private static final int DEFAULT_CAPACITY = 16; 8 9 /** 10 * The largest possible (non-power of two) array size. 11 * Needed by toArray and related methods. 12 */ 13 static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; 14 15 /** 16 * The default concurrency level for this table. Unused but 17 * defined for compatibility with previous versions of this class. 18 */ 19 private static final int DEFAULT_CONCURRENCY_LEVEL = 16; 20 21 /** 22 * The load factor for this table. Overrides of this value in 23 * constructors affect only the initial table capacity. The 24 * actual floating point value isn't normally used -- it is 25 * simpler to use expressions such as {@code n - (n >>> 2)} for 26 * the associated resizing threshold. 27 */ 28 private static final float LOAD_FACTOR = 0.75f; 29 30 /** 31 * The bin count threshold for using a tree rather than list for a 32 * bin. Bins are converted to trees when adding an element to a 33 * bin with at least this many nodes. The value must be greater 34 * than 2, and should be at least 8 to mesh with assumptions in 35 * tree removal about conversion back to plain bins upon 36 * shrinkage. 37 */ 38 static final int TREEIFY_THRESHOLD = 8; 39 40 /** 41 * The bin count threshold for untreeifying a (split) bin during a 42 * resize operation. Should be less than TREEIFY_THRESHOLD, and at 43 * most 6 to mesh with shrinkage detection under removal. 44 */ 45 static final int UNTREEIFY_THRESHOLD = 6; 46 47 /** 48 * The smallest table capacity for which bins may be treeified. 49 * (Otherwise the table is resized if too many nodes in a bin.) 50 * The value should be at least 4 * TREEIFY_THRESHOLD to avoid 51 * conflicts between resizing and treeification thresholds. 52 */ 53 static final int MIN_TREEIFY_CAPACITY = 64; 54 55 /** 56 * Minimum number of rebinnings per transfer step. Ranges are 57 * subdivided to allow multiple resizer threads. This value 58 * serves as a lower bound to avoid resizers encountering 59 * excessive memory contention. The value should be at least 60 * DEFAULT_CAPACITY. 61 */ 62 private static final int MIN_TRANSFER_STRIDE = 16; 63 64 /** 65 * The number of bits used for generation stamp in sizeCtl. 66 * Must be at least 6 for 32bit arrays. 67 */ 68 private static int RESIZE_STAMP_BITS = 16; 69 70 /** 71 * The maximum number of threads that can help resize. 72 * Must fit in 32 - RESIZE_STAMP_BITS bits. 73 */ 74 private static final int MAX_RESIZERS = (1 << (32 - RESIZE_STAMP_BITS)) - 1; 75 76 /** 77 * The bit shift for recording size stamp in sizeCtl. 78 */ 79 private static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS; 80 81 82 83 84 85 86 ...... 87 88 89 90 91 /* 92 * Encodings for Node hash fields. See above for explanation. 93 */ 94 // 其它成員變量主要用來控制線程之間的并發(fā)操作,比如可以同時可以進(jìn)行擴(kuò)容的線程數(shù)等等。 95 static final int MOVED = -1; // hash for forwarding nodes 96 static final int TREEBIN = -2; // hash for roots of trees 97 static final int RESERVED = -3; // hash for transient reservations 98 static final int HASH_BITS = 0x7fffffff; // usable bits of normal node hash 99 100 101 102 ....... 103 104 105 106 107 /** 108 * Table initialization and resizing control. When negative, the 109 * table is being initialized or resized: -1 for initialization, 110 * else -(1 + the number of active resizing threads). Otherwise, 111 * when table is null, holds the initial table size to use upon 112 * creation, or 0 for default. After initialization, holds the 113 * next element count value upon which to resize the table. 114 */ 115 // sizeCtl是size control,是做大小控制的標(biāo)識符,是哈希表初始化和擴(kuò)容時候的一個控制位標(biāo)識量,負(fù)數(shù)代表正在進(jìn)行初始化或者擴(kuò)容操作,-1代表正在初始化,-n代表有n-1個線程正在擴(kuò)容操作,正數(shù)或者0代表哈希表還沒有被初始化操作。這個數(shù)值表示初始化或者下一次進(jìn)行擴(kuò)容的大小,因?yàn)橛辛藇olatile修飾符, sizeCtl是多線程之間可見的,對它的改動,其他線程可以立即看得到,確實(shí)可以起到控制的作用的。 116 private transient volatile int sizeCtl; 117 118 119 120 121 122 ....... 123 124 125 126 127 128 129 /** 130 * Maps the specified key to the specified value in this table. 131 * Neither the key nor the value can be null. 132 * 133 * <p>The value can be retrieved by calling the {@code get} method 134 * with a key that is equal to the original key. 135 * 136 * @param key key with which the specified value is to be associated 137 * @param value value to be associated with the specified key 138 * @return the previous value associated with {@code key}, or 139 * {@code null} if there was no mapping for {@code key} 140 * @throws NullPointerException if the specified key or value is null 141 */ 142 // ConcurrentHashMap的put方法。 143 public V put(K key, V value) { 144 return putVal(key, value, false); 145 } 146 147 /** Implementation for put and putIfAbsent */ 148 final V putVal(K key, V value, boolean onlyIfAbsent) { 149 // ConcurrentHashMap不允許插入null的鍵值對,即key不能為null或者value不能為null 150 if (key == null || value == null) throw new NullPointerException(); 151 // 計(jì)算key的哈希值 152 int hash = spread(key.hashCode()); 153 int binCount = 0; 154 // for循環(huán),因?yàn)槲覀儗?shù)組元素的更新是使用CAS的機(jī)制進(jìn)行更新的,需要不斷的做失敗重試,直到成功為止,因此這里使用了for循環(huán)。 155 for (Node<K,V>[] tab = table;;) { 156 Node<K,V> f; int n, i, fh; 157 // 先判斷數(shù)組是否為空,如果為空或者length等于0 158 if (tab == null || (n = tab.length) == 0) 159 // 就進(jìn)行初始化操作 160 tab = initTable(); 161 // 如果不為空,且不等于0,就使用哈希值來找到f,f表示的是鏈表或者紅黑二叉樹的頭節(jié)點(diǎn),即我們數(shù)組里面的元素,根據(jù)哈希值定位到的元素來檢查元素是否存在 162 else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) { 163 // 如果不存在,嘗試使用CAS進(jìn)行添加,如果添加失敗,則break掉,進(jìn)入下一次循環(huán) 164 if (casTabAt(tab, i, null, 165 new Node<K,V>(hash, key, value, null))) 166 break; // no lock when adding to empty bin 167 } 168 // 如果我們發(fā)現(xiàn)原先的元素已經(jīng)存在了,此時,由于我們的ConcurrentHashMap是隨時存在于多線程環(huán)境下的,有可能別的線程正在移動它,也就是說,ConcurrentHashMap的內(nèi)部呢,正在移動元素,那么我們就協(xié)助其擴(kuò)容。 169 else if ((fh = f.hash) == MOVED) 170 tab = helpTransfer(tab, f); 171 else { 172 // 這里表示發(fā)生了哈希碰撞 173 V oldVal = null; 174 // 此時,鎖住鏈表或者紅黑二叉樹的頭節(jié)點(diǎn),即我們的數(shù)組元素 175 synchronized (f) { 176 // 判斷,f是否的鏈表的頭節(jié)點(diǎn) 177 if (tabAt(tab, i) == f) { 178 // fh代表的是頭節(jié)點(diǎn)的哈希值 179 if (fh >= 0) { 180 // 如果是鏈表的頭節(jié)點(diǎn),就初始化鏈表的計(jì)數(shù)器 181 binCount = 1; 182 // 遍歷該鏈表,每遍歷一次,就將計(jì)數(shù)器加一 183 for (Node<K,V> e = f;; ++binCount) { 184 K ek; 185 // 此時,發(fā)現(xiàn),如果節(jié)點(diǎn)存在呢,就去更新對應(yīng)的value值 186 if (e.hash == hash && 187 ((ek = e.key) == key || 188 (ek != null && key.equals(ek)))) { 189 oldVal = e.val; 190 if (!onlyIfAbsent) 191 e.val = value; 192 break; 193 } 194 Node<K,V> pred = e; 195 // 如果不存在,就在鏈表尾部,添加新的節(jié)點(diǎn) 196 if ((e = e.next) == null) { 197 pred.next = new Node<K,V>(hash, key, 198 value, null); 199 break; 200 } 201 } 202 } 203 // 如果頭節(jié)點(diǎn)是紅黑二叉樹的節(jié)點(diǎn) 204 else if (f instanceof TreeBin) { 205 Node<K,V> p; 206 binCount = 2; 207 // 則嘗試調(diào)用紅黑二叉樹的操作邏輯,去嘗試往樹里面添加節(jié)點(diǎn) 208 if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key, 209 value)) != null) { 210 oldVal = p.val; 211 if (!onlyIfAbsent) 212 p.val = value; 213 } 214 } 215 } 216 } 217 // 如果鏈表長度已經(jīng)已經(jīng)達(dá)到了臨界值8 218 if (binCount != 0) { 219 if (binCount >= TREEIFY_THRESHOLD) 220 // 那么,就將鏈表轉(zhuǎn)化為樹結(jié)構(gòu) 221 treeifyBin(tab, i); 222 if (oldVal != null) 223 return oldVal; 224 break; 225 } 226 } 227 } 228 // 在添加完節(jié)點(diǎn)之后呢,就將當(dāng)前的ConcurrentHashMap的size數(shù)量呢,加上1。 229 addCount(1L, binCount); 230 return null; 231 } 21、java.util.concurrent,提供了并發(fā)編程的解決方案,java.util.concurrent簡稱為JUC,JUC包里面有兩大核心。 答:1)、CAS是java.util.concurrent.atomic包的基礎(chǔ)。 22、java.util.concurrent簡稱為JUC,JUC包的分類。 答:1)、線程執(zhí)行器executor,就是任務(wù)的執(zhí)行和調(diào)度的框架,此外,在tools包可以看到和executor相關(guān)的Executors類,用來創(chuàng)建ExecutorService、ScheduledExecutorService、ThreadFactory、Callable對象等等。 23、java.util.concurrent簡稱為JUC,JUC包的并發(fā)工具類。 答:四個同步器,同步器的作用主要是用于協(xié)助線程的同步。 1)、閉鎖CountDownLatch。讓主線程等待一組事件發(fā)生后繼續(xù)執(zhí)行,這里面的事件指的是CountDownLatch里的countDown()方法。 值的注意的是其它線程調(diào)用完countDown()方法之后還是會繼續(xù)執(zhí)行的,也就是說,countDown()方法調(diào)用之后并不代表該子線程已經(jīng)執(zhí)行完畢,而是告訴主線程說你可以繼續(xù)執(zhí)行,至少我這邊不托你后腿了,具體還需要看其它線程給不給力了,如圖,引入了CountDownLatch之后了,主線程就進(jìn)入了等待狀態(tài),此時CountDownLatch里面有一個cnt變量開始的時候初始化為一個整數(shù),這里就是事件的個數(shù),我們的變量初始化為3,m每當(dāng)其中一個子線程調(diào)用countDown()方法之后,這個計(jì)數(shù)器便會減一,直到所有的子線程都調(diào)用了countDown()方法,cnt變?yōu)榱阒?,主線程才得以重新恢復(fù)到執(zhí)行的狀態(tài)。 2)、柵欄CyclicBarrier,阻塞當(dāng)前線程,等待其它線程。 a)、等待其它線程,且會阻塞自己當(dāng)前線程,所有線程必須同時到達(dá)柵欄位置后,才能繼續(xù)執(zhí)行。 CyclicBarrier和CountDownLatch一樣,內(nèi)部也有一個計(jì)數(shù)器,如圖中的cnt,T1,T2,T3沒調(diào)用一次await()方法,計(jì)數(shù)器就會減一,且在它們調(diào)用await方法的時候,如果計(jì)數(shù)器不為零,這些線程也會被阻塞,另外TA線程j即當(dāng)前線程會在所有線程到達(dá)柵欄處即計(jì)數(shù)器為0的時候才會跟著T1,T2,T3一起去執(zhí)行,同樣都是阻塞當(dāng)前線程來等待其它線程,計(jì)數(shù)的時候CountDownLatch的其它子線程是可以繼續(xù)執(zhí)行的,而CyclicBarrier的所有線程會被阻塞直到計(jì)數(shù)器變?yōu)榱?,這是兩者作用上的區(qū)別。
3)、信號量Semaphore,控制某個資源可被同時訪問的線程個數(shù)。 通過acquire()方法獲取一個許可,如果沒有就去等待,而一旦利用資源執(zhí)行完業(yè)務(wù)邏輯之后,線程就會調(diào)用release方法去釋放出一個許可出來。
4)、交換器Exchanger,兩個線程到達(dá)同步點(diǎn)后,相互交換數(shù)據(jù)。 Exchanger提供一個同步點(diǎn),在這個同步點(diǎn),兩個線程k可以交換彼此的數(shù)據(jù),Exchanger會產(chǎn)生一個同步點(diǎn),一個線程先執(zhí)行到達(dá)同步點(diǎn),就會被阻塞,直到另外一個線程也進(jìn)入到同步點(diǎn)為止,當(dāng)兩個都到達(dá)了同步點(diǎn)之后就開始交換數(shù)據(jù),線程中調(diào)用Exchanger.Exchange()的地方就是同步點(diǎn)了,Exchanger只能用于連個線程互相 交換數(shù)據(jù)。
24、BlockingQueue,阻塞隊(duì)列,提供了可阻塞的入隊(duì)和出隊(duì)操作。 答:Collections里面除了ConcurrentHashMap之外,還有BlockingQueue。 1)、如果隊(duì)列滿了,入隊(duì)操作將阻塞,直到有空間可用,如果隊(duì)列空了,出隊(duì)操作將阻塞,直到有元素可用。根據(jù)出入隊(duì)的規(guī)則和底層數(shù)據(jù)結(jié)構(gòu)的實(shí)現(xiàn),可以劃分出多個BlockingQueue的實(shí)現(xiàn)子類。 2)、主要用于生產(chǎn)者-消費(fèi)者模式,在多線程場景的時候生產(chǎn)者線程在隊(duì)列尾部添加元素,而消費(fèi)者線程在隊(duì)列頭部消費(fèi)元素,通過這種方式能夠達(dá)到將任務(wù)的生產(chǎn)和消費(fèi)進(jìn)行隔離的目的。 25、BlockingQueue主要有以下的七個隊(duì)列實(shí)現(xiàn),它們都是線程安全的。 1)、ArrayBlockingQueue,一個由數(shù)據(jù)結(jié)構(gòu)組成的有界阻塞隊(duì)列,有邊界的意思容量是有限的,我們必須在其初始化的時候指定它的容量大小,容量大小一旦指定就不可改變了,以先進(jìn)先出的方式來存儲數(shù)據(jù)的,最新插入的對象是在尾部,最先移除的對象是在頭部。 26、Java中 BIO、NIO、AIO的主要區(qū)別。 答:1)、BIO是Block-IO,是傳統(tǒng)的java.io以及部分java.net包下的接口或者類,java.net里面比如socket、server socket、http,UrlConnection,因?yàn)榫W(wǎng)絡(luò)通信同樣是IO行為,因此都可以說是輸入BIO的范疇。 a)、傳統(tǒng)IO基于字節(jié)流和字符流j進(jìn)行操作,提供了InputStream和OutputStream,Reader和Writer。4
2)、NIO是NonBlock-IO即非阻塞IO,在jdk1.4以后引入了NIO框架,提供了channel、selector、buffer等新的抽象,構(gòu)建多路復(fù)用的,同步非阻塞的IO操作,同時提供了更接近操作系統(tǒng)底層高性能數(shù)據(jù)操作方式。 a)、NIO與BIO明顯區(qū)別就是,在發(fā)起第一次請求之后,線程并沒有被阻塞,它是反復(fù)去檢查數(shù)據(jù)是否已經(jīng)準(zhǔn)備好,把原來大塊不能用的阻塞的時間分成了許多小阻塞,檢查的是會有一些些阻塞,線程不斷有機(jī)會去被執(zhí)行,檢查這個數(shù)據(jù)有沒有準(zhǔn)備好,有點(diǎn)類似于輪詢,類比成client/server模式呢,其實(shí)現(xiàn)模式為一個請求一個線程,即客戶端發(fā)送的連接請求都會注冊到多路復(fù)用器上,多路復(fù)用器輪循到有IO請求時,才啟動一個線程進(jìn)行處理。NIO的特點(diǎn)就是程序不斷去主動詢問內(nèi)核是否已經(jīng)準(zhǔn)備好,第一個階段是非阻塞的,第二個階段是阻塞的。
27、NIO的核心部分組成,Channels、Buffers、Selectors。 基本上所有的IO在NIO中都是從一個Channel開始,Channel有點(diǎn)像流,數(shù)據(jù)可以從Channel讀到Buffer中,也可以從Buffer中寫到Channel中。 29、NIO-Channels的類型,涵蓋了TCP和UDP網(wǎng)絡(luò)IO以及文件IO。 1)、FileChannel,擁有transferTo方法和transferFron方法。transferTo方法把FileChannel中的數(shù)據(jù)拷貝到另外一個Channel。transferFron方法把另外一個Channel中的數(shù)據(jù)拷貝到FileChannel中。該接口常被用于高效的網(wǎng)絡(luò)文件的數(shù)據(jù)傳輸和大文件拷貝,在操作系統(tǒng)支持的情況下,通過該方法傳輸數(shù)據(jù),并不需要將源數(shù)據(jù)從內(nèi)核態(tài)拷貝到用戶態(tài),再從用戶態(tài)拷貝到目標(biāo)通道的內(nèi)核態(tài)。同時也避免了兩次用戶態(tài)和內(nèi)核態(tài)間的上下文切換,即零拷貝,效率較高,其性能高于BIO中提供的方法。 30、NIO-Buffers的類型,這些Buffer覆蓋了我們能通過IO發(fā)送的基本數(shù)據(jù)類型。 1)、ByteBuffer。 31、NIO-Selector。 Selector允許單線程處理多個Channel,如果你的應(yīng)用打開了多個連接即通道,但每個連接的流量都比較低,使用Selector就會很方便了,例如開發(fā)一個聊天服務(wù)器就排上用場了。如圖所示的是使用一個Selector處理三個Channel的時候,使用Selector得向Selector注冊Channel,然后調(diào)用它的select方法,這個方法會一直阻塞,直到某個注冊的通道有事件就緒,一旦這個方法返回呢,線程就可以處理這些事件了,事件可以是,比如說是有新的連接進(jìn)來,或者說Buffer已經(jīng)有內(nèi)容可以讀取到了等等。
32、NIO的底層使用了操作系統(tǒng)底層的IO多路復(fù)用,調(diào)用系統(tǒng)級別的select、poll、epoll等不同方式,優(yōu)點(diǎn)在于單線程可以同時處理多個網(wǎng)絡(luò)IO,IO多路復(fù)用調(diào)用系統(tǒng)級別的select、poll、epoll模型,由系統(tǒng)監(jiān)控IO狀態(tài),select輪詢可以監(jiān)控許多的IO請求,當(dāng)有一個socket的數(shù)據(jù)被準(zhǔn)備好的時候就可以返回了。 1)、支持一個進(jìn)程所能打開的最大連接數(shù)。 33、AIO,Asynchronous IO,基于事件和回調(diào)機(jī)制,異步非阻塞的方式,可以理解為應(yīng)用操作直接返回,而不會阻塞在哪里,當(dāng)后臺處理完成,操作系統(tǒng)就會通知相應(yīng)線程進(jìn)行后續(xù)工作。 AIO屬于異步模型,用戶線程可以同時處理別的事情,AIO如何進(jìn)一步加功處理結(jié)果。Java提供了兩種方法。 1)、基于回調(diào),實(shí)現(xiàn)CompletionHandler接口,調(diào)用的時候觸發(fā)回調(diào)函數(shù),在調(diào)用的時候,把回調(diào)函數(shù)傳遞給對應(yīng)的API即可。 34、BIO、NIO、AIO對比。
|
|