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

一级站点和二级站点区别网站建设项目软件开发招标文件

一级站点和二级站点区别,网站建设项目软件开发招标文件,网站建设 费用 入哪个科目,广州招投标交易中心java多线程-概念创建启动中断守护线程优先级线程状态#xff08;多线程编程之一#xff09;java多线程同步以及线程间通信详解消费者生产者模式死锁Thread.join()#xff08;多线程编程之二#xff09;javaandroid线程池-Exe…  java多线程-概念创建启动中断守护线程优先级线程状态多线程编程之一java多线程同步以及线程间通信详解消费者生产者模式死锁Thread.join()多线程编程之二javaandroid线程池-Executor框架之ThreadPoolExcutorScheduledThreadPoolExecutor浅析多线程编程之三Java多线程Callable、Future和FutureTask浅析多线程编程之四   无论是在java还是在android中其实使用到的线程池都基本是一样的因此本篇我们将来认识一下线程池Executor框架相关知识点结合了并发编程艺术书以及Android开发艺术探索而总结下面是本篇的主要知识点 1.Executor框架浅析 首先我们得明白一个 问题为什么需要线程池在java中使用线程来执行异步任务时线程的创建和销毁需要一定的开销如果我们为每一个任务创建一个新的线程来执行的话那么这些线程的创建与销毁将消耗大量的计算资源。同时为每一个任务创建一个新线程来执行这样的方式可能会使处于高负荷状态的应用最终崩溃。所以线程池的出现为解决这个问题带来曙光。我们将在线程池中创建若干条线程当有任务需要执行时就从该线程池中获取一条线程来执行任务如果一时间任务过多超出线程池的线程数量那么后面的线程任务就进入一个等待队列进行等待直到线程池有线程处于空闲时才从等待队列获取要执行的任务进行处理以此循环.....这样就大大减少了线程创建和销毁的开销也会缓解我们的应用处于超负荷时的情况。1.1Executor框架的两级调度模型在java线程启动时会创建一个本地操作系统线程当该java线程终止时这个操作系统线程也会被回收。而每一个java线程都会被一对一映射为本地操作系统的线程操作系统会调度所有的线程并将它们分别给可用的CPU。而所谓的映射方式是这样实现的在上层java多线程程序通过把应用分为若干个任务然后使用用户级的调度器Executor框架将这些任务映射为固定数量的线程在底层操作系统内核将这些线程映射到硬件处理器上。这样种两级调度模型如下图所示从图中我们可以看出应用程序通过Executor框架控制上层的调度而下层的调度由操作系统内核控制下层的调度不受应用程序的控制。1.2 Executor框架的结构Executor框架的结构主要包括3个部分1.任务包括被执行任务需要实现的接口Runnable接口或Callable接口2.任务的执行包括任务执行机制的核心接口Executor以及继承自Executor的EexcutorService接口。Exrcutor有两个关键类实现了ExecutorService接口ThreadPoolExecutor和ScheduledThreadPoolExecutor。3.异步计算的结果包括接口Future和实现Future接口的FutureTask类这个我们放在下一篇文章说明下面我们通过一个UML图来认识一下这些类间的关系Extecutor是一个接口它是Executor框架的基础它将任务的提交与任务的执行分离开来。ThreadPoolExecutor是线程池的核心实现类用来执行被提交的任务。ScheduledThreadPoolExecutor是一个实现类可以在给定的延迟后运行命令或者定期执行命令。ScheduledThreadPoolExecutor比Timer更灵活功能更强大。Future接口和实现Future接口的FutureTask类代表异步计算的结果。Runnable接口和Callable接口的实现类都可以被ThreadPoolExecutor或者ScheduledThreadPoolExecutor执行。区别就是Runnable无法返回执行结果而Callable可以返回执行结果。下面我们通过一张图来理解它们间的执行关系分析说明主线程首先创建实现Runnable或Callable接口的任务对象工具类Executors可以把一个Runnable对象封装为一个Callable对象,使用如下两种方式Executors.callable(Runnable task)或者Executors.callable(Runnable task,Object resule)。然后可以把Runnable对象直接提交给ExecutorService执行方法为ExecutorService.execute(Runnable command)或者也可以把Runnable对象或者Callable对象提交给ExecutorService执行方法为ExecutorService.submit(Runnable task)或ExecutorService.submit(CallableT task)。这里需要注意的是如果执行ExecutorService.submit(...),ExecutorService将返回一个实现Future接口的对象其实就是FutureTask。当然由于FutureTask实现了Runnable接口我们也可以直接创建FutureTask然后提交给ExecutorService执行。到此Executor框架的主要体系结构我们都介绍完了我们对此有了大概了解后下面我们就重点聊聊两个主要的线程池实现类。2.ThreadPoolExecutor浅析 ThreadPoolExecutor是线程的真正实现通常使用工厂类Executors来创建但它的构造方法提供了一系列参数来配置线程池下面我们就先介绍ThreadPoolExecutor的构造方法中各个参数的含义。public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueueRunnable workQueue,ThreadFactory threadFactory) {this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,threadFactory, defaultHandler);} corePoolSize线程池的核心线程数默认情况下核心线程数会一直在线程池中存活即使它们处理闲置状态。如果将ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true那么闲置的核心线程在等待新任务到来时会执行超时策略这个时间间隔由keepAliveTime所指定当等待时间超过keepAliveTime所指定的时长后核心线程就会被终止。 maximumPoolSize线程池所能容纳的最大线程数量当活动线程数到达这个数值后后续的新任务将会被阻塞。keepAliveTime非核心线程闲置时的超时时长超过这个时长非核心线程就会被回收。当ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true时keepAliveTime同样会作用于核心线程。unit用于指定keepAliveTime参数的时间单位这是一个枚举常用的有TimeUnit.MILLISECONDS(毫秒)TimeUnit.SECONDS(秒)以及TimeUnit.MINUTES(分钟)等。workQueue线程池中的任务队列通过线程池的execute方法提交Runnable对象会存储在这个队列中。threadFactory线程工厂为线程池提供创建新线程的功能。ThreadFactory是一个接口它只有一个方法Thread newThreadRunnable r。除了上面的参数外还有个不常用的参数RejectExecutionHandler这个参数表示当ThreadPoolExecutor已经关闭或者ThreadPoolExecutor已经饱和时达到了最大线程池大小而且工作队列已经满execute方法将会调用Handler的rejectExecution方法来通知调用者默认情况 下是抛出一个RejectExecutionException异常。了解完相关构造函数的参数我们再来看看ThreadPoolExecutor执行任务时的大致规则1如果线程池的数量还未达到核心线程的数量那么会直接启动一个核心线程来执行任务2如果线程池中的线程数量已经达到或者超出核心线程的数量那么任务会被插入到任务队列中排队等待执行。3如果在步骤2中无法将任务插入到任务队列中这往往是由于任务队列已满这个时候如果线程数量未达到线程池规定的最大值那么会立刻启动一个非核心线程来执行任务。4如果在步骤3中线程数量已经达到线程池规定的最大值那么就会拒绝执行此任务ThreadPoolExecutor会调用RejectExecutionHandler的rejectExecution方法来通知调用者。到此ThreadPoolExecutor的详细配置了解完了ThreadPoolExecutor的执行规则也了解完了那么接下来我们就来介绍3种常见的线程池它们都直接或者间接地通过配置ThreadPoolExecutor来实现自己的功能特性这个3种线程池分别是FixedThreadPoolCachedThreadPoolScheduledThreadPool以及SingleThreadExecutor。2.1FixedThreadPoolFixedThreadPool模式会使用一个优先固定数目的线程来处理若干数目的任务。规定数目的线程处理所有任务一旦有线程处理完了任务就会被用来处理新的任务(如果有的话)。FixedThreadPool模式下最多的线程数目是一定的。创建FixedThreadPool对象代码如下ExecutorService fixedThreadPoolExecutors.newFixedThreadPool(5); 我们来看看FixedThreadPool创建方法源码 public static ExecutorService newFixedThreadPool(int nThreads) {return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueueRunnable());}FixedThreadPool的corePoolSize和maximumPoolSize参数都被设置为nThreads。当线程池中的线程数量大于corePoolSize时keepAliveTime为非核心空闲线程等待新任务的最长时间超过这个时间后非核心线程将被终止这里keepAliveTime设置为0L就说明非核心线程会立即被终止。事实上这里也没有非核心线程创建因为核心线程数和最大线程数都一样的。下面我们来看看FixedThreadPool的execute()方法的运行流程 分析1如果当前运行线程数少corePoolSize则创建一个新的线程来执行任务。2如果当前线程池的运行线程数等于corePoolSize那么后面提交的任务将加入LinkedBlockingQueue。3线程在执行完图中的1后会在循环中反复从LinkedBlockingQueue获取任务来执行。这里还有点要说明的是FixedThreadPool使用的是无界队列LinkedBlockingQueue作为线程池的工作队列队列容量为Integer.MAX_VALUE。使用该队列作为工作队列会对线程池产生如下影响1当前线程池中的线程数量达到corePoolSize后新的任务将在无界队列中等待。2由于我们使用的是无界队列所以参数maximumPoolSize和keepAliveTime无效。3由于使用无界队列运行中的FixedThreadPool不会拒绝任务当然此时是未执行shutdown和shutdownNow方法所以不会去调用RejectExecutionHandler的rejectExecution方法抛出异常。下面我们给出案例该案例来自java编程思想一书public class LiftOff implements Runnable{ protected int countDown 10; //Default private static int taskCount 0; private final int id taskCount; public LiftOff() {} public LiftOff(int countDown) { this.countDown countDown; } public String status() { return # id ( (countDown 0 ? countDown : LiftOff!) ) ; } Override public void run() { while(countDown-- 0) { System.out.print(status()); Thread.yield(); } } } 声明一个Runnable对象使用FixedThreadPool执行任务如下 public class FixedThreadPool { public static void main(String[] args) { //三个线程来执行五个任务 ExecutorService exec Executors.newFixedThreadPool(3); for(int i 0; i 5; i) { exec.execute(new LiftOff()); } exec.shutdown(); } } 2.2 CachedThreadPool CachedThreadPool首先会按照需要创建足够多的线程来执行任务(Task)。随着程序执行的过程有的线程执行完了任务可以被重新循环使用时才不再创建新的线程来执行任务。创建方式ExecutorService cachedThreadPoolExecutors.newCachedThreadPool(); public static ExecutorService newCachedThreadPool() {return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueueRunnable());}从该静态方法我们可以看到CachedThreadPool的corePoolSize被设置为0而maximumPoolSize被设置Integer.MAX_VALUE即maximumPoolSize是无界的而keepAliveTime被设置为60L单位为妙。也就是空闲线程等待时间最长为60秒超过该时间将会被终止。而且在这里CachedThreadPool使用的是没有容量的SynchronousQueue作为线程池的工作队列但其maximumPoolSize是无界的也就是意味着如果主线程提交任务的速度高于maximumPoolSize中线程处理任务的速度时CachedThreadPool将会不断的创建新的线程在极端情况下CachedThreadPool会因为创建过多线程而耗尽CPU和内存资源。CachedThreadPool的execute()方法的运行流程 分析1首先执行SynchronousQueue.offer(Runnable task)添加一个任务。如果当前CachedThreadPool中有空闲线程正在执行SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS),其中NANOSECONDS是毫微秒即十亿分之一秒就是微秒/1000那么主线程执行offer操作与空闲线程执行poll操作配对成功主线程把任务交给空闲线程执行execute()方法执行完成否则进入第2步。2当CachedThreadPool初始线程数为空时或者当前没有空闲线程将没有线程去执行SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS)。这样的情况下步骤1将会失败此时CachedThreadPool会创建一个新的线程来执行任务execute()方法执行完成。3在步骤2中创建的新线程将任务执行完成后会执行SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS)这个poll操作会让空闲线程最多在SynchronousQueue中等待60秒如果60秒内主线程提交了一个新任务那么这个空闲线程将会执行主线程提交的新任务否则这个空闲线程将被终止。由于空闲60秒的空闲线程会被终止因此长时间保持空闲的 CachedThreadPool是不会使用任何资源的。根据前面的分析我们知道SynchronousQueue是一个没有容量的阻塞队列其实个人认为是相对应时间而已的没有容量因为时间到空闲线程就会被移除。每个插入操作必须等到一个线程与之对应。CachedThreadPool使用SynchronousQueue把主线程的任务传递给空闲线程执行。流程如下CachedThreadPool使用的案例代码如下 public class CachedThreadPool { public static void main(String[] args) { ExecutorService exec Executors.newCachedThreadPool(); for(int i 0; i 10; i) { exec.execute(new LiftOff()); } exec.shutdown(); } } 2.3 SingleThreadExecutor SingleThreadExecutor模式只会创建一个线程。它和FixedThreadPool比较类似不过线程数是一个。如果多个任务被提交给SingleThreadExecutor的话那么这些任务会被保存在一个队列中并且会按照任务提交的顺序一个先执行完成再执行另外一个线程。SingleThreadExecutor模式可以保证只有一个任务会被执行。这种特点可以被用来处理共享资源的问题而不需要考虑同步的问题。 创建方式ExecutorService singleThreadExecutorExecutors.newSingleThreadExecutor(); public static ExecutorService newSingleThreadExecutor() {return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueueRunnable()));}从静态方法可以看出SingleThreadExecutor的corePoolSize和maximumPoolSize被设置为1其他参数则与FixedThreadPool相同。SingleThreadExecutor使用的工作队列也是无界队列LinkedBlockingQueue。由于SingleThreadExecutor采用无界队列的对线程池的影响与FixedThreadPool一样这里就不过多描述了。同样的我们先来看看其运行流程 分析1如果当前线程数少于corePoolSize即线程池中没有线程运行则创建一个新的线程来执行任务。2在线程池的线程数量等于corePoolSize时将任务加入到LinkedBlockingQueue。3线程执行完成1中的任务后会在一个无限循环中反复从LinkedBlockingQueue获取任务来执行。SingleThreadExecutor使用的案例代码如下public class SingleThreadExecutor { public static void main(String[] args) { ExecutorService exec Executors.newSingleThreadExecutor(); for (int i 0; i 2; i) { exec.execute(new LiftOff()); } } } 2.4 各自的适用场景 FixedThreadPool适用于为了满足资源管理需求而需要限制当前线程的数量的应用场景它适用于负载比较重的服务器。SingleThreadExecutor适用于需要保证执行顺序地执行各个任务并且在任意时间点不会有多个线程是活动的场景。CachedThreadPool大小无界的线程池适用于执行很多的短期异步任务的小程序或者负载较轻的服务器。3.ScheduledThreadPoolExecutor浅析 3.1 ScheduledThreadPoolExecutor执行机制分析ScheduledThreadPoolExecutor继承自ThreadPoolExecutor。它主要用来在给定的延迟之后执行任务或者定期执行任务。ScheduledThreadPoolExecutor的功能与Timer类似但比Timer更强大更灵活Timer对应的是单个后台线程而ScheduledThreadPoolExecutor可以在构造函数中指定多个对应的后台线程数。接下来我们先来了解一下ScheduledThreadPoolExecutor的运行机制分析DelayQueue是一个无界队列所以ThreadPoolExecutor的maximumPoolSize在ScheduledThreadPoolExecutor中无意义。ScheduledThreadPoolExecutor的执行主要分为以下两个部分1当调用ScheduledThreadPoolExecutor的scheduleAtFixedRate()方法或者scheduleWithFixedDelay()方法时会向ScheduledThreadPoolExecutor的DelayQueue添加一个实现了RunnableScheduledFuture接口的ScheduleFutureTask。2线程池中的线程从DelayQueue中获取ScheduleFutureTask然后执行任务。3.2 如何创建ScheduledThreadPoolExecutorScheduledThreadPoolExecutor通常使用工厂类Executors来创建Executors可以创建两种类型的ScheduledThreadPoolExecutor如下1ScheduledThreadPoolExecutor可以执行并行任务也就是多条线程同时执行。2SingleThreadScheduledExecutor可以执行单条线程。创建ScheduledThreadPoolExecutor的方法构造如下public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory)创建SingleThreadScheduledExecutor的方法构造如下 public static ScheduledExecutorService newSingleThreadScheduledExecutor() public static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory)创建实例对象代码如下 ScheduledExecutorService scheduledThreadPoolExecutorExecutors.newScheduledThreadPool(5); ScheduledExecutorService singleThreadScheduledExecutorExecutors.newSingleThreadScheduledExecutor(); 3.3 ScheduledThreadPoolExecutor和SingleThreadScheduledExecutor的适用场景 ScheduledThreadPoolExecutor适用于多个后台线程执行周期性任务同时为了满足资源管理的需求而需要限制后台线程数量的应用场景。SingleThreadScheduledExecutor适用于需要单个后台线程执行周期任务同时需要保证任务顺序执行的应用场景。3.4 ScheduledThreadPoolExecutor使用案例我们创建一个Runnable的对象然后使用ScheduledThreadPoolExecutor的Scheduled()来执行延迟任务输出执行时间即可:我们先来介绍一下该类延迟执行的方法public ScheduledFuture? schedule(Runnable command,long delay, TimeUnit unit); 参数解析command就是一个实现Runnable接口的类 delay延迟多久后执行。unit用于指定keepAliveTime参数的时间单位这是一个枚举常用的有TimeUnit.MILLISECONDS(毫秒)TimeUnit.SECONDS(秒)以及TimeUnit.MINUTES(分钟)等。这里要注意这个方法会返回ScheduledFuture实例可以用于获取线程状态信息和延迟时间。package com.zejian.Executor; import java.text.ParsePosition; import java.text.SimpleDateFormat; import java.util.Date; /*** author zejian* time 2016年3月14日 下午9:10:41* decrition 创建一个工作线程继承Runnable*/ public class WorkerThread implements Runnable{Overridepublic void run() {System.out.println(Thread.currentThread().getName() Start. Time getNowDate());threadSleep();System.out.println(Thread.currentThread().getName() End. Time getNowDate());}/*** 睡3秒*/public void threadSleep(){try {Thread.sleep(3000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}/*** 获取现在时间* * return 返回时间类型 yyyy-MM-dd HH:mm:ss*/public static String getNowDate() {Date currentTime new Date();SimpleDateFormat formatter; formatter new SimpleDateFormat (yyyy-MM-dd HH:mm:ss); String ctime formatter.format(currentTime); return ctime;} }执行类如下 package com.zejian.Executor; import java.text.ParsePosition; import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; /*** author zejian* time 2016年3月14日 下午9:27:06* decrition 执行类*/ public class ScheduledThreadPoolTest {public static void main(String[] args) {ScheduledExecutorService scheduledThreadPool Executors.newScheduledThreadPool(5);try {//schedule to run after sometimeSystem.out.println(Current Time getNowDate());for(int i0; i3; i){Thread.sleep(1000);WorkerThread worker new WorkerThread();//延迟10秒后执行scheduledThreadPool.schedule(worker, 10, TimeUnit.SECONDS);}Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}scheduledThreadPool.shutdown();while(!scheduledThreadPool.isTerminated()){//wait for all tasks to finish}System.out.println(Finished all threads);}/*** 获取现在时间* * return 返回时间类型 yyyy-MM-dd HH:mm:ss*/public static String getNowDate() {Date currentTime new Date();SimpleDateFormat formatter; formatter new SimpleDateFormat (yyyy-MM-dd HH:mm:ss); String ctime formatter.format(currentTime); return ctime;} }运行输入执行结果 线程任务确实在10秒延迟后才开始执行。这就是schedule()方法的使用。下面我们再介绍2个可用于周期性执行任务的方法。 public ScheduledFuture? scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnit unit) scheduleAtFixedRate方法的作用是预定在初始的延迟结束后周期性地执行给定的任务周期长度为period其中initialDelay为初始延迟。 按照固定的时间来执行即到点执行 public ScheduledFuture? scheduleWithFixedDelay(Runnable command,long initialDelay,long delay,TimeUnit unit); scheduleWithFixedDelay方法的作用是预定在初始的延迟结束后周期性地执行给定任务在一次调用完成和下一次调用开始之间有长度为delay的延迟其中initialDelay为初始延迟简单说是是等上一个任务结束后在等固定的时间然后执行。即执行完上一个任务后再执行。 下面给出实现案例代码参考package com.zejian.Executor; import java.util.Date; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; /*** author zejian* time 2016年3月14日 下午10:05:07* decrition 周期函数测试类*/ public class ScheduledTask {public ScheduledThreadPoolExecutor se new ScheduledThreadPoolExecutor(5);public static void main(String[] args) {new ScheduledTask();}public void fixedPeriodSchedule() {// 设定可以循环执行的runnable,初始延迟为0这里设置的任务的间隔为5秒for(int i0;i5;i){se.scheduleAtFixedRate(new FixedSchedule(), 0, 5, TimeUnit.SECONDS);}}public ScheduledTask() {fixedPeriodSchedule();}class FixedSchedule implements Runnable {public void run() {System.out.println(当前线程Thread.currentThread().getName() 当前时间new Date(System.currentTimeMillis()));}} }运行结果(后来补贴的结果所以时间是2017) 当前线程pool-1-thread-5 当前时间Tue Aug 08 09:43:18 CST 2017 当前线程pool-1-thread-4 当前时间Tue Aug 08 09:43:18 CST 2017 当前线程pool-1-thread-3 当前时间Tue Aug 08 09:43:18 CST 2017 当前线程pool-1-thread-1 当前时间Tue Aug 08 09:43:18 CST 2017 当前线程pool-1-thread-2 当前时间Tue Aug 08 09:43:18 CST 2017 当前线程pool-1-thread-1 当前时间Tue Aug 08 09:43:23 CST 2017 当前线程pool-1-thread-4 当前时间Tue Aug 08 09:43:23 CST 2017 当前线程pool-1-thread-3 当前时间Tue Aug 08 09:43:23 CST 2017 当前线程pool-1-thread-5 当前时间Tue Aug 08 09:43:23 CST 2017 当前线程pool-1-thread-2 当前时间Tue Aug 08 09:43:23 CST 2017 当前线程pool-1-thread-1 当前时间Tue Aug 08 09:43:28 CST 2017 当前线程pool-1-thread-4 当前时间Tue Aug 08 09:43:28 CST 2017 当前线程pool-1-thread-5 当前时间Tue Aug 08 09:43:28 CST 2017 当前线程pool-1-thread-3 当前时间Tue Aug 08 09:43:28 CST 2017 当前线程pool-1-thread-1 当前时间Tue Aug 08 09:43:28 CST 2017至于scheduleWithFixedDelay方法大家就把代码稍微修改一下执行试试就行这里就不重复了。而SingleThreadScheduledExecutor的使用的方法基本是类似只不过是单线程罢了这里也不再描述了。好了今天就到这吧。 主要参考书籍 java核心技术卷1 android开发艺术探索 java并发编程的艺术 --------------------- 作者zejian_ 来源CSDN 原文https://blog.csdn.net/javazejian/article/details/50890554 版权声明本文为作者原创文章转载请附上博文链接 内容解析ByCSDN,CNBLOG博客文章一键转载插件
http://www.zqtcl.cn/news/605221/

相关文章:

  • 郑州企业建设网站北京企业网站模板建站开发
  • 宣传旅游网站建设的观点是什么公众号怎么推广和引流
  • 企业网站制作多少钱山西网络营销方案
  • 焦作住房和城乡建设局网站旅行网站模板
  • 男做基视频网站国家重点高新技术企业名单
  • 公司官方网站开发网站建设电子商务
  • seo网站优化系统搜索引擎优化排名案例
  • 郑州网站建设工作室网站建设全流程 知乎
  • 如何利用源码做网站外贸网站制作推广
  • 国内做网站哪家公司好免费查找资料的网站
  • 自己做的网站百度搜不到搭建网站seo
  • 奇墙网站建设高端网站建设公司联系电话
  • 宁波那家公司做网站好中企动力科技股份有限公司招聘
  • 水果网站推广网站首页静态好还是动态好
  • iis网站属性小程序源码无需服务器
  • 景区网站建设材料代运营有哪些套路坑
  • 六安电商网站建设哪家好有关做美食的网站
  • 卸载wordpress插件网店seo关键词
  • 金山网站制作赤城seo网站优化排名
  • 提供坪山网站建设深圳商城网站哪家做的好
  • 有什么网站可以帮人做模具吗热搜榜百度一下你就知道
  • 深圳网站优化技巧邹城住房城乡建设部网站
  • 小型企业网站建站桂林市中考信息网官网
  • 雏鸟app网站推广做网站用宋体有版权问题吗
  • 建立网站数据库开公司流程及费用2022最新
  • 外贸谷歌网站推广wordpress调用上传图片
  • 360提示危险网站原因威海 网站开发
  • 赣州本地网站网站怎么写
  • 物业公司网站设计湛江做网站软件
  • 做招聘求职网站wordpress启用插件出错