www.久久久久|狼友网站av天堂|精品国产无码a片|一级av色欲av|91在线播放视频|亚洲无码主播在线|国产精品草久在线|明星AV网站在线|污污内射久久一区|婷婷综合视频网站

當(dāng)前位置:首頁(yè) > 公眾號(hào)精選 > 架構(gòu)師社區(qū)
[導(dǎo)讀]每個(gè)時(shí)代,都不會(huì)虧待會(huì)學(xué)習(xí)的人 大家好,我是yes。 本來(lái)打算繼續(xù)寫消息隊(duì)列的東西的,但是最近在帶新同事,發(fā)現(xiàn)新同事對(duì)于鎖這方面有一些誤解,所以今天就來(lái)談?wù)劇版i”事和 Java 中的并發(fā)安全容器使用有哪些注意點(diǎn)。 不過(guò)在這之前還是得先來(lái)盤一盤為什么需要

每個(gè)時(shí)代,都不會(huì)虧待會(huì)學(xué)習(xí)的人

大家好,我是yes。

本來(lái)打算繼續(xù)寫消息隊(duì)列的東西的,但是最近在帶新同事,發(fā)現(xiàn)新同事對(duì)于鎖這方面有一些誤解,所以今天就來(lái)談?wù)劇版i”事和 Java 中的并發(fā)安全容器使用有哪些注意點(diǎn)。

不過(guò)在這之前還是得先來(lái)盤一盤為什么需要鎖這玩意,這得從并發(fā) BUG 的源頭說(shuō)起。

并發(fā) BUG 的源頭

這個(gè)問(wèn)題我 19 年的時(shí)候?qū)戇^(guò)一篇文章, 現(xiàn)在回頭看那篇文章真的是羞澀啊。

你用對(duì)鎖了嗎?淺談 Java “鎖” 事

讓我們來(lái)看下這個(gè)源頭是什么,我們知道電腦有CPU、內(nèi)存、硬盤,硬盤的讀取速度最慢,其次是內(nèi)存的讀取,內(nèi)存的讀取相對(duì)于 CPU 的運(yùn)行又太慢了,因此又搞了個(gè)CPU緩存,L1、L2、L3。

正是這個(gè)CPU緩存再加上現(xiàn)在多核CPU的情況產(chǎn)生了并發(fā)BUG

你用對(duì)鎖了嗎?淺談 Java “鎖” 事

這就一個(gè)很簡(jiǎn)單的代碼,如果此時(shí)有線程 A 和線程 B 分別在 CPU - A 和 CPU - B 中執(zhí)行這個(gè)方法,它們的操作是先將 a 從主存取到 CPU 各自的緩存中,此時(shí)它們緩存中 a 的值都是 0。

然后它們分別執(zhí)行 a++,此時(shí)它們各自眼中 a 的值都是 1,之后把 a 刷到主存的時(shí)候 a 的值還是1,這就出現(xiàn)問(wèn)題了,明明執(zhí)行了兩次加一最終的結(jié)果卻是 1,而不是 2。

這個(gè)問(wèn)題就叫可見(jiàn)性問(wèn)題

在看我們 a++ 這條語(yǔ)句,我們現(xiàn)在的語(yǔ)言都是高級(jí)語(yǔ)言,這其實(shí)和語(yǔ)法糖很類似,用起來(lái)好像很方便實(shí)際上那只是表面,真正需要執(zhí)行的指令一條都少不了。

高級(jí)語(yǔ)言的一條語(yǔ)句翻譯成 CPU 指令的時(shí)候可不止一條, 就例如 a++ 轉(zhuǎn)換成 CPU 指令至少就有三條。

  • 把 a 從內(nèi)存拿到寄存器中;

  • 在寄存器中 +1;

  • 將結(jié)果寫入緩存或內(nèi)存中;

所以我們以為 a++ 這條語(yǔ)句是不可能中斷的是具備原子性的,而實(shí)際上 CPU 可以能執(zhí)行一條指令時(shí)間片就到了,此時(shí)上下文切換到另一個(gè)線程,它也執(zhí)行 a++。再次切回來(lái)的時(shí)候 a 的值其實(shí)就已經(jīng)不對(duì)了。

這個(gè)問(wèn)題叫做原子性問(wèn)題

并且編譯器或解釋器為了優(yōu)化性能,可能會(huì)改變語(yǔ)句的執(zhí)行順序,這叫指令重排,最經(jīng)典的例子莫過(guò)于單例模式的雙重檢查了。而 CPU 為了提高執(zhí)行效率,還會(huì)亂序執(zhí)行,例如 CPU 在等待內(nèi)存數(shù)據(jù)加載的時(shí)候發(fā)現(xiàn)后面的加法指令不依賴前面指令的計(jì)算結(jié)果,因此它就先執(zhí)行了這條加法指令。

這個(gè)問(wèn)題就叫有序性問(wèn)題。

至此已經(jīng)分析完了并發(fā) BUG 的源頭,即這三大問(wèn)題??梢钥吹讲还苁?CPU 緩存、多核 CPU 、高級(jí)語(yǔ)言還是亂序重排其實(shí)都是必要的存在,所以我們只能直面這些問(wèn)題。

而解決這些問(wèn)題就是通過(guò)禁用緩存、禁止編譯器指令重排、互斥等手段,今天我們的主題和互斥相關(guān)。

互斥就是保證對(duì)共享變量的修改是互斥的,即同一時(shí)刻只有一個(gè)線程在執(zhí)行。而說(shuō)到互斥相信大家腦海中浮現(xiàn)的就是。沒(méi)錯(cuò),我們今天的主題就是鎖!鎖就是為了解決原子性問(wèn)題。

說(shuō)到鎖可能 Java 的同學(xué)第一反應(yīng)就是 synchronized 關(guān)鍵字,畢竟是語(yǔ)言層面支持的。我們就先來(lái)看看 synchronized,有些同學(xué)對(duì) synchronized 理解不到位所以用起來(lái)會(huì)有很多坑。

synchronized 注意點(diǎn)

我們先來(lái)看一份代碼,這段代碼就是咱們的漲工資之路,最終百萬(wàn)是灑灑水的。而一個(gè)線程時(shí)刻的對(duì)比著我們工資是不是相等的。我簡(jiǎn)單說(shuō)一下IntStream.rangeClosed(1,1000000).forEach,可能有些人對(duì)這個(gè)不太熟悉,這個(gè)代碼的就等于 for 循環(huán)了100W次。

你用對(duì)鎖了嗎?淺談 Java “鎖” 事

你先自己理解下,看看覺(jué)得有沒(méi)有什么問(wèn)題?第一反應(yīng)好像沒(méi)問(wèn)題,你看著漲工資就一個(gè)線程執(zhí)行著,這比工資也沒(méi)有修改值,看起來(lái)好像沒(méi)啥毛???沒(méi)有啥并發(fā)資源的競(jìng)爭(zhēng),也用 volatile 修飾了保證了可見(jiàn)性。

讓我們來(lái)看一下結(jié)果,我截取了一部分。

你用對(duì)鎖了嗎?淺談 Java “鎖” 事

可以看到首先有 log 打出來(lái)就已經(jīng)不對(duì)了,其次打出來(lái)的值竟然還相等!有沒(méi)有出乎你的意料之外?有同學(xué)可能下意識(shí)就想到這就raiseSalary在修改,所以肯定是線程安全問(wèn)題來(lái)給raiseSalary 加個(gè)鎖!

請(qǐng)注意只有一個(gè)線程在調(diào)用raiseSalary方法,所以單給raiseSalary方法加鎖并沒(méi)啥用。

這其實(shí)就是我上面提到的原子性問(wèn)題,想象一下漲工資線程在執(zhí)行完yesSalary++還未執(zhí)行yourSalary++時(shí),比工資線程剛好執(zhí)行到yesSalary != yourSalary 是不是肯定是 true ?所以才會(huì)打印出 log。

再者由于用 volatile 修飾保證了可見(jiàn)性,所以當(dāng)打 log 的時(shí)候,可能yourSalary++已經(jīng)執(zhí)行完了,這時(shí)候打出來(lái)的 log 才會(huì)是yesSalary == yourSalary。

所以最簡(jiǎn)單的解決辦法就是把raiseSalary()compareSalary() 都用 synchronized 修飾,這樣漲工資和比工資兩個(gè)線程就不會(huì)在同一時(shí)刻執(zhí)行,因此肯定就安全了!

你用對(duì)鎖了嗎?淺談 Java “鎖” 事

看起來(lái)鎖好像也挺簡(jiǎn)單,不過(guò)這個(gè) synchronized 的使用還是對(duì)于新手來(lái)說(shuō)還是有坑的,就是你要關(guān)注 synchronized 鎖的究竟是什么。

比如我改成多線程來(lái)漲工資。這里再提一下parallel,這個(gè)其實(shí)就是利用了 ForkJoinPool 線程池操作,默認(rèn)線程數(shù)是 CPU 核心數(shù)。

你用對(duì)鎖了嗎?淺談 Java “鎖” 事

由于 raiseSalary() 加了鎖,所以最終的結(jié)果是對(duì)的。這是因?yàn)?synchronized 修飾的是yesLockDemo實(shí)例,我們的 main 中只有一個(gè)實(shí)例,所以等于多線程競(jìng)爭(zhēng)的是一把鎖,所以最終計(jì)算出來(lái)的數(shù)據(jù)正確。

那我再修改下代碼,讓每個(gè)線程自己有一個(gè) yesLockDemo 實(shí)例來(lái)漲工資。

你用對(duì)鎖了嗎?淺談 Java “鎖” 事

你會(huì)發(fā)現(xiàn)這鎖怎么沒(méi)用了?這說(shuō)好的百萬(wàn)年薪我就變 10w 了??這你還好還有 70w。

這是因?yàn)榇藭r(shí)我們的鎖修飾的是非靜態(tài)方法,是實(shí)例級(jí)別的鎖,而我們?yōu)槊總€(gè)線程都創(chuàng)建了一個(gè)實(shí)例,因此這幾個(gè)線程競(jìng)爭(zhēng)的就根本不是一把鎖,而上面多線程計(jì)算正確代碼是因?yàn)槊總€(gè)線程用的是同一個(gè)實(shí)例,所以競(jìng)爭(zhēng)的是一把鎖。如果想要此時(shí)的代碼正確,只需要把實(shí)例級(jí)別的鎖變成類級(jí)別的鎖

很簡(jiǎn)單只需要把這個(gè)方法變成靜態(tài)方法,synchronized  修飾靜態(tài)方法就是類級(jí)別的鎖。

你用對(duì)鎖了嗎?淺談 Java “鎖” 事

還有一種就是聲明一個(gè)靜態(tài)變量,比較推薦這種,因?yàn)榘逊庆o態(tài)方法變成靜態(tài)方法其實(shí)就等于改了代碼結(jié)構(gòu)了。

你用對(duì)鎖了嗎?淺談 Java “鎖” 事

我們來(lái)小結(jié)一下,使用 synchronized 的時(shí)候需要注意鎖的到底是什么,如果修飾靜態(tài)字段和靜態(tài)方法那就是類級(jí)別的鎖,如果修飾非靜態(tài)字段和非靜態(tài)方法就是實(shí)例級(jí)別的鎖

鎖的粒度

相信大家知道 Hashtable 不被推薦使用,要用就用 ConcurrentHashMap,是因?yàn)?Hashtable 雖然是線程安全的,但是它太粗暴了,它為所有的方法都上了同一把鎖!我們來(lái)看下源碼。

你用對(duì)鎖了嗎?淺談 Java “鎖” 事

你說(shuō)這 contains 和 size 方法有啥關(guān)系?我在調(diào)用 contains 的時(shí)候憑啥不讓我調(diào) size ? 這就是鎖的粒度太粗了我們得評(píng)估一下,不同的方法用不同的鎖,這樣才能在線程安全的情況下再提高并發(fā)度。

但是不同方法不同鎖還不夠的,因?yàn)橛袝r(shí)候一個(gè)方法里面有些操作其實(shí)是線程安全的,只有涉及競(jìng)爭(zhēng)競(jìng)態(tài)資源的那一段代碼才需要加鎖。特別是不需要鎖的代碼很耗時(shí)的情況,就會(huì)長(zhǎng)時(shí)間占著這把鎖,而且其他線程只能排隊(duì)等著,比如下面這段代碼。

你用對(duì)鎖了嗎?淺談 Java “鎖” 事

很明顯第二段代碼才是正常的使用鎖的姿勢(shì),不過(guò)在平時(shí)的業(yè)務(wù)代碼中可不是像我代碼里貼的 sleep 這么容易一眼就看出的,有時(shí)候還需要修改代碼執(zhí)行的順序等等來(lái)保證鎖的粒度足夠細(xì)。

而有時(shí)候又需要保證鎖足夠的粗,不過(guò)這部分JVM會(huì)檢測(cè)到,它會(huì)幫我們做優(yōu)化,比如下面的代碼。

你用對(duì)鎖了嗎?淺談 Java “鎖” 事

可以看到明明是一個(gè)方法里面調(diào)用的邏輯卻經(jīng)歷了加鎖-執(zhí)行A-解鎖-加鎖-執(zhí)行B-解鎖,很明顯的可以看出其實(shí)只需要經(jīng)歷加鎖-執(zhí)行A-執(zhí)行B-解鎖。

所以 JVM 會(huì)在即時(shí)編譯的時(shí)候做鎖的粗化,將鎖的范圍擴(kuò)大,類似變成下面的情況。

你用對(duì)鎖了嗎?淺談 Java “鎖” 事

而且 JVM 還會(huì)有鎖消除的動(dòng)作,通過(guò)逃逸分析判斷實(shí)例對(duì)象是線程私有的,那么肯定是線程安全的,于是就會(huì)忽略對(duì)象里面的加鎖動(dòng)作,直接調(diào)用。

讀寫鎖

讀寫鎖就是我們上面提交的根據(jù)場(chǎng)景減小鎖的粒度了,把一個(gè)鎖拆成了讀鎖和寫鎖,特別適合在讀多寫少的情況下使用,例如自己實(shí)現(xiàn)的一個(gè)緩存。

ReentrantReadWriteLock

讀寫鎖允許多個(gè)線程同時(shí)讀共享變量,但是寫操作是互斥的,即寫寫互斥、讀寫互斥。講白了就是寫的時(shí)候就只能一個(gè)線程寫,其他線程也讀不了也寫不了。

我們來(lái)看個(gè)小例子,里面也有個(gè)小細(xì)節(jié)。這段代碼就是模擬緩存的讀取,先上讀鎖去緩存拿數(shù)據(jù),如果緩存沒(méi)數(shù)據(jù)則釋放讀鎖,再上寫鎖去數(shù)據(jù)庫(kù)取數(shù)據(jù),然后塞入緩存中返回。

你用對(duì)鎖了嗎?淺談 Java “鎖” 事

這里面的小細(xì)節(jié)就是再次判斷 data = getFromCache() 是否有值,因?yàn)橥粫r(shí)刻可能會(huì)有多個(gè)線程調(diào)用getData(),然后緩存都為空因此都去競(jìng)爭(zhēng)寫鎖,最終只有一個(gè)線程會(huì)先拿到寫鎖,然后將數(shù)據(jù)又塞入緩存中。

此時(shí)等待的線程最終一個(gè)個(gè)的都會(huì)拿到寫鎖,獲取寫鎖的時(shí)候其實(shí)緩存里面已經(jīng)有值了所以沒(méi)必要再去數(shù)據(jù)庫(kù)查詢。

當(dāng)然 Lock 的使用范式大家都知道,需要用 try- finally,來(lái)保證一定會(huì)解鎖。而讀寫鎖還有一個(gè)要點(diǎn)需要注意,也就是說(shuō)鎖不能升級(jí)。什么意思呢?我改一下上面的代碼。

你用對(duì)鎖了嗎?淺談 Java “鎖” 事

但是寫鎖內(nèi)可以再用讀鎖,來(lái)實(shí)現(xiàn)鎖的降級(jí),有些人可能會(huì)問(wèn)了這寫鎖都加了還要什么讀鎖。

還是有點(diǎn)用處的,比如某個(gè)線程搶到了寫鎖,在寫的動(dòng)作要完畢的時(shí)候加上讀鎖,接著釋放了寫鎖,此時(shí)它還持有讀鎖可以保證能馬上使用寫鎖操作完的數(shù)據(jù),而別的線程也因?yàn)榇藭r(shí)寫鎖已經(jīng)沒(méi)了也能讀數(shù)據(jù)

其實(shí)就是當(dāng)前已經(jīng)不需要寫鎖這種比較霸道的鎖!所以來(lái)降個(gè)級(jí)讓大家都能讀。

小結(jié)一下,讀寫鎖適用于讀多寫少的情況,無(wú)法升級(jí),但是可以降級(jí)。Lock 的鎖需要配合 try- finally,來(lái)保證一定會(huì)解鎖。

對(duì)了,我再稍稍提一下讀寫鎖的實(shí)現(xiàn),熟悉 AQS 的同學(xué)可能都知道里面的 state ,讀寫鎖就是將這個(gè) int 類型的 state 分成了兩半,高 16 位與低 16 位分別記錄讀鎖和寫鎖的狀態(tài)。它和普通的互斥鎖的區(qū)別就在于要維護(hù)這兩個(gè)狀態(tài)和在等待隊(duì)列處區(qū)別處理這兩種鎖。

所以在不適用于讀寫鎖的場(chǎng)景還不如直接用互斥鎖,因?yàn)樽x寫鎖還需要對(duì)state進(jìn)行位移判斷等等操作。

StampedLock

這玩意我也稍微提一下,是 1.8 提出來(lái)的出鏡率似乎沒(méi)有 ReentrantReadWriteLock 高。它支持寫鎖、悲觀讀鎖和樂(lè)觀讀。寫鎖和悲觀讀鎖其實(shí)和 ReentrantReadWriteLock 里面的讀寫鎖是一致的,它就多了個(gè)樂(lè)觀讀。

從上面的分析我們知道讀寫鎖在讀的時(shí)候其實(shí)是無(wú)法寫的,而 StampedLock 的樂(lè)觀讀則允許一個(gè)線程寫。樂(lè)觀讀其實(shí)就是和我們知道的數(shù)據(jù)庫(kù)樂(lè)觀鎖一樣,數(shù)據(jù)庫(kù)的樂(lè)觀鎖例如通過(guò)一個(gè)version字段來(lái)判斷,例如下面這條 sql。

你用對(duì)鎖了嗎?淺談 Java “鎖” 事

StampedLock 樂(lè)觀讀就是與其類似,我們來(lái)看一下簡(jiǎn)單的用法。

你用對(duì)鎖了嗎?淺談 Java “鎖” 事

它與 ReentrantReadWriteLock 對(duì)比也就強(qiáng)在這里,其他的不行,比如 StampedLock 不支持重入,不支持條件變量。還有一點(diǎn)使用 StampedLock 一定不要調(diào)用中斷操作,因?yàn)闀?huì)導(dǎo)致CPU 100%,我跑了下并發(fā)編程網(wǎng)上面提供的例子,復(fù)現(xiàn)了。

你用對(duì)鎖了嗎?淺談 Java “鎖” 事

具體的原因這里不再贅述,文末會(huì)貼上鏈接,上面說(shuō)的很詳細(xì)了。

所以出來(lái)一個(gè)看似好像很厲害的東西,你需要真正的去理解它,熟悉它才能做到有的放矢。

CopyOnWrite

寫時(shí)復(fù)制的在很多地方也會(huì)用到,比如進(jìn)程 fork() 操作。對(duì)于我們業(yè)務(wù)代碼層面而言也是很有幫助的,在于它的讀操作不會(huì)阻塞寫,寫操作也不會(huì)阻塞讀。適用于讀多寫少的場(chǎng)景。

例如 Java 中的實(shí)現(xiàn) CopyOnWriteArrayList,有人可能一聽(tīng),這玩意線程安全讀的時(shí)候還不會(huì)阻塞寫,好家伙就用它了!

你得先搞清楚,寫時(shí)復(fù)制是會(huì)拷貝一份數(shù)據(jù),你的任何一個(gè)修改動(dòng)作在CopyOnWriteArrayList 中都會(huì)觸發(fā)一次Arrays.copyOf,然后在副本上修改。假如修改的動(dòng)作很多,并且拷貝的數(shù)據(jù)也很大,這將是災(zāi)難!

并發(fā)安全容器

最后再來(lái)談一下并發(fā)安全容器的使用,我就拿相對(duì)而言大家比較熟悉的 ConcurrentHashMap 來(lái)作為例子。我看新來(lái)的同事好像認(rèn)為只要是使用并發(fā)安全容器一定就是線程安全了。其實(shí)不盡然,還得看怎么用。

我們先來(lái)看下以下的代碼,簡(jiǎn)單的說(shuō)就是利用 ConcurrentHashMap 來(lái)記錄每個(gè)人的工資,最多就記錄 100 個(gè)。

你用對(duì)鎖了嗎?淺談 Java “鎖” 事

最終的結(jié)果都會(huì)超標(biāo),即 map 里面不僅僅只記錄了100個(gè)人。那怎么樣結(jié)果才會(huì)是對(duì)的?很簡(jiǎn)單就是加個(gè)鎖。

你用對(duì)鎖了嗎?淺談 Java “鎖” 事

看到這有人說(shuō),你這都加鎖了我還用啥 ConcurrentHashMap ,我 HashMap 加個(gè)鎖也能完事!是的你說(shuō)的沒(méi)錯(cuò)!因?yàn)楫?dāng)前我們的使用場(chǎng)景是復(fù)合型操作,也就是我們先拿 map 的 size 做了判斷,然后再執(zhí)行了 put 方法,ConcurrentHashMap 無(wú)法保證復(fù)合型的操作是線程安全的!

而 ConcurrentHashMap 合適只是用其暴露出來(lái)的線程安全的方法,而不是復(fù)合操作的情況下。比如以下代碼

你用對(duì)鎖了嗎?淺談 Java “鎖” 事

當(dāng)然,我這個(gè)例子不夠恰當(dāng)其實(shí),因?yàn)?ConcurrentHashMap 性能比 HashMap + 鎖高的原因在于分段鎖,需要多個(gè) key 操作才能體現(xiàn)出來(lái),不過(guò)我想突出的重點(diǎn)是使用的時(shí)候不能大意,不能純粹的認(rèn)為用了就線程安全了。

總結(jié)一下

今天談了談并發(fā) BUG 的源頭,即三大問(wèn)題:可見(jiàn)性問(wèn)題、原子性問(wèn)題和有序性問(wèn)題。然后簡(jiǎn)單的說(shuō)了下 synchronized 關(guān)鍵字的注意點(diǎn),即修飾靜態(tài)字段或者靜態(tài)方法是類層面的鎖,而修飾非靜態(tài)字段和非靜態(tài)方法是實(shí)例層面的類。

再說(shuō)了下鎖的粒度,在不同場(chǎng)景定義不同的鎖不能粗暴的一把鎖搞定,并且方法內(nèi)部鎖的粒度要細(xì)。例如在讀多寫少的場(chǎng)景可以使用讀寫鎖、寫時(shí)復(fù)制等。

最終要正確的使用并發(fā)安全容器,不能一味的認(rèn)為使用并發(fā)安全容器就一定線程安全了,要注意復(fù)合操作的場(chǎng)景。

你用對(duì)鎖了嗎?淺談 Java “鎖” 事

當(dāng)然我今天只是淺淺的談了一下,關(guān)于并發(fā)編程其實(shí)還有很多點(diǎn),要寫出線程安全的代碼不是一件容易的事情,就像我之前分析的 Kafka 事件處理全流程一樣,原先的版本就是各種鎖控制并發(fā)安全,到后來(lái)bug根本修不動(dòng),多線程編程難,調(diào)試也難,修bug也難。

因此 Kafka 事件處理模塊最終改成了單線程事件隊(duì)列模式,將涉及到共享數(shù)據(jù)競(jìng)爭(zhēng)相關(guān)方面的訪問(wèn)抽象成事件,將事件塞入阻塞隊(duì)列中,然后單線程處理。

所以在用鎖之前我們要先想想,有必要么?能簡(jiǎn)化么?不然之后維護(hù)起來(lái)有多痛苦到時(shí)候你就知道了。

特別推薦一個(gè)分享架構(gòu)+算法的優(yōu)質(zhì)內(nèi)容,還沒(méi)關(guān)注的小伙伴,可以長(zhǎng)按關(guān)注一下:

你用對(duì)鎖了嗎?淺談 Java “鎖” 事

你用對(duì)鎖了嗎?淺談 Java “鎖” 事

你用對(duì)鎖了嗎?淺談 Java “鎖” 事

長(zhǎng)按訂閱更多精彩▼

你用對(duì)鎖了嗎?淺談 Java “鎖” 事

如有收獲,點(diǎn)個(gè)在看,誠(chéng)摯感謝

免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。文章僅代表作者個(gè)人觀點(diǎn),不代表本平臺(tái)立場(chǎng),如有問(wèn)題,請(qǐng)聯(lián)系我們,謝謝!

本站聲明: 本文章由作者或相關(guān)機(jī)構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點(diǎn),本站亦不保證或承諾內(nèi)容真實(shí)性等。需要轉(zhuǎn)載請(qǐng)聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請(qǐng)及時(shí)聯(lián)系本站刪除。
關(guān)閉
關(guān)閉