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

如何做企业网站建设wordpress好用的插件推荐

如何做企业网站建设,wordpress好用的插件推荐,婚纱网站内容制作,网站没被收录怎么办JUC并发编程- 03 (六)、共享模型之无锁1.问题提出(1).为什么不安全?(2).安全实现_使用锁(3).安全实现_使用CAS 2.CAS与volatile(1).CAS_原理介绍(2).CAS_Debug分析(3).volatile(4).为什么无锁效率高(5).CAS的特点 3.原子整形(1).原子整数类型_ 自增自减(2).原子整数类型_乘除模… JUC并发编程- 03 (六)、共享模型之无锁1.问题提出(1).为什么不安全?(2).安全实现_使用锁(3).安全实现_使用CAS 2.CAS与volatile(1).CAS_原理介绍(2).CAS_Debug分析(3).volatile(4).为什么无锁效率高(5).CAS的特点 3.原子整形(1).原子整数类型_ 自增自减(2).原子整数类型_乘除模 4. 原子引用类型(1).不安全的实现(2).安全实现_使用锁(3).安全实现_使用 CAS (AtomicReference)(4).ABA 问题(5).ABA问题解决1_AtomicStampedReference(6).ABA问题解决2_AtomicMarkableReference 5.原子数组(1).不安全的实现(2).安全实现_ 使用CAS 6.字段更新器(1).未使用voliatile修饰的时候会报异常(2).使用voliatile修饰后(3).存在问题ABA问题 7.原子累加器(1).运用累加器类性能更高(2).为什么性能高呢?(4).源码之 LongAdder(5).cas 锁源码(6).原理之伪共享(7).add源码 8.UnSafe(1).概述(2).获取UnSafe实列(3).UnSafe的Case操作(4).模拟原子整数 (七)、共享模型之不可变1.日期转换的问题(1).问题提出(2).思路 - 同步锁(3).思路 - 不可变 2.不可变设计(1).final 的使用(2).保护性拷贝 3.享元模式(1). 简介(2).体现(3).DIY 4.finnal原理(1).设置 final 变量的原理(2).获取final 变量的原理 5.无状态 (六)、共享模型之无锁 CAS 与 volatile原子整数原子引用原子累加器Unsafe 1.问题提出 有如下需求能保证 account.withdraw 取款方法的线程安全嘛? package com.jsxs.Test;import java.util.ArrayList; import java.util.List;/*** Author Jsxs* Date 2023/10/11 14:58* PackageName:com.jsxs.Test* ClassName: Account* Description: TODO* Version 1.0*/ public 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);} }原有实现并不是线程安全的 package com.jsxs.Test;/*** Author Jsxs* Date 2023/10/11 14:59* PackageName:com.jsxs.Test* ClassName: AccountUnsafe* Description: TODO* Version 1.0*/ public 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));} } 某次的执行结果 (1).为什么不安全? withdraw 方法: 对共享资源产生了竞态条件。 public void withdraw(Integer amount) {balance - amount;}对应的字节码 ALOAD 0 // - this ALOAD 0 GETFIELD cn/itcast/AccountUnsafe.balance : Ljava/lang/Integer; // - this.balance INVOKEVIRTUAL java/lang/Integer.intValue ()I // 拆箱 ALOAD 1 // - amount INVOKEVIRTUAL java/lang/Integer.intValue ()I // 拆箱 ISUB // 减法 INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer; // 结果装箱 PUTFIELD cn/itcast/AccountUnsafe.balance : Ljava/lang/Integer; // - this.balance多线程执行流程 ALOAD 0 // thread-0 - this ALOAD 0 GETFIELD cn/itcast/AccountUnsafe.balance // thread-0 - this.balance INVOKEVIRTUAL java/lang/Integer.intValue // thread-0 拆箱 ALOAD 1 // thread-0 - amount INVOKEVIRTUAL java/lang/Integer.intValue // thread-0 拆箱 ISUB // thread-0 减法 INVOKESTATIC java/lang/Integer.valueOf // thread-0 结果装箱 PUTFIELD cn/itcast/AccountUnsafe.balance // thread-0 - this.balance ALOAD 0 // thread-1 - this ALOAD 0 GETFIELD cn/itcast/AccountUnsafe.balance // thread-1 - this.balance INVOKEVIRTUAL java/lang/Integer.intValue // thread-1 拆箱 ALOAD 1 // thread-1 - amount INVOKEVIRTUAL java/lang/Integer.intValue // thread-1 拆箱 ISUB // thread-1 减法 INVOKESTATIC java/lang/Integer.valueOf // thread-1 结果装箱 PUTFIELD cn/itcast/AccountUnsafe.balance // thread-1 - this.balance(2).安全实现_使用锁 首先想到的是给 Account 对象加锁 (3).安全实现_使用CAS package com.jsxs.Test;import java.util.concurrent.atomic.AtomicInteger;/*** Author Jsxs* Date 2023/10/11 15:15* PackageName:com.jsxs.Test* ClassName: AccountSafe* Description: TODO* Version 1.0*/ public class AccountSafe implements Account {// 1. AtomicInteger 原子性整形 ⭐private AtomicInteger balance;public AccountSafe(Integer balance) {this.balance new AtomicInteger(balance); // ⭐⭐ 通过AtomicInteger 进行构造}Overridepublic Integer getBalance() {return balance.get(); // ⭐⭐⭐ 调用原子类的get方法}Overridepublic void withdraw(Integer amount) {while (true) {// 获取余额的最新值 调用get方法 ⭐⭐⭐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));} }2.CAS与volatile (1).CAS_原理介绍 前面看到的 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 的说法它必须是原子操作。 解释: 比如说线程1先获取余额为100然后减去10结果为90此时还没有来得及进行cas的操作线程2已经把原来100的值修改为了90。经比较线程2的将prev90更新到主存主存prev为90与线程1获取的prev值100是不相等那么返回fasle线程1再次进行重新-10操作. 注意 其实 CAS 的底层是 lock cmpxchg 指令X86 架构在单核 CPU 和多核 CPU 下都能够保证【比较-交换】的原子性。 在多核状态下某个核执行到带 lock 的指令时CPU 会让总线锁住当这个核把此指令执行完毕再开启总线。这个过程中不会被线程的调度机制所打断保证了多个线程对内存操作的准确性是原子的。 (2).CAS_Debug分析 1. 设置线程数为1然后在CAS的地方进行断点 2.查看断点达到的信息并且给共享变量赋值9000 3.获取最新值再次比较如果其他线程没有再次改变那么就返回true (3).volatile 获取共享变量时为了保证该变量的可见性需要使用 volatile 修饰。 它可以用来修饰成员变量和静态成员变量他可以避免线程从自己的工作缓存中查找变量的值必须到主存中获取它的值线程操作 volatile 变量都是直接操作主存。即一个线程对 volatile 变量的修改对另一个线程可见。 注意 volatile 仅仅保证了共享变量的可见性让其它线程能够看到最新值也能保证程序的有序性但不能解决指令交错问题不能保证原子性 CAS 必须借助 volatile 才能读取到共享变量的最新值来实现【比较并交换】的效果 (4).为什么无锁效率高 无锁情况下即使重试失败线程始终在高速运行没有停歇而 synchronized 会让线程在没有获得锁的时候发生上下文切换进入阻塞。打个比喻线程就好像高速跑道上的赛车高速运行时速度超快一旦发生上下文切换就好比赛车要减速、熄火等被唤醒又得重新打火、启动、加速… 恢复到高速运行代价比较大但无锁情况下因为线程要保持运行需要额外 CPU 的支持CPU 在这里就好比高速跑道没有额外的跑道线程想高速运行也无从谈起虽然不会进入阻塞但由于没有分到时间片仍然会进入可运行状态还是会导致上下文切换。 线程数不超过CPU的核定线程数都很高效假如超过了效率就会大幅降低。 (5).CAS的特点 结合 CAS 和 volatile 可以实现无锁并发适用于线程数少、多核 CPU 的场景下。 CAS 是基于乐观锁的思想最乐观的估计不怕别的线程来修改共享变量就算改了也没关系我吃亏点再重试呗。synchronized 是基于悲观锁的思想最悲观的估计得防着其它线程来修改共享变量我上了锁你们都别想改我改完了解开锁你们才有机会。CAS 体现的是无锁并发、无阻塞并发请仔细体会这两句话的意思 因为没有使用 synchronized所以线程不会陷入阻塞这是效率提升的因素之一。但如果竞争激烈可以想到重试必然频繁发生反而效率会受影响。 3.原子整形 J.U.C 并发包提供了 AtomicBooleanAtomicIntegerAtomicLong 以 AtomicInteger 为例 (1).原子整数类型_ 自增自减 原子类的所有方法都是线程安全的都具有原子性且CAS自旋的操作。所以刚才的取账问题我们可以用 addAndGet() 方法进行替换。 package com.jsxs.Test;import java.util.concurrent.atomic.AtomicInteger;/*** Author Jsxs* Date 2023/10/11 17:11* PackageName:com.jsxs.Test* ClassName: Test09* Description: TODO* Version 1.0*/ public class Test09 {public static void main(String[] args) {AtomicInteger integer new AtomicInteger(1);System.out.println(先获取再自增的操作 integer.getAndIncrement()); // iSystem.out.println(先自增再获取的操作: integer.incrementAndGet()); // iSystem.out.println(先自减再获取的操作:integer.decrementAndGet()); // --iSystem.out.println(先获取再自检的操作:integer.getAndDecrement()); // i--System.out.println(最终得值为:integer);System.out.println();System.out.println(自定义后增加:integer.getAndAdd(4)); // 先获取再4System.out.println(最终得值为:integer);System.out.println(自定义先增加:integer.addAndGet(4)); // 先自增4 在获取System.out.println(最终得值为:integer);System.out.println();} }默认都是cas自旋的操作 (2).原子整数类型_乘除模 package com.jsxs.Test;import java.util.concurrent.atomic.AtomicInteger;/*** Author Jsxs* Date 2023/10/11 17:11* PackageName:com.jsxs.Test* ClassName: Test09* Description: TODO* Version 1.0*/ public class Test09 {public static void main(String[] args) {AtomicInteger integer new AtomicInteger(1);// 这里是一个接口类型返回值类型是int,且参数类型是int, value是读取到的值 value*10是设置的值 会发生CAS自旋的操作System.out.println(integer.updateAndGet((value)-{return value*10;})); // 先修改在获取之System.out.println(integer.getAndUpdate((value)-{return value*10;})); // 先获取值再修改} }默认都是CAS自旋的操作 4. 原子引用类型 为什么需要原子引用类型 AtomicReferenceAtomicMarkableReferenceAtomicStampedReference 有如下方法 package com.jsxs.Test;import java.math.BigDecimal; import java.util.ArrayList; import java.util.List;/*** Author Jsxs* Date 2023/10/11 19:28* PackageName:com.jsxs.Test* ClassName: DecimalAccount* Description: TODO* Version 1.0*/ 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());} }(1).不安全的实现 package com.jsxs.Test;import java.math.BigDecimal;/*** Author Jsxs* Date 2023/10/11 19:30* PackageName:com.jsxs.Test* ClassName: DecimalAccountUnsafe* Description: TODO* Version 1.0*/ public 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);}public static void main(String[] args) {DecimalAccount.demo(new DecimalAccountUnsafe(new BigDecimal(10000)));} } (2).安全实现_使用锁 package com.jsxs.Test;import java.math.BigDecimal;/*** Author Jsxs* Date 2023/10/11 20:09* PackageName:com.jsxs.Test* ClassName: DecimalAccountSafeLock* Description: TODO* Version 1.0*/ public 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);}}public static void main(String[] args) {DecimalAccount.demo(new DecimalAccountSafeLock(new BigDecimal(10000)));}} (3).安全实现_使用 CAS (AtomicReference) package com.jsxs.Test;import java.math.BigDecimal; import java.util.concurrent.atomic.AtomicReference;/*** Author Jsxs* Date 2023/10/11 19:38* PackageName:com.jsxs.Test* ClassName: DecimalAccountSafeCas* Description: TODO* Version 1.0*/ public class DecimalAccountSafeCas implements DecimalAccount {// 1.我们这里使用原子引用类型进行修饰 ⭐AtomicReferenceBigDecimal ref;public DecimalAccountSafeCas(BigDecimal balance) {// 2.创建我们的原子引用类 ⭐⭐ref new AtomicReference(balance);}Overridepublic BigDecimal getBalance() {// 3.调用get()返回最新值 ⭐⭐⭐return ref.get();}Overridepublic void withdraw(BigDecimal amount) {// 4. CAS ⭐⭐⭐⭐while (true) {// 获取现在的余额 (旧值)BigDecimal prev ref.get();// 返回的新余额(新值) 现在的余额 - 取款金额BigDecimal next prev.subtract(amount);if (ref.compareAndSet(prev, next)) { // 进行我们的CAS操作, 主存与新值进行比较一样则false。break;}}}public static void main(String[] args) {DecimalAccount.demo(new DecimalAccountSafeCas(new BigDecimal(10000))); // 构造金额为10000} }(4).ABA 问题 无线程冲突的时候 package com.jsxs.Test;import lombok.extern.slf4j.Slf4j;import java.util.concurrent.atomic.AtomicReference;/*** Author Jsxs* Date 2023/10/11 20:18* PackageName:com.jsxs.Test* ClassName: Test10* Description: TODO* Version 1.0*/ Slf4j(topic c.test10) public class Test10 {// 1. 设置原子引用类型static AtomicReferenceString ref new AtomicReference(A);public static void main(String[] args) throws InterruptedException {log.debug(main start...);// 获取值 AString prev ref.get();Thread.sleep(1000);// 尝试改为 Clog.debug(change A-C {}, ref.compareAndSet(prev, C));} }ABA问题的展现 A-B-A-C package com.jsxs.Test;import lombok.extern.slf4j.Slf4j;import java.util.concurrent.atomic.AtomicReference;/*** Author Jsxs* Date 2023/10/11 20:18* PackageName:com.jsxs.Test* ClassName: Test10* Description: TODO* Version 1.0*/ Slf4j(topic c.test10) public class Test10 {// 1. 设置原子引用类型static AtomicReferenceString ref new AtomicReference(A);public static void main(String[] args) throws InterruptedException {log.debug(main start...);// 获取值 A// 这个共享变量被它线程修改过String prev ref.get();other();Thread.sleep(1000);// 尝试改为 Clog.debug(change A-C {}, ref.compareAndSet(prev, A));}private static void other() throws InterruptedException {new Thread(() - {log.debug(change A-B {}, ref.compareAndSet(ref.get(), B));}, t1).start();Thread.sleep(500);new Thread(() - {log.debug(change B-A {}, ref.compareAndSet(ref.get(), A));}, t2).start();} } ABA问题成立!!!! 估计将A的地址进行修改之后 package com.jsxs.Test;import lombok.extern.slf4j.Slf4j;import java.util.concurrent.atomic.AtomicReference;/*** Author Jsxs* Date 2023/10/11 20:18* PackageName:com.jsxs.Test* ClassName: Test10* Description: TODO* Version 1.0*/ Slf4j(topic c.test10) public class Test10 {// 1. 设置原子引用类型static AtomicReferenceString ref new AtomicReference(A);public static void main(String[] args) throws InterruptedException {log.debug(main start...);// 获取值 A// 这个共享变量被它线程修改过String prev ref.get();other();Thread.sleep(1000);// 尝试改为 Clog.debug(change A-C {}, ref.compareAndSet(prev, A));}private static void other() throws InterruptedException {new Thread(() - {log.debug(change A-B {}, ref.compareAndSet(ref.get(), B));}, t1).start();Thread.sleep(500);new Thread(() - {log.debug(change B-A {}, ref.compareAndSet(ref.get(), new String(A)));}, t2).start();} }我们发现ABA问题将不成立!!!! 主线程仅能判断出共享变量的值与最初值 A 是否相同不能感知到这种从 A 改为 B 又 改回 A 的情况如果主线程希望 (5).ABA问题解决1_AtomicStampedReference 只要有其它线程【动过了】共享变量那么自己的 cas 就算失败这时仅比较值是不够的需要再加一个版本号。 package com.jsxs.Test;import lombok.extern.slf4j.Slf4j;import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicStampedReference;/*** Author Jsxs* Date 2023/10/11 20:18* PackageName:com.jsxs.Test* ClassName: Test10* Description: TODO* Version 1.0*/ Slf4j(topic c.test10) public class Test10 {// 1. 设置原子版本引用类型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();Thread.sleep(1000);// 尝试改为 Clog.debug(change A-C {}, ref.compareAndSet(prev, C, stamp, stamp 1)); // 判断stamp当前版本号与主存中的版本号是否一致}private static void other() throws InterruptedException {new Thread(() - {log.debug(change A-B {}, ref.compareAndSet(ref.getReference(), B,ref.getStamp(), ref.getStamp() 1));log.debug(更新版本为 {}, ref.getStamp());}, t1).start();Thread.sleep(500);new Thread(() - {log.debug(change B-A {}, ref.compareAndSet(ref.getReference(), A,ref.getStamp(), ref.getStamp() 1));log.debug(更新版本为 {}, ref.getStamp());}, t2).start();} }AtomicStampedReference 可以给原子引用加上版本号追踪原子引用整个的变化过程如 A - B - A -C 通过AtomicStampedReference我们可以知道引用变量中途被更改了几次。 (6).ABA问题解决2_AtomicMarkableReference 但是有时候并不关心引用变量更改了几次只是单纯的关心是否更改过所以就有了AtomicMarkableReference. package com.jsxs.Test;/*** Author Jsxs* Date 2023/10/11 21:23* PackageName:com.jsxs.Test* ClassName: GarbageBag* Description: TODO* Version 1.0*/ public 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;} }package com.jsxs.Test;import lombok.extern.slf4j.Slf4j;import java.util.concurrent.atomic.AtomicMarkableReference;/*** Author Jsxs* Date 2023/10/11 21:24* PackageName:com.jsxs.Test* ClassName: TestABAAtomicMarkableReference* Description: TODO* Version 1.0*/ Slf4j(topic c.test007) public class TestABAAtomicMarkableReference {public static void main(String[] args) throws InterruptedException {GarbageBag bag new GarbageBag(装满了垃圾);// 参数2 mark 可以看作一个标记true表示垃圾袋满了 ⭐AtomicMarkableReferenceGarbageBag ref new AtomicMarkableReference(bag, true);log.debug(主线程 start...);// 1.获取余额GarbageBag prev ref.getReference();log.debug(prev.toString());// 2.保洁阿姨打扫卫生...new Thread(() - {log.debug(打扫卫生的线程 start...);bag.setDesc(空垃圾袋);// 3. 保洁阿姨对判断是否是空垃圾袋 (也就是新的和主存是否一致)while (!ref.compareAndSet(bag, bag, true, false)) {}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());} }依然是同一个垃圾袋... 5.原子数组 AtomicIntegerArrayAtomicLongArrayAtomicReferenceArray 原子数组主要保护的是我们数组中的元素!!! (1).不安全的实现 package com.jsxs.Test;import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier;/*** Author Jsxs* Date 2023/10/13 19:15* PackageName:com.jsxs.Test* ClassName: Test11* Description: TODO* Version 1.0*/ public class Test11 {public static void main(String[] args) {demo(()-new int[10], // ⭐非原子数组的创建(array)-array.length,(array,index)-array[index],(array)- System.out.println(Arrays.toString(array)));}/*** 参数1提供数组、可以是线程不安全数组或线程安全数组* 参数2获取数组长度的方法* 参数3自增方法回传 array, index* 参数4打印数组的方法*/ // supplier 提供者 无中生有 ()-结果 // function 函数 一个参数一个结果 (参数)-结果 , BiFunction (参数1,参数2)-结果 // consumer 消费者 一个参数没结果 (参数)-void, BiConsumer (参数1,参数2)-private static T void demo(SupplierT arraySupplier, // 1.没有参数一个结果FunctionT, Integer lengthFun, // 2. 一个参数一个结果BiConsumerT, Integer putConsumer, // 3.两个参数一个结果的ConsumerT printConsumer) { // 4.一个参数但没有返回值 (用作打印)ListThread ts new ArrayList();// 1.获取数组T array arraySupplier.get();// 2.设置数组的长度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);} }(2).安全实现_ 使用CAS package com.jsxs.Test;import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.atomic.AtomicIntegerArray; import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier;/*** Author Jsxs* Date 2023/10/13 19:15* PackageName:com.jsxs.Test* ClassName: Test11* Description: TODO* Version 1.0*/ public class Test11 {public static void main(String[] args) {demo(()-new AtomicIntegerArray(10), // 1.创建数组 (array)-array.length(), // 2. 提供长度(array,index)-array.getAndIncrement(index), // 3.对原子数组中的元素(下标)进行 1 的操作(array)- System.out.println(array));}/*** 参数1提供数组、可以是线程不安全数组或线程安全数组* 参数2获取数组长度的方法* 参数3自增方法回传 array, index* 参数4打印数组的方法*/ // supplier 提供者 无中生有 ()-结果 // function 函数 一个参数一个结果 (参数)-结果 , BiFunction (参数1,参数2)-结果 // consumer 消费者 一个参数没结果 (参数)-void, BiConsumer (参数1,参数2)-private static T void demo(SupplierT arraySupplier, // 1.没有参数一个结果FunctionT, Integer lengthFun, // 2. 一个参数一个结果BiConsumerT, Integer putConsumer, // 3.两个参数一个结果的ConsumerT printConsumer) { // 4.一个参数但没有返回值 (用作打印)ListThread ts new ArrayList();// 1.获取数组T array arraySupplier.get();// 2.设置数组的长度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);} }6.字段更新器 AtomicReferenceFieldUpdater // 域 字段AtomicIntegerFieldUpdaterAtomicLongFieldUpdater 字段更新器保护的是对象的属性和成员变量!!! 利用字段更新器可以针对对象的某个域Field进行原子操作只能配合 volatile 修饰的字段使用否则会出现异常。 (1).未使用voliatile修饰的时候会报异常 package com.jsxs.Test;import lombok.extern.slf4j.Slf4j;import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;/*** Author Jsxs* Date 2023/10/13 20:10* PackageName:com.jsxs.Test* ClassName: Test12* Description: TODO* Version 1.0*/ Slf4j(topic c.test12) public class Test12 {public static void main(String[] args) {Student student new Student();// 1.第一个参数: 类名; 第二个参数: 参数类型; 第三个: 参数名AtomicReferenceFieldUpdater updater AtomicReferenceFieldUpdater.newUpdater(Student.class,String.class,name);// 1.第一个参数: 对象名; 第二个参数:属性的原始值; 第三个参数: 修改成什么值updater.compareAndSet(student,null,张三);} }class Student {String name; // 我们这里没有使用 voliateOverridepublic String toString() {return Student{ name name \ };} } (2).使用voliatile修饰后 为什么要添加voliatile进行修饰呢? 为了保证我们保护的成员变量的可见性!!! package com.jsxs.Test;import lombok.extern.slf4j.Slf4j;import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;/*** Author Jsxs* Date 2023/10/13 20:10* PackageName:com.jsxs.Test* ClassName: Test12* Description: TODO* Version 1.0*/ Slf4j(topic c.test12) public class Test12 {public static void main(String[] args) {Student student new Student();// 1.第一个参数: 类名; 第二个参数: 参数类型; 第三个: 参数名AtomicReferenceFieldUpdater updater AtomicReferenceFieldUpdater.newUpdater(Student.class,String.class,name);// 1.第一个参数: 对象名; 第二个参数:属性的原始值; 第三个参数: 修改成什么值updater.compareAndSet(student,null,张三);System.out.println(student.toString());} }class Student {volatile String name;Overridepublic String toString() {return Student{ name name \ };} }(3).存在问题ABA问题 package com.jsxs.Test;import lombok.extern.slf4j.Slf4j;import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;/*** Author Jsxs* Date 2023/10/13 20:10* PackageName:com.jsxs.Test* ClassName: Test12* Description: TODO* Version 1.0*/ Slf4j(topic c.test12) public class Test12 {public static void main(String[] args) {Student student new Student();// 1.第一个参数: 类名; 第二个参数: 参数类型; 第三个: 参数名 ⭐AtomicReferenceFieldUpdater updater AtomicReferenceFieldUpdater.newUpdater(Student.class,String.class,name);// 2.第一个参数: 对象名; 第二个参数:属性的原始值; 第三个参数: 修改成什么值 ⭐⭐updater.compareAndSet(student,null,张三);System.out.println(student.toString());// 3.原始值指的正确那么会成功。⭐⭐⭐updater.compareAndSet(student,张三,张四);System.out.println(student.toString());// 4.原始值指的更改过的那么会失败。 底层原理还是CAS进行比较的 ⭐⭐⭐⭐updater.compareAndSet(student,张三,张五);System.out.println(student.toString());} }class Student {volatile String name;Overridepublic String toString() {return Student{ name name \ };} }7.原子累加器 累加器性能比较 (1).运用累加器类性能更高 package com.jsxs.Test;import lombok.extern.slf4j.Slf4j;import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.LongAdder; import java.util.function.Consumer; import java.util.function.Supplier;/*** Author Jsxs* Date 2023/10/13 20:33* PackageName:com.jsxs.Test* ClassName: Test13* Description: TODO* Version 1.0*/ Slf4j(topic c.test13) public class Test13 {public static void main(String[] args) {demo(()-new AtomicLong(0), // 1.设置我们的原子长整型(adder)-adder.getAndIncrement() //2.进行自增的操作);System.out.println(上面是原子长整型类下面是长整型累加器);demo(()-new LongAdder(),(adder)-adder.increment());}/**** param adderSupplier : ()-结果 提供累加器对象* param action : (参数)- 无返回结果, 提供累加操作* param T*/private static T void demo(SupplierT adderSupplier, ConsumerT action) {T adder adderSupplier.get();long start System.nanoTime();ListThread ts new ArrayList();// 4 个线程每人累加 50 万for (int i 0; i 40; i) {ts.add(new Thread(() - {for (int j 0; j 500000; j) {action.accept(adder);}}));}ts.forEach(t - t.start());ts.forEach(t - {try {t.join();} catch (InterruptedException e) {e.printStackTrace();}});long end System.nanoTime();System.out.println(adder cost: (end - start) / 1000_000);} }(2).为什么性能高呢? 性能提升的原因很简单就是在有竞争时设置多个累加单元Therad-0 累加 Cell[0]而 Thread-1 累加Cell[1]… 最后将结果汇总。这样它们在累加时操作的不同的 Cell 变量因此减少了 CAS 重试失败从而提高性能。相当于不共享一个累加单元每一个线程都有一个自己的累加单元!!! (4).源码之 LongAdder LongAdder 是并发大师 author Doug Lea 大哥李的作品设计的非常精巧 LongAdder 类有几个关键域 // 累加单元数组, 懒惰初始化transient volatile Cell[] cells;// 基础值, 如果没有竞争, 则用 cas 累加这个域transient volatile long base;// 在 cells 创建或扩容时, 置为 1, 表示加锁transient volatile int cellsBusy;(5).cas 锁源码 CAS锁的源码实践但是实际开发中我们不用用下面这样的代码进行开发 package com.jsxs.Test;import lombok.extern.slf4j.Slf4j;import java.util.concurrent.atomic.AtomicInteger;/*** Author Jsxs* Date 2023/10/15 11:45* PackageName:com.jsxs.Test* ClassName: LockCas* Description: TODO* Version 1.0*/ Slf4j(topic c.test) public class LockCas {// 0的时候表示没有加锁1的时候表示已经加锁private AtomicInteger state new AtomicInteger(0);public void lock() {while (true) {if (state.compareAndSet(0, 1)) {break;}}}public void unlock() {log.debug(unlock...);state.set(0);}public static void main(String[] args) {LockCas lock new LockCas();// 1.线程1执行完毕解锁之后线程2才能继续执行new Thread(() - {log.debug(begin...);lock.lock();try {log.debug(lock...);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}} finally {lock.unlock();}}).start();new Thread(() - {log.debug(begin...);lock.lock();try {log.debug(lock...);} finally {lock.unlock();}}).start();} }(6).原理之伪共享 其中 Cell 即为累加单元 sun.misc.Contended //⭐注解: 防止缓存行伪共享 static final class Cell {volatile long value;Cell(long x) {value x;}// 最重要的方法, 用来 cas 方式进行累加, prev 表示旧值, next 表示新值final boolean cas(long prev, long next) {return UNSAFE.compareAndSwapLong(this, valueOffset, prev, next);}// 省略不重要代码 }得从缓存说起缓存与内存的速度比较 为什么设置那么多的缓存呢 因为 CPU 与 内存 的速度差异很大需要靠预读数据至缓存来提升效率。 而缓存以缓存行为单位每个缓存行对应着一块内存一般是 64 byte8 个 long 缓存的加入会造成数据副本的产生即同一份数据会缓存在不同核心的缓存行中。 也就是相当于取两份。 CPU 要保证数据的一致性如果某个 CPU 核心更改了数据其它 CPU 核心对应的整个缓存行必须失效。 因为 Cell 是数组形式在内存中是连续存储的一个 Cell 为 24 字节16 字节的对象头和 8 字节的 value因此缓存行可以存下 2 个的 Cell 对象。这样问题来了 Core-0 要修改 Cell[0]Core-1 要修改 Cell[1] 无论谁修改成功都会导致对方 Core 的缓存行失效比如 Core-0 中 Cell[0]6000, Cell[1]8000 要累加Cell[0]6001, Cell[1]8000 这时会让 Core-1 的缓存行失效 sun.misc.Contended 用来解决这个问题它的原理是在使用此注解的对象或字段的前后各增加 128 字节大小的padding从而让 CPU 将对象预读至缓存时占用不同的缓存行这样不会造成对方缓存行的失效。 (7).add源码 public void add(long x) {// as 为累加单元数组// b 为基础值// x 为累加值Cell[] as;long b, v;int m;Cell a;// 进入 if 的两个条件// 1. as 有值, 表示已经发生过竞争, 进入 if// 2. cas 给 base 累加时失败了, 表示 base 发生了竞争, 进入 ifif ((as cells) ! null || !casBase(b base, b x)) {// uncontended 表示 cell 没有竞争boolean uncontended true;if (// as 还没有创建as null || (m as.length - 1) 0 ||// 当前线程对应的 cell 还没有(a as[getProbe() m]) null ||// cas 给当前线程的 cell 累加失败 uncontendedfalse ( a 为当前线程的 cell )!(uncontended a.cas(v a.value, v x))) {// 进入 cell 数组创建、cell 创建的流程longAccumulate(x, null, uncontended);}}}8.UnSafe (1).概述 Unsafe 对象提供了非常底层的操作内存、线程的方法Unsafe 对象不能直接调用只能通过反射获得。 (2).获取UnSafe实列 package com.jsxs.Test;import sun.misc.Unsafe;import java.lang.reflect.Field;/*** Author Jsxs* Date 2023/10/18 21:00* PackageName:com.jsxs.Test* ClassName: Test14* Description: TODO* Version 1.0*/ public class Test14 {public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {// 1.通过反射指定属性名获取具体属性Field unsafe Unsafe.class.getDeclaredField(theUnsafe);// 2.因为是私有的成员变量所以我们需要先设置Accessibleunsafe.setAccessible(true);// 3.返回我们的真正的UnSafe对象Unsafe o (Unsafe)unsafe.get(null);System.out.println(o);} }(3).UnSafe的Case操作 package com.jsxs.Test;import lombok.Data; import sun.misc.Unsafe;import java.lang.reflect.Field;/*** Author Jsxs* Date 2023/10/18 21:00* PackageName:com.jsxs.Test* ClassName: Test14* Description: TODO* Version 1.0*/ public class Test14 {public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {// 1.通过反射指定属性名获取具体属性Field o Unsafe.class.getDeclaredField(theUnsafe);// 2.因为是私有的成员变量所以我们需要先设置Accessibleo.setAccessible(true);// 3.返回我们的真正的UnSafe对象Unsafe unsafe (Unsafe)o.get(null);System.out.println(o);// 1.获取欲的偏移地址 : 获取Student1中id属性的偏移地址long idOffset unsafe.objectFieldOffset(Student1.class.getDeclaredField(id));long nameOffset unsafe.objectFieldOffset(Student1.class.getDeclaredField(name));Student1 student1 new Student1();// 2.执行 cas 操作unsafe.compareAndSwapInt(student1,idOffset,0,1); // 第一个: 对象, 第二个: id的偏移地址第三个: 旧值,第四个: 新值unsafe.compareAndSwapObject(student1,nameOffset,null,张三); // 第一个: 对象, 第二个: id的偏移地址第三个: 旧值,第四个: 新值// 3.查看我们是否成功!! System.out.println(student1.id student1.name);} }Data class Student1 {volatile int id;volatile String name; }(4).模拟原子整数 使用自定义的 AtomicData 实现之前线程安全的原子整数 Account 实现。 1.Account接口 package com.jsxs.Test;import java.util.ArrayList; import java.util.List;/*** Author Jsxs* Date 2023/10/11 14:58* PackageName:com.jsxs.Test* ClassName: Account* Description: TODO* Version 1.0*/ public 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(() - {// 每次取10块钱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);} } 2.实现 package com.jsxs.Test;import com.jsxs.utils.UnSafeUtils; import sun.misc.Unsafe;import java.lang.reflect.Field;/*** Author Jsxs* Date 2023/10/18 21:32* PackageName:com.jsxs.Test* ClassName: Test15* Description: TODO* Version 1.0*/ public class Test15 {public static void main(String[] args) {Account.demo(new MyAtomicInteger(10000));} }class MyAtomicInteger implements Account{volatile int value;static final long valueOffset;static Unsafe unsafe;static {try {// 1.通过反射机制进行获取UnSafe ⭐Field theUnsafe Unsafe.class.getDeclaredField(theUnsafe);theUnsafe.setAccessible(true);unsafe (Unsafe) theUnsafe.get(null);// 2.获取我们偏移域valueOffsetunsafe.objectFieldOffset(MyAtomicInteger.class.getDeclaredField(value));} catch (NoSuchFieldException | IllegalAccessException e) {throw new Error(e);}}public int getValue() {return value;}public void decrement(int amount){while (true){// 1.旧值int prev this.value;// 2.新值int nextprev-amount;// 3.利用 UnSafe 进行设置 ⭐⭐if (unsafe.compareAndSwapInt(this,valueOffset,prev,next)){break;}}}Overridepublic Integer getBalance() {return getValue();}public MyAtomicInteger(int value) {this.value value;}Overridepublic void withdraw(Integer amount) {decrement(amount);}}测试成功 (七)、共享模型之不可变 不可变类的使用不可变类设计无状态类设计 1.日期转换的问题 (1).问题提出 下面的代码在运行时由于 SimpleDateFormat 不是线程安全的。 package com.jsxs.Test;import lombok.extern.slf4j.Slf4j;import java.text.SimpleDateFormat;/*** Author Jsxs* Date 2023/10/19 20:17* PackageName:com.jsxs.Test* ClassName: Test16* Description: TODO* Version 1.0*/Slf4j(topic test16) public class Test16 {public static void main(String[] args) {// 1.进行我们的序列化操作SimpleDateFormat sdf new SimpleDateFormat(yyyy-MM-dd);// 2.开启十个线程进行解析for (int i 0; i 10; i) {new Thread(() - {try {log.debug({}, sdf.parse(1951-04-21));} catch (Exception e) {log.error({}, e);}}).start();}} }有很大几率出现 java.lang.NumberFormatException (数字类型错误) 或者出现不正确的日期解析结果例如 (2).思路 - 同步锁 这样虽能解决问题但带来的是性能上的损失并不算很好 package com.jsxs.Test;import lombok.extern.slf4j.Slf4j;import java.text.SimpleDateFormat;/*** Author Jsxs* Date 2023/10/19 20:17* PackageName:com.jsxs.Test* ClassName: Test16* Description: TODO* Version 1.0*/Slf4j(topic c.test16) public class Test16 {public static void main(String[] args) {// 1.进行我们的序列化操作SimpleDateFormat sdf new SimpleDateFormat(yyyy-MM-dd);// 2.开启十个线程进行解析for (int i 0; i 10; i) {new Thread(() - {synchronized (sdf) { // ⭐try {log.debug({}, sdf.parse(1951-04-21));} catch (Exception e) {log.error({}, e);}}}).start();}} }(3).思路 - 不可变 如果一个对象在不能够修改其内部状态属性那么它就是线程安全的因为不存在并发修改啊这样的对象在Java 中有很多例如在 Java 8 后提供了一个新的日期格式化类 package com.jsxs.Test;import lombok.extern.slf4j.Slf4j;import java.text.SimpleDateFormat; import java.time.LocalDate; import java.time.format.DateTimeFormatter;/*** Author Jsxs* Date 2023/10/19 20:17* PackageName:com.jsxs.Test* ClassName: Test16* Description: TODO* Version 1.0*/Slf4j(topic c.test16) public class Test16 {public static void main(String[] args) {// 1. 利用 线程安全的方法DateTimeFormatter dtf DateTimeFormatter.ofPattern(yyyy-MM-dd);for (int i 0; i 10; i) {new Thread(() - {LocalDate date dtf.parse(2018-10-01, LocalDate::from);log.debug({}, date);}).start();}} }2.不可变设计 另一个大家更为熟悉的 String 类也是不可变的以它为例说明一下不可变设计的要素 public final class Stringimplements java.io.Serializable, ComparableString, CharSequence {/*** The value is used for character storage.*/private final char value[];/*** Cache the hash code for the string*/private int hash; // Default to 0// ...}(1).final 的使用 发现该类、类中所有属性都是 final 的 属性用 final 修饰保证了该属性是只读的不能修改。类用 final 修饰保证了该类中的方法不能被覆盖防止子类无意间破坏不可变性。 (2).保护性拷贝 但有同学会说使用字符串时也有一些跟修改相关的方法啊比如 substring 等那么下面就看一看这些方法是如何实现的就以 substring 为例 深拷贝!!!: 实质上也就是创建了一个新的类。 public String substring(int beginIndex) {if (beginIndex 0) {throw new StringIndexOutOfBoundsException(beginIndex);}int subLen value.length - beginIndex;if (subLen 0) {throw new StringIndexOutOfBoundsException(subLen);}return (beginIndex 0) ? this : new String(value, beginIndex, subLen);}发现其内部是调用 String 的构造方法创建了一个新字符串再进入这个构造看看是否对 final char[] value 做出了修改 public String(char value[], int offset, int count) {if (offset 0) {throw new StringIndexOutOfBoundsException(offset);}if (count 0) {if (count 0) {throw new StringIndexOutOfBoundsException(count);}if (offset value.length) {this.value .value;return;}}if (offset value.length - count) {throw new StringIndexOutOfBoundsException(offset count);}this.value Arrays.copyOfRange(value, offset, offset count);}结果发现也没有构造新字符串对象时会生成新的 char[] value对内容进行复制 。这种通过创建副本对象来避免共享的手段称之为【保护性拷贝defensive copy】。 3.享元模式 (1). 简介 定义 英文名称Flyweight pattern. 当需要重用数量有限的同一类对象时。 (2).体现 包装类 在JDK中 BooleanByteShortIntegerLongCharacter 等包装类提供了 valueOf 方法例如 Long 的 valueOf 会缓存 -128~127 之间的 Long 对象在这个范围之间会重用对象大于这个范围才会新建 Long 对象 public static Long valueOf(long l) {final int offset 128;if (l -128 l 127) { // will cachereturn LongCache.cache[(int) l offset];}return new Long(l);}Byte, Short, Long 缓存的范围都是 -128~127Character 缓存的范围是 0~127Integer的默认范围是 -128~127 最小值不能变但最大值可以通过调整虚拟机参数 -Djava.lang.Integer.IntegerCache.high 来改变 Boolean 缓存了 TRUE 和 FALSE (3).DIY 例如一个线上商城应用QPS 达到数千如果每次都重新创建和关闭数据库连接性能会受到极大影响。 这时预先创建好一批连接放入连接池。一次请求到达后从连接池获取连接使用完毕后再还回连接池这样既节约了连接的创建和关闭时间也实现了连接的重用不至于让庞大的连接数压垮数据库。 class Pool {// 1. 连接池大小private final int poolSize;// 2. 连接对象数组private Connection[] connections;// 3. 连接状态数组 0 表示空闲 1 表示繁忙private AtomicIntegerArray states;// 4. 构造方法初始化public Pool(int poolSize) {this.poolSize poolSize;this.connections new Connection[poolSize];this.states new AtomicIntegerArray(new int[poolSize]);for (int i 0; i poolSize; i) {connections[i] new MockConnection(连接 (i 1));}}// 5. 借连接public Connection borrow() {while (true) {for (int i 0; i poolSize; i) {// 获取空闲连接if (states.get(i) 0) {if (states.compareAndSet(i, 0, 1)) {log.debug(borrow {}, connections[i]);return connections[i];}}}// 如果没有空闲连接当前线程进入等待synchronized (this) {try {log.debug(wait...);this.wait();} catch (InterruptedException e) {e.printStackTrace();}}}}// 6. 归还连接public void free(Connection conn) {for (int i 0; i poolSize; i) {if (connections[i] conn) {states.set(i, 0);synchronized (this) {log.debug(free {}, conn);this.notifyAll();}break;}}}}class MockConnection implements Connection {// 实现略 }4.finnal原理 (1).设置 final 变量的原理 理解了 volatile 原理再对比 final 的实现就比较简单了 public class TestFinal {final int a 20; }字节码 0: aload_0 1: invokespecial #1 // Method java/lang/Object.init:()V 4: aload_0 5: bipush 20 7: putfield #2 // Field a:I-- 写屏障 10: return发现 final 变量的赋值也会通过 putfield 指令来完成同样在这条指令之后也会加入写屏障保证在其它线程读到它的值时不会出现为 0 的情况。 因为finnal不能进行修改的特点。 (2).获取final 变量的原理 Monitor 原理 5.无状态 在 web 阶段学习时设计 Controller 时为了保证其线程安全都会有这样的建议不要为 Controller 设置成员变量这种没有任何成员变量的类是线程安全的。 因为成员变量保存的数据也可以称为状态信息因此没有成员变量就称之为【无状态】
http://www.ho-use.cn/article/10817315.html

相关文章:

  • 老网站删除做新站会影响收录吗广州建站费用
  • 想做个外贸网站免备案虚拟空间
  • 美乐乐网站首页如何修改做网站的空间和服务器
  • 电商网站服务排名武宁县建设工程招标公告门户网站
  • 网站密码忘记了怎么办做网站汉狮网络
  • 网站关键词提升市场调研公司和咨询公司
  • 洛阳制作网站哪家好绵竹seo
  • 当牛做吗网站源代码分享做视频找空镜头那个网站比较全
  • 网站怎么收费动画设计招聘信息
  • 网站建设销售渠道全球门户中企动力
  • 缠绕机东莞网站建设技术支持淘宝运营工作内容
  • 网站优化升级网站开发必看书籍
  • wordpress建站多个域名北京网站建设手机号
  • 网站模板修改器微信公众号怎么创建要多少钱
  • 360元网站建设 网络服务wordpress能注册
  • 网站开发如何赚钱网页设计师是什么专业
  • 做什麽网站有前景网站开发流程原理
  • html网页制作内容网站做301对优化有影响
  • 浙江嘉兴网站建设凌河建设网站
  • 企业网站建设设计任务书排版设计网站有哪些
  • 高端网站制作费用怎么做wordpress主题
  • 比较好的做网站公司微网站 pc网站同步
  • 网站横幅怎做用wordPress搭建图片库
  • 网站开发系统流程图如何做好网络宣传工作
  • 企业网站推广工具做国际网站阿里巴巴
  • 做网站三河百度竞价员
  • 网站建设服务谁便宜大气的个人网站
  • 织梦网站做图床手机网站指向什么意思
  • 网站建设公司走进深圳易百讯网站内链怎么布局
  • 做的美食视频网站新手小白怎样运营1688店铺