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

网站开发目的和意义小企业网站制作

网站开发目的和意义,小企业网站制作,seo搜索引擎优化入门,ps做全屏网站画布要多大1. 多线程案例 1.1 单例模式 单例模式能保证某个类在程序中只存在唯一一份实例#xff0c;不会创建出多个实例#xff08;这一点在很多场景上都需要#xff0c;比如 JDBC 中的 DataSource 实例就只需要一个 tip#xff1a;设计模式就是编写代码过程中的 “软性约束”不会创建出多个实例这一点在很多场景上都需要比如 JDBC 中的 DataSource 实例就只需要一个 tip设计模式就是编写代码过程中的 “软性约束”不是强制的框架就是编写代码过程中的 “硬性约束”针对一些特定的场景问题基本的代码逻辑是固定的 单例模式具体的实现方式有很多最常见的是 “饿汉” 和 “懒汉” 两种 1.1.2 饿汉模式 //创建一个单例的类饿汉方式实现 //饿 的意思是 “迫切” //在类被加载的时候就会创建出这个单例的实例 class Singleton {private static Singleton instance new Singleton();public static Singleton getInstance() {return instance;}//单例模式的最关键部分private Singleton() { } } public class Demo24 {public static void main(String[] args) {Singleton s1 Singleton.getInstance();Singleton s2 Singleton.getInstance();System.out.println(s1 s2);} }其中的构造方法用 private 修饰意味着在类的外面就无法调用构造方法也就无法创建实例了 只要不在其他代码中 new 这个类每次使用都通过 getInstance 来获取实例这个类就是单例的了 单例主要解决的问题就是防止别人不小心 new 了对象 tip 单例模式只能避免失误不能应对 “故意攻击”如使用 反射 或 序列化反序列化 能打破上述单例模式 单例模式的前提是 “一个进程中”如果有多个 Java 进程自然是每个进程中都可以有一个实例了 1.1.3 懒汉模式 //懒汉模式实现的单例模式 class SingletonLazy {//此处先把这个实例的引用设为 null不着急创建实例private static SingletonLazy instance null;public static SingletonLazy getInstance() {if (instance null) {instance new SingletonLazy();}return instance;}private SingletonLazy() { } } public class Demo25 {public static void main(String[] args) {SingletonLazy s1 SingletonLazy.getInstance();SingletonLazy s2 SingletonLazy.getInstance();System.out.println(s1 s2);} }计算机中谈到的 “懒” 是褒义词意思是效率会更高懒汉模式推迟了创建实例的时机第一次使用的时候才会创建实例 上述代码中当首次调用 getInstance由于此时引用为 null就会进入 if 分支创建实例后续再重复调用 getInstance 就不会创建实例了直接返回 1.1.4 饿汉模式和懒汉模式的线程安全问题 观察饿汉模式的代码发现其只有读操作不涉及到修改操作所以没有线程安全问题 而懒汉模式中有 if 判定和其中的修改操作这种代码模式是典型的线程不安全代码因为判定和修改之间可能涉及到线程的切换如下图 上述例子中就创建了两个实例虽然第二次创建覆盖了第一次的值使得第一次创建的实例没有引用指向很快就会被垃圾回收机制给消除掉但是仍然认为上述代码是存在 bug 的 tip在实际场景中构造方法内部可能会执行很多逻辑假设现有 100G 数据加载到内存中需要 10 分钟若是上述代码构造实例来管理加载数据到内存中耗时就会翻倍成 20 分钟 1.1.5 通过加锁来解决懒汉模式中的线程安全问题 问题一 上面加锁之后确实解决了线程安全问题但是当已经 new 完对象后if 分支就再也进不去了后续的代码就应该是单纯的读操作此时 getInstance 不加锁也是线程安全的 问题二 但是当前代码的写法只要调用 getInstance 都会触发加锁操作虽然没有线程安全问题了但是会因为加锁产生阻塞影响到性能 通过加一个条件判断改进该问题 tip 创建的局部变量处于 JVM 内存的 “栈” 区域中new 出来的对象处于 JVM 内存的 “堆” 区域中 对于整个 JVM 进程来说堆是只有一份线程之间公用的栈则是每个线程有自己独立的这是 Java 语法的限制t1 无法访问 t2 栈上的变量C、系统原生 API 中不存在这样的限制任何一个变量都是可以给其他线程用的 正是因为变量的共享是常态所以就容易触发多个线程修改同一个变量从而引起线程安全问题 问题三 该代码还可能会因为指令重排序引起线程安全问题指令重排序也是一种编译器的优化方式 编译器可能会按照 1 2 3 的顺序来执行也可能按照 1 3 2 的顺序来执行对于单线程来说先执行 2 和先执行 3 本质上是一样的但是在多线程的环境下按照 1 3 2 的顺序来执行可能会出现问题 为解决该问题引入关键字 volatile编译器就发现 instance 是易失的围绕这个变量的优化就会非常克制不仅在读取变量的优化上克制也会在修改变量的优化上克制上述的 1 2 3 操作不会再成为 1 3 2 了 tipJava 中的 voiatile 两个功能 1) 保证内存可见性 2) 禁止指令重排序针对赋值操作 1.2 阻塞队列 1.2.1 概念 阻塞队列是一种特殊的队列也遵循 “先进先出” 原则是一种线程安全的数据结构标准库中原有的队列 Queue 和其子类默认都是线程不安全的并且具有以下特性 当队列满的时候继续入队列就会阻塞直到有其他线程从队列中取走元素当队列空的时候继续出队列就会阻塞直到有其他线程往队列中插入元素 阻塞队列的一个典型应用场景就是 “生产者消费者模型”是一种非常典型的开发模型 1.2.2 生产者消费者模型 生产者消费者模型就是通过一个容器来解决生产者和消费者的强耦合问题 生产者和消费者彼此之间不直接通讯而通过阻塞队列来进行通讯所以生产者生产完数据之后不用等待消费者处理直接扔给阻塞队列消费者不找生产者要数据而是直接从阻塞队列中取 优点一阻塞队列就相当于一个缓冲区平衡了生产者和消费者的处理能力削峰填谷 比如在“双十一秒杀”的场景下服务器同一时刻可能会收到大量的支付请求如果直接处理这些支付请求服务器可能扛不住每个支付请求的处理都需要比较复杂的流程这时就可以把这些请求放到一个阻塞队列中然后再由消费者线程慢慢处理每个支付请求有效进行“削峰”防止服务器被突然到来的一波请求直接冲垮 如上图A 中请求突然激增若是没有阻塞队列B 很可能就挂了若有阻塞队列在A 往队列中写入数据变快了但是 B 仍然可以按照原有速度来消费数据 优点二阻塞队列能使生产者和消费者之间解耦 比如过年一家人一起包饺子一般都是有明确分工一人负责擀皮其他人负责包擀饺子皮的人就是“生产者”包饺子的人就是“消费者”擀饺子皮的人不关心包饺子的是谁能包就行无论是手工包还是机器包包饺子的人也不关心擀饺子皮的人是谁有饺子皮就行无论是用擀面杖擀的还是从超市买的 如上图“直接调用”的关系编写 A 代码中会出现很多 B 服务器相关的代码编写 B 代码中也会出现很多 A 服务器相关的代码若 B 服务器挂了可能 A 服务器也会直接受到影响若后续想继续增加一个 C 服务器对 A 的改动就很大 如上图A 只和队列通信B 也只和队列通信A 不知道 B 的存在代码中更没有 B 的影子B 同理 这样看起来 AB 之间是解耦合了但 A 和队列B 和队列之间是否引入了新的耦合呢 我们之所以害怕耦合是因为耦合的代码在后续变更的过程中比较复杂容易出现 bug 消息队列是成熟稳定的产品代码不会频繁修改A 和队列B 和队列之间的交互逻辑基本写一次就固定下来了 tip 1. 消息队列 通常谈到的 “阻塞队列” 是代码中的一个数据结构但是由于其实用性很强就把这个数据结构单独封装成一个服务器程序并且在单独的服务器上进行部署称其为 “消息队列”Message QueueMQ 2. 为什么一个服务器收到请求变多可能会挂崩溃 一台服务器就是一台“电脑”上面提供了一些硬件资源包括不限于 CPU、内存、硬盘、网络带宽...就算机器配置再好硬件资源也是有限的而服务器每次收到一个请求处理这个请求的过程需要执行一系列的代码在执行这些代码的过程中需要消耗一定的硬件资源CPU、内存、硬盘、网络带宽...这些请求消耗的总的硬件资源量超出了机器能提供的上限机器就会出现问题卡死、程序崩溃等... 3. 在请求激增的时候 A 为什么不会挂队列为什么不会挂 A 的角色是一个“网关服务器”收到客户端请求再把请求转发给其他的服务器这样的服务器中的代码做的工作比较简单单纯的数据转发消耗的硬件资源通常更少处理一个请求消耗的资源更少同配置下就能支持更多的请求处理同理队列也是如此。 像 MySQL 这样的数据库处理每个请求的时候做的工作就比较多消耗的硬件资源也是比较多的因此 MySQL 也是后端系统中容易挂的部分 像 Redis 这样的内存数据库处理请求做的工作远远少于 MySQL 做的工作消耗的资源更少Redis 就不容易挂 生产者消费者模型的缺点 1) 需要更多的机器来部署这样的消息队列 2) A 和 B 之间通信的延时会边长如果对于 A 和 B 之间的调用要求响应时间比较短就不适合了若是“转账”这样的场景宁愿慢点也要稳 1.2.3 Java 标准库中的阻塞队列 BlockingQueue 是一个接口真正实现的类是 LinkedBlockingQueueput 方法用于阻塞式的入队列take 方法用于阻塞式的出队列其可以被 Interrupt 方法所唤醒是线程安全的因为 BlockingQueue 继承于 Queue 所以其也有 offer、poll、peek 等方法但是这些方法不具有阻塞特性 示例代码 import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue;public class Demo27 {public static void main(String[] args) {BlockingQueueInteger queue new ArrayBlockingQueue(1000);//生产者线程Thread t1 new Thread(() - {int i 1;while (true) {try {queue.put(i);System.out.println(生产元素 i);i;//给生产操作加上 sleep生产慢点消费快点//Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}});//消费者线程Thread t2 new Thread(() - {while (true) {try {Integer i queue.take();System.out.println(消费元素 i);//给消费操作加上 sleep生产快点消费慢点Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}});t1.start();t2.start();} }当给消费者操作加上 sleep 时运行结果 当给生产者操作加上 sleep 时运行结果 模拟实现基于数组的阻塞队列 //此处不考虑泛型参数只是基于 String 进行存储 class MyBlockingQueue {private String[] data null;private volatile int head 0;//标记头节点private volatile int tail 0;//标记尾节点private volatile int size 0;//记录有效数据个数public MyBlockingQueue(int capacity) {data new String[capacity];}public void put(String s) throws InterruptedException {//加锁的对象可以单独定义一个 locker也可以直接使用 thissynchronized (this) {if (size data.length) { //此处使用 while 是最稳妥的//队列满了此处的 wait 由 take 操作this.wait();}data[tail] s;tail;if (tail data.length) {tail 0;}size;this.notify();}}public String take() throws InterruptedException {String ret ;synchronized (this) {if (size 0) { //此处使用 while 是最稳妥的//队列为空此处的 wait 由 put 操作this.wait();}ret data[head];head;if (head data.length) {head 0;}size--;this.notify();}return ret;} } tip1上述两种写法优劣分析针对写法方面功能上是一样的 1) 方法一的可读性更高更直观更简单方法二求余运算需要程序员非常熟悉求余是什么样的效果无形中提高了要求 2) 方法一的效率也高于方法二因为对于计算机来说% 就是算除法除非是针对 2 的 N 次方进行乘除运算会被编译器优化成移位运算速度会非常快否则 CPU 计算乘除法是一个比较慢的操作特别是除法而方法一的写法是 “判定”往往是一个非常简单快速的 cmp 指令比乘除法快很多 tip2 1.3 线程池 最初引入线程就是因为进程太重了频繁创建销毁进程开销比较大大/小 是相对的 而随着业务上对于性能要求越来越高线程创建/销毁的频次越来越多这时线程创建销毁的开销变得比较明显无法忽视了 线程池就是解决上述问题的常见方案线程池就是把线程提前从系统中申请好放到一个地方后面需要使用线程的时候直接从这个地方来取而不是从系统重新申请线程用完之后也是回到刚才的地方资源是进程申请好了的创建线程本身不需要资源分配 1.3.1 内核态 用户态 操作系统 操作系统内核操作系统的核心功能部分负责完成一个操作系统的核心工作 “管理” 操作系统配套的应用程序 执行很多代码逻辑是需要用户态和内核态的代码配合完成的实际场景中应用程序有很多其都是由内核统一负责管理和服务的内核中非常繁忙 假设场景让同学帮忙带饭和自己去买饭 从系统创建线程就相当于让同学帮忙带饭这样的逻辑就是调用系统 api由系统内核执行一系列逻辑来完成这个过程同学不光给你带饭还要买自己的可能他还想去买个饮料整个过程不可控 直接从线程池里取就相当于自己去买饭整个过程都是纯用户态代码整个过程更可控效率更高 因此通常认为纯用户态操作就比经过内核的操作效率更高 1.3.2 Java 标准库中的线程池 标准库提供了类 ThreadPoolExecutor构造方法 经典面试题上面红框中构造方法的参数含义 1) int corePoolSize核心线程数 int maximumPoolSize最大线程数 此线程池中支持 ”线程扩容“某个线程池初始状态下可能有 M 个线程实际使用中发现 M 不够用就会自动增加 M 个数 tip在 Java 标准库的线程中的线程分为两类 a) 核心线程也可理解成最少有多少个线程相当于正式员工一旦录用不会轻易辞掉 b) 非核心线程线程扩容过程中新增的相当于临时工一段时间不干活就被辞退 出现上述线程分类的原因是cpu 上的核心数目是有限的 最大线程数 核心线程数 非核心线程数 2) long keepAliveTime数值TimeUnit unit单位 秒、分钟、小时、天... 非核心线程会在线程空闲的时候被销毁该参数就是允许非核心线程摸鱼的最大时间 3) BlockingQueueRunnable workQueue工作队列 线程池的工作过程是典型的“生产者消费者模型”程序员使用的时候通过形如 “submit” 这样的方法把要执行的任务设定到线程池里线程池内部的工作线程负责执行这些任务 Runnable 接口本身的含义就是一段可以执行的任务 此处的阻塞队列由我们自行指定a) 队列的容量 capacityb) 队列的类型 4) ThreadFactory threadFactory线程工厂 就是 Thread 类的工厂类通过整个类完成 Thread 实例的创建和初始化操作此处的 ThreadFactory 就可以针对线程池里的线程进行批量的设置数据一般使用标准库提供的 ThreadFactory 的默认值 tip“工厂” 指的是 “工厂设计模式”是一种常见的设计模式是在创建类的实例时使用的设计模式因为构造方法有 “坑”所以通过工厂模式来 “填坑” 详细分析 构造方法是一个特殊的方法必须和类名一样要想实现多个版本的构造方法必须通过 “重载”overload如下在一个平面描述一个点可以通过平面坐标 “x y”也可以通过极坐标 “α r” 两种构造方法来实例化 class Point {public Point (double x, double y) {...}public Point (double α, double r) {...} } 发现这两个构造方法无法构成重载这就是构造方法的 “坑”局限性 为解决以上问题引入了 “工厂设计模式”。通过 “普通方法”通常是静态方法完成对象构造和初始化的操作如下 class Point {}class PointFactory {public static Point makePointByXY (double x, double y) {Point p;p.setX(x);p.setY(y);return p;}public static Point makePointByRA (double r, double α) {Point p;p.setR(r);p.setA(α);return p;} } 此处用来创建对象的 static 方法就称为 “工厂方法”有时工厂方法也会放到单独的类里实现该类成为 “工厂类” 5) RejectedExecutionHandler handler拒绝策略最重要 如果线程池的任务队列满了还要继续给这个队列添加任务的话不会阻塞而是直接拒绝 Java 标准库给出了四种不同的拒绝策略 ThreadPoolExecutor 的功能很强大使用也很麻烦所以 Java 标准库对这个类进一步封装了一下Executors 提供了一些工厂方法可以更方便的构造出线程池如下 如下线程池的示例代码 发生了变量捕获只需要再创建一个变量来接收 i 即可如下 运行结果 执行这个代码虽然 100 个任务都执行完毕了但是整个进程并没有结束这是因为此处线程创建出来的线程默认都是 “前台线程”虽然 main 线程结束了但是这些线程池里的前台线程仍然存在 此时就需要使用 shutdown() 方法手动结束线程池里的线程如下 import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors;public class thread {public static void main(String[] args) throws InterruptedException {ExecutorService service Executors.newFixedThreadPool(4);for (int i 0; i 100; i) {int id i;service.submit(() - {Thread current Thread.currentThread();System.out.println(hello thread id , current.getName());});}//最好不要立即就终止可能使任务还没执行完呢线程就被终止了Thread.sleep(2000);//把线程池里所有的线程都终止掉service.shutdown();System.out.println(程序退出);} }运行结果 指定线程个数 使用线程池的时候需要指定线程个数因为 1) 一台主机上并不只是运行这一个程序 2) 这个程序也不是 100% 每个线程都跑满 cpu线程工作过程中可能会涉及到一些 IO 操作/阻塞操作而主动放弃 cpu如果线程代码里都是算数运算确实能跑满 cpu如果代码中包含了 sleep、wait、加锁、打印、网络通信、读写硬盘等等操作就会使线程主动放弃 cpu 一会 在实际开发中更建议通过 “实验” 的方式找到一个合适的线程个数值给线程池设置不同的线程数分别进行性能测试关注响应时间/消耗资源挑选一个比较合适的数值 模拟实现线程池 import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue;class MyThreadPool {private BlockingQueueRunnable queue new ArrayBlockingQueue(1000);//此处 n 表示创建几个线程public MyThreadPool(int n) {//先创建出 n 个线程for (int i 0; i n; i) {Thread t new Thread(() - {//循环的从队列中取任务while (true) {Runnable runnable null;try {runnable queue.take();runnable.run();} catch (InterruptedException e) {throw new RuntimeException(e);}}});t.start();}}//添加任务public void submit(Runnable runnable) {try {queue.put(runnable);} catch (InterruptedException e) {throw new RuntimeException(e);}} }public class Demo {public static void main(String[] args) {MyThreadPool pool new MyThreadPool(4);for (int i 0; i 1000; i) {int id i;pool.submit(() - {System.out.println(执行任务 id , Thread.currentThread().getName());});}} }1.4 实例定时器 定时器相当于一个 “闹钟”像网络通信中就经常需要设定一个 “超时时间”此时就需要用到这个 “闹钟”机制 1.4.1 Java标准库中也提供了定时器实现 import java.util.Timer; import java.util.TimerTask;public class Demo32 {public static void main(String[] args) {Timer timer new Timer();timer.schedule(new TimerTask() {Overridepublic void run() {System.out.println(hello);}}, 3000);System.out.println(程序开始运行);} }运行结果 从运行结果可以看出当我们传入第二个参数 3000ms 之后它就会等待 3s然后再执行打印操作 而且 Timer 也支持管理多个任务 import java.util.Timer; import java.util.TimerTask;public class Demo32 {public static void main(String[] args) {Timer timer new Timer();timer.schedule(new TimerTask() {Overridepublic void run() {System.out.println(hello3);}}, 3000);timer.schedule(new TimerTask() {Overridepublic void run() {System.out.println(hello2);}}, 2000);timer.schedule(new TimerTask() {Overridepublic void run() {System.out.println(hello1);}}, 1000);System.out.println(程序开始运行);} }运行结果 tip 1.4.2 模拟实现定时器 对于定时器来说其核心主要有 1) 创建类描述一个要执行的任务任务的内容任务的时间 //表示一个任务 class MyTimerTask {private Runnable runnable; //任务的具体实现//此处的 time 通过毫秒时间戳来表示这个任务具体什么时候执行//意思是此时是 900时间设为 一小时就是 1000 执行绝对时间//而不是像 Demo32 中一样只是 3000ms 之后执行相对时间没有具体时间private long time;public MyTimerTask(Runnable runnable, long delay) {this.runnable runnable;this.time System.currentTimeMillis() delay;}public void run() {runnable.run();}public long getTime() {return time;} } 2) 管理多个任务通过某个数据结构把多个任务存起来 若使用 List 因为这里的 MyTimerTask 是按照时间来执行任务的只要能够确定所有任务中时间最小的任务判定它是否到时间该执行即可时间最小的任务还没到时间的话其他任务就不必考虑了 所以使用 堆 来保存任务是更好的选择能够很方便的找到“最小值/第二小/第三小” 采用这个优先级队列来实现但是 MyTimerTask 不能直接传入需要指定明确的比较规则方式一实现 Comparable 接口方式二指定 Comparator 传入下面实现一个 Comparable //表示一个任务 class MyTimerTask implements ComparableMyTimerTask {private Runnable runnable; //任务的具体实现//此处的 time 通过毫秒时间戳来表示这个任务具体什么时候执行//意思是此时是 900时间设为 一小时就是 1000 执行绝对时间//而不是像 Demo32 中一样只是 3000ms 之后执行相对时间没有具体时间private long time;public MyTimerTask(Runnable runnable, long delay) {this.runnable runnable;this.time System.currentTimeMillis() delay;}public void run() {runnable.run();}public long getTime() {return time;}Overridepublic int compareTo(MyTimerTask o) {//此处的 - 的顺序决定了是大堆还是小堆//本代码实现中需要小堆//这里 - 的顺序不要背通过“实验”的方式来确认return (int) (this.time - o.time);} }//表示自己实现定时器 class MyTimer {//private ListMyTimerTask list new ArrayList(); //不是最优选private PriorityQueueMyTimerTask queue new PriorityQueue(); } 3) 有专门的线程执行这里的任务 //表示自己实现定时器 class MyTimer {//private ListMyTimerTask list new ArrayList(); //不是最优选private PriorityQueueMyTimerTask queue new PriorityQueue();public MyTimer() {//创建线程负责执行上述队列中的内容Thread t new Thread(() - {while (true) {if (queue.isEmpty()) {continue;}MyTimerTask current queue.peek();//查看栈顶元素//比较当前时间与需执行时间若当前为1200需执行时间为1155则应该执行if (System.currentTimeMillis() current.getTime()) {//要执行任务current.run();//把执行过的任务从队列中删除queue.poll();} else {//时间未到不执行任务continue;}}});t.start();}//创建任务到队列中public void schedule(Runnable runnable, long delay) {MyTimerTask myTimerTask new MyTimerTask(runnable, delay);queue.offer(myTimerTask);} } 1.4.3 当前代码的线程安全问题 PriorityQueue 这个类本身不带线程安全的控制能力并且代码中又是多个线程来进行操作所以一定会存在线程安全问题的风险 将代码中两个线程操作都加锁 运行结果 确实能够正常运行了但依旧有问题未解决 1) 初始情况下如果队列中没有任何元素 改进使用 wait 方法进行阻塞等待 2) 假设队列中已经包含元素了 当前时间是 1045任务时间 1200 改进此处 wait 不能等着别人唤醒而是设定一个具体时间该时间为 任务时间 - 当前时间 使用 wait 的时候线程阻塞就可以释放 cpu 资源给其他线程使用了 tip 1) 不使用 sleep 的原因 sleep 休眠的时候不会释放锁所以当 sleep 1h 15min 的过程中来了一个时间更早的任务 1130 要执行那么这个任务根本就添加不进来 如果使用 wait每次来新的任务都会把 wait 唤醒重新设定等待时间 2) 不使用 PriorityBlockingQueue 的原因 若使用 PriorityBlockingQueue 代码就变成 两把锁 多个线程了容易出现死锁的情况并非 100% 出现需要精心控制这里的加锁顺序代码的编写复杂程度提高不少 此处不适用阻塞队列的话整个代码只需要一把锁 locker 就可以解决所有问题了 除了可以基于优先级队列来实现定时器外还可以根据 “时间轮也是一个巧妙的数据结构” 的方式实现做了解不展开
http://www.w-s-a.com/news/414196/

相关文章:

  • 响应式网站建设服务商wordpress 非小工具形式 微博秀
  • 网站安全检测漏洞扫描风险等级分布建设一个网站步骤
  • 摄影网站的意义开发企业小程序公司
  • 龙岩网站设计招聘信息网上免费logo设计
  • 高端定制网站开发建站教程详解网站共享备案可以申请支付接口
  • 做房产网站接不到电话企业推广宣传方式
  • 网站建设费用不用摊销下一页p30
  • 北京 工业网站建设公司国外服务器公司有哪些
  • 怎样局域网站建设盈利网站
  • 公司做网站广告语济南建网站价格消费品展
  • 建德网站网站建设规划设计书
  • 谷歌网站流量分析wordpress置顶浮标
  • 江苏新宁建设集团网站网络规划设计师2023论文
  • 合作建站协议python wordpress采集器
  • 集团网站网页模板网站建设图片大全
  • 举报非法网站要求做笔录wordpress怎么插视频
  • 网站服务器防护如何搭建网站平台
  • 设计师接私活的网站如何做网站的搜索栏
  • ps做图下载网站网站子目录设计
  • 厦门网站制作策划高中生做网站网页
  • 高端品牌网站建设在哪济南兴田德润优惠吗专业定制网站开发公司
  • 怎么做网站卖东西汽车网站排行榜前十名
  • 网站关键字没有排名只有单页面的网站怎么做seo
  • 网站流量盈利模式宝塔没有域名直接做网站怎么弄
  • 淡蓝色网站qq推广中心
  • 设计网站价格餐饮吸引客流的活动方案
  • 手机网站建设电话百度搜索量
  • 条件查询 php网站源码中国白云手机网站建设
  • 网上注册公司流程及材料班级优化大师免费下载电脑版
  • 应用网站如何做营销型网站的重要特点