中文字幕日韩精品一区二区免费_精品一区二区三区国产精品无卡在_国精品无码专区一区二区三区_国产αv三级中文在线

死磕Synchronized底層實(shí)現(xiàn),面試你還怕什么?

關(guān)于synchronized的底層實(shí)現(xiàn),網(wǎng)上有很多文章了。但是很多文章要么作者根本沒看代碼,僅僅是根據(jù)網(wǎng)上其他文章總結(jié)、照搬而成,難免有些錯(cuò)誤;要么很多點(diǎn)都是一筆帶過,對(duì)于為什么這樣實(shí)現(xiàn)沒有一個(gè)說法,讓像我這樣的讀者意猶未盡。

我們提供的服務(wù)有:成都網(wǎng)站制作、成都網(wǎng)站建設(shè)、外貿(mào)營銷網(wǎng)站建設(shè)、微信公眾號(hào)開發(fā)、網(wǎng)站優(yōu)化、網(wǎng)站認(rèn)證、晉中ssl等。為1000多家企事業(yè)單位解決了網(wǎng)站和推廣的問題。提供周到的售前咨詢和貼心的售后服務(wù),是有科學(xué)管理、有技術(shù)的晉中網(wǎng)站制作公司

本系列文章將對(duì)HotSpot的synchronized鎖實(shí)現(xiàn)進(jìn)行全面分析,內(nèi)容包括偏向鎖、輕量級(jí)鎖、重量級(jí)鎖的加鎖、解鎖、鎖升級(jí)流程的原理及源碼分析,希望給在研究synchronized路上的同學(xué)一些幫助。

?

大概花費(fèi)了兩周的實(shí)現(xiàn)看代碼(花費(fèi)了這么久時(shí)間有些懺愧,主要是對(duì)C++、JVM底層機(jī)制、JVM調(diào)試以及匯編代碼不太熟),將synchronized涉及到的代碼基本都看了一遍,其中還包括在JVM中添加日志驗(yàn)證自己的猜想,總的來說目前對(duì)synchronized這塊有了一個(gè)比較全面清晰的認(rèn)識(shí),但水平有限,有些細(xì)節(jié)難免有些疏漏,還望請(qǐng)大家指正。

本篇文章將對(duì)synchronized機(jī)制做個(gè)大致的介紹,包括用以承載鎖狀態(tài)的對(duì)象頭、鎖的幾種形式、各種形式鎖的加鎖和解鎖流程、什么時(shí)候會(huì)發(fā)生鎖升級(jí)。需要注意的是本文旨在介紹背景和概念,在講述一些流程的時(shí)候,只提到了主要case,對(duì)于實(shí)現(xiàn)細(xì)節(jié)、運(yùn)行時(shí)的不同分支都在后面的文章中詳細(xì)分析。

本人看的JVM版本是jdk8u,具體版本號(hào)以及代碼可以在這里看到。

synchronized簡(jiǎn)介

Java中提供了兩種實(shí)現(xiàn)同步的基礎(chǔ)語義:synchronized方法和synchronized塊, 我們來看個(gè)demo:

public?class?SyncTest?{
????public?void?syncBlock(){
????????synchronized?(this){
????????????System.out.println("hello?block");
????????}
????}
????public?synchronized?void?syncMethod(){
????????System.out.println("hello?method");
????}
}

當(dāng)SyncTest.java被編譯成class文件的時(shí)候,synchronized關(guān)鍵字和synchronized方法的字節(jié)碼略有不同,我們可以用javap -v?命令查看class文件對(duì)應(yīng)的JVM字節(jié)碼信息,部分信息如下:

{
??public?void?syncBlock();
????descriptor:?()V
????flags:?ACC_PUBLIC
????Code:
??????stack=2,?locals=3,?args_size=1
?????????0:?aload_0
?????????1:?dup
?????????2:?astore_1
?????????3:?monitorenter				?	??//?monitorenter指令進(jìn)入同步塊
?????????4:?getstatic?????#2??????????????????//?Field?java/lang/System.out:Ljava/io/PrintStream;
?????????7:?ldc???????????#3??????????????????//?String?hello?block
?????????9:?invokevirtual?#4??????????????????//?Method?java/io/PrintStream.println:(Ljava/lang/String;)V
????????12:?aload_1
????????13:?monitorexit						??//?monitorexit指令退出同步塊
????????14:?goto??????????22
????????17:?astore_2
????????18:?aload_1
????????19:?monitorexit						??//?monitorexit指令退出同步塊
????????20:?aload_2
????????21:?athrow
????????22:?return
??????Exception?table:
?????????from????to??target?type
?????????????4????14????17???any
????????????17????20????17???any
?

??public?synchronized?void?syncMethod();
????descriptor:?()V
????flags:?ACC_PUBLIC,?ACC_SYNCHRONIZED??????//添加了ACC_SYNCHRONIZED標(biāo)記
????Code:
??????stack=2,?locals=1,?args_size=1
?????????0:?getstatic?????#2??????????????????//?Field?java/lang/System.out:Ljava/io/PrintStream;
?????????3:?ldc???????????#5??????????????????//?String?hello?method
?????????5:?invokevirtual?#4??????????????????//?Method?java/io/PrintStream.println:(Ljava/lang/String;)V
?????????8:?return
?
}

從上面的中文注釋處可以看到,對(duì)于synchronized關(guān)鍵字而言,javac在編譯時(shí),會(huì)生成對(duì)應(yīng)的monitorentermonitorexit指令分別對(duì)應(yīng)synchronized同步塊的進(jìn)入和退出,有兩個(gè)monitorexit指令的原因是:為了保證拋異常的情況下也能釋放鎖,所以javac為同步代碼塊添加了一個(gè)隱式的try-finally,在finally中會(huì)調(diào)用monitorexit命令釋放鎖。而對(duì)于synchronized方法而言,javac為其生成了一個(gè)ACC_SYNCHRONIZED關(guān)鍵字,在JVM進(jìn)行方法調(diào)用時(shí),發(fā)現(xiàn)調(diào)用的方法被ACC_SYNCHRONIZED修飾,則會(huì)先嘗試獲得鎖。

在JVM底層,對(duì)于這兩種synchronized語義的實(shí)現(xiàn)大致相同,在后文中會(huì)選擇一種進(jìn)行詳細(xì)分析。

因?yàn)楸疚闹荚诜治?code>synchronized的實(shí)現(xiàn)原理,因此對(duì)于其使用的一些問題就不贅述了,不了解的朋友可以看看這篇文章。

鎖的幾種形式

傳統(tǒng)的鎖(也就是下文要說的重量級(jí)鎖)依賴于系統(tǒng)的同步函數(shù),在linux上使用mutex互斥鎖,最底層實(shí)現(xiàn)依賴于futex,關(guān)于futex可以看我之前的文章,這些同步函數(shù)都涉及到用戶態(tài)和內(nèi)核態(tài)的切換、進(jìn)程的上下文切換,成本較高。對(duì)于加了synchronized關(guān)鍵字但運(yùn)行時(shí)并沒有多線程競(jìng)爭(zhēng),或兩個(gè)線程接近于交替執(zhí)行的情況,使用傳統(tǒng)鎖機(jī)制無疑效率是會(huì)比較低的。

在JDK 1.6之前,synchronized只有傳統(tǒng)的鎖機(jī)制,因此給開發(fā)者留下了synchronized關(guān)鍵字相比于其他同步機(jī)制性能不好的印象。

在JDK 1.6引入了兩種新型鎖機(jī)制:偏向鎖和輕量級(jí)鎖,它們的引入是為了解決在沒有多線程競(jìng)爭(zhēng)或基本沒有競(jìng)爭(zhēng)的場(chǎng)景下因使用傳統(tǒng)鎖機(jī)制帶來的性能開銷問題。

在看這幾種鎖機(jī)制的實(shí)現(xiàn)前,我們先來了解下對(duì)象頭,它是實(shí)現(xiàn)多種鎖機(jī)制的基礎(chǔ)。

對(duì)象頭

因?yàn)樵贘ava中任意對(duì)象都可以用作鎖,因此必定要有一個(gè)映射關(guān)系,存儲(chǔ)該對(duì)象以及其對(duì)應(yīng)的鎖信息(比如當(dāng)前哪個(gè)線程持有鎖,哪些線程在等待)。一種很直觀的方法是,用一個(gè)全局map,來存儲(chǔ)這個(gè)映射關(guān)系,但這樣會(huì)有一些問題:需要對(duì)map做線程安全保障,不同的synchronized之間會(huì)相互影響,性能差;另外當(dāng)同步對(duì)象較多時(shí),該map可能會(huì)占用比較多的內(nèi)存。

所以最好的辦法是將這個(gè)映射關(guān)系存儲(chǔ)在對(duì)象頭中,因?yàn)閷?duì)象頭本身也有一些hashcode、GC相關(guān)的數(shù)據(jù),所以如果能將鎖信息與這些信息共存在對(duì)象頭中就好了。

在JVM中,對(duì)象在內(nèi)存中除了本身的數(shù)據(jù)外還會(huì)有個(gè)對(duì)象頭,對(duì)于普通對(duì)象而言,其對(duì)象頭中有兩類信息:mark word和類型指針。另外對(duì)于數(shù)組而言還會(huì)有一份記錄數(shù)組長(zhǎng)度的數(shù)據(jù)。

類型指針是指向該對(duì)象所屬類對(duì)象的指針,mark word用于存儲(chǔ)對(duì)象的HashCode、GC分代年齡、鎖狀態(tài)等信息。在32位系統(tǒng)上mark word長(zhǎng)度為32bit,64位系統(tǒng)上長(zhǎng)度為64bit。為了能在有限的空間里存儲(chǔ)下更多的數(shù)據(jù),其存儲(chǔ)格式是不固定的,在32位系統(tǒng)上各狀態(tài)的格式如下:

死磕Synchronized底層實(shí)現(xiàn),面試你還怕什么?

可以看到鎖信息也是存在于對(duì)象的mark word中的。當(dāng)對(duì)象狀態(tài)為偏向鎖(biasable)時(shí),mark word存儲(chǔ)的是偏向的線程ID;當(dāng)狀態(tài)為輕量級(jí)鎖(lightweight locked)時(shí),mark word存儲(chǔ)的是指向線程棧中Lock Record的指針;當(dāng)狀態(tài)為重量級(jí)鎖(inflated)時(shí),為指向堆中的monitor對(duì)象的指針。

重量級(jí)鎖

重量級(jí)鎖是我們常說的傳統(tǒng)意義上的鎖,其利用操作系統(tǒng)底層的同步機(jī)制去實(shí)現(xiàn)Java中的線程同步。

重量級(jí)鎖的狀態(tài)下,對(duì)象的mark word為指向一個(gè)堆中monitor對(duì)象的指針。

一個(gè)monitor對(duì)象包括這么幾個(gè)關(guān)鍵字段:cxq(下圖中的ContentionList),EntryList ,WaitSet,owner。

其中cxq ,EntryList ,WaitSet都是由ObjectWaiter的鏈表結(jié)構(gòu),owner指向持有鎖的線程。

死磕Synchronized底層實(shí)現(xiàn),面試你還怕什么?

當(dāng)一個(gè)線程嘗試獲得鎖時(shí),如果該鎖已經(jīng)被占用,則會(huì)將該線程封裝成一個(gè)ObjectWaiter對(duì)象插入到cxq的隊(duì)列尾部,然后暫停當(dāng)前線程。當(dāng)持有鎖的線程釋放鎖前,會(huì)將cxq中的所有元素移動(dòng)到EntryList中去,并喚醒EntryList的隊(duì)首線程。

如果一個(gè)線程在同步塊中調(diào)用了Object#wait方法,會(huì)將該線程對(duì)應(yīng)的ObjectWaiter從EntryList移除并加入到WaitSet中,然后釋放鎖。當(dāng)wait的線程被notify之后,會(huì)將對(duì)應(yīng)的ObjectWaiter從WaitSet移動(dòng)到EntryList中。

以上只是對(duì)重量級(jí)鎖流程的一個(gè)簡(jiǎn)述,其中涉及到的很多細(xì)節(jié),比如ObjectMonitor對(duì)象從哪來?釋放鎖時(shí)是將cxq中的元素移動(dòng)到EntryList的尾部還是頭部?notfiy時(shí),是將ObjectWaiter移動(dòng)到EntryList的尾部還是頭部?

關(guān)于具體的細(xì)節(jié),會(huì)在重量級(jí)鎖的文章中分析。

輕量級(jí)鎖

JVM的開發(fā)者發(fā)現(xiàn)在很多情況下,在Java程序運(yùn)行時(shí),同步塊中的代碼都是不存在競(jìng)爭(zhēng)的,不同的線程交替的執(zhí)行同步塊中的代碼。這種情況下,用重量級(jí)鎖是沒必要的。因此JVM引入了輕量級(jí)鎖的概念。

線程在執(zhí)行同步塊之前,JVM會(huì)先在當(dāng)前的線程的棧幀中創(chuàng)建一個(gè)Lock Record,其包括一個(gè)用于存儲(chǔ)對(duì)象頭中的?mark word(官方稱之為Displaced Mark Word)以及一個(gè)指向?qū)ο蟮闹羔?。下圖右邊的部分就是一個(gè)Lock Record。

死磕Synchronized底層實(shí)現(xiàn),面試你還怕什么?

加鎖過程

1.在線程棧中創(chuàng)建一個(gè)Lock Record,將其obj(即上圖的Object reference)字段指向鎖對(duì)象。

2.直接通過CAS指令將Lock Record的地址存儲(chǔ)在對(duì)象頭的mark word中,如果對(duì)象處于無鎖狀態(tài)則修改成功,代表該線程獲得了輕量級(jí)鎖。如果失敗,進(jìn)入到步驟3。

3.如果是當(dāng)前線程已經(jīng)持有該鎖了,代表這是一次鎖重入。設(shè)置Lock Record第一部分(Displaced Mark Word)為null,起到了一個(gè)重入計(jì)數(shù)器的作用。然后結(jié)束。

4.走到這一步說明發(fā)生了競(jìng)爭(zhēng),需要膨脹為重量級(jí)鎖。

解鎖過程

1.遍歷線程棧,找到所有obj字段等于當(dāng)前鎖對(duì)象的Lock Record。

2.如果Lock RecordDisplaced Mark Word為null,代表這是一次重入,將obj設(shè)置為null后continue。

3.如果Lock RecordDisplaced Mark Word不為null,則利用CAS指令將對(duì)象頭的mark word恢復(fù)成為Displaced Mark Word。如果成功,則continue,否則膨脹為重量級(jí)鎖。

偏向鎖

Java是支持多線程的語言,因此在很多二方包、基礎(chǔ)庫中為了保證代碼在多線程的情況下也能正常運(yùn)行,也就是我們常說的線程安全,都會(huì)加入如synchronized這樣的同步語義。但是在應(yīng)用在實(shí)際運(yùn)行時(shí),很可能只有一個(gè)線程會(huì)調(diào)用相關(guān)同步方法。比如下面這個(gè)demo:

import?java.util.ArrayList;
import?java.util.List;

public?class?SyncDemo1?{

????public?static?void?main(String[]?args)?{
????????SyncDemo1?syncDemo1?=?new?SyncDemo1();
????????for?(int?i?=?0;?i?<?100;?i++)?{
????????????syncDemo1.addString("test:"?+?i);
????????}
????}

????private?List<String>?list?=?new?ArrayList<>();

????public?synchronized?void?addString(String?s)?{
????????list.add(s);
????}

}

在這個(gè)demo中為了保證對(duì)list操縱時(shí)線程安全,對(duì)addString方法加了synchronized的修飾,但實(shí)際使用時(shí)卻只有一個(gè)線程調(diào)用到該方法,對(duì)于輕量級(jí)鎖而言,每次調(diào)用addString時(shí),加鎖解鎖都有一個(gè)CAS操作;對(duì)于重量級(jí)鎖而言,加鎖也會(huì)有一個(gè)或多個(gè)CAS操作(這里的’一個(gè)‘、’多個(gè)‘?dāng)?shù)量詞只是針對(duì)該demo,并不適用于所有場(chǎng)景)。

在JDK1.6中為了提高一個(gè)對(duì)象在一段很長(zhǎng)的時(shí)間內(nèi)都只被一個(gè)線程用做鎖對(duì)象場(chǎng)景下的性能,引入了偏向鎖,在第一次獲得鎖時(shí),會(huì)有一個(gè)CAS操作,之后該線程再獲取鎖,只會(huì)執(zhí)行幾個(gè)簡(jiǎn)單的命令,而不是開銷相對(duì)較大的CAS命令。我們來看看偏向鎖是如何做的。

對(duì)象創(chuàng)建

當(dāng)JVM啟用了偏向鎖模式(1.6以上默認(rèn)開啟),當(dāng)新創(chuàng)建一個(gè)對(duì)象的時(shí)候,如果該對(duì)象所屬的class沒有關(guān)閉偏向鎖模式(什么時(shí)候會(huì)關(guān)閉一個(gè)class的偏向模式下文會(huì)說,默認(rèn)所有class的偏向模式都是是開啟的),那新創(chuàng)建對(duì)象的mark word將是可偏向狀態(tài),此時(shí)mark word中的thread id(參見上文偏向狀態(tài)下的mark word格式)為0,表示未偏向任何線程,也叫做匿名偏向(anonymously biased)。

加鎖過程

case 1:當(dāng)該對(duì)象第一次被線程獲得鎖的時(shí)候,發(fā)現(xiàn)是匿名偏向狀態(tài),則會(huì)用CAS指令,將mark word中的thread id由0改成當(dāng)前線程Id。如果成功,則代表獲得了偏向鎖,繼續(xù)執(zhí)行同步塊中的代碼。否則,將偏向鎖撤銷,升級(jí)為輕量級(jí)鎖。

case 2:當(dāng)被偏向的線程再次進(jìn)入同步塊時(shí),發(fā)現(xiàn)鎖對(duì)象偏向的就是當(dāng)前線程,在通過一些額外的檢查后(細(xì)節(jié)見后面的文章),會(huì)往當(dāng)前線程的棧中添加一條Displaced Mark Word為空的Lock Record中,然后繼續(xù)執(zhí)行同步塊的代碼,因?yàn)椴倏v的是線程私有的棧,因此不需要用到CAS指令;由此可見偏向鎖模式下,當(dāng)被偏向的線程再次嘗試獲得鎖時(shí),僅僅進(jìn)行幾個(gè)簡(jiǎn)單的操作就可以了,在這種情況下,synchronized關(guān)鍵字帶來的性能開銷基本可以忽略。

case 3.當(dāng)其他線程進(jìn)入同步塊時(shí),發(fā)現(xiàn)已經(jīng)有偏向的線程了,則會(huì)進(jìn)入到撤銷偏向鎖的邏輯里,一般來說,會(huì)在safepoint中去查看偏向的線程是否還存活,如果存活且還在同步塊中則將鎖升級(jí)為輕量級(jí)鎖,原偏向的線程繼續(xù)擁有鎖,當(dāng)前線程則走入到鎖升級(jí)的邏輯里;如果偏向的線程已經(jīng)不存活或者不在同步塊中,則將對(duì)象頭的mark word改為無鎖狀態(tài)(unlocked),之后再升級(jí)為輕量級(jí)鎖。

由此可見,偏向鎖升級(jí)的時(shí)機(jī)為:當(dāng)鎖已經(jīng)發(fā)生偏向后,只要有另一個(gè)線程嘗試獲得偏向鎖,則該偏向鎖就會(huì)升級(jí)成輕量級(jí)鎖。當(dāng)然這個(gè)說法不絕對(duì),因?yàn)檫€有批量重偏向這一機(jī)制。

解鎖過程

當(dāng)有其他線程嘗試獲得鎖時(shí),是根據(jù)遍歷偏向線程的lock record來確定該線程是否還在執(zhí)行同步塊中的代碼。因此偏向鎖的解鎖很簡(jiǎn)單,僅僅將棧中的最近一條lock recordobj字段設(shè)置為null。需要注意的是,偏向鎖的解鎖步驟中并不會(huì)修改對(duì)象頭中的thread id。

下圖展示了鎖狀態(tài)的轉(zhuǎn)換流程:

死磕Synchronized底層實(shí)現(xiàn),面試你還怕什么?

另外,偏向鎖默認(rèn)不是立即就啟動(dòng)的,在程序啟動(dòng)后,通常有幾秒的延遲,可以通過命令?-XX:BiasedLockingStartupDelay=0來關(guān)閉延遲。

批量重偏向與撤銷

從上文偏向鎖的加鎖解鎖過程中可以看出,當(dāng)只有一個(gè)線程反復(fù)進(jìn)入同步塊時(shí),偏向鎖帶來的性能開銷基本可以忽略,但是當(dāng)有其他線程嘗試獲得鎖時(shí),就需要等到safe point時(shí)將偏向鎖撤銷為無鎖狀態(tài)或升級(jí)為輕量級(jí)/重量級(jí)鎖。safe point這個(gè)詞我們?cè)贕C中經(jīng)常會(huì)提到,其代表了一個(gè)狀態(tài),在該狀態(tài)下所有線程都是暫停的(大概這么個(gè)意思),詳細(xì)可以看這篇文章??傊?,偏向鎖的撤銷是有一定成本的,如果說運(yùn)行時(shí)的場(chǎng)景本身存在多線程競(jìng)爭(zhēng)的,那偏向鎖的存在不僅不能提高性能,而且會(huì)導(dǎo)致性能下降。因此,JVM中增加了一種批量重偏向/撤銷的機(jī)制。

存在如下兩種情況:(見官方論文第4小節(jié)):

1.一個(gè)線程創(chuàng)建了大量對(duì)象并執(zhí)行了初始的同步操作,之后在另一個(gè)線程中將這些對(duì)象作為鎖進(jìn)行之后的操作。這種case下,會(huì)導(dǎo)致大量的偏向鎖撤銷操作。

2.存在明顯多線程競(jìng)爭(zhēng)的場(chǎng)景下使用偏向鎖是不合適的,例如生產(chǎn)者/消費(fèi)者隊(duì)列。

批量重偏向(bulk rebias)機(jī)制是為了解決第一種場(chǎng)景。批量撤銷(bulk revoke)則是為了解決第二種場(chǎng)景。

其做法是:以class為單位,為每個(gè)class維護(hù)一個(gè)偏向鎖撤銷計(jì)數(shù)器,每一次該class的對(duì)象發(fā)生偏向撤銷操作時(shí),該計(jì)數(shù)器+1,當(dāng)這個(gè)值達(dá)到重偏向閾值(默認(rèn)20)時(shí),JVM就認(rèn)為該class的偏向鎖有問題,因此會(huì)進(jìn)行批量重偏向。每個(gè)class對(duì)象會(huì)有一個(gè)對(duì)應(yīng)的epoch字段,每個(gè)處于偏向鎖狀態(tài)對(duì)象的mark word中也有該字段,其初始值為創(chuàng)建該對(duì)象時(shí),class中的epoch的值。每次發(fā)生批量重偏向時(shí),就將該值+1,同時(shí)遍歷JVM中所有線程的棧,找到該class所有正處于加鎖狀態(tài)的偏向鎖,將其epoch字段改為新值。下次獲得鎖時(shí),發(fā)現(xiàn)當(dāng)前對(duì)象的epoch值和class的epoch不相等,那就算當(dāng)前已經(jīng)偏向了其他線程,也不會(huì)執(zhí)行撤銷操作,而是直接通過CAS操作將其mark word的Thread Id 改成當(dāng)前線程Id。

當(dāng)達(dá)到重偏向閾值后,假設(shè)該class計(jì)數(shù)器繼續(xù)增長(zhǎng),當(dāng)其達(dá)到批量撤銷的閾值后(默認(rèn)40),JVM就認(rèn)為該class的使用場(chǎng)景存在多線程競(jìng)爭(zhēng),會(huì)標(biāo)記該class為不可偏向,之后,對(duì)于該class的鎖,直接走輕量級(jí)鎖的邏輯。

End

Java中的synchronized有偏向鎖、輕量級(jí)鎖、重量級(jí)鎖三種形式,分別對(duì)應(yīng)了鎖只被一個(gè)線程持有、不同線程交替持有鎖、多線程競(jìng)爭(zhēng)鎖三種情況。當(dāng)條件不滿足時(shí),鎖會(huì)按偏向鎖->輕量級(jí)鎖->重量級(jí)鎖 的順序升級(jí)。JVM種的鎖也是能降級(jí)的,只不過條件很苛刻,不在我們討論范圍之內(nèi)。該篇文章主要是對(duì)Java的synchronized做個(gè)基本介紹,后文會(huì)有更詳細(xì)的分析。

?

網(wǎng)站題目:死磕Synchronized底層實(shí)現(xiàn),面試你還怕什么?
轉(zhuǎn)載注明:http://www.rwnh.cn/article0/jdciio.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供靜態(tài)網(wǎng)站網(wǎng)站導(dǎo)航、微信公眾號(hào)服務(wù)器托管、企業(yè)網(wǎng)站制作標(biāo)簽優(yōu)化

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)

網(wǎng)站建設(shè)網(wǎng)站維護(hù)公司
吕梁市| 汉中市| 沁阳市| 格尔木市| 乌兰浩特市| 惠来县| 勐海县| 延寿县| 五家渠市| 土默特左旗| 墨竹工卡县| 南澳县| 溆浦县| 大姚县| 富民县| 石景山区| 山东| 辉县市| 蓝田县| 色达县| 烟台市| 华蓥市| 缙云县| 托克逊县| 汉川市| 琼结县| 高雄县| 洪泽县| 洪泽县| 耒阳市| 铜陵市| 简阳市| 三门县| 永安市| 深圳市| 上高县| 海宁市| 镇巴县| 永新县| 文水县| 会同县|