当前位置: 首页 > news >正文

可信赖的常州网站建设网站建设实现后台数据导出excel

可信赖的常州网站建设,网站建设实现后台数据导出excel,南京建设主管部门网站,企业信用查询官网简介 volatile是Java提供的一种轻量级的同步机制。Java 语言包含两种内在的同步机制#xff1a;同步块#xff08;或方法#xff09;和 volatile 变量#xff0c;相比于synchronized#xff08;synchronized通常称为重量级锁#xff09;#xff0c;volatile更轻量级同步块或方法和 volatile 变量相比于synchronizedsynchronized通常称为重量级锁volatile更轻量级因为它不会引起线程上下文的切换和调度。但是volatile 变量的同步性较差有时它更简单并且开销更低而且其使用也更容易出错。 Java volatile关键字用于将Java变量标记为“存储在主存储器中”。更确切地说这意味着每次读取一个volatile变量都将从计算机的主内存中读取而不是从CPU缓存中读取并且每次写入volatile变量都将写入主内存而不仅仅是CPU缓存。 实际上自Java 5以来volatile关键字保证的不仅仅是向主存储器写入和读取volatile变量。我将在以下部分解释。 特性 可以把对volatile变量的单个读/写看成是使用同一个锁对这些单个读/写操作做了同步 当我们声明共享变量为volatile后对这个变量的读/写将会很特别。理解volatile特性的一个好方法是把对volatile变量的单个读/写看成是使用同一个锁对这些单个读/写操作做了同步。 COPYclass VolatileFeaturesExample {//使用volatile声明64位的long型变量volatile long vl 0L;public void set(long l) {vl l; //单个volatile变量的写}public void getAndIncrement () {vl; //复合多个volatile变量的读/写}public long get() {return vl; //单个volatile变量的读} }假设有多个线程分别调用上面程序的三个方法这个程序在语义上和下面程序等价 COPYclass VolatileFeaturesExample {long vl 0L; // 64位的long型普通变量//对单个的普通 变量的写用同一个锁同步public synchronized void set(long l) { vl l;}public void getAndIncrement () { //普通方法调用long temp get(); //调用已同步的读方法temp 1L; //普通写操作set(temp); //调用已同步的写方法}public synchronized long get() { //对单个的普通变量的读用同一个锁同步return vl;} }如上面示例程序所示对一个volatile变量的单个读/写操作与对一个普通变量的读/写操作使用同一个锁来同步它们之间的执行效果相同。 锁的happens-before规则保证释放锁和获取锁的两个线程之间的内存可见性这意味着对一个volatile变量的读总是能看到任意线程对这个volatile变量最后的写入。 锁的语义决定了临界区代码的执行具有原子性。这意味着即使是64位的long型和double型变量只要它是volatile变量对该变量的读写就将具有原子性。如果是多个volatile操作或类似于volatile这种复合操作这些操作整体上不具有原子性。 简而言之volatile变量自身具有下列特性 原子性 即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断要么就都不执行。 原子性是拒绝多线程操作的不论是多核还是单核具有原子性的量同一时刻只能有一个线程来对它进行操作。简而言之在整个操作过程中不会被线程调度器中断的操作都可认为是原子性。例如 a1是原子性操作但是a和a 1就不是原子性操作。Java中的原子性操作包括 基本类型的读取和赋值操作且赋值必须是数字赋值给变量变量之间的相互赋值不是原子性操作。所有引用reference的赋值操作java.concurrent.Atomic.* 包中所有类的一切操作 可见性 指当多个线程访问同一个变量时一个线程修改了这个变量的值其他线程能够立即看得到修改的值。 在多线程环境下一个线程对共享变量的操作对其他线程是不可见的。Java提供了volatile来保证可见性当一个变量被volatile修饰后表示着线程本地内存无效当一个线程修改共享变量后他会立即被更新到主内存中其他线程读取共享变量时会直接从主内存中读取。当然synchronize和Lock都可以保证可见性。synchronized和Lock能保证同一时刻只有一个线程获取锁然后执行同步代码并且在释放锁之前会将对变量的修改刷新到主存当中。因此可以保证可见性。 在线程使用非volatile变量的多线程应用程序中出于性能原因每个线程可以在处理它们时将变量从主存储器拷贝到CPU高速缓存中。如果您的计算机包含多个CPU则每个线程可以在不同的CPU上运行。这意味着每个线程都可以将变量复制到不同CPU的CPU缓存中。这在这里说明 对于volatile变量无法保证Java虚拟机JVM何时将数据从主内存读取到CPU缓存中或将数据从CPU缓存写入主内存。这可能会导致一些问题我将在以下部分中解释。 想象一下两个或多个线程可以访问共享对象的情况该共享对象包含一个声明如下的计数器变量 COPYpublic class SharedObject {public int counter 0; }再想象一下只有线程1对counter变量进行增加操作但线程1和线程2都可能读取变量counter。 如果counter变量未声明volatile则无法保证何时将counter变量的值从CPU缓存写回主存储器。这意味着CPU高速缓存中的counter变量值可能与主存储器中的变量值不同。这种情况如下所示 线程没有看到变量的最新值的问题是因为它还没有被另一个线程写回主内存这被称为“可见性”问题其他线程看不到一个线程的某些更新。 volatile可见性保证 Java volatile关键字旨在解决变量可见性问题。通过使用volatile声明counter变量对变量counter的所有写操作都将立即写回主存储器。此外counter变量的所有读取都将直接从主存储器中读取。 下面是counter变量声明为volatile的样子 COPYpublic class SharedObject {public volatile int counter 0; }声明变量为volatile对其他线程写入该变量 保证了可见性。 在上面给出的场景中一个线程T1修改计数器另一个线程T2读取计数器但从不修改它声明该counter变量为volatile足以保证写入counter变量对T2的可见性。 但是如果T1和T2都在增加counter变量那么声明counter变量为volatile就不够了。稍后会详细介绍。 完全volatile可见性保证 实际上Java volatile的可见性保证超出了volatile变量本身。可见性保证如下 如果线程A写入volatile变量并且线程B随后读取这个volatile变量则在写入volatile变量之前对线程A可见的所有变量在线程B读取volatile变量后也将对线程B可见。如果线程A读取volatile变量则读取volatile变量时对线程A可见的所有变量也将从主存储器重新读取。 让我用代码示例说明 COPYpublic class MyClass {private int years;private int monthsprivate volatile int days;public void update(int years, int months, int days){this.years years;this.months months;this.days days;} }udpate()方法写入三个变量其中只有days是volatile变量。 完全volatile可见性保证意味着当将一个值写入days时对线程可见的其他所有变量也会写入主存储器。这意味着当一个值被写入daysyears和months的值也被写入主存储器(注意days的写入在最后)。 当读取yearsmonths和days的值你可以这样做 COPYpublic class MyClass {private int years;private int monthsprivate volatile int days;public int totalDays() {int total this.days;total months * 30;total years * 365;return total;}public void update(int years, int months, int days){this.years years;this.months months;this.days days;} }注意totalDays()方法通过读取days的值到total变量中开始。当读取days的值时后续months 和years值的读取也会从主存储器中读取。因此使用上述读取序列可以保证看到最新的daysmonths和years值。 有序性 即程序执行的顺序按照代码的先后顺序执行。 java内存模型中的有序性可以总结为如果在本线程内观察所有操作都是有序的如果在一个线程中观察另一个线程所有操作都是无序的。前半句是指“线程内表现为串行语义”后半句是指“指令重排序”现象和“工作内存主主内存同步延迟”现象。 ​ 在Java内存模型中为了效率是允许编译器和处理器对指令进行重排序当然重排序不会影响单线程的运行结果但是对多线程会有影响。Java提供volatile来保证一定的有序性。最著名的例子就是单例模式里面的DCL双重检查锁。另外可以通过synchronized和Lock来保证有序性synchronized和Lock保证每个时刻是有一个线程执行同步代码相当于是让线程顺序执行同步代码自然就保证了有序性。 volatile变量的特性 保证可见性不保证原子性 当写一个volatile变量时JMM会把该线程本地内存中的变量强制刷新到主内存中去这个写会操作会导致其他线程中的缓存无效。 禁止指令重排 重排序是指编译器和处理器为了优化程序性能而对指令序列进行排序的一种手段。重排序需要遵守一定规则 重排序操作不会对存在数据依赖关系的操作进行重排序。 比如a1;ba; 这个指令序列由于第二个操作依赖于第一个操作所以在编译时和处理器运 行时这两个操作不会被重排序。 重排序是为了优化性能但是不管怎么重排序单线程下程序的执行结果不能被改变 比如a1;b2;cab这三个操作第一步a1)和第二步(b2)由于不存在数据依赖关系 所以可能会发 生重排序但是cab这个操作是不会被重排序的因为需要保证最终的结果一定是cab3。 重排序在单线程下一定能保证结果的正确性但是在多线程环境下可能发生重排序影响结果下例中的1和2由于不存在数据依赖关系则有可能会被重排序先执行statustrue再执行a2。而此时线程B会顺利到达4处而线程A中a2这个操作还未被执行所以ba1的结果也有可能依然等于2。 指令重排序 出于性能原因允许JVM和CPU重新排序程序中的指令只要指令的语义含义保持不变即可。例如查看下面的指令 COPYint a 1; int b 2;a; b;这些指令可以按以下顺序重新排序而不会丢失程序的语义含义 COPYint a 1; a;int b 2; b;然而当其中一个变量是volatile变量时指令重排序会出现一个挑战。让我们看看MyClass这个前面Java volatile教程中的例子中出现的类 COPYpublic class MyClass {private int years;private int monthsprivate volatile int days;public void update(int years, int months, int days){this.years years;this.months months;this.days days;} }一旦update()方法写入一个值days新写入的值以years和months也被写入主存储器。但是如果JVM重新排序指令如下所示 COPYpublic void update(int years, int months, int days){this.days days;this.months months;this.years years; }当days变量被修改时months和years的值仍然写入主内存中但是这一次它发生在新的值被写入months和years之前也就是这两个变量的旧值会写入主存中后面两句的写入操作只是写到缓存中。因此新值不能正确地对其他线程可见。重新排序的指令的语义含义已经改变。 happens before 上面讲的是volatile变量自身的特性对程序员来说volatile对线程的内存可见性的影响比volatile自身的特性更为重要也更需要我们去关注。 从JSR-133开始volatile变量的写-读可以实现线程之间的通信。 从内存语义的角度来说volatile与锁有相同的效果volatile写和锁的释放有相同的内存语义volatile读与锁的获取有相同的内存语义。 请看下面使用volatile变量的示例代码 COPYclass VolatileExample {int a 0;volatile boolean flag false;public void writer() {a 1; //1flag true; //2}public void reader() {if (flag) { //3int i a; //4……}} }假设线程A执行writer()方法之后线程B执行reader()方法。根据happens before规则这个过程建立的happens before 关系可以分为两类 根据程序次序规则1 happens before 2; 3 happens before 4。根据volatile规则2 happens before 3。根据happens before 的传递性规则1 happens before 4。 上述happens before 关系的图形化表现形式如下 上图中每一个箭头链接的两个节点代表了一个happens before 关系。黑色箭头表示程序顺序规则橙色箭头表示volatile规则蓝色箭头表示组合这些规则后提供的happens before保证。 这里A线程写一个volatile变量后B线程读同一个volatile变量。A线程在写volatile变量之前所有可见的共享变量在B线程读同一个volatile变量后将立即变得对B线程可见。 Happens-Before 保证 为了解决指令重排序挑战除了可见性保证之外Java volatile关键字还提供“happens-before”保证。happens-before保证保证 volatile 之前读写 如果读取/写入最初发生在写入volatile变量之前读取/写入其他变量不能重新排序在写入volatile变量之后。 ​ 写入volatile变量之前的读/写操作被保证 “happen before” 写入volatile变量。请注意发生在写入volatile变量之后的读/写操作依然可以重排序到写入volatile变量前只是不能相反。允许从后到前但不允许从前到后。 volatile 之后读写 如果读/写操作最初发生在读取volatile变量之后则读取/写入其他变量不能重排序到发生在读取volatile变量之前。请注意发生在读取volatile变量之前的读/写操作依然可以重排序到读取volatile变量后只是不能相反。允许从前到后但不允许从后到前。 上述 “happens-before”规则保证确保volatile关键字的可见性保证在强制执行。 COPYpublic class VolatileTest {private volatile int vi 1;private int i 2;private int i2 3;Testpublic void test() {System.out.println(i); //1 读取普通变量i3; //2 写入普通变量//1 2 不能重排序到3之后操作4可以重排序到3前面vi 2; //3 写入volatile变量i2 5; //4 写入普通变量}Testpublic void test2() {System.out.println(i); //1 读取普通变量//3不能重排序到在2前但1可以重排序到2后System.out.println(vi); //2 读取volatile变量System.out.println(i2); //3 读取普通变量} } volatile注意事项 volatile 线程不安全 即使volatile关键字保证volatile变量的所有读取直接从主存储器读取并且所有对volatile变量的写入都直接写入主存储器仍然存在声明volatile变量线程不安全。 在前面解释的情况中只有线程1写入共享counter变量声明counter变量为volatile足以确保线程2始终看到最新的写入值。 实际上如果写入volatile变量的新值不依赖于其先前的值则甚至可以多个线程写入共享变量并且仍然可以在主存储器中存储正确的值。换句话说就是将值写入共享volatile变量的线程开始并不需要读取其旧值来计算其下一个值。 一旦线程需要首先读取volatile变量的旧值并且基于该值为共享volatile变量生成新值volatile变量就不再足以保证正确的可见性。读取volatile 变量和写入新值之间的短时间间隔会产生竞争条件 其中多个线程可能读取volatile变量的同一个旧值然后为其生成新值并将该值写回主内存 - 覆盖彼此的值。 多个线程递增同一个计数器的情况正是 volatile变量并不安全的情况。以下部分更详细地解释了这种情况。 想象一下如果线程1将值为0的共享变量counter读入其CPU高速缓存将其增加到1并且不将更改的值写回主存储器。然后线程2也从主存储器读取相同的counter变量进入自己的CPU高速缓存其中变量的值仍为0。然后线程2也将计数器递增到1也不将其写回主存储器。这种情况如下图所示 线程1和线程2现在失去了同步。共享变量counter的实际值应为2但每个线程的CPU缓存中的变量值为1而在主内存中该值仍为0。这是一个混乱即使线程最终将共享变量counter的值写回主存储器该值也将是错误的。 保证线程安全 正如我前面提到的如果两个线程都在读取和写入共享变量那么使用 volatile关键字是不安全的。 在这种情况下您需要使用synchronized来保证变量的读取和写入是原子性的。读取或写入一个volatile变量不会阻塞其他线程读取或写入这个变量。为此您必须在临界区周围使用synchronized关键字。 作为synchronized块的替代方法您还可以使用java.util.concurrent包中众多的原子数据类型。例如AtomicLong或者 AtomicReference或其他的。 如果只有一个线程读取和写入volatile变量的值而其他线程只读取这个变量那么此线程将保证其他线程能看到volatile变量的最新值。如果不将变量声明为volatile则无法保证。 volatile关键字也可以保证在64位变量上正常使用。 volatile的性能考虑 读取和写入volatile变量会导致变量从主存中读取或写入主存读取和写入主内存比访问CPU缓存开销更大。访问volatile变量也会阻止指令重排序这是一种正常的性能提升技术。因此当您确实需要强制实施变量可见性时才使用volatile变量。 原理 volatile可以保证线程可见性且提供了一定的有序性但是无法保证原子性。在JVM底层volatile是采用“内存屏障”来实现的。观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现加入volatile关键字时会多出一个lock前缀指令lock前缀指令实际上相当于一个内存屏障也成内存栅栏内存屏障会提供3个功能 它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置也不会把前面的指令排到内存屏障的后面即在执行到内存屏障这句指令时在它前面的操作已经全部完成它会强制将对缓存的修改操作立即写入主存如果是写操作它会导致其他CPU中对应的缓存行无效。 内存语义 volatile写的内存语义 当写一个 volatile 变量时JMM 会把该线程对应的本地内存中的共享变量值刷新到主内存。 以上面示例程序VolatileExample为例假设线程A首先执行writer()方法随后线程B执行reader()方法初始时两个线程的本地内存中的flag和a都是初始状态。下图是线程A执行volatile写后共享变量的状态示意图 如上图所示线程A在写flag变量后本地内存A中被线程A更新过的两个共享变量的值被刷新到主内存中。此时本地内存A和主内存中的共享变量的值是一致的。 volatile读的内存语义 当读一个 volatile 变量时JMM 会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量。 下面是线程 B 读同一个 volatile 变量后共享变量的状态示意图 如上图所示在读flag变量后本地内存B已经被置为无效。此时线程B必须从主内存中读取共享变量。线程B的读取操作将导致本地内存B与主内存中的共享变量的值也变成一致的了。 如果我们把volatile写和volatile读这两个步骤综合起来看的话在读线程B读一个volatile变量后写线程A在写这个volatile变量之前所有可见的共享变量的值都将立即变得对读线程B可见。 小结 下面对volatile写和volatile读的内存语义做个总结 线程A写一个volatile变量实质上是线程A向接下来将要读这个volatile变量的某个线程发出了其对共享变量所在修改的消息。线程B读一个volatile变量实质上是线程B接收了之前某个线程发出的在写这个volatile变量之前对共享变量所做修改的消息。线程A写一个volatile变量随后线程B读这个volatile变量这个过程实质上是线程A通过主内存向线程B发送消息。 volatile内存语义的实现 前文我们提到过重排序分为编译器重排序和处理器重排序。为了实现volatile内存语义JMM会分别限制这两种类型的重排序类型。下面是JMM针对编译器制定的volatile重排序规则表 是否能重排序第二个操作第二个操作第二个操作第一个操作普通读/写volatile读volatile写普通读/写NOvolatile读NONONOvolatile写NONO 举例来说第三行最后一个单元格的意思是在程序顺序中当第一个操作为普通变量的读或写时如果第二个操作为volatile写则编译器不能重排序这两个操作。 从上表我们可以看出 当第二个操作为volatile写操作时,不管第一个操作是什么(普通读写或者volatile读写),都不能进行重排序。这个规则确保volatile写之前的所有操作都不会被重排序到volatile写之后;当第一个操作为volatile读操作时,不管第二个操作是什么,都不能进行重排序。这个规则确保volatile读之后的所有操作都不会被重排序到volatile读之前;当第一个操作是volatile写操作时,第二个操作是volatile读操作,不能进行重排序。 为了实现 volatile 的内存语义编译器在生成字节码时会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。下面是基于保守策略的 JMM 内存屏障插入策略 在每个 volatile 写操作的前面插入一个 StoreStore 屏障禁止前面的写与volatile写重排序。在每个 volatile 写操作的后面插入一个 StoreLoad 屏障禁止volatile写与后面可能有的读和写重排序。在每个 volatile 读操作的后面插入一个 LoadLoad 屏障禁止volatile读与后面的读操作重排序。在每个 volatile 读操作的后面插入一个 LoadStore 屏障禁止volatile读与后面的写操作重排序。 其中重点说下StoreLaod屏障它是确保可见性的关键因为它会将屏障之前的写缓冲区中的数据全部刷新到主内存中。上述内存屏障插入策略非常保守但它可以保证在任意处理平台任意的程序中都能得到正确的volatile语义。下面是保守策略为什么说保守呢因为有些在实际的场景是可省略的下volatile 写操作 插入内存屏障后生成的指令序列示意图 其中StoreStore屏障可以保证在volatile写之前其前面的所有普通写操作对任意处理器可见把它刷新到主内存。 另外volatile写后面有StoreLoad屏障此屏障的作用是避免volatile写与后面可能有的读或写操作进行重排序。因为编译器常常无法准确判断在一个volatile写的后面是否需要插入一个StoreLoad屏障比如一个volatile写之后方法立即return为了保证能正确实现volatile的内存语义JMM采取了保守策略在每个volatile写的后面插入一个StoreLoad屏障。因为volatile写-读内存语义的常见模式是一个写线程写volatile变量多个度线程读同一个volatile变量。当读线程的数量大大超过写线程时选择在volatile写之后插入StoreLoad屏障将带来可观的执行效率的提升。从这里也可看出JMM在实现上的一个特点首先确保正确性然后再去追求效率其实我们工作中编码也是一样。 下面是在保守策略下volatile读插入内存屏障后生产的指令序列示意图 上述volatile写和volatile读的内存屏障插入策略非常保守。在实际执行时只要不改变volatile写-读的内存语义编译器可以根据具体情况忽略不必要的屏障。在JMM基础中就有提到过各个处理器对各个屏障的支持度其中x86处理器仅会对写-读操作做重排序。 下面我们通过具体的示例代码来说明 COPYclass VolatileBarrierExample {int a;volatile int v1 1;volatile int v2 2;void readAndWrite() {int i v1; //第一个volatile读int j v2; // 第二个volatile读a i j; //普通写v1 i 1; // 第一个volatile写v2 j * 2; //第二个 volatile写}… //其他方法 }针对 readAndWrite() 方法编译器在生成字节码时可以做如下的优化 注意最后的StoreLoad屏障不能省略。因为第二个volatile写之后方法立即return。此时编译器可能无法准确断定后面是否会有volatile读或写为了安全起见编译器常常会在这里插入一个StoreLoad屏障。 上面的优化是针对任意处理器平台由于不同的处理器有不同“松紧度”的处理器内存模型内存屏障的插入还可以根据具体的处理器内存模型继续优化。以x86处理器为例上图中除最后的StoreLoad屏障外其它的屏障都会被省略。 前面保守策略下的volatile读和写在 x86处理器平台可以优化成 前文提到过x86 处理器仅会对写 - 读操作做重排序。x86处理器仅会对写-读操作做重排序。X86不会对读-读读-写和写-写操作做重排序因此在x86处理器中会省略掉这三种操作类型对应的内存屏障。在x86中JMM仅需在volatile写后面插入一个StoreLoad屏障即可正确实现volatile写-读的内存语义。这意味着在x86处理器中volatile写的开销比volatile读的开销会大很多因为执行StoreLoad屏障开销会比较大。 为什么要增强volatile的内存语义 在 JSR-133 之前的旧 Java 内存模型中虽然不允许 volatile 变量之间重排序但旧的 Java 内存模型允许 volatile 变量与普通变量之间重排序。在旧的内存模型中VolatileExample 示例程序可能被重排序成下列时序来执行 在旧的内存模型中当 1 和 2 之间没有数据依赖关系时1 和 2 之间就可能被重排序3 和 4 类似。其结果就是读线程 B 执行 4 时不一定能看到写线程 A 在执行 1 时对共享变量的修改。 因此在旧的内存模型中 volatile的写-读没有锁的释放-获所具有的内存语义。为了提供一种比锁更轻量级的线程之间通信的机制JSR-133专家组决定增强volatile的内存语义严格限制编译器和处理器对volatile变量与普通变量的重排序确保volatile的写-读和锁的释放-获取一样具有相同的内存语义。从编译器重排序规则和处理器内存屏障插入策略来看只要volatile变量与普通变量之间的重排序可能会破坏volatile的内存语意这种重排序就会被编译器重排序规则和处理器内存屏障插入策略禁止。 由于volatile仅仅保证对单个volatile变量的读/写具有原子性而锁的互斥执行的特性可以确保对整个临界区代码的执行具有原子性。在功能上锁比volatile更强大在可伸缩性和执行性能上volatile更有优势。如果读者想在程序中用volatile代替监视器锁请一定谨慎具体细节请参阅参考Java理论与实践正确使用Volatile变量。 本文由传智教育博学谷狂野架构师教研团队发布。 如果本文对您有帮助欢迎关注和点赞如果您有任何建议也可留言评论或私信您的支持是我坚持创作的动力。 转载请注明出处
http://www.dnsts.com.cn/news/178252.html

相关文章:

  • 尧都网站建设tq网站建设
  • 易企互联网站建设推广怎么做才可以赚钱
  • 我要自学网网站南京做网站的
  • 网站开发毕业设计说明陕西网渭南站
  • 商丘网站制作费用app优化
  • 个人网站备案网址wordpress spa
  • 怎么用dw做地图网站公司官网制作多少钱
  • 学做电商网站设计wordpress折腾
  • 天津自贸区建设局网站wordpress 匿名投票
  • 网站重新建设的申请书wordpress qq主题下载失败
  • 宜宾网站制作asp程序设计做网站
  • 非盈利性备案网站 淘宝客网站网站网络设计是怎么做的
  • 广安门外网站建设建设云南省癌症中心网站
  • 义乌论坛网站建设湖州公司网站建设
  • 商城网站设计服务如何进行电商网站设计开发
  • 公司网站建设详细方案网站建设费用明细湖南岚鸿
  • 购物网站如何建设商标买卖
  • 哪个网站可以免费做电子请柬wordpress上传文档
  • 网站设计在线培训机构网络服务类型及其采用的网络协议
  • 南阳网站排名优化公司微信电影网站建设教程
  • 个人网站 百度收录《梦幻西游》官网
  • 网站的整体风格包括怎样做软件开发
  • 网站建设中问题分析与解决网络销售模式 自建网站
  • 如何在阿里云云服务器上搭建网站公司官网怎么弄
  • 旅游网站设计规划书Python爬取wordpress
  • 传诚信网站建设公司如何建设网站
  • 网站公司做网站修改会收费吗烟台网站制作策划
  • 朝阳住房和城乡建设官方网站全球网站排名
  • tp钱包下载抓取的网站如何做seo
  • 静态网站素材浙江备案需要开启网站吗