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

网站如何做mip人才网站建设的目标

网站如何做mip,人才网站建设的目标,六安网站制作多少钱,摄影网站设计与制作#x1f44f;作者简介#xff1a;大家好#xff0c;我是爱吃芝士的土豆倪#xff0c;24届校招生Java选手#xff0c;很高兴认识大家#x1f4d5;系列专栏#xff1a;Spring源码、JUC源码#x1f525;如果感觉博主的文章还不错的话#xff0c;请#x1f44d;三连支持作者简介大家好我是爱吃芝士的土豆倪24届校招生Java选手很高兴认识大家系列专栏Spring源码、JUC源码如果感觉博主的文章还不错的话请三连支持一下博主哦博主正在努力完成2023计划中源码溯源一探究竟联系方式nhs19990716加我进群大家一起学习一起进步一起对抗互联网寒冬 文章目录 问题提出为什么不安全解决思路-锁解决思路-无锁是否真的无锁呢 CAS 与 volatile慢动作分析volatile为什么无锁效率高CAS 的特点 原子整数原子引用不安全实现安全实现-使用锁安全实现-使用 CASABA 问题及解决ABA 问题AtomicStampedReferenceAtomicMarkableReference 原子数组不安全的数组安全的数组 字段更新器原子累加器并发编程大师 - 与人类优秀的灵魂对话版Unsafe概述Unsafe CAS 操作 问题提出 有如下需求保证 account.withdraw 取款方法的线程安全 import java.util.ArrayList; import java.util.List;interface Account {// 获取余额Integer getBalance();// 取款void withdraw(Integer amount);/*** 方法内会启动 1000 个线程每个线程做 -10 元 的操作* 如果初始余额为 10000 那么正确的结果应当是 0*/static void demo(Account account) {ListThread ts new ArrayList();long start System.nanoTime();for (int i 0; i 1000; i) {ts.add(new Thread(() - {account.withdraw(10);}));}ts.forEach(Thread::start);ts.forEach(t - {try {t.join();} catch (InterruptedException e) {e.printStackTrace();}});long end System.nanoTime();System.out.println(account.getBalance() cost: (end-start)/1000_000 ms);} }原有实现并不是线程安全的 class AccountUnsafe implements Account {private Integer balance;public AccountUnsafe(Integer balance) {this.balance balance;}Overridepublic Integer getBalance() {return balance;}Overridepublic void withdraw(Integer amount) {balance - amount;} }执行测试代码 public static void main(String[] args) {Account.demo(new AccountUnsafe(10000));}某次的执行结果 330 cost: 306 ms为什么不安全 withdraw 方法 public void withdraw(Integer amount) {balance - amount; }原因是因为 会出现指令交错的情况因为正常的逻辑比如一个i–的操作会分为四步1.先获取值、2.获取要减的数、3.相减、4.写回。正常来说如果所有的都按照这个顺序来执行的话不可能出现线程安全的问题但是实际上不是这样的多线程的时候或许可以保证有序性但是没办法保证指令交错所以导致 可能的顺序是 11234234这样就会出现线程不安全的情况拉。 解决思路-锁 首先想到的是给 Account 对象加锁但是太笨重了 class AccountUnsafe implements Account {private Integer balance;public AccountUnsafe(Integer balance) {this.balance balance;}Overridepublic synchronized Integer getBalance() {return balance;}Overridepublic synchronized void withdraw(Integer amount) {balance - amount;} }结果为 0 cost: 399 ms 解决思路-无锁 class AccountSafe implements Account {private AtomicInteger balance;public AccountSafe(Integer balance) {this.balance new AtomicInteger(balance);}Overridepublic Integer getBalance() {return balance.get();}Overridepublic void withdraw(Integer amount) {while (true) {int prev balance.get();// 获取余额的最新值int next prev - amount;// 要修改的余额if (balance.compareAndSet(prev, next)) { // 真正修改如果成功结束循环如果失败继续循环break;}}// 可以简化为下面的方法// balance.addAndGet(-1 * amount);} }执行测试代码 public static void main(String[] args) {Account.demo(new AccountSafe(10000)); }某次的执行结果 0 cost: 302 ms是否真的无锁呢 我们通过 Java 中的 AtomicInteger类中的 getAndIncrement()来看下 CAS 底层是怎么实现的。 public final int getAndIncrement() {return unsafe.getAndAddInt(this, valueOffset, 1);}可以看到它是调用的Unsafe类的getAndAddInt方法 public final int getAndAddInt(Object obj, long offset, int delta) {int value;do {value this.getIntVolatile(obj, offset);} while(!this.compareAndSwapInt(obj, offset, value, value delta));return v; }可以看到该方法内部是先获取到该对象的偏移量对应的值(value)然后调用 compareAndSwapInt 方法通过对比来修改该值如果这个值和value一样说明此过程中间没有 人修改该数据此时可以将该地址的值改为 valuedelta, 返回true,结束循环。否则说明有人修改该地址处的值返回false继续下一次循环。 那么是怎么保证 compareAndSwapInt(CAS)的原子性呢这个就由操作系统底层来提供了。 其实 CAS 的底层是 lock cmpxchg 指令X86 架构在单核 CPU 和多核 CPU 下都能够保证【比较-交换】的原子性。 在多核状态下某个核执行到带 lock 的指令时CPU 会让总线锁住当这个核把此指令执行完毕再开启总线。这个过程中不会被线程的调度机制所打断保证了多个线程对内存操作的准确性是原子的。 CAS 与 volatile 前面看到的 AtomicInteger 的解决方法内部并没有用锁来保护共享变量的线程安全。那么它是如何实现的呢 public void withdraw(Integer amount) {// 需要不断尝试直到成功为止while (true) {// 比如拿到了旧值 1000int prev balance.get();// 在这个基础上 1000-10 990int next prev - amount;/*compareAndSet 正是做这个检查在 set 前先比较 prev 与当前值- 不一致了next 作废返回 false 表示失败比如别的线程已经做了减法当前值已经被减成了 990那么本线程的这次 990 就作废了进入 while 下次循环重试- 一致以 next 设置为新值返回 true 表示成功*/if (balance.compareAndSet(prev, next)) {break;}}}其中的关键是 compareAndSet它的简称就是 CAS 也有 Compare And Swap 的说法它必须是原子操作。 其中左侧的两个cas操作都失败了。 慢动作分析 Slf4j public class SlowMotion {public static void main(String[] args) {AtomicInteger balance new AtomicInteger(10000);int mainPrev balance.get();log.debug(try get {}, mainPrev);new Thread(() - {sleep(1000);int prev balance.get();balance.compareAndSet(prev, 9000);log.debug(balance.toString());}, t1).start();sleep(2000);log.debug(try set 8000...);boolean isSuccess balance.compareAndSet(mainPrev, 8000);log.debug(is success ? {}, isSuccess);if(!isSuccess){mainPrev balance.get();log.debug(try set 8000...);isSuccess balance.compareAndSet(mainPrev, 8000);log.debug(is success ? {}, isSuccess);}}private static void sleep(int millis) {try {Thread.sleep(millis);} catch (InterruptedException e) {e.printStackTrace();}} }输出结果 2023-10-13 11:28:37.134 [main] try get 10000 2023-10-13 11:28:38.154 [t1] 9000 2023-10-13 11:28:39.154 [main] try set 8000... 2023-10-13 11:28:39.154 [main] is success ? false 2023-10-13 11:28:39.154 [main] try set 8000... 2023-10-13 11:28:39.154 [main] is success ? true volatile AtomicInteger 源码里面 应用到了 volatile 获取共享变量时为了保证该变量的可见性需要使用 volatile 修饰。 它可以用来修饰成员变量和静态成员变量他可以避免线程从自己的工作缓存中查找变量的值必须到主存中获取 它的值线程操作 volatile 变量都是直接操作主存。即一个线程对 volatile 变量的修改对另一个线程可见。 注意 volatile 仅仅保证了共享变量的可见性让其它线程能够看到最新值但不能解决指令交错问题不能保证原 子性 CAS 必须借助 volatile 才能读取到共享变量的最新值来实现【比较并交换】的效果 为什么无锁效率高 无锁情况下即使重试失败线程始终在高速运行没有停歇而 synchronized 会让线程在没有获得锁的时 候发生上下文切换进入阻塞。打个比喻 线程就好像高速跑道上的赛车高速运行时速度超快一旦发生上下文切换就好比赛车要减速、熄火 等被唤醒又得重新打火、启动、加速… 恢复到高速运行代价比较大。cas 不会让线程停下来while(true) 不停的循环 但无锁情况下因为线程要保持运行需要额外 CPU 的支持CPU 在这里就好比高速跑道没有额外的跑 道线程想高速运行也无从谈起虽然不会进入阻塞但由于没有分到时间片仍然会进入可运行状态还 是会导致上下文切换。在多核cpu下能发挥出优势虽然没有陷入block阻塞但是没有分到时间片还是要上下文切换 CAS 的特点 结合 CAS 和 volatile 可以实现无锁并发适用于线程数少、多核 CPU 的场景下。 CAS 是基于乐观锁的思想最乐观的估计不怕别的线程来修改共享变量就算改了也没关系我吃亏点再 重试呗。synchronized 是基于悲观锁的思想最悲观的估计得防着其它线程来修改共享变量我上了锁你们都别想 改我改完了解开锁你们才有机会。CAS 体现的是无锁并发、无阻塞并发请仔细体会这两句话的意思因为没有使用 synchronized所以线程不会陷入阻塞这是效率提升的因素之一、但如果竞争激烈可以想到重试必然频繁发生反而效率会受影响 原子整数 J.U.C 并发包提供了 AtomicBooleanAtomicIntegerAtomicLong 以 AtomicInteger 为例 AtomicInteger i new AtomicInteger(0);// 获取并自增i 0, 结果 i 1, 返回 0类似于 i System.out.println(i.getAndIncrement());// 自增并获取i 1, 结果 i 2, 返回 2类似于 i System.out.println(i.incrementAndGet());// 自减并获取i 2, 结果 i 1, 返回 1类似于 --i System.out.println(i.decrementAndGet());// 获取并自减i 1, 结果 i 0, 返回 1类似于 i-- System.out.println(i.getAndDecrement());// 获取并加值i 0, 结果 i 5, 返回 0 System.out.println(i.getAndAdd(5));// 加值并获取i 5, 结果 i 0, 返回 0 System.out.println(i.addAndGet(-5));以getAndIncrement 源码为例 具体来说这个方法会先读取对象var1上偏移量为var2的整数值然后将其与给定的var4相加在尝试使用CASCompare And Swap操作将它们的和写回到这个偏移量上存储的值中。如果CAS操作成功那么方法返回更新前的偏移量上存储的值否则就重复执行这个过程直到CAS操作成功为止。 // 获取并更新i 0, p 为 i 的当前值, 结果 i -2, 返回 0 // 其中函数中的操作能保证原子但函数需要无副作用 System.out.println(i.getAndUpdate(p - p - 2));// 更新并获取i -2, p 为 i 的当前值, 结果 i 0, 返回 0 // 其中函数中的操作能保证原子但函数需要无副作用 System.out.println(i.updateAndGet(p - p 2));原子引用 为什么需要原子引用类型 AtomicReferenceAtomicMarkableReferenceAtomicStampedReference 有如下方法 public interface DecimalAccount {// 获取余额BigDecimal getBalance();// 取款void withdraw(BigDecimal amount);/*** 方法内会启动 1000 个线程每个线程做 -10 元 的操作* 如果初始余额为 10000 那么正确的结果应当是 0*/static void demo(DecimalAccount account) {ListThread ts new ArrayList();for (int i 0; i 1000; i) {ts.add(new Thread(() - {account.withdraw(BigDecimal.TEN);}));}ts.forEach(Thread::start);ts.forEach(t - {try {t.join();} catch (InterruptedException e) {e.printStackTrace();}});System.out.println(account.getBalance());} }试着提供不同的 DecimalAccount 实现实现安全的取款操作 不安全实现 class DecimalAccountUnsafe implements DecimalAccount {BigDecimal balance;public DecimalAccountUnsafe(BigDecimal balance) {this.balance balance;}Overridepublic BigDecimal getBalance() {return balance;}Overridepublic void withdraw(BigDecimal amount) {BigDecimal balance this.getBalance();this.balance balance.subtract(amount);} }安全实现-使用锁 class DecimalAccountSafeLock implements DecimalAccount {private final Object lock new Object();BigDecimal balance;public DecimalAccountSafeLock(BigDecimal balance) {this.balance balance;}Overridepublic BigDecimal getBalance() {return balance;}Overridepublic void withdraw(BigDecimal amount) {synchronized (lock) {BigDecimal balance this.getBalance();this.balance balance.subtract(amount);}} }安全实现-使用 CAS class DecimalAccountSafeCas implements DecimalAccount {AtomicReferenceBigDecimal ref;public DecimalAccountSafeCas(BigDecimal balance) {ref new AtomicReference(balance);}Overridepublic BigDecimal getBalance() {return ref.get();}Overridepublic void withdraw(BigDecimal amount) {while (true) {BigDecimal prev ref.get();BigDecimal next prev.subtract(amount);if (ref.compareAndSet(prev, next)) {break;}}} }测试代码 DecimalAccount.demo(new DecimalAccountUnsafe(new BigDecimal(10000))); DecimalAccount.demo(new DecimalAccountSafeLock(new BigDecimal(10000))); DecimalAccount.demo(new DecimalAccountSafeCas(new BigDecimal(10000)));运行结果 4310 cost: 425 ms 0 cost: 285 ms 0 cost: 274 msABA 问题及解决 ABA 问题 static AtomicReferenceString ref new AtomicReference(A);public static void main(String[] args) throws InterruptedException {log.debug(main start...);// 获取值 A// 这个共享变量被它线程修改过String prev ref.get();other();sleep(1);// 尝试改为 Clog.debug(change A-C {}, ref.compareAndSet(prev, C));}private static void other() {new Thread(() - {log.debug(change A-B {}, ref.compareAndSet(ref.get(), B));}, t1).start();sleep(0.5);new Thread(() - {log.debug(change B-A {}, ref.compareAndSet(ref.get(), A));}, t2).start();}输出 11:29:52.325 c.Test36 [main] - main start... 11:29:52.379 c.Test36 [t1] - change A-B true 11:29:52.879 c.Test36 [t2] - change B-A true 11:29:53.880 c.Test36 [main] - change A-C true 主线程仅能判断出共享变量的值与最初值 A 是否相同不能感知到这种从 A 改为 B 又 改回 A 的情况如果主线程 希望 只要有其它线程【动过了】共享变量那么自己的 cas 就算失败这时仅比较值是不够的需要再加一个版本号 AtomicStampedReference static AtomicStampedReferenceString ref new AtomicStampedReference(A, 0);public static void main(String[] args) throws InterruptedException {log.debug(main start...);// 获取值 AString prev ref.getReference();// 获取版本号int stamp ref.getStamp();log.debug(版本 {}, stamp);// 如果中间有其它线程干扰发生了 ABA 现象other();sleep(1);// 尝试改为 Clog.debug(change A-C {}, ref.compareAndSet(prev, C, stamp, stamp 1));}private static void other() {new Thread(() - {log.debug(change A-B {}, ref.compareAndSet(ref.getReference(), B,ref.getStamp(), ref.getStamp() 1));log.debug(更新版本为 {}, ref.getStamp());}, t1).start();sleep(0.5);new Thread(() - {log.debug(change B-A {}, ref.compareAndSet(ref.getReference(), A,ref.getStamp(), ref.getStamp() 1));log.debug(更新版本为 {}, ref.getStamp());}, t2).start();}输出为 15:41:34.891 c.Test36 [main] - main start... 15:41:34.894 c.Test36 [main] - 版本 0 15:41:34.956 c.Test36 [t1] - change A-B true 15:41:34.956 c.Test36 [t1] - 更新版本为 1 15:41:35.457 c.Test36 [t2] - change B-A true 15:41:35.457 c.Test36 [t2] - 更新版本为 2 15:41:36.457 c.Test36 [main] - change A-C false AtomicStampedReference 可以给原子引用加上版本号追踪原子引用整个的变化过程如 A - B - A - C 通过AtomicStampedReference我们可以知道引用变量中途被更改了几次。 但是有时候并不关心引用变量更改了几次只是单纯的关心是否更改过所以就有了AtomicMarkableReference AtomicMarkableReference class GarbageBag {String desc;public GarbageBag(String desc) {this.desc desc;}public void setDesc(String desc) {this.desc desc;}Overridepublic String toString() {return super.toString() desc;} }Slf4j public class TestABAAtomicMarkableReference {public static void main(String[] args) throws InterruptedException {GarbageBag bag new GarbageBag(装满了垃圾);// 参数2 mark 可以看作一个标记表示垃圾袋满了AtomicMarkableReferenceGarbageBag ref new AtomicMarkableReference(bag, true);log.debug(主线程 start...);GarbageBag prev ref.getReference();log.debug(prev.toString());new Thread(() - {log.debug(打扫卫生的线程 start...);bag.setDesc(空垃圾袋);while (!ref.compareAndSet(bag, bag, true, false)) {} // 如果状态被改成了false那么下次compareAndSet就不会成功了log.debug(bag.toString());}).start();Thread.sleep(1000);log.debug(主线程想换一只新垃圾袋);boolean success ref.compareAndSet(prev, new GarbageBag(空垃圾袋), true, false);log.debug(换了么 success);log.debug(ref.getReference().toString());} }输出 2023-10-13 15:30:09.264 [main] 主线程 start... 2023-10-13 15:30:09.270 [main] cn.itcast.GarbageBag5f0fd5a0 装满了垃圾 2023-10-13 15:30:09.293 [Thread-1] 打扫卫生的线程 start... 2023-10-13 15:30:09.294 [Thread-1] cn.itcast.GarbageBag5f0fd5a0 空垃圾袋 2023-10-13 15:30:10.294 [main] 主线程想换一只新垃圾袋 2023-10-13 15:30:10.294 [main] 换了么false 2023-10-13 15:30:10.294 [main] cn.itcast.GarbageBag5f0fd5a0 空垃圾袋原子数组 AtomicIntegerArrayAtomicLongArrayAtomicReferenceArray /**参数1提供数组、可以是线程不安全数组或线程安全数组参数2获取数组长度的方法参数3自增方法回传 array, index参数4打印数组的方法*/ // supplier 提供者 无中生有 ()-结果 // function 函数 一个参数一个结果 (参数)-结果 , BiFunction (参数1,参数2)-结果 // consumer 消费者 一个参数没结果 (参数)-void, BiConsumer (参数1,参数2)-private static T void demo(SupplierT arraySupplier,FunctionT, Integer lengthFun,BiConsumerT, Integer putConsumer,ConsumerT printConsumer ) {ListThread ts new ArrayList();T array arraySupplier.get();int length lengthFun.apply(array);for (int i 0; i length; i) {// 每个线程对数组作 10000 次操作ts.add(new Thread(() - {for (int j 0; j 10000; j) {putConsumer.accept(array, j%length);}}));}ts.forEach(t - t.start()); // 启动所有线程ts.forEach(t - {try {t.join();} catch (InterruptedException e) {e.printStackTrace();}}); // 等所有线程结束printConsumer.accept(array);} 不安全的数组 demo(()-new int[10],(array)-array.length,(array, index) - array[index],array- System.out.println(Arrays.toString(array)) );结果 [9870, 9862, 9774, 9697, 9683, 9678, 9679, 9668, 9680, 9698] 其实本质上是这样的 其执行的是多个线程在对应数组里面进行 操作那么就会有这么一种情况两个线程 刚好读到了同样的位置然后 都对同一个数进行了 操作此时理论上 两个线程的操作 最后加了2实际上确加了1 安全的数组 demo(()- new AtomicIntegerArray(10),(array) - array.length(),(array, index) - array.getAndIncrement(index),array - System.out.println(array) );结果 [10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000] 字段更新器 AtomicReferenceFieldUpdater // 域 字段AtomicIntegerFieldUpdaterAtomicLongFieldUpdater 利用字段更新器可以针对对象的某个域Field进行原子操作只能配合 volatile 修饰的字段使用否则会出现异常 Exception in thread main java.lang.IllegalArgumentException: Must be volatile typepublic class Test5 {private volatile int field;public static void main(String[] args) {AtomicIntegerFieldUpdater fieldUpdater AtomicIntegerFieldUpdater.newUpdater(Test5.class, field);Test5 test5 new Test5();fieldUpdater.compareAndSet(test5, 0, 10);// 修改成功 field 10System.out.println(test5.field);// 修改成功 field 20fieldUpdater.compareAndSet(test5, 10, 20);System.out.println(test5.field);// 修改失败 field 20fieldUpdater.compareAndSet(test5, 10, 30);System.out.println(test5.field);} }原子累加器并发编程大师 - 与人类优秀的灵魂对话版 剑指JUC原理-10.并发编程大师的原子累加器底层优化原理与人类的优秀灵魂对话-CSDN博客 Unsafe cas 底层是调用的 unsafe 概述 Unsafe 对象提供了非常底层的操作内存、线程的方法Unsafe 对象不能直接调用只能通过反射获得 public class UnsafeAccessor {static Unsafe unsafe;static {try {Field theUnsafe Unsafe.class.getDeclaredField(theUnsafe);theUnsafe.setAccessible(true);unsafe (Unsafe) theUnsafe.get(null);} catch (NoSuchFieldException | IllegalAccessException e) {throw new Error(e);}}static Unsafe getUnsafe() {return unsafe;} }不要被名字所迷惑名字虽然叫 Unsafe但是这里并不是指什么线程安全的方面的不安全而是指这个类比较底层操作的都是内存线程不建议我们编程人员直接对它使用 Unsafe CAS 操作 其底层是通过内存偏移量来定位到这个属性定位到属性以后再对里面的属性值做一个比较并交换的动作。 Data class Student {volatile int id;volatile String name; }Unsafe unsafe UnsafeAccessor.getUnsafe(); Field id Student.class.getDeclaredField(id); Field name Student.class.getDeclaredField(name);// 获得成员变量的偏移量 long idOffset UnsafeAccessor.unsafe.objectFieldOffset(id); long nameOffset UnsafeAccessor.unsafe.objectFieldOffset(name); Student student new Student();// 使用 cas 方法替换成员变量的值 UnsafeAccessor.unsafe.compareAndSwapInt(student, idOffset, 0, 20); // 返回 true UnsafeAccessor.unsafe.compareAndSwapObject(student, nameOffset, null, 张三); // 返回 true System.out.println(student);输出 Student(id20, name张三) 使用自定义的 AtomicData 实现之前线程安全的原子整数 Account 实现 class AtomicData {private volatile int data;static final Unsafe unsafe;static final long DATA_OFFSET;static {unsafe UnsafeAccessor.getUnsafe();try {// data 属性在 DataContainer 对象中的偏移量用于 Unsafe 直接访问该属性DATA_OFFSET unsafe.objectFieldOffset(AtomicData.class.getDeclaredField(data));} catch (NoSuchFieldException e) {throw new Error(e);}}public AtomicData(int data) {this.data data;}public void decrease(int amount) {int oldValue;while(true) {// 获取共享变量旧值可以在这一行加入断点修改 data 调试来加深理解oldValue data;// cas 尝试修改 data 为 旧值 amount如果期间旧值被别的线程改了返回 falseif (unsafe.compareAndSwapInt(this, DATA_OFFSET, oldValue, oldValue - amount)) {return;}}}public int getData() {return data;} }Account 实现 Account.demo(new Account() {AtomicData atomicData new AtomicData(10000);Overridepublic Integer getBalance() {return atomicData.getData();}Overridepublic void withdraw(Integer amount) {atomicData.decrease(amount);} });
http://www.pierceye.com/news/867983/

相关文章:

  • 芜湖seo网站优化域名邮箱免费注册
  • 做宠物网站需要实现什么功能成都建工网站
  • jsp购物网站开发 论文海口澄迈县建设局网站
  • 单页销售网站制作制作花都区网站建设
  • 如何建立自己的购物网站discuz手机模板
  • 网站被刷流量怎么办wordpress fold主题
  • 做的新网站网上搜不到临沂免费做网站
  • 高端大气的网站制作建筑人才网下载
  • 如何快速提升网站关键词排名综合服务平台一站式服务平台网站开发
  • 阿土伯 是做网站的吗建设厅国网查询网站
  • 天长哪个广告公司做网站中国菲律宾汇率换算
  • 动漫在线制作网站wordpress get_template_part
  • 肇庆高端品牌网站建设住建部网站资质查询中宏建设集团
  • 扁平化网站模板下载莱西网站建设哪家好
  • 用vis做的简单网站网站建设需求方案pdf
  • 怎么免费做网站视频教学沈阳网站备案
  • 徐州有哪些网站制作公司太原住房和城乡建设部网站
  • 专门做摩托车的网站注册域名阿里云
  • 做个简单的网站建站公司费用
  • 网站建设举措网站免费建站方法
  • 遵义市双控体系建设网站wamp wordpress安装
  • 厦门的网站建设公司龙岗网站-建设深圳信科
  • 上海网站建设q.479185700強成都上界品牌设计事务所
  • 产品设计优秀网站做网站申请多少类商标
  • 中国行业网站贵州网站建设seo优化
  • 网站部兼容ie6没有防盗链的网站
  • google网站推广网站自助平台
  • 外贸自建站多久能出单wordpress的pdf阅读
  • 深圳东莞的网站建设公司网店代运营哪里好
  • 做费网站wordpress折叠代码