日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問(wèn)題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷解決方案
不會(huì)這些“高級(jí)貨”,活該你面試當(dāng)炮灰...

 今天聊一個(gè)非常硬核的技術(shù)知識(shí),給大家分析一下 CopyOnWrite 思想是什么,以及在 Java 并發(fā)包中的具體體現(xiàn),包括在 Kafka 內(nèi)核源碼中是如何運(yùn)用這個(gè)思想來(lái)優(yōu)化并發(fā)性能的。

為南雄等地區(qū)用戶提供了全套網(wǎng)頁(yè)設(shè)計(jì)制作服務(wù),及南雄網(wǎng)站建設(shè)行業(yè)解決方案。主營(yíng)業(yè)務(wù)為做網(wǎng)站、成都網(wǎng)站建設(shè)、南雄網(wǎng)站設(shè)計(jì),以傳統(tǒng)方式定制建設(shè)網(wǎng)站,并提供域名空間備案等一條龍服務(wù),秉承以專業(yè)、用心的態(tài)度為用戶提供真誠(chéng)的服務(wù)。我們深信只要達(dá)到每一位用戶的要求,就會(huì)得到認(rèn)可,從而選擇與我們長(zhǎng)期合作。這樣,我們也可以走得更遠(yuǎn)!

這個(gè) CopyOnWrite 在面試的時(shí)候,很可能成為面試官的一個(gè)殺手锏把候選人給一擊必殺,也很有可能成為候選人拿下 Offer 的獨(dú)門秘籍,是相對(duì)高級(jí)的一個(gè)知識(shí)。

讀多寫少的場(chǎng)景下引發(fā)的問(wèn)題?

大家可以設(shè)想一下現(xiàn)在我們的內(nèi)存里有一個(gè) ArrayList,這個(gè) ArrayList 默認(rèn)情況下肯定是線程不安全的,要是多個(gè)線程并發(fā)讀和寫這個(gè) ArrayList 可能會(huì)有問(wèn)題。

好,問(wèn)題來(lái)了,我們應(yīng)該怎么讓這個(gè) ArrayList 變成線程安全的呢?有一個(gè)非常簡(jiǎn)單的辦法,對(duì)這個(gè) ArrayList 的訪問(wèn)都加上線程同步的控制。

比如說(shuō)一定要在 Synchronized 代碼段來(lái)對(duì)這個(gè) ArrayList 進(jìn)行訪問(wèn),這樣的話,就能同一時(shí)間就讓一個(gè)線程來(lái)操作它了,或者是用 ReadWriteLock 讀寫鎖的方式來(lái)控制,都可以。

我們假設(shè)就是用 ReadWriteLock 讀寫鎖的方式來(lái)控制對(duì)這個(gè) ArrayList 的訪問(wèn)。

這樣多個(gè)讀請(qǐng)求可以同時(shí)執(zhí)行從 ArrayList 里讀取數(shù)據(jù),但是讀請(qǐng)求和寫請(qǐng)求之間互斥,寫請(qǐng)求和寫請(qǐng)求也是互斥的。

大家看看,代碼大概就是類似下面這樣:

 
 
 
 
  1. public Object  read() { 
  2.     lock.readLock().lock(); 
  3.     // 對(duì)ArrayList讀取 
  4.     lock.readLock().unlock(); 
  5. public void write() { 
  6.     lock.writeLock().lock(); 
  7.     // 對(duì)ArrayList寫 
  8.     lock.writeLock().unlock(); 

大家想想,類似上面的代碼有什么問(wèn)題呢?***的問(wèn)題,其實(shí)就在于寫鎖和讀鎖的互斥。假設(shè)寫操作頻率很低,讀操作頻率很高,是寫少讀多的場(chǎng)景。

那么偶爾執(zhí)行一個(gè)寫操作的時(shí)候,是不是會(huì)加上寫鎖,此時(shí)大量的讀操作過(guò)來(lái)是不是就會(huì)被阻塞住,無(wú)法執(zhí)行?

這個(gè)就是讀寫鎖可能遇到的***的問(wèn)題。

引入 CopyOnWrite 思想解決問(wèn)題

這個(gè)時(shí)候就要引入 CopyOnWrite 思想來(lái)解決問(wèn)題了。

它的思想就是,不用加什么讀寫鎖,鎖統(tǒng)統(tǒng)給我去掉,有鎖就有問(wèn)題,有鎖就有互斥,有鎖就可能導(dǎo)致性能低下,你阻塞我的請(qǐng)求,導(dǎo)致我的請(qǐng)求都卡著不能執(zhí)行。

那么它怎么保證多線程并發(fā)的安全性呢?很簡(jiǎn)單,顧名思義,利用“CopyOnWrite”的方式,這個(gè)英語(yǔ)翻譯成中文,大概就是“寫數(shù)據(jù)的時(shí)候利用拷貝的副本來(lái)執(zhí)行”。

你在讀數(shù)據(jù)的時(shí)候,其實(shí)不加鎖也沒(méi)關(guān)系,大家左右都是一個(gè)讀罷了,互相沒(méi)影響。

問(wèn)題主要是在寫的時(shí)候,寫的時(shí)候你既然不能加鎖了,那么就得采用一個(gè)策略。

假如說(shuō)你的 ArrayList 底層是一個(gè)數(shù)組來(lái)存放你的列表數(shù)據(jù),那么這時(shí)比如你要修改這個(gè)數(shù)組里的數(shù)據(jù),你就必須先拷貝這個(gè)數(shù)組的一個(gè)副本。

然后你可以在這個(gè)數(shù)組的副本里寫入你要修改的數(shù)據(jù),但是在這個(gè)過(guò)程中實(shí)際上你都是在操作一個(gè)副本而已。

這樣的話,讀操作是不是可以同時(shí)正常的執(zhí)行?這個(gè)寫操作對(duì)讀操作是沒(méi)有任何的影響的吧!

大家看下面的圖,一起來(lái)體會(huì)一下這個(gè)過(guò)程:

關(guān)鍵問(wèn)題來(lái)了,那那個(gè)寫線程現(xiàn)在把副本數(shù)組給修改完了,現(xiàn)在怎么才能讓讀線程感知到這個(gè)變化呢?

關(guān)鍵點(diǎn)來(lái)了,劃重點(diǎn)!這里要配合上 Volatile 關(guān)鍵字的使用。

筆者之前寫過(guò)文章,給大家解釋過(guò) Volatile 關(guān)鍵字的使用,核心就是讓一個(gè)變量被寫線程給修改之后,立馬讓其他線程可以讀到這個(gè)變量引用的最近的值,這就是 Volatile 最核心的作用。

所以一旦寫線程搞定了副本數(shù)組的修改之后,那么就可以用 Volatile 寫的方式,把這個(gè)副本數(shù)組賦值給 Volatile 修飾的那個(gè)數(shù)組的引用變量了。

只要一賦值給那個(gè) Volatile 修飾的變量,立馬就會(huì)對(duì)讀線程可見(jiàn),大家都能看到***的數(shù)組了。

下面是 JDK 里的 CopyOnWriteArrayList 的源碼:

 
 
 
 
  1. // 這個(gè)數(shù)組是核心的,因?yàn)橛胿olatile修飾了 
  2.    // 只要把***的數(shù)組對(duì)他賦值,其他線程立馬可以看到***的數(shù)組 
  3.    private transient volatile Object[] array; 
  4.  
  5.    public boolean add(E e) { 
  6.        final ReentrantLock lock = this.lock; 
  7.        lock.lock(); 
  8.        try { 
  9.            Object[] elements = getArray(); 
  10.            int len = elements.length; 
  11.            // 對(duì)數(shù)組拷貝一個(gè)副本出來(lái) 
  12.            Object[] newElements = Arrays.copyOf(elements, len + 1); 
  13.            // 對(duì)副本數(shù)組進(jìn)行修改,比如在里面加入一個(gè)元素 
  14.            newElements[len] = e; 
  15.            // 然后把副本數(shù)組賦值給volatile修飾的變量 
  16.            setArray(newElements); 
  17.            return true; 
  18.        } finally { 
  19.            lock.unlock(); 
  20.        } 
  21.    } 

大家看看寫數(shù)據(jù)的時(shí)候,他是怎么拷貝一個(gè)數(shù)組副本,然后修改副本,接著通過(guò) Volatile 變量賦值的方式,把修改好的數(shù)組副本給更新回去,立馬讓其他線程可見(jiàn)的。

然后大家想,因?yàn)槭峭ㄟ^(guò)副本來(lái)進(jìn)行更新的,萬(wàn)一要是多個(gè)線程都要同時(shí)更新呢?那搞出來(lái)多個(gè)副本會(huì)不會(huì)有問(wèn)題?

當(dāng)然不能多個(gè)線程同時(shí)更新了,這個(gè)時(shí)候就是看上面源碼里,加入了 Lock 鎖的機(jī)制,也就是同一時(shí)間只有一個(gè)線程可以更新。

那么更新的時(shí)候,會(huì)對(duì)讀操作有任何的影響嗎?絕對(duì)不會(huì),因?yàn)樽x操作就是非常簡(jiǎn)單的對(duì)那個(gè)數(shù)組進(jìn)行讀而已,不涉及任何的鎖。

而且只要他更新完畢對(duì) Volatile 修飾的變量賦值,那么讀線程立馬可以看到***修改后的數(shù)組,這是 Volatile 保證的:

 
 
 
 
  1. private E get(Object[] a, int index) { 
  2.         // 最簡(jiǎn)單的對(duì)數(shù)組進(jìn)行讀取 
  3.         return (E) a[index]; 
  4.     }  

這樣就***解決了我們之前說(shuō)的讀多寫少的問(wèn)題。如果用讀寫鎖互斥的話,會(huì)導(dǎo)致寫鎖阻塞大量讀操作,影響并發(fā)性能。

但是如果用了 CopyOnWriteArrayList,就是用空間換時(shí)間,更新的時(shí)候基于副本更新,避免鎖,然后***用 Volatile 變量來(lái)賦值保證可見(jiàn)性,更新的時(shí)候?qū)ψx線程沒(méi)有任何的影響!

CopyOnWrite 思想在 Kafka 源碼中的運(yùn)用

在 Kafka 的內(nèi)核源碼中,有這么一個(gè)場(chǎng)景,客戶端在向 Kafka 寫數(shù)據(jù)的時(shí)候,會(huì)把消息先寫入客戶端本地的內(nèi)存緩沖,然后在內(nèi)存緩沖里形成一個(gè) Batch 之后再一次性發(fā)送到 Kafka 服務(wù)器上去,這樣有助于提升吞吐量。

話不多說(shuō),大家看下圖:

這個(gè)時(shí)候 Kafka 的內(nèi)存緩沖用的是什么數(shù)據(jù)結(jié)構(gòu)呢?大家看源碼:

 
 
 
 
  1. private final ConcurrentMap> batches =  
  2. new CopyOnWriteMap>(); 

這個(gè)數(shù)據(jù)結(jié)構(gòu)就是核心的用來(lái)存放寫入內(nèi)存緩沖中的消息的數(shù)據(jù)結(jié)構(gòu),要看懂這個(gè)數(shù)據(jù)結(jié)構(gòu)需要對(duì)很多 Kafka 內(nèi)核源碼里的概念進(jìn)行解釋,這里先不展開(kāi)。

但是大家關(guān)注一點(diǎn),他是自己實(shí)現(xiàn)了一個(gè) CopyOnWriteMap,這個(gè)CopyOnWriteMap 采用的就是 CopyOnWrite 思想。

我們來(lái)看一下這個(gè) CopyOnWriteMap 的源碼實(shí)現(xiàn):

 
 
 
 
  1. // 典型的volatile修飾普通Map 
  2.    private volatile Map map; 
  3.    @Override 
  4.    public synchronized V put(K k, V v) { 
  5.        // 更新的時(shí)候先創(chuàng)建副本,更新副本,然后對(duì)volatile變量賦值寫回去 
  6.        Map copy = new HashMap(this.map); 
  7.        V prev = copy.put(k, v); 
  8.        this.map = Collections.unmodifiableMap(copy); 
  9.        return prev; 
  10.    } 
  11.    @Override 
  12.    public V get(Object k) { 
  13.        // 讀取的時(shí)候直接讀volatile變量引用的map數(shù)據(jù)結(jié)構(gòu),無(wú)需鎖 
  14.        return map.get(k); 
  15.    } 

Kafka 這個(gè)核心數(shù)據(jù)結(jié)構(gòu)在這里之所以采用 CopyOnWriteMap 思想來(lái)實(shí)現(xiàn),就是因?yàn)檫@個(gè) Map 的 Key-Value 對(duì),其實(shí)沒(méi)那么頻繁更新。

也就是 TopicPartition-Deque 這個(gè) Key-Value 對(duì),更新頻率很低。

但是它的 Get 操作卻是高頻的讀取請(qǐng)求,因?yàn)闀?huì)高頻的讀取出來(lái)一個(gè) TopicPartition 對(duì)應(yīng)的 Deque 數(shù)據(jù)結(jié)構(gòu),來(lái)對(duì)這個(gè)隊(duì)列進(jìn)行入隊(duì)出隊(duì)等操作,所以對(duì)于這個(gè) Map 而言,高頻的是其 Get 操作。

這個(gè)時(shí)候,Kafka 就采用了 CopyOnWrite 思想來(lái)實(shí)現(xiàn)這個(gè) Map,避免更新 Key-Value 的時(shí)候阻塞住高頻的讀操作,實(shí)現(xiàn)無(wú)鎖的效果,優(yōu)化線程并發(fā)的性能。

相信大家看完這個(gè)文章,對(duì)于 CopyOnWrite 思想以及適用場(chǎng)景,包括 JDK 中的實(shí)現(xiàn),以及在 Kafka 源碼中的運(yùn)用,都有了一個(gè)切身的體會(huì)了。

如果你能在面試時(shí)說(shuō)清楚這個(gè)思想以及他在 JDK 中的體現(xiàn),并且還能結(jié)合知名的開(kāi)源項(xiàng)目 Kafka 的底層源碼進(jìn)一步向面試官進(jìn)行闡述,面試官對(duì)你的印象肯定大大的加分。

中華石杉:十余年 BAT 架構(gòu)經(jīng)驗(yàn),一線互聯(lián)網(wǎng)公司技術(shù)總監(jiān)。帶領(lǐng)上百人團(tuán)隊(duì)開(kāi)發(fā)過(guò)多個(gè)億級(jí)流量高并發(fā)系統(tǒng)?,F(xiàn)將多年工作中積累下的研究手稿、經(jīng)驗(yàn)總結(jié)整理成文,傾囊相授。微信公眾號(hào):石杉的架構(gòu)筆記(ID:shishan100)。


分享標(biāo)題:不會(huì)這些“高級(jí)貨”,活該你面試當(dāng)炮灰...
本文URL:http://www.5511xx.com/article/dhpoeej.html