java中并發(fā)問題的示例分析

這篇文章將為大家詳細(xì)講解有關(guān)java中并發(fā)問題的示例分析,小編覺得挺實用的,因此分享給大家做個參考,希望大家閱讀完這篇文章后可以有所收獲。

在金川等地區(qū),都構(gòu)建了全面的區(qū)域性戰(zhàn)略布局,加強(qiáng)發(fā)展的系統(tǒng)性、市場前瞻性、產(chǎn)品創(chuàng)新能力,以專注、極致的服務(wù)理念,為客戶提供網(wǎng)站設(shè)計、網(wǎng)站制作 網(wǎng)站設(shè)計制作按需設(shè)計,公司網(wǎng)站建設(shè),企業(yè)網(wǎng)站建設(shè),高端網(wǎng)站設(shè)計,成都全網(wǎng)營銷,成都外貿(mào)網(wǎng)站建設(shè)公司,金川網(wǎng)站建設(shè)費(fèi)用合理。

1什么是并發(fā)問題。

多個進(jìn)程或線程同時(或著說在同一段時間內(nèi))訪問同一資源會產(chǎn)生并發(fā)問題。

銀行兩操作員同時操作同一賬戶就是典型的例子。比如A、B操作員同時讀取一余額為1000元的賬戶,A操作員為該賬戶增加100元,B操作員同時為該賬戶減去50元,A先提交,B后提交。最后實際賬戶余額為1000-50=950元,但本該為1000+100-50=1050。這就是典型的并發(fā)問題。如何解決?可以用鎖。

2java中synchronized的用法

用法1

public class Test{
	public synchronized void print(){
		….;
	}
}

某線程執(zhí)行print()方法,則該對象將加鎖。其它線程將無法執(zhí)行該對象的所有synchronized塊。

用法2

public class Test{
	public void print(){
		synchronized(this){
			//鎖住本對象 
			…;
		}
	}
}

同用法1, 但更能體現(xiàn)synchronized用法的本質(zhì)。

用法3

public class Test{
	private String a = “test”;
	public void print(){
		synchronized(a){
			//鎖住a對象 
			…;
		}
	}
	public synchronized void t(){
		…;
		//這個同步代碼塊不會因為print()而鎖定.
	}
}

執(zhí)行print(),會給對象a加鎖,注意不是給Test的對象加鎖,也就是說Test對象的其它synchronized方法不會因為print()而被鎖。同步代碼塊執(zhí)行完,則釋放對a的鎖。

為了鎖住一個對象的代碼塊而不影響該對象其它synchronized塊的高性能寫法:

public class Test{
	private byte[] lock = new byte[0];
	public void print(){
		synchronized(lock){
			…;
		}
	}
	public synchronized void t(){
		…;
	}
}

靜態(tài)方法的鎖

public class Test{
	public synchronized static void execute(){
		…;
	}
}

效果同

public class Test{
	public static void execute(){
		synchronized(TestThread.class){
			…;
		}
	}
}

3 Java中的鎖與排隊上廁所。

鎖就是阻止其它進(jìn)程或線程進(jìn)行資源訪問的一種方式,即鎖住的資源不能被其它請求訪問。在JAVA中,sychronized關(guān)鍵字用來對一個對象加鎖。比如:

public class MyStack {
	int idx = 0;
	char [] data = new char[6];
	public synchronized void push(char c) {
		data[idx] = c;
		idx++;
	}
	public synchronized char pop() {
		idx--;
		return data[idx];
	}
	public static void main(String args[]){
		MyStack m = new MyStack();
		/**
    下面對象m被加鎖。嚴(yán)格的說是對象m的所有synchronized塊被加鎖。
    如果存在另一個試圖訪問m的線程T,那么T無法執(zhí)行m對象的push和
    pop方法。
  */
		m.pop();
		//對象m被加鎖。
	}
}

Java的加鎖解鎖跟多個人排隊等一個公共廁位完全一樣。第一個人進(jìn)去后順手把門從里面鎖住,其它人只好排隊等。第一個人結(jié)束后出來時,門才會打開(解鎖)。輪到第二個人進(jìn)去,同樣他又會把門從里面鎖住,其它人繼續(xù)排隊等待。

用廁所理論可以很容易明白:一個人進(jìn)了一個廁位,這個廁位就會鎖住,但不會導(dǎo)致另一個廁位也被鎖住,因為一個人不能同時蹲在兩個廁位里。對于Java就是說:Java中的鎖是針對同一個對象的,不是針對class的??聪吕?/p>

MyStatckm1=newMyStack();
MyStatckm2=newMystatck();
m1.pop();
m2.pop();

m1對象的鎖是不會影響m2的鎖的,因為它們不是同一個廁位。就是說,假設(shè)有3線程t1,t2,t3操作m1,那么這3個線程只可能在m1上排隊等,假設(shè)另2個線程t8,t9在操作m2,那么t8,t9只會在m2上等待。而t2和t8則沒有關(guān)系,即使m2上的鎖釋放了,t1,t2,t3可能仍要在m1上排隊。原因無它,不是同一個廁位耳。

Java不能同時對一個代碼塊加兩個鎖,這和數(shù)據(jù)庫鎖機(jī)制不同,數(shù)據(jù)庫可以對一條記錄同時加好幾種不同的鎖。

4何時釋放鎖?

一般是執(zhí)行完畢同步代碼塊(鎖住的代碼塊)后就釋放鎖,也可以用wait()方式半路上釋放鎖。wait()方式就好比蹲廁所到一半,突然發(fā)現(xiàn)下水道堵住了,不得已必須出來站在一邊,好讓修下水道師傅(準(zhǔn)備執(zhí)行notify的一個線程)進(jìn)去疏通馬桶,疏通完畢,師傅大喊一聲:“已經(jīng)修好了”(notify),剛才出來的同志聽到后就重新排隊。注意啊,必須等師傅出來啊,師傅不出來,誰也進(jìn)不去。也就是說notify后,不是其它線程馬上可以進(jìn)入封鎖區(qū)域活動了,而是必須還要等notify代碼所在的封鎖區(qū)域執(zhí)行完畢從而釋放鎖以后,其它線程才可進(jìn)入。

這里是wait與notify代碼示例:

public synchronized char pop() {
	char c;
	while (buffer.size() == 0) {
		try {
			this.wait();
			//從廁位里出來
		}
		catch (InterruptedException e) {
			// ignore it…
		}
	}
	c = ((Character)buffer.remove(buffer.size()-1)). 
	charValue();
	return c;
}
public synchronized void push(char c) {
	this.notify();
	//通知那些wait()的線程重新排隊。注意:僅僅是通知它們重新排隊。 
	Character charObj = new Character(c);
	buffer.addElement(charObj);
}
//執(zhí)行完畢,釋放鎖。那些排隊的線程就可以進(jìn)來了。

再深入一些。

由于wait()操作而半路出來的同志沒收到notify信號前是不會再排隊的,他會在旁邊看著這些排隊的人(其中修水管師傅也在其中)。注意,修水管的師傅不能插隊,也得跟那些上廁所的人一樣排隊,不是說一個人蹲了一半出來后,修水管師傅就可以突然冒出來然后立刻進(jìn)去搶修了,他要和原來排隊的那幫人公平競爭,因為他也是個普通線程。如果修水管師傅排在后面,則前面的人進(jìn)去后,發(fā)現(xiàn)堵了,就wait,然后出來站到一邊,再進(jìn)去一個,再wait,出來,站到一邊,只到師傅進(jìn)去執(zhí)行notify.這樣,一會兒功夫,排隊的旁邊就站了一堆人,等著notify.

終于,師傅進(jìn)去,然后notify了,接下來呢?

1.有一個wait的人(線程)被通知到。

2.為什么被通知到的是他而不是另外一個wait的人?取決于JVM.我們無法預(yù)先

判斷出哪一個會被通知到。也就是說,優(yōu)先級高的不一定被優(yōu)先喚醒,等待

時間長的也不一定被優(yōu)先喚醒,一切不可預(yù)知!(當(dāng)然,如果你了解該JVM的

實現(xiàn),則可以預(yù)知)。

3.他(被通知到的線程)要重新排隊。

4.他會排在隊伍的第一個位置嗎?回答是:不一定。他會排最后嗎?也不一定。

但如果該線程優(yōu)先級設(shè)的比較高,那么他排在前面的概率就比較大。

5.輪到他重新進(jìn)入廁位時,他會從上次wait()的地方接著執(zhí)行,不會重新執(zhí)行。

惡心點說就是,他會接著拉巴巴,不會重新拉。

6.如果師傅notifyAll().則那一堆半途而廢出來的人全部重新排隊。順序不可知。

JavaDOC上說,Theawakenedthreadswillnotbeabletoproceeduntilthecurrentthreadrelinquishesthelockonthisobject(當(dāng)前線程釋放鎖前,喚醒的線程不能去執(zhí)行)。

這用廁位理論解釋就是顯而易見的事。

5Lock的使用

用synchronized關(guān)鍵字可以對資源加鎖。用Lock關(guān)鍵字也可以。它是JDK1.5中新增內(nèi)容。用法如下:

class BoundedBuffer {
	final Lock lock = new ReentrantLock();
	final Condition notFull = lock.newCondition();
	final Condition notEmpty = lock.newCondition();
	final Object[] items = new Object[100];
	int putptr, takeptr, count;
	public void put(Object x) throws InterruptedException {
		lock.lock();
		try {
			while (count == items.length) 
			      notFull.await();
			items[putptr] = x;
			if (++putptr == items.length) putptr = 0;
			++count;
			notEmpty.signal();
		}
		finally {
			lock.unlock();
		}
	}
	public Object take() throws InterruptedException {
		lock.lock();
		try {
			while (count == 0) 
			      notEmpty.await();
			Object x = items[takeptr];
			if (++takeptr == items.length) takeptr = 0;
			--count;
			notFull.signal();
			return x;
		}
		finally {
			lock.unlock();
		}
	}
}

(注:這是JavaDoc里的例子,是一個阻塞隊列的實現(xiàn)例子。所謂阻塞隊列,就是一個隊列如果滿了或者空了,都會導(dǎo)致線程阻塞等待。Java里的ArrayBlockingQueue提供了現(xiàn)成的阻塞隊列,不需要自己專門再寫一個了。)

一個對象的lock.lock()和lock.unlock()之間的代碼將會被鎖住。這種方式比起synchronize好在什么地方?簡而言之,就是對wait的線程進(jìn)行了分類。用廁位理論來描述,則是那些蹲了一半而從廁位里出來等待的人原因可能不一樣,有的是因為馬桶堵了,有的是因為馬桶沒水了。通知(notify)的時候,就可以喊:因為馬桶堵了而等待的過來重新排隊(比如馬桶堵塞問題被解決了),或者喊,因為馬桶沒水而等待的過來重新排隊(比如馬桶沒水問題被解決了)。這樣可以控制得更精細(xì)一些。不像synchronize里的wait和notify,不管是馬桶堵塞還是馬桶沒水都只能喊:剛才等待的過來排隊!假如排隊的人進(jìn)來一看,發(fā)現(xiàn)原來只是馬桶堵塞問題解決了,而自己渴望解決的問題(馬桶沒水)還沒解決,只好再回去等待(wait),白進(jìn)來轉(zhuǎn)一圈,浪費(fèi)時間與資源。

Lock方式與synchronized對應(yīng)關(guān)系:

LockawaitsignalsignalAll

synchronizedwaitnotifynotifyAll

注意:不要在Lock方式鎖住的塊里調(diào)用wait、notify、notifyAll

6利用管道進(jìn)行線程間通信

原理簡單。兩個線程,一個操作PipedInputStream,一個操作PipedOutputStream。PipedOutputStream寫入的數(shù)據(jù)先緩存在Buffer中,如果Buffer滿,此線程wait。PipedInputStream讀出Buffer中的數(shù)據(jù),如果Buffer沒數(shù)據(jù),此線程wait。

jdk1.5中的阻塞隊列可實現(xiàn)同樣功能。

package io;
import java.io.*;
public class PipedStreamTest {
	public static void main(String[] args) {
		PipedOutputStream ops=new PipedOutputStream();
		PipedInputStream pis=new PipedInputStream();
		try{
			ops.connect(pis);
			//實現(xiàn)管道連接 
			new Producer(ops).run();
			new Consumer(pis).run();
		}
		catch(Exception e){
			e.printStackTrace();
		}
	}
}
//生產(chǎn)者 
class Producer implements Runnable{
	private PipedOutputStream ops;
	public Producer(PipedOutputStream ops) 
	{
		this.ops=ops;
	}
	public void run()
	{
		try{
			ops.write("hell,spell".getBytes());
			ops.close();
		}
		catch(Exception e)
		    {
			e.printStackTrace();
		}
	}
}
//消費(fèi)者 
class Consumer implements Runnable{
	private PipedInputStream pis;
	public Consumer(PipedInputStream pis) 
	{
		this.pis=pis;
	}
	public void run()
	{
		try{
			byte[] bu=new byte[100];
			int len=pis.read(bu);
			System.out.println(new String(bu,0,len));
			pis.close();
		}
		catch(Exception e)
		    {
			e.printStackTrace();
		}
	}
}

例2 對上面的程序做少許改動就成了兩個線程。

package io;
import java.io.*;
public class PipedStreamTest {
	public static void main(String[] args) {
		PipedOutputStream ops=new PipedOutputStream();
		PipedInputStream pis=new PipedInputStream();
		try{
			ops.connect(pis);
			//實現(xiàn)管道連接 
			Producer p = new Producer(ops);
			new Thread(p).start();
			Consumer c = new Consumer(pis);
			new Thread(c).start();
		}
		catch(Exception e){
			e.printStackTrace();
		}
	}
}
//生產(chǎn)者 
class Producer implements Runnable{
	private PipedOutputStream ops;
	public Producer(PipedOutputStream ops) 
	{
		this.ops=ops;
	}
	public void run()
	{
		try{
			for (;;){
				ops.write("hell,spell".getBytes());
				ops.close();
			}
		}
		catch(Exception e)
		    {
			e.printStackTrace();
		}
	}
}
//消費(fèi)者 
class Consumer implements Runnable{
	private PipedInputStream pis;
	public Consumer(PipedInputStream pis) 
	{
		this.pis=pis;
	}
	public void run()
	{
		try{
			for (;;){
				byte[] bu=new byte[100];
				int len=pis.read(bu);
				System.out.println(new String(bu,0,len));
			}
			pis.close();
		}
		catch(Exception e)
		    {
			e.printStackTrace();
		}
	}
}

例3. 這個例子更加貼進(jìn)應(yīng)用

import java.io.*;
public class PipedIO {
	//程序運(yùn)行后將sendFile文件的內(nèi)容拷貝到receiverFile文件中 
	public static void main(String args[]){
		try{
			//構(gòu)造讀寫的管道流對象 
			PipedInputStream pis=new PipedInputStream();
			PipedOutputStream pos=new PipedOutputStream();
			//實現(xiàn)關(guān)聯(lián) 
			pos.connect(pis);
			//構(gòu)造兩個線程,并且啟動。 
			new Sender(pos,”c:\text2.txt”).start();
			new Receiver(pis,”c:\text3.txt”).start();
		}
		catch(IOException e){
			System.out.println(“Pipe Error”+ e);
		}
	}
}
//線程發(fā)送 
class Sender extends Thread{
	PipedOutputStream pos;
	File file;
	//構(gòu)造方法 
	Sender(PipedOutputStream pos, String fileName){
		this.pos=pos;
		file=new File(fileName);
	}
	//線程運(yùn)行方法 
	public void run(){
		try{
			//讀文件內(nèi)容 
			FileInputStream fs=new FileInputStream(file);
			int data;
			while((data=fs.read())!=-1){
				//寫入管道始端 
				pos.write(data);
			}
			pos.close();
		}
		catch(IOException e) {
			System.out.println(“Sender Error” +e);
		}
	}
}
//線程讀 
class Receiver extends Thread{
	PipedInputStream pis;
	File file;
	//構(gòu)造方法 
	Receiver(PipedInputStream pis, String fileName){
		this.pis=pis;
		file=new File(fileName);
	}
	//線程運(yùn)行 
	public void run(){
		try {
			//寫文件流對象 
			FileOutputStream fs=new FileOutputStream(file);
			int data;
			//從管道末端讀 
			while((data=pis.read())!=-1){
				//寫入本地文件    
				fs.write(data);
			}
			pis.close();
		}
		catch(IOException e){
			System.out.println("Receiver Error" +e);
		}
	}
}

7阻塞隊列

阻塞隊列可以代替管道流方式來實現(xiàn)進(jìn)水管/排水管模式(生產(chǎn)者/消費(fèi)者).JDK1.5提供了幾個現(xiàn)成的阻塞隊列.現(xiàn)在來看ArrayBlockingQueue的代碼如下:

這里是一個阻塞隊列

BlockingQueue blockingQ = new ArrayBlockingQueue 10;

一個線程從隊列里取

for(;;){ 
Object o = blockingQ.take();//隊列為空,則等待(阻塞) 
}

另一個線程往隊列存

for(;;){ 
blockingQ.put(new Object());//隊列滿,則等待(阻塞) 
}

可見,阻塞隊列使用起來比管道簡單。

8使用Executors、Executor、ExecutorService、ThreadPoolExecutor

可以使用線程管理任務(wù)。還可以使用jdk1.5提供的一組類來更方便的管理任務(wù)。從這些類里我們可以體會一種面向任務(wù)的思維方式。這些類是:

Executor接口。使用方法:

Executor executor = anExecutor;//生成一個Executor實例。 
executor.execute(new RunnableTask1());

用意:使用者只關(guān)注任務(wù)執(zhí)行,不用操心去關(guān)注任務(wù)的創(chuàng)建、以及執(zhí)行細(xì)節(jié)等這些第三方實現(xiàn)者關(guān)心的問題。也就是說,把任務(wù)的調(diào)用執(zhí)行和任務(wù)的實現(xiàn)解耦。

實際上,JDK1.5中已經(jīng)有該接口出色的實現(xiàn)。夠用了。

Executors是一個如同Collections一樣的工廠類或工具類,用來產(chǎn)生各種不同接口的實例。

ExecutorService接口它繼承自Executor.Executor只管把任務(wù)扔進(jìn)executor()里去執(zhí)行,剩余的事就不管了。而ExecutorService則不同,它會多做點控制工作。比如:

class NetworkService {
	private final ServerSocket serverSocket;
	private final ExecutorService pool;
	public NetworkService(int port, int poolSize) throws IOException {
		serverSocket = new ServerSocket(port);
		pool = Executors.newFixedThreadPool(poolSize);
	}
	public void serve() {
		try {
			for (;;) {
				pool.execute(new Handler(serverSocket.accept()));
			}
		}
		catch (IOException ex) {
			pool.shutdown();
			//不再執(zhí)行新任務(wù)
		}
	}
}
class Handler implements Runnable {
	private final Socket socket;
	Handler(Socket socket) {
		this.socket = socket;
	}
	public void run() {
		// read and service request
	}
}

ExecutorService(也就是代碼里的pool對象)執(zhí)行shutdown后,它就不能再執(zhí)行新任務(wù)了,但老任務(wù)會繼續(xù)執(zhí)行完畢,那些等待執(zhí)行的任務(wù)也不再等待了。

任務(wù)提交者與執(zhí)行者通訊

public static void main(String args[])throws Exception {
	ExecutorService executor = Executors.newSingleThreadExecutor();
	Callable task = new Callable(){
		public String call()throws Exception{
			return “test”;
		}
	}
	;
	Future f = executor.submit(task);
	String result = f.get();
	//等待(阻塞)返回結(jié)果 
	System.out.println(result);
	executor.shutdown();
}

Executors.newSingleThreadExecutor()取得的Executor實例有以下特性:

任務(wù)順序執(zhí)行.比如:

executor.submit(task1); 
executor.submit(task2);

必須等task1執(zhí)行完,task2才能執(zhí)行。

task1和task2會被放入一個隊列里,由一個工作線程來處理。即:一共有2個線程(主線程、處理任務(wù)的工作線程)。

其它的類請參考JavaDoc

9并發(fā)流程控制

本節(jié)例子來自溫少的Java并發(fā)教程,可能會有改動。向溫少致敬。

CountDownLatch門插銷計數(shù)器

啟動線程,然后等待線程結(jié)束。即常用的主線程等所有子線程結(jié)束后再執(zhí)行的問題。

public static void main(String[] args)throws Exception {
	// TODO Auto-generated method stub 
	final int count=10;
	final CountDownLatch completeLatch = new CountDownLatch(count);
	//定義了門插銷的數(shù)目是10
	for (int i=0;i<count;i++){
		Thread thread = new Thread("worker thread"+i){
			public void run(){
				//do xxxx                  
				completeLatch.countDown();
				//減少一根門插銷
			}
		}
		;
		thread.start();
	}
	completeLatch.await();
	//如果門插銷還沒減完則等待。
}

JDK1.4時,常用辦法是給子線程設(shè)置狀態(tài),主線程循環(huán)檢測。易用性和效率都不好。

啟動很多線程,等待通知才能開始

public static void main(String[] args) throws Exception {
	// TODO Auto-generated method stub 
	final CountDownLatch startLatch = new CountDownLatch(1);
	//定義了一根門插銷
	for (int i = 0; i < 10; i++) {
		Thread thread = new Thread("worker thread" + i) {
			public void run() {
				try {
					startLatch.await();
					//如果門插銷還沒減完則等待
				}
				catch (InterruptedException e) {
				}
				// do xxxx
			}
		}
		;
		thread.start();
	}
	startLatch.countDown();
	//減少一根門插銷
}

CycliBarrier. 等所有線程都達(dá)到一個起跑線后才能開始繼續(xù)運(yùn)行。

public class CycliBarrierTest implements Runnable {
	private CyclicBarrier barrier;
	public CycliBarrierTest(CyclicBarrier barrier) {
		this.barrier = barrier;
	}
	public void run() {
		//do xxxx;
		try {
			this.barrier.await();
			//線程運(yùn)行至此會檢查是否其它線程都到齊了,沒到齊就繼續(xù)等待。到齊了就執(zhí)行barrier的run函數(shù)體里的內(nèi)容
		}
		catch (Exception e) {
		}
	}
	/**
 * @param args
 */
	public static void main(String[] args) {
		//參數(shù)2代表兩個線程都達(dá)到起跑線才開始一起繼續(xù)往下執(zhí)行
		CyclicBarrier barrier = new CyclicBarrier(2, new Runnable() {
			public void run() {
				//do xxxx;
			}
		}
		);
		Thread t1 = new Thread(new CycliBarrierTest(barrier));
		Thread t2 = new Thread(new CycliBarrierTest(barrier));
		t1.start();
		t2.start();
	}
}

這簡化了傳統(tǒng)的用計數(shù)器+wait/notifyAll來實現(xiàn)該功能的方式。

10并發(fā)3定律

Amdahl定律.給定問題規(guī)模,可并行化部分占12%,那么即使把并行運(yùn)用到極致,系統(tǒng)的性能最多也只能提高1/(1-0.12)=1.136倍。即:并行對提高系統(tǒng)性能有上限。

Gustafson定律.Gustafson定律說Amdahl定律沒有考慮隨著cpu的增多而有更多的計算能力可被使用。其本質(zhì)在于更改問題規(guī)模從而可以把Amdahl定律中那剩下的88%的串行處理并行化,從而可以突破性能門檻。本質(zhì)上是一種空間換時間。

Sun-Ni定律.是前兩個定律的進(jìn)一步推廣。其主要思想是計算的速度受限于存儲而不是CPU的速度.所以要充分利用存儲空間等計算資源,盡量增大問題規(guī)模以產(chǎn)生更好/更精確的解.

11由并發(fā)到并行

計算機(jī)識別物體需要飛速的計算,以至于芯片發(fā)熱發(fā)燙,而人在識別物體時卻一目了然,卻并不會導(dǎo)致某個腦細(xì)胞被燒熱燒焦(夸張)而感到不適,是由于大腦是一個分布式并行運(yùn)行系統(tǒng),就像google用一些廉價的linux服務(wù)器可以進(jìn)行龐大復(fù)雜的計算一樣,大腦內(nèi)部無數(shù)的神經(jīng)元的獨(dú)自計算,互相分享成果,從而瞬間完成需要單個cpu萬億次運(yùn)算才能有的效果。試想,如果在并行處理領(lǐng)域有所創(chuàng)建,將對計算機(jī)的發(fā)展和未來產(chǎn)生不可估量的影響。當(dāng)然,其中的挑戰(zhàn)也可想而知:許多的問題是并不容易輕易就“分割”的了的。

關(guān)于“java中并發(fā)問題的示例分析”這篇文章就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,使各位可以學(xué)到更多知識,如果覺得文章不錯,請把它分享出去讓更多的人看到。

網(wǎng)頁標(biāo)題:java中并發(fā)問題的示例分析
瀏覽地址:http://bm7419.com/article18/jddsgp.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供移動網(wǎng)站建設(shè)、網(wǎng)站收錄網(wǎng)站制作、虛擬主機(jī)、網(wǎng)站內(nèi)鏈定制網(wǎng)站

廣告

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

成都seo排名網(wǎng)站優(yōu)化