在 Java 中,同步容器主要包括 2 類(lèi):
成都創(chuàng)新互聯(lián)專(zhuān)注于企業(yè)全網(wǎng)營(yíng)銷(xiāo)推廣、網(wǎng)站重做改版、三門(mén)網(wǎng)站定制設(shè)計(jì)、自適應(yīng)品牌網(wǎng)站建設(shè)、H5網(wǎng)站設(shè)計(jì)、成都做商城網(wǎng)站、集團(tuán)公司官網(wǎng)建設(shè)、成都外貿(mào)網(wǎng)站建設(shè)、高端網(wǎng)站制作、響應(yīng)式網(wǎng)頁(yè)設(shè)計(jì)等建站業(yè)務(wù),價(jià)格優(yōu)惠性?xún)r(jià)比高,為三門(mén)等各大城市提供網(wǎng)站開(kāi)發(fā)制作服務(wù)。
同步容器的同步原理就是在方法上用?synchronized?修飾。那么,這些方法每次只允許一個(gè)線(xiàn)程調(diào)用執(zhí)行。
由于被?synchronized?修飾的方法,每次只允許一個(gè)線(xiàn)程執(zhí)行,其他試圖訪(fǎng)問(wèn)這個(gè)方法的線(xiàn)程只能等待。顯然,這種方式比沒(méi)有使用?synchronized?的容器性能要差。
同步容器真的一定安全嗎?
答案是:未必。同步容器未必真的安全。在做復(fù)合操作時(shí),仍然需要加鎖來(lái)保護(hù)。
常見(jiàn)復(fù)合操作如下:
<pre Courier New" !important; font-size: 12px !important;">public class Test { static Vector<Integer> vector = new Vector<Integer>();
public static void main(String[] args) throws InterruptedException {
while(true) {
for (int i=0;i<10;i++)
vector.add(i);
Thread thread1 = new Thread(){
public void run() {
for (int i=0;i<vector.size();i++)
vector.remove(i);
}
;
}
;
Thread thread2 = new Thread(){
public void run() {
for (int i=0;i<vector.size();i++)
vector.get(i);
}
;
}
;
thread1.start();
thread2.start();
while(Thread.activeCount()>10) {
}
}
}
}
</pre>執(zhí)行時(shí)可能會(huì)出現(xiàn)數(shù)組越界錯(cuò)誤。
Vector 是線(xiàn)程安全的,為什么還會(huì)報(bào)這個(gè)錯(cuò)?很簡(jiǎn)單,對(duì)于 Vector,雖然能保證每一個(gè)時(shí)刻只能有一個(gè)線(xiàn)程訪(fǎng)問(wèn)它,但是不排除這種可能:
當(dāng)某個(gè)線(xiàn)程在某個(gè)時(shí)刻執(zhí)行這句時(shí):
<pre Courier New" !important; font-size: 12px !important;">for (int i=0;i<vector.size();i++)
vector.get(i);
</pre>假若此時(shí) vector 的 size 方法返回的是 10,i 的值為 9
然后另外一個(gè)線(xiàn)程執(zhí)行了這句:
<pre Courier New" !important; font-size: 12px !important;">for (int i=0;i<vector.size();i++)
vector.remove(i);
</pre>將下標(biāo)為 9 的元素刪除了。
那么通過(guò) get 方法訪(fǎng)問(wèn)下標(biāo)為 9 的元素肯定就會(huì)出問(wèn)題了。
因此為了保證線(xiàn)程安全,必須在方法調(diào)用端做額外的同步措施,如下面所示:
public class Test {
static Vector<Integer> vector = new Vector<Integer>();
public static void main(String[] args) throws InterruptedException {
while(true) {
for (int i=0;i<10;i++)
vector.add(i);
Thread thread1 = new Thread(){
public void run() {
synchronized (Test.class) {
//進(jìn)行額外的同步
for (int i=0;i<vector.size();i++)
vector.remove(i);
}
}
;
}
;
Thread thread2 = new Thread(){
public void run() {
synchronized (Test.class) {
for (int i=0;i<vector.size();i++)
vector.get(i);
}
}
;
}
;
thread1.start();
thread2.start();
while(Thread.activeCount()>10) {
}
}
}
}
在對(duì) Vector 等容器并發(fā)地進(jìn)行迭代修改時(shí),會(huì)報(bào) ConcurrentModificationException 異常,關(guān)于這個(gè)異常將會(huì)在后續(xù)文章中講述。
但是在并發(fā)容器中不會(huì)出現(xiàn)這個(gè)問(wèn)題。
JDK 的?java.util.concurrent?包(即 juc)中提供了幾個(gè)非常有用的并發(fā)容器。
ConcurrentHashMap 類(lèi)在 jdk1.7 中的設(shè)計(jì),其基本結(jié)構(gòu)如圖所示:

每一個(gè) segment 都是一個(gè) HashEntry<K,V>[] table, table 中的每一個(gè)元素本質(zhì)上都是一個(gè) HashEntry 的單向隊(duì)列。比如 table[3]為首節(jié)點(diǎn),table[3]->next 為節(jié)點(diǎn) 1,之后為節(jié)點(diǎn) 2,依次類(lèi)推。
<pre Courier New" !important; font-size: 12px !important;">public class ConcurrentHashMap<K, V> extends AbstractMap<K, V>
implements ConcurrentMap<K, V>, Serializable { // 將整個(gè)hashmap分成幾個(gè)小的map,每個(gè)segment都是一個(gè)鎖;與hashtable相比,這么設(shè)計(jì)的目的是對(duì)于put, remove等操作,可以減少并發(fā)沖突,對(duì) // 不屬于同一個(gè)片段的節(jié)點(diǎn)可以并發(fā)操作,大大提高了性能
final Segment<K,V>[] segments;
// 本質(zhì)上Segment類(lèi)就是一個(gè)小的hashmap,里面table數(shù)組存儲(chǔ)了各個(gè)節(jié)點(diǎn)的數(shù)據(jù),繼承了ReentrantLock, 可以作為互拆鎖使用
static final class Segment<K,V> extends ReentrantLock implements Serializable {
transient volatile HashEntry<K,V>[] table;
transient int count;
}
// 基本節(jié)點(diǎn),存儲(chǔ)Key, Value值
static final class HashEntry<K,V> {
final int hash;
final K key;
volatile V value;
volatile HashEntry<K,V> next;
}
}
</pre>
transient volatile HashEntry<K,V>[] table?保存數(shù)據(jù),采用 table 數(shù)組元素作為鎖,從而實(shí)現(xiàn)了對(duì)每一行數(shù)據(jù)進(jìn)行加鎖,進(jìn)一步減少并發(fā)沖突的概率。<pre Courier New" !important; font-size: 12px !important;">final V putVal(K key, V value, Boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();
int hash = spread(key.hashCode());
int binCount = 0;
for (Node<K,V>[] tab = table;;) {
Node<K,V> f;
int n, i, fh;
// 如果table為空,初始化;否則,根據(jù)hash值計(jì)算得到數(shù)組索引i,如果tab[i]為空,直接新建節(jié)點(diǎn)Node即可。注:tab[i]實(shí)質(zhì)為鏈表或者紅黑樹(shù)的首節(jié)點(diǎn)。
if (tab == null || (n = tab.length) == 0)
tab = initTable(); else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null))) break;
// no lock when adding to empty bin
}
// 如果tab[i]不為空并且hash值為MOVED,說(shuō)明該鏈表正在進(jìn)行transfer操作,返回?cái)U(kuò)容完成后的table。 else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f); else {
V oldVal = null;
// 針對(duì)首個(gè)節(jié)點(diǎn)進(jìn)行加鎖操作,而不是segment,進(jìn)一步減少線(xiàn)程沖突
synchronized (f) {
if (tabAt(tab, i) == f) {
if (fh >= 0) {
binCount = 1;
for (Node<K,V> e = f;; ++binCount) {
K ek;
// 如果在鏈表中找到值為key的節(jié)點(diǎn)e,直接設(shè)置e.val = value即可。
if (e.hash == hash && ((ek = e.key) == key || (ek != null && key.equals(ek)))) {
oldVal = e.val;
if (!onlyIfAbsent)
e.val = value;
break;
}
// 如果沒(méi)有找到值為key的節(jié)點(diǎn),直接新建Node并加入鏈表即可。
Node<K,V> pred = e;
if ((e = e.next) == null) {
pred.next = new Node<K,V>(hash, key,
value, null);
break;
}
}
}
// 如果首節(jié)點(diǎn)為T(mén)reeBin類(lèi)型,說(shuō)明為紅黑樹(shù)結(jié)構(gòu),執(zhí)行putTreeVal操作。 else if (f instanceof TreeBin) {
Node<K,V> p;
binCount = 2;
if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
value)) != null) {
oldVal = p.val;
if (!onlyIfAbsent)
p.val = value;
}
}
}
}
if (binCount != 0) {
// 如果節(jié)點(diǎn)數(shù)>=8,那么轉(zhuǎn)換鏈表結(jié)構(gòu)為紅黑樹(shù)結(jié)構(gòu)。
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null) return oldVal;
break;
}
}
}
// 計(jì)數(shù)增加1,有可能觸發(fā)transfer操作(擴(kuò)容)。
addCount(1L, binCount);
return null;
}
</pre>示例
<pre Courier New" !important; font-size: 12px !important;">public class ConcurrentHashMapDemo { public static void main(String[] args) throws InterruptedException { // HashMap 在并發(fā)迭代訪(fǎng)問(wèn)時(shí)會(huì)拋出 ConcurrentModificationException 異常 // Map<Integer, Character> map = new HashMap<>();
Map<Integer, Character> map = new ConcurrentHashMap<>();
Thread wthread = new Thread(() -> {
System.out.println("寫(xiě)操作線(xiàn)程開(kāi)始執(zhí)行"); for (int i = 0; i < 26; i++) {
map.put(i, (char) ('a' + i));
}
});
Thread rthread = new Thread(() -> {
System.out.println("讀操作線(xiàn)程開(kāi)始執(zhí)行"); for (Integer key : map.keySet()) {
System.out.println(key + " - " + map.get(key));
}
});
wthread.start();
rthread.start();
Thread.sleep(1000);
}
}</pre>

array - 對(duì)象數(shù)組,用于存放元素
<pre Courier New" !important; font-size: 12px !important;">
/** The lock protecting all mutators */
final transient ReentrantLock lock = new ReentrantLock();
/** The array, accessed only via getArray/setArray. */
private transient volatile Object[] array;
</pre>
<pre Courier New" !important; font-size: 12px !important;">public Boolean add(E e) {
//ReentrantLock加鎖,保證線(xiàn)程安全
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
//拷貝原容器,長(zhǎng)度為原容器長(zhǎng)度加一
Object[] newElements = Arrays.copyOf(elements, len + 1);
//在新副本上執(zhí)行添加操作
newElements[len] = e;
//將原容器引用指向新副本
setArray(newElements);
return true;
}
finally {
//解鎖
lock.unlock();
}
}
</pre>刪除操作
<pre Courier New" !important; font-size: 12px !important;">public E remove(int index) {
//加鎖
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
E oldValue = get(elements, index);
int numMoved = len - index - 1;
if (numMoved == 0) //如果要?jiǎng)h除的是列表末端數(shù)據(jù),拷貝前l(fā)en-1個(gè)數(shù)據(jù)到新副本上,再切換引用
setArray(Arrays.copyOf(elements, len - 1)); else {
//否則,將除要?jiǎng)h除元素之外的其他元素拷貝到新副本中,并切換引用
Object[] newElements = new Object[len - 1];
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index + 1, newElements, index,
numMoved);
setArray(newElements);
}
return oldValue;
}
finally {
//解鎖
lock.unlock();
}
}
</pre>
<pre Courier New" !important; font-size: 12px !important;">public E get(int index) {
return get(getArray(), index);
}
private E get(Object[] a, int index) {
return (E) a[index];
}
</pre><pre Courier New" !important; font-size: 12px !important;">public class CopyOnWriteArrayListDemo { static class ReadTask implements Runnable {
List<String> list;
ReadTask(List<String> list) {
this.list = list;
}
public void run() {
for (String str : list) {
System.out.println(str);
}
}
}
static class WriteTask implements Runnable {
List<String> list;
int index;
WriteTask(List<String> list, int index) {
this.list = list;
this.index = index;
}
public void run() {
list.remove(index);
list.add(index, "write_" + index);
}
}
public void run() {
final int NUM = 10;
// ArrayList 在并發(fā)迭代訪(fǎng)問(wèn)時(shí)會(huì)拋出 ConcurrentModificationException 異常 // List<String> list = new ArrayList<>();
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
for (int i = 0; i < NUM; i++) {
list.add("main_" + i);
}
ExecutorService executorService = Executors.newFixedThreadPool(NUM);
for (int i = 0; i < NUM; i++) {
executorService.execute(new ReadTask(list));
executorService.execute(new WriteTask(list, i));
}
executorService.shutdown();
}
public static void main(String[] args) {
new CopyOnWriteArrayListDemo().run();
}
}
</pre>
針對(duì)于上面所涉及到的知識(shí)點(diǎn)我總結(jié)出了有1到5年開(kāi)發(fā)經(jīng)驗(yàn)的程序員在面試中涉及到的絕大部分架構(gòu)面試題及答案做成了文檔和架構(gòu)視頻資料免費(fèi)分享給大家(包括Dubbo、redis、Netty、zookeeper、Spring cloud、分布式、高并發(fā)等架構(gòu)技術(shù)資料),希望能幫助到您面試前的復(fù)習(xí)且找到一個(gè)好的工作,也節(jié)省大家在網(wǎng)上搜索資料的時(shí)間來(lái)學(xué)習(xí),也可以關(guān)注我一下以后會(huì)有更多干貨分享。



分享題目:阿里P7教你巧用Java的同步容器和并發(fā)容器
地址分享:http://chinadenli.net/article26/jhggcg.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供全網(wǎng)營(yíng)銷(xiāo)推廣、網(wǎng)站內(nèi)鏈、營(yíng)銷(xiāo)型網(wǎng)站建設(shè)、App開(kāi)發(fā)、用戶(hù)體驗(yàn)、云服務(wù)器
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶(hù)投稿、用戶(hù)轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話(huà):028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)