這篇文章主要介紹Java并發(fā)之ReentrantLock源碼的示例分析,文中介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們一定要看完!
創(chuàng)新互聯(lián)專業(yè)提供大邑服務(wù)器托管服務(wù),為用戶提供五星數(shù)據(jù)中心、電信、雙線接入解決方案,用戶可自行在線購(gòu)買(mǎi)大邑服務(wù)器托管服務(wù),并享受7*24小時(shí)金牌售后服務(wù)。在Java5.0之前,協(xié)調(diào)對(duì)共享對(duì)象的訪問(wèn)可以使用的機(jī)制只有synchronized和volatile。我們知道synchronized關(guān)鍵字實(shí)現(xiàn)了內(nèi)置鎖,而volatile關(guān)鍵字保證了多線程的內(nèi)存可見(jiàn)性。在大多數(shù)情況下,這些機(jī)制都能很好地完成工作,但卻無(wú)法實(shí)現(xiàn)一些更高級(jí)的功能,例如,無(wú)法中斷一個(gè)正在等待獲取鎖的線程,無(wú)法實(shí)現(xiàn)限定時(shí)間的獲取鎖機(jī)制,無(wú)法實(shí)現(xiàn)非阻塞結(jié)構(gòu)的加鎖規(guī)則等。而這些更靈活的加鎖機(jī)制通常都能夠提供更好的活躍性或性能。因此,在Java5.0中增加了一種新的機(jī)制:ReentrantLock。ReentrantLock類(lèi)實(shí)現(xiàn)了Lock接口,并提供了與synchronized相同的互斥性和內(nèi)存可見(jiàn)性,它的底層是通過(guò)AQS來(lái)實(shí)現(xiàn)多線程同步的。與內(nèi)置鎖相比ReentrantLock不僅提供了更豐富的加鎖機(jī)制,而且在性能上也不遜色于內(nèi)置鎖(在以前的版本中甚至優(yōu)于內(nèi)置鎖)。說(shuō)了ReentrantLock這么多的優(yōu)點(diǎn),那么下面我們就來(lái)揭開(kāi)它的源碼看看它的具體實(shí)現(xiàn)。
1.synchronized關(guān)鍵字的介紹
Java提供了內(nèi)置鎖來(lái)支持多線程的同步,JVM根據(jù)synchronized關(guān)鍵字來(lái)標(biāo)識(shí)同步代碼塊,當(dāng)線程進(jìn)入同步代碼塊時(shí)會(huì)自動(dòng)獲取鎖,退出同步代碼塊時(shí)會(huì)自動(dòng)釋放鎖,一個(gè)線程獲得鎖后其他線程將會(huì)被阻塞。每個(gè)Java對(duì)象都可以用做一個(gè)實(shí)現(xiàn)同步的鎖,synchronized關(guān)鍵字可以用來(lái)修飾對(duì)象方法,靜態(tài)方法和代碼塊,當(dāng)修飾對(duì)象方法和靜態(tài)方法時(shí)鎖分別是方法所在的對(duì)象和Class對(duì)象,當(dāng)修飾代碼塊時(shí)需提供額外的對(duì)象作為鎖。每個(gè)Java對(duì)象之所以可以作為鎖,是因?yàn)樵趯?duì)象頭中關(guān)聯(lián)了一個(gè)monitor對(duì)象(管程),線程進(jìn)入同步代碼塊時(shí)會(huì)自動(dòng)持有monitor對(duì)象,退出時(shí)會(huì)自動(dòng)釋放monitor對(duì)象,當(dāng)monitor對(duì)象被持有時(shí)其他線程將會(huì)被阻塞。當(dāng)然這些同步操作都由JVM底層幫你實(shí)現(xiàn)了,但以synchronized關(guān)鍵字修飾的方法和代碼塊在底層實(shí)現(xiàn)上還是有些區(qū)別的。synchronized關(guān)鍵字修飾的方法是隱式同步的,即無(wú)需通過(guò)字節(jié)碼指令來(lái)控制的,JVM可以根據(jù)方法表中的ACC_SYNCHRONIZED訪問(wèn)標(biāo)志來(lái)區(qū)分一個(gè)方法是否是同步方法;而synchronized關(guān)鍵字修飾的代碼塊是顯式同步的,它是通過(guò)monitorenter和monitorexit字節(jié)碼指令來(lái)控制線程對(duì)管程的持有和釋放。monitor對(duì)象內(nèi)部持有_count字段,_count等于0表示管程未被持有,_count大于0表示管程已被持有,每次持有線程重入時(shí)_count都會(huì)加1,每次持有線程退出時(shí)_count都會(huì)減1,這就是內(nèi)置鎖重入性的實(shí)現(xiàn)原理。另外,monitor對(duì)象內(nèi)部還有兩條隊(duì)列_EntryList和_WaitSet,對(duì)應(yīng)著AQS的同步隊(duì)列和條件隊(duì)列,當(dāng)線程獲取鎖失敗時(shí)會(huì)到_EntryList中阻塞,當(dāng)調(diào)用鎖對(duì)象的wait方法時(shí)線程將會(huì)進(jìn)入_WaitSet中等待,這是內(nèi)置鎖的線程同步和條件等待的實(shí)現(xiàn)原理。
2.ReentrantLock和Synchronized的比較
synchronized關(guān)鍵字是Java提供的內(nèi)置鎖機(jī)制,其同步操作由底層JVM實(shí)現(xiàn),而ReentrantLock是java.util.concurrent包提供的顯式鎖,其同步操作由AQS同步器提供支持。ReentrantLock在加鎖和內(nèi)存上提供的語(yǔ)義與內(nèi)置鎖相同,此外它還提供了一些其他功能,包括定時(shí)的鎖等待,可中斷的鎖等待,公平鎖,以及實(shí)現(xiàn)非塊結(jié)構(gòu)的加鎖。另外,在早期的JDK版本中ReentrantLock在性能上還占有一定的優(yōu)勢(shì),既然ReentrantLock擁有這么多優(yōu)勢(shì),為什么還要使用synchronized關(guān)鍵字呢?事實(shí)上確實(shí)有許多人使用ReentrantLock來(lái)替代synchronized關(guān)鍵字的加鎖操作。但是內(nèi)置鎖仍然有它特有的優(yōu)勢(shì),內(nèi)置鎖為許多開(kāi)發(fā)人員所熟悉,使用方式也更加的簡(jiǎn)潔緊湊,因?yàn)轱@式鎖必須手動(dòng)在finally塊中調(diào)用unlock,所以使用內(nèi)置鎖相對(duì)來(lái)說(shuō)會(huì)更加安全些。同時(shí)未來(lái)更加可能會(huì)去提升synchronized而不是ReentrantLock的性能。因?yàn)閟ynchronized是JVM的內(nèi)置屬性,它能執(zhí)行一些優(yōu)化,例如對(duì)線程封閉的鎖對(duì)象的鎖消除優(yōu)化,通過(guò)增加鎖的粒度來(lái)消除內(nèi)置鎖的同步,而如果通過(guò)基于類(lèi)庫(kù)的鎖來(lái)實(shí)現(xiàn)這些功能,則可能性不大。所以當(dāng)需要一些高級(jí)功能時(shí)才應(yīng)該使用ReentrantLock,這些功能包括:可定時(shí)的,可輪詢的與可中斷的鎖獲取操作,公平隊(duì)列,以及非塊結(jié)構(gòu)的鎖。否則,還是應(yīng)該優(yōu)先使用synchronized。
3.獲取鎖和釋放鎖的操作
我們首先來(lái)看一下使用ReentrantLock加鎖的示例代碼。
public void doSomething() { //默認(rèn)是獲取一個(gè)非公平鎖 ReentrantLock lock = new ReentrantLock(); try{ //執(zhí)行前先加鎖 lock.lock(); //執(zhí)行操作... }finally{ //最后釋放鎖 lock.unlock(); } }
以下是獲取鎖和釋放鎖這兩個(gè)操作的API。
//獲取鎖的操作 public void lock() { sync.lock(); } //釋放鎖的操作 public void unlock() { sync.release(1); }
可以看到獲取鎖和釋放鎖的操作分別委托給Sync對(duì)象的lock方法和release方法。
public class ReentrantLock implements Lock, java.io.Serializable { private final Sync sync; abstract static class Sync extends AbstractQueuedSynchronizer { abstract void lock(); } //實(shí)現(xiàn)非公平鎖的同步器 static final class NonfairSync extends Sync { final void lock() { ... } } //實(shí)現(xiàn)公平鎖的同步器 static final class FairSync extends Sync { final void lock() { ... } } }
每個(gè)ReentrantLock對(duì)象都持有一個(gè)Sync類(lèi)型的引用,這個(gè)Sync類(lèi)是一個(gè)抽象內(nèi)部類(lèi)它繼承自AbstractQueuedSynchronizer,它里面的lock方法是一個(gè)抽象方法。ReentrantLock的成員變量sync是在構(gòu)造時(shí)賦值的,下面我們看看ReentrantLock的兩個(gè)構(gòu)造方法都做了些什么?
//默認(rèn)無(wú)參構(gòu)造器 public ReentrantLock() { sync = new NonfairSync(); } //有參構(gòu)造器 public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }
調(diào)用默認(rèn)無(wú)參構(gòu)造器會(huì)將NonfairSync實(shí)例賦值給sync,此時(shí)鎖是非公平鎖。有參構(gòu)造器允許通過(guò)參數(shù)來(lái)指定是將FairSync實(shí)例還是NonfairSync實(shí)例賦值給sync。NonfairSync和FairSync都是繼承自Sync類(lèi)并重寫(xiě)了lock()方法,所以公平鎖和非公平鎖在獲取鎖的方式上有些區(qū)別,這個(gè)我們下面會(huì)講到。再來(lái)看看釋放鎖的操作,每次調(diào)用unlock()方法都只是去執(zhí)行sync.release(1)操作,這步操作會(huì)調(diào)用AbstractQueuedSynchronizer類(lèi)的release()方法,我們?cè)賮?lái)回顧一下。
//釋放鎖的操作(獨(dú)占模式) public final boolean release(int arg) { //撥動(dòng)密碼鎖, 看看是否能夠開(kāi)鎖 if (tryRelease(arg)) { //獲取head結(jié)點(diǎn) Node h = head; //如果head結(jié)點(diǎn)不為空并且等待狀態(tài)不等于0就去喚醒后繼結(jié)點(diǎn) if (h != null && h.waitStatus != 0) { //喚醒后繼結(jié)點(diǎn) unparkSuccessor(h); } return true; } return false; }
這個(gè)release方法是AQS提供的釋放鎖操作的API,它首先會(huì)去調(diào)用tryRelease方法去嘗試獲取鎖,tryRelease方法是抽象方法,它的實(shí)現(xiàn)邏輯在子類(lèi)Sync里面。
//嘗試釋放鎖 protected final boolean tryRelease(int releases) { int c = getState() - releases; //如果持有鎖的線程不是當(dāng)前線程就拋出異常 if (Thread.currentThread() != getExclusiveOwnerThread()) { throw new IllegalMonitorStateException(); } boolean free = false; //如果同步狀態(tài)為0則表明鎖被釋放 if (c == 0) { //設(shè)置鎖被釋放的標(biāo)志為真 free = true; //設(shè)置占用線程為空 setExclusiveOwnerThread(null); } setState(c); return free; }
這個(gè)tryRelease方法首先會(huì)獲取當(dāng)前同步狀態(tài),并將當(dāng)前同步狀態(tài)減去傳入的參數(shù)值得到新的同步狀態(tài),然后判斷新的同步狀態(tài)是否等于0,如果等于0則表明當(dāng)前鎖被釋放,然后先將鎖的釋放狀態(tài)置為真,再將當(dāng)前占有鎖的線程清空,最后調(diào)用setState方法設(shè)置新的同步狀態(tài)并返回鎖的釋放狀態(tài)。
4.公平鎖和非公平鎖
我們知道ReentrantLock是公平鎖還是非公平鎖是基于sync指向的是哪個(gè)具體實(shí)例。在構(gòu)造時(shí)會(huì)為成員變量sync賦值,如果賦值為NonfairSync實(shí)例則表明是非公平鎖,如果賦值為FairSync實(shí)例則表明為公平鎖。如果是公平鎖,線程將按照它們發(fā)出請(qǐng)求的順序來(lái)獲得鎖,但在非公平鎖上,則允許插隊(duì)行為:當(dāng)一個(gè)線程請(qǐng)求非公平的鎖時(shí),如果在發(fā)出請(qǐng)求的同時(shí)該鎖的狀態(tài)變?yōu)榭捎?,那么這個(gè)線程將跳過(guò)隊(duì)列中所有等待的線程直接獲得這個(gè)鎖。下面我們先看看非公平鎖的獲取方式。
//非公平同步器 static final class NonfairSync extends Sync { //實(shí)現(xiàn)父類(lèi)的抽象獲取鎖的方法 final void lock() { //使用CAS方式設(shè)置同步狀態(tài) if (compareAndSetState(0, 1)) { //如果設(shè)置成功則表明鎖沒(méi)被占用 setExclusiveOwnerThread(Thread.currentThread()); } else { //否則表明鎖已經(jīng)被占用, 調(diào)用acquire讓線程去同步隊(duì)列排隊(duì)獲取 acquire(1); } } //嘗試獲取鎖的方法 protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); } } //以不可中斷模式獲取鎖(獨(dú)占模式) public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) { selfInterrupt(); } }
可以看到在非公平鎖的lock方法中,線程第一步就會(huì)以CAS方式將同步狀態(tài)的值從0改為1。其實(shí)這步操作就等于去嘗試獲取鎖,如果更改成功則表明線程剛來(lái)就獲取了鎖,而不必再去同步隊(duì)列里面排隊(duì)了。如果更改失敗則表明線程剛來(lái)時(shí)鎖還未被釋放,所以接下來(lái)就調(diào)用acquire方法。我們知道這個(gè)acquire方法是繼承自AbstractQueuedSynchronizer的方法,現(xiàn)在再來(lái)回顧一下該方法,線程進(jìn)入acquire方法后首先去調(diào)用tryAcquire方法嘗試去獲取鎖,由于NonfairSync覆蓋了tryAcquire方法,并在方法中調(diào)用了父類(lèi)Sync的nonfairTryAcquire方法,所以這里會(huì)調(diào)用到nonfairTryAcquire方法去嘗試獲取鎖。我們看看這個(gè)方法具體做了些什么。
//非公平的獲取鎖 final boolean nonfairTryAcquire(int acquires) { //獲取當(dāng)前線程 final Thread current = Thread.currentThread(); //獲取當(dāng)前同步狀態(tài) int c = getState(); //如果同步狀態(tài)為0則表明鎖沒(méi)有被占用 if (c == 0) { //使用CAS更新同步狀態(tài) if (compareAndSetState(0, acquires)) { //設(shè)置目前占用鎖的線程 setExclusiveOwnerThread(current); return true; } //否則的話就判斷持有鎖的是否是當(dāng)前線程 }else if (current == getExclusiveOwnerThread()) { //如果鎖是被當(dāng)前線程持有的, 就直接修改當(dāng)前同步狀態(tài) int nextc = c + acquires; if (nextc < 0) { throw new Error("Maximum lock count exceeded"); } setState(nextc); return true; } //如果持有鎖的不是當(dāng)前線程則返回失敗標(biāo)志 return false; }
nonfairTryAcquire方法是Sync的方法,我們可以看到線程進(jìn)入此方法后首先去獲取同步狀態(tài),如果同步狀態(tài)為0就使用CAS操作更改同步狀態(tài),其實(shí)這又是獲取了一遍鎖。如果同步狀態(tài)不為0表明鎖被占用,此時(shí)會(huì)先去判斷持有鎖的線程是否是當(dāng)前線程,如果是的話就將同步狀態(tài)加1,否則的話這次嘗試獲取鎖的操作宣告失敗。于是會(huì)調(diào)用addWaiter方法將線程添加到同步隊(duì)列。綜上來(lái)看,在非公平鎖的模式下一個(gè)線程在進(jìn)入同步隊(duì)列之前會(huì)嘗試獲取兩遍鎖,如果獲取成功則不進(jìn)入同步隊(duì)列排隊(duì),否則才進(jìn)入同步隊(duì)列排隊(duì)。接下來(lái)我們看看公平鎖的獲取方式。
//實(shí)現(xiàn)公平鎖的同步器 static final class FairSync extends Sync { //實(shí)現(xiàn)父類(lèi)的抽象獲取鎖的方法 final void lock() { //調(diào)用acquire讓線程去同步隊(duì)列排隊(duì)獲取 acquire(1); } //嘗試獲取鎖的方法 protected final boolean tryAcquire(int acquires) { //獲取當(dāng)前線程 final Thread current = Thread.currentThread(); //獲取當(dāng)前同步狀態(tài) int c = getState(); //如果同步狀態(tài)0則表示鎖沒(méi)被占用 if (c == 0) { //判斷同步隊(duì)列是否有前繼結(jié)點(diǎn) if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { //如果沒(méi)有前繼結(jié)點(diǎn)且設(shè)置同步狀態(tài)成功就表示獲取鎖成功 setExclusiveOwnerThread(current); return true; } //否則判斷是否是當(dāng)前線程持有鎖 }else if (current == getExclusiveOwnerThread()) { //如果是當(dāng)前線程持有鎖就直接修改同步狀態(tài) int nextc = c + acquires; if (nextc < 0) { throw new Error("Maximum lock count exceeded"); } setState(nextc); return true; } //如果不是當(dāng)前線程持有鎖則獲取失敗 return false; } }
調(diào)用公平鎖的lock方法時(shí)會(huì)直接調(diào)用acquire方法。同樣的,acquire方法首先會(huì)調(diào)用FairSync重寫(xiě)的tryAcquire方法來(lái)嘗試獲取鎖。在該方法中也是首先獲取同步狀態(tài)的值,如果同步狀態(tài)為0則表明此時(shí)鎖剛好被釋放,這時(shí)和非公平鎖不同的是它會(huì)先去調(diào)用hasQueuedPredecessors方法查詢同步隊(duì)列中是否有人在排隊(duì),如果沒(méi)人在排隊(duì)才會(huì)去修改同步狀態(tài)的值,可以看到公平鎖在這里采取禮讓的方式而不是自己馬上去獲取鎖。除了這一步和非公平鎖不一樣之外,其他的操作都是一樣的。綜上所述,可以看到公平鎖在進(jìn)入同步隊(duì)列之前只檢查了一遍鎖的狀態(tài),即使是發(fā)現(xiàn)了鎖是開(kāi)的也不會(huì)自己馬上去獲取,而是先讓同步隊(duì)列中的線程先獲取,所以可以保證在公平鎖下所有線程獲取鎖的順序都是先來(lái)后到的,這也保證了獲取鎖的公平性。
那么我們?yōu)槭裁床幌M墟i都是公平的呢?畢竟公平是一種好的行為,而不公平是一種不好的行為。由于線程的掛起和喚醒操作存在較大的開(kāi)銷(xiāo)而影響系統(tǒng)性能,特別是在競(jìng)爭(zhēng)激烈的情況下公平鎖將導(dǎo)致線程頻繁的掛起和喚醒操作,而非公平鎖可以減少這樣的操作,所以在性能上將會(huì)優(yōu)于公平鎖。另外,由于大部分線程使用鎖的時(shí)間都是非常短暫的,而線程的喚醒操作會(huì)存在延時(shí)情況,有可能在A線程被喚醒期間B線程馬上獲取了鎖并使用完釋放了鎖,這就導(dǎo)致了雙贏的局面,A線程獲取鎖的時(shí)刻并沒(méi)有推遲,但B線程提前使用了鎖,并且吞吐量也獲得了提高。
5.條件隊(duì)列的實(shí)現(xiàn)機(jī)制
內(nèi)置條件隊(duì)列存在一些缺陷,每個(gè)內(nèi)置鎖都只能有一個(gè)相關(guān)聯(lián)的條件隊(duì)列,這導(dǎo)致多個(gè)線程可能在同一個(gè)條件隊(duì)列上等待不同的條件謂詞,那么每次調(diào)用notifyAll時(shí)都會(huì)將所有等待的線程喚醒,當(dāng)線程醒來(lái)后發(fā)現(xiàn)并不是自己等待的條件謂詞,轉(zhuǎn)而又會(huì)被掛起。這導(dǎo)致做了很多無(wú)用的線程喚醒和掛起操作,而這些操作將會(huì)大量浪費(fèi)系統(tǒng)資源,降低系統(tǒng)的性能。如果想編寫(xiě)一個(gè)帶有多個(gè)條件謂詞的并發(fā)對(duì)象,或者想獲得除了條件隊(duì)列可見(jiàn)性之外的更多控制權(quán),就需要使用顯式的Lock和Condition而不是內(nèi)置鎖和條件隊(duì)列。一個(gè)Condition和一個(gè)Lock關(guān)聯(lián)在一起,就像一個(gè)條件隊(duì)列和一個(gè)內(nèi)置鎖相關(guān)聯(lián)一樣。要?jiǎng)?chuàng)建一個(gè)Condition,可以在相關(guān)聯(lián)的Lock上調(diào)用Lock.newCondition方法。我們先來(lái)看一個(gè)使用Condition的示例。
public class BoundedBuffer { final Lock lock = new ReentrantLock(); final Condition notFull = lock.newCondition(); //條件謂詞:notFull final Condition notEmpty = lock.newCondition(); //條件謂詞:notEmpty final Object[] items = new Object[100]; int putptr, takeptr, count; //生產(chǎn)方法 public void put(Object x) throws InterruptedException { lock.lock(); try { while (count == items.length) notFull.await(); //隊(duì)列已滿, 線程在notFull隊(duì)列上等待 items[putptr] = x; if (++putptr == items.length) putptr = 0; ++count; notEmpty.signal(); //生產(chǎn)成功, 喚醒notEmpty隊(duì)列的結(jié)點(diǎn) } finally { lock.unlock(); } } //消費(fèi)方法 public Object take() throws InterruptedException { lock.lock(); try { while (count == 0) notEmpty.await(); //隊(duì)列為空, 線程在notEmpty隊(duì)列上等待 Object x = items[takeptr]; if (++takeptr == items.length) takeptr = 0; --count; notFull.signal(); //消費(fèi)成功, 喚醒notFull隊(duì)列的結(jié)點(diǎn) return x; } finally { lock.unlock(); } } }
一個(gè)lock對(duì)象可以產(chǎn)生多個(gè)條件隊(duì)列,這里產(chǎn)生了兩個(gè)條件隊(duì)列notFull和notEmpty。當(dāng)容器已滿時(shí)再調(diào)用put方法的線程需要進(jìn)行阻塞,等待條件謂詞為真(容器不滿)才醒來(lái)繼續(xù)執(zhí)行;當(dāng)容器為空時(shí)再調(diào)用take方法的線程也需要阻塞,等待條件謂詞為真(容器不空)才醒來(lái)繼續(xù)執(zhí)行。這兩類(lèi)線程是根據(jù)不同的條件謂詞進(jìn)行等待的,所以它們會(huì)進(jìn)入兩個(gè)不同的條件隊(duì)列中阻塞,等到合適時(shí)機(jī)再通過(guò)調(diào)用Condition對(duì)象上的API進(jìn)行喚醒。下面是newCondition方法的實(shí)現(xiàn)代碼。
//創(chuàng)建條件隊(duì)列 public Condition newCondition() { return sync.newCondition(); } abstract static class Sync extends AbstractQueuedSynchronizer { //新建Condition對(duì)象 final ConditionObject newCondition() { return new ConditionObject(); } }
ReentrantLock上的條件隊(duì)列的實(shí)現(xiàn)都是基于AbstractQueuedSynchronizer的,我們?cè)谡{(diào)用newCondition方法時(shí)所獲得的Condition對(duì)象就是AQS的內(nèi)部類(lèi)ConditionObject的實(shí)例。所有對(duì)條件隊(duì)列的操作都是通過(guò)調(diào)用ConditionObject對(duì)外提供的API來(lái)完成的。有關(guān)于ConditionObject的具體實(shí)現(xiàn)大家可以查閱我的這篇文章《Java并發(fā)系列[4]----AbstractQueuedSynchronizer源碼分析之條件隊(duì)列》 ,這里就不重復(fù)贅述了。至此,我們對(duì)ReentrantLock源碼的剖析也告一段落,希望閱讀本篇文章能夠?qū)ψx者們理解并掌握ReentrantLock起到一定的幫助作用。
以上是“Java并發(fā)之ReentrantLock源碼的示例分析”這篇文章的所有內(nèi)容,感謝各位的閱讀!希望分享的內(nèi)容對(duì)大家有幫助,更多相關(guān)知識(shí),歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道!