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

忠县网站制作企业网络安全方案

忠县网站制作,企业网络安全方案,在深圳注册公司流程及费用,洛阳西工区做网站哪家好目录 前言#xff1a; 1.中断一个线程 2.等待一个线程-join() 2.1join()无参调用的使用 2.2join()有参调用的使用 3.线程的状态 3.1观察线程的所有状态 4.多线程带来的风险—线程安全 4.1观察线程不安全 4.2该问题出现的原因 4.3线程不安全问题的解决 4.3.1synchro…目录 前言 1.中断一个线程 2.等待一个线程-join() 2.1join()无参调用的使用 2.2join()有参调用的使用 3.线程的状态 3.1观察线程的所有状态 4.多线程带来的风险—线程安全 4.1观察线程不安全 4.2该问题出现的原因 4.3线程不安全问题的解决 4.3.1synchronized关键字解决 4.3.1.1synchronized的特性 4.3.1.2synchronized的工作过程 4.3.1.3synchronized的使用示例 4.3.2volatile关键字保证内存可见性 4.3.3volatile关键字禁止指令重排序 4.3.4volatile关键字不保证原子性 4.3.5wait()方法与notify()方法 4.3.6notifyAll()方法 4.4wait和sleep对比 结束语 前言 上一节中小编与大家分享了多线程的概念、线程与进程之间的区别与联系以及教大家如何创建一个线程和Thread的一些属性那么这节中小编就带着大家继续来了解线程中的知识点。 1.中断一个线程 中断这里就是字面意思就是让一个线程停下来让线程终止。例如下面的这个例子 李四一旦进到工作状态他就会按照行动指南上的步骤去进行工作不完成是不会结束的。但有时我们需要增加一些机制例如老板突然来电话了说转账的对方是个骗子需要赶紧停止转账那张三该如何通知李四停止呢这就涉及到了我们停止线程的方式了。 目前常见的有以下两种方式 通过共享的标记阿里进行沟通。调用interrupt()方法来通知。 ①给线程中设置一个结束标志位。 代码展示 package Thread; //使用结束标志位 public class ThreadDemo8 {public static boolean isQuit false;public static void main(String[] args) {Thread t new Thread(() - {while (!isQuit) {System.out.println(hello t);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(t 线程终止);});t.start();//在主线程中修改isQuittry {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}isQuit true;} }结果展示 与我们之前最大的区别就是在while循环的条件上增加了一个标志量这样就可以在主线程中控制标志量从而结束线程了。 当然在Thread类中内置了一个标志位让我们更方便实现上述的效果。 代码展示 package Thread;public class ThreadDemo9 {public static void main(String[] args) {Thread t new Thread(() - {//currentThread 是获取到当前线程实例//此处currentThread 得到的对象就是t//isInterrupted 就是t对象里面自带的一个标志位while (!Thread.currentThread().isInterrupted()){System.out.println(hello t);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace(); // try { // Thread.sleep(2000); // } catch (InterruptedException ex) { // ex.printStackTrace(); // } // break;}}});t.start();try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}//把t内部的标志位给设置成truet.interrupt();} }结果展示  在上述中我们是通过Thread的内置类首先调用currentThread来获取到当前线程的对象然后调用该对象中的isInterrupted来设置标志量在主线程中我们使用对象.interrupt来将其在设置成true使得该线程在阻塞中比如在执行sleep此时就会把阻塞状态唤醒通过抛出异常的方式让sleep立即结束。 注意当sleep被唤醒的时候sleep会自动把isInterrupted标志位给清空true——false这样就会导致我们进入下次的while循环也就是我们上述看到结果中的报错情况。 这个就像是我们生活中遇到的开关有的开关就是按下去就按下去了而有的开关是按下去之后会自动回弹我们这里的sleep就和后者一样唤醒之后会自动清空isInterrupted的标志位。那么为什么sleep要清空标志位呢目的就是为了让线程能够对于线程何时结束有一个更明确的控制。sleep在这里是一种“通知”而不是“命令”。 所以这里就会涉及到三种情况 ①直接无视“通知”。 ②立即执行“通知”。 ③等一会在做。 这就像是我们在家里当我们在打游戏的时候父母让我下楼买菜此时我们就会出现三种情况一种是直接无视父母的要求继续打着游戏第二种是立即下楼去买第三种是给父母说等一会再去先把这关打完再去买。  2.等待一个线程-join() 有时我们需要等待一个线程完成它的工作之后才能进行自己的下一步工作例如张三只有等李四转账成功才决定是否存钱这时我们需要一个方法明确等待线程的结束。 操作系统是并发执行的操作系统对于线程的调度是无序的。无法判定两个线程谁先执行结束谁后执行结束。就像是下面的这个例子此时我们就不能判断是先输出“hello main”还是“hello t”。 join的方法 方法 说明 public void join()等待线程结束public void join(long millis)等待线程结束最多等millis毫秒public void join(long millis int nanos)同理但可以更高精度 2.1join()无参调用的使用 代码展示 package Thread;public class ThreadDemo10 {public static void main(String[] args) {Thread t new Thread(() - {System.out.println(hello t);});t.start();System.out.println(hello main);} } 结果展示 但是一般来说程序猿是不喜欢不确定的所以有时候需要明确规定线程的结顺序这里我们就可以使用线程等待来实现join方法。 代码展示 package Thread;public class ThreadDemo10 {public static void main(String[] args) throws InterruptedException {Thread t new Thread(() - {System.out.println(hello t);});t.start();t.join();System.out.println(hello main);} }结果展示 分析此时我们就可以明确的看到先打印的是“hello t”然后再打印“hello main”了这里在上述的位置上加上t.join()就表示在t线程还没有结束的时候main线程就进入到阻塞状态等待直到t线程结束才执行main线程。详细看下述讲解 main线程调用t.join的时候如果t还在运行此时main线程阻塞直到t执行完毕t的run执行完了main才从阻塞中解除才继续执行。main线程调用t.join的时候如果t已经结束了此时main线程就不会阻塞就会继续往下执行。 2.2join()有参调用的使用 join的无参版本效果是“死等”不见不散。join的有参版本则是指定最大超时时间如果等待时间到了上限还没等到也就不在等了。 3.线程的状态 3.1观察线程的所有状态 线程的状态是一个枚举类型Thread.State。操作系统里的线程自身是有一个状态的但是Java Thread是对系统线程的封装把这里的状态又进一步的细化了。 NEW安排工作还没开始行动就是系统中的线程还没创建出来呢只有一个Thread对象。RUNNABLE就绪状态里面又分为两种一种是正在CPU上运行另一种是准备好随时可以去CPU上运行了。BLOCKED表示等待锁出现的状态。WAITING使用with方法出现的状态表示排队等着。TIMED_WAITHING在指定时间内等待.sleep方法。TERMINATEND表示系统中的线程已经执行完了Thread对象还在。 线程的状态转化图  注意这里的细节后期小编还会继续补充的 我们可以通过getState()方法来获取状态。 代码展示 package Thread; //状态的获取 public class ThreadDemo11 {public static void main(String[] args) throws InterruptedException {Thread t new Thread(() - {while (true) {//为了防止hello把线程状态冲没了先注释掉//System.out.println(hello t);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});//在启动之前获取线程状态NEWSystem.out.println(t.getState());t.start();Thread.sleep(2000);System.out.println(t.getState());} }结果展示 4.多线程带来的风险—线程安全 4.1观察线程不安全 首先我们先来观察一个线程不安全的例子。 代码展示 package Thread; //线程不安全 class Counter{private int count 0;public void sub() {count;}public int get() {return count;} } public class ThreadDemo12 {public static void main(String[] args) throws InterruptedException {Counter counter new Counter();//搞两个线程两个线程分别对这个counter自增5w次。Thread t1 new Thread(() - {for (int i 0; i 50000; i) {counter.sub();}});Thread t2 new Thread(() - {for (int i 0; i 50000; i) {counter.sub();}});t1.start();t2.start();//等待两个线程执行结束然后看结果。t1.join();t2.join();System.out.println(counter.get());} }结果展示  我们本来预想的是两个都自增5W次那么总数应该是10W结果我在上述结果中看到的却是一个意想不到的数字。这是什么原因造成的呢这就出现了实际结果与预想结果不符的情况就是一个bug这就是有多线程引起来的一个bug这里就引发出了两个问题线程不安全/线程安全问题。 4.2该问题出现的原因 上述代码中的count操作本质上是三个CPU指令构成 load把内存中的数据读取到CPU寄存器中。add就是把寄存器中的值进加1运算。save把寄存器中的值写回到内存中。 下面我们通过画图来解释一番 注意此处这两线程的指令排列顺序执行的先后有很多种的排列情况  不同的排列顺序下执行结果是截然不同的下面来给大家演示一下具体的执行过程。 在上述的演示中我我们可以看到本来两次自增后的结果应该是2但是最终写回内存中的结果是1所以此时就出现bug了本质是一次自增的结果被覆盖掉了所以才会出现bug。在上述众多的调用序列中只有②和③的结果最终会是正确的其他的都会出现bug。 所以归根结底线程安全问题全是因为线程的无序调度/随机调度抢占式执行导致了执行顺序不确定结果就变化了 所以通过上述我们可以总结出线程不安全的原因有 抢占式执行。多个线程修改同一个变量。修改操作不是原子的。内存可见性引起的线程不安全即一个线程对共享变量的修改能够及时地被其他线程看到。指令重排序引起的线程不安全。 解释 原子是可不分割的最小单位在上述中我们可以看到在执行调度的时候不是只有一个指令而是由三个指令构成的load、add、save。 4.3线程不安全问题的解决 在上述中我们给大家讲解和分析了线程不安全的原因那么如何解决线程不安全的问题我们还需要“解铃还须系铃人”从问题本质出发来解决它。 4.3.1synchronized关键字解决 首先是原子性问题要想让他变成原子性问题那么我们就可以让其进行加锁这样我们就可以保证“原子性”的效果了。 这里我们提到了锁的概念那么锁的核心操作有两个加锁和解锁。 原理一旦某个线程加锁之后其他线程也想加锁就不能直接加上了就需要阻塞等待一直等到拿到锁的线程释放了锁为止。 那么Java中是如何加锁的呢在Java中有一个关键字“synchronized”加上这个关键字就达到了加锁的效果。 4.3.1.1synchronized的特性 互斥刷新内存可重入 ①互斥 synchronized会引起到互斥效果某个线程执行到某个对象的synchronized中时其他线程如果也执行到同一个对象synchronized就会阻塞等待。 ②刷新内存 当进入到synchronized中的时候会读取内存中的数据在修改完数据之后就会有一个刷新内存的操作将内存中的数据进行修改。 ③可重入 synchronized同步块对同一条线程来说是可重入的不会出现自己把自己锁死的问题。 那么这里提到了一个“死锁”的概念下面给大家解释一下什么是死锁的情况。 按照我们对锁的设定在第二次加锁的时候就会出现阻塞等待第一个锁还没释放的前提下直到第一次锁释放才能获取到第二个锁但是释放第一个锁也是由第一个线程完成的结果这个线程现在不想动了已经躺平了也就无法进行解锁操作了这时就会出现死锁。这样的锁也叫“不可重入锁”。 注意在Java中synchronized是可重入锁因此就没有上述问题的出现。 4.3.1.2synchronized的工作过程 获得互斥锁。从主内存拷贝变量的最新副本到工作的内存。执行代码。将更改后的共享变量的值刷新到主内存。释放互斥锁。 下面我们使用代码来演示一下 代码展示 package Thread; //synchronized加锁机制 class Counter1{private int count 0;private Object locker new Object();public void add() {synchronized (locker) {count;}}public void sub() {count;}public int get() {return count;} } public class ThreadDemo13 {public static void main(String[] args) throws InterruptedException {Counter1 counter new Counter1();Thread t1 new Thread(() - {for (int i 0; i 50000; i) {counter.add();}});Thread t2 new Thread(() - {for (int i 0; i 50000; i) {counter.add();}});t1.start();t2.start();t1.join();t2.join();System.out.println(counter.get());} }结果展示 在上述代码的结果中我们可以看到两个自增5W次之后得要的结果是10W与我们预期的结果是一样的说明代码是正确的。 锁有两个核心操作加锁和解锁。 此处使用代码块来表示在进入synchronized的{}中就会出发加锁机制。出了代码块之后就会触发解锁机制。上面的locker就是一个锁对象表示你在针对于那个对象加锁如果两个线程针对同一个对象加锁此时就会出现“锁竞争” 一个线程拿到了锁另一个线程阻塞等待如果两个线程针对不同对象加锁此时就不会出现“锁竞争”各自获取锁即可。 里面的锁对象可以是写作任意一个Object对象内置类不行如果此处写了this就相当于是Counter counte  new Counter();这里的counter。 下面小编举个例子方便大家对于锁机制的理解。 当t1和t2都竞争同一个锁对象的时候就像是我们在上厕所的时候如果此时只有一个厕所那么其他人就得排队进去的那个人会给厕所上锁等到这个人出来之后才会释放掉这个锁其他人再去竞争这个锁谁先拿到这个锁谁就进去上厕所。  但是如果是多个厕所的话就不需要去竞争同一个锁对象了。如果这个锁对象被上锁了那么其他人就可以去找其他锁对象。  那么经过上述的这个例子相信大家可以更好的理解锁对象的机制。 通过“锁”就很好的解决了我们原子性的问题。这样就可以保证操作就是原子性的不受影响了。其实这里的加锁本质上就是将并行的变成了串行的。  4.3.1.3synchronized的使用示例 ①直接修饰普通方法 代码展示 package Thread; //1直接修饰普通方法锁的是ThreadDemo14对象 public class ThreadDemo14 {public synchronized void fun() {} } ②修饰静态方法 代码展示 package Thread; //修饰静态方法锁的ThreadDemo15类的对象 public class ThreadDemo15 {public synchronized static void fun() {} } ③修饰代码块 修饰代码块的时候它明确指定了锁哪个对象。 锁当前对象 代码展示 package Thread; //3.修饰代码块 //3.1锁当前对象 public class ThreadDemo16 {public void fun() {synchronized (this) {}} } 锁类对象 代码展示 package Thread; //3.修饰代码块 //3.2锁类对象 public class ThreadDemo17 {public void fun() {synchronized (ThreadDemo17.class){}} }4.3.2volatile关键字保证内存可见性 另一个线程不安全的场景是由内存可见性引起的线程不安全问题。 先看下面的这个实例 代码展示 package Thread;import java.util.Scanner;public class ThreadDemo18 {public static int flag 0;public static void main(String[] args) {Thread t1 new Thread(() - {while (flag 0) {//空着}System.out.println(循环结束t1结束);});Thread t2 new Thread(() -{Scanner scanner new Scanner(System.in);System.out.println(请输入一个整数);flag scanner.nextInt();});t1.start();t2.start();} }结果展示 分析 预期效果应该是t1通过flag 0作为条件进行循环初识情况将进入循环t2通过控制台输入一个整数一旦用户输入了非0的值此时t1就会立即结束从而t1线程退出 实际结果输入非0的值之后t1线程并没有退出循环没有结束通过jconsole可以看到t1线程仍然处于RUNNABLE状态。 此时预期结果 ! 实际结果就产生了bug了。 那么为什么会产生这种状况呢这就是内存可见性的锅。 在while循环中flag 0会有两步操作 首先是进行load操作先将数据从内存中读取到CPU寄存器中。然后再进行CMP操作比较寄存器里的值是否是0。 注意此处的这两操作load的时间开销远远高于cmp读内存虽然比读硬盘来的快但是读寄存器比读内存又要快几千倍 上述的这些操作电脑一秒钟就要执行上亿次此时编译器就会发现load的开销很大而且每次load的结果都是一样的此时编译器就有一个大胆的操作把load就给优化掉了也就是说只有第一次执行的时候load才真正的执行了后续循环都只有cmp操作没有load操作相当于是直复制了前面第一次的load的值。 这里我们提到了编译器优化那么什么是编译器优化呢先来给大家讲解一下编译器优化的原理。 编译器优化是一个非常普遍的事情就是能够智能的调整你的代码逻辑保证程序结果不变的前提下通过加减语句通过语句变换通过一些列操作让整个程序执行的效率大大提升。 编译器对于“程序结果不变”在单线程情况下判定是非常准确的但是在多线程中就不一定了可能就会导致调整之后效率是提高了但是结果却变了也就是编译器出现了误判从而导致了bug。 所谓的内存可见性就是在多线程的环境下编译器对于代码优化产生了误判从而引起bug进一步导致了代码的bug。此时咱们的处理方式就是让编译器针对这种场景暂停优化此时我们就用到了一个关键字“volatile”。 被“volatile”修饰的变量此时编译器就会禁止上述的优化能够保证每次都是从内存中重新读取数据。 代码展示 package Thread;import java.util.Scanner;public class ThreadDemo18 {volatile public static int flag 0;public static void main(String[] args) {Thread t1 new Thread(() - {while (flag 0) {//空着}System.out.println(循环结束t1结束);});Thread t2 new Thread(() -{Scanner scanner new Scanner(System.in);System.out.println(请输入一个整数);flag scanner.nextInt();});t1.start();t2.start();} }结果展示 我们在此处加上volatile关键之后就能保证每一次都会进行load操作编译器每一次都会去内存中重新读取数据到CPU寄存器中了。 此时t2修改flag变量t1就可以感知到了程序就可以正确退出了。 还需要大家注意的一点是在while循环中是空着的如果加上我之前的sleep操作可能就不会出现上述的内存可见性的问题了。 上述的volatile这个效果称为“保证内存可见性”。  volatile还有一个效果是禁止指令重排序。接下来我们就看看是怎么禁止的吧 4.3.3volatile关键字禁止指令重排序 指令重排序也是编译器优化的策略他是调整了代码的执行顺序让程序变得更高效前提是在保证整体逻辑不变的情况下。 例如下面的这个例子 在家里的时候父母可能会经常让大家去超市里买菜会有一个购物清单比如是1.西红柿 2.鸡蛋 3.黄瓜 4.芹菜。 那么我进入超市之后就按照上述的顺序采买采买路线如下图所示。 会发现上述的路线采买图效率并不高。 那么如果我们调整顺序先买黄瓜、鸡蛋、芹菜最后再买西红柿这样效率就会大大的提高。如下所示 谈到优化都得保证调整之后的结果和之前的结果是不变的单线程下容易保证如果是多线程就不好说了。 下述代码就可能出现指令重排序。 伪代码Student s; t1 :         s new Student(); t2:         if(s ! null) {                 s.learn();         } 上述t1中的 s new Student(); 大体可以分为三步操作 申请空间。调用构造方法初始化内存的数据。把对象的引用赋值给s内存地址的赋值。 如果是在单线程的情况下此处就可以进行指令重排序了1可定是最先执行的但是2 和 3谁先谁后都可以。 但是在多线程的情况下如果t1是先执行1再执行3最后执行2按照这样的顺序执行的话当t2开始执行的时候由于t1的3已经执行过了这个引用就已经是非空了所以t2就会尝试调用s.learn()但是由于t1只是赋值了还没有初始化此时的learn会成啥样就不知道了很可能就会产生bug 所以我们使用关键字“volatile”就会禁止指令重排序就会避免上述的问题了。 4.3.4volatile关键字不保证原子性 我们上述学习了synchronized他可以保证原子性但是volatile是不保证原子性的它只是保证了内存的可见性。 4.3.5wait()方法与notify()方法 由于线程之间是抢占式执行的因此线程之间执行的先后顺序难以预知。但是由于实际开发中有时候我们希望合理的协调多个线程之间的执行先后顺序。 我们之前学习了join算是一种控制顺序的方式但是工序有限。所以我我们现在又提出来了wait和notify。 wait就是让某个线程先暂停下来等一等。notify就是把该线程唤醒能够继续执行。 举个例子 滑稽1号现在在取钱进去ATM机之后发现没有钱1号就出来了解锁接下来哪个滑稽会进去取钱说不定1号滑稽也可能会再次进去也就是具体哪个线程能够拿到锁不一定这就会导致一种极端情况1号滑稽一直处于一个进进出出的状态就会使得线程饿死。 代码展示 package Thread;public class ThreadDemo19 {public static void main(String[] args) throws InterruptedException {Object object new Object();System.out.println(wait 之前);synchronized (object) {object.wait();}System.out.println(wait 之后);} }结果展示 此时我们可以借助jconsole来查看线程的运行状态是处于WAITING。 但是我们如果使用wait/notify就可以有效的解决上述问题了。 1号滑稽发现ATM机没有钱就waitwait就会释放锁并进行阻塞等待。暂时不参与CPU的调度不参与锁竞争了当另外一个线程给ATM机里充上钱就可以唤醒1号了。  wait发现条件不满足/时机不成熟就先阻塞等待。notify其他线程构造了一个成熟的条件就可以唤醒1号了。 代码展示 package Thread;public class ThreadDemo20 {public static void main(String[] args) throws InterruptedException {Object locker new Object();Thread t1 new Thread(() - {System.out.println(wait 之前);synchronized (locker) {try {locker.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(wait 之后);});t1.start();Thread.sleep(1000);Thread t2 new Thread(() - {synchronized (locker) {System.out.println(notify 开始);locker.notify();System.out.println(notify 结束);}});t2.start();} }结果展示 解释 t1先执行到wait就进入了阻塞状态1s之后t2开始执行执行到notify就会通知t1线程唤醒注意notify是在synchronized内部所以就需要等t2释放了锁之后才能继续往下走 wait做的事情 使用当前执行的代码的线程进行等待把线程放到等待队列中。释放当前锁。满足一定条件时被唤醒重新尝试获取这个锁。 注意wait要搭配synchronized来使用脱离synchronized使用wait会直接抛出异常 wait结束等待的条件 其他线程调用该对象的notify方法。wait等待时间超时wait方法提供了一个带有timeout参数的版本来指定等待的时间。其他线程调用该等待线程的interrupt方法导致wait抛出InterruptedException异常。 notify方法是唤醒等待线程 方法notify()也要在同步方法或同步块中调用该方法是用来通知那些可能等待该对象锁的其他线程对其发出通知notify并使它们重新获取该对象的对象锁。如果有多个线程等待则由线程调度器随机挑选出一个呈wait状态的线程。并没有先来后到在notify()方法后当前线程不会马上释放该对象锁要等到执行notify()方法的线程将程序执行完也就是退出同步代码块之后才会释放对象锁。 4.3.6notifyAll()方法 唤醒操作除了上述的notify还有一个是notifyAllnotify只是会随机唤醒一个等待线程而使用notifyAll方法可以一次唤醒所有的等待线程。 下面举个例子让大家更好理解一下。 notify只唤醒等待队列中的一个线程其他的线程还是得乖乖的等着。 notifyAll一下子全部都唤醒需要这些线程重新竞争锁。 4.4wait和sleep对比 wait有一个带参数的版本用来体现超时时间这个时候感觉好像和sleep差不多wait也能提前唤醒sleep也能提前唤醒。 ①但是它两最大的区别就是在于“初心不同”。 wait解决的是线程之间的顺序控制问题。sleep单纯是让当前线程休眠一会。 ②同时在使用上也有所不同wait需要搭配synchronized来使用而sleep不需要。 ③wait是Object的方法sleep是Thread的静态方法。 结束语 这节中小编主要与大家分享了有关于如何中断一个线程线程的状态、线程的安全问题以及他的解决方法希望这节对大家学习JavaEE有一定的帮助想要学习的同学记得关注小编和小编一起学习吧如果文章中有任何错误也欢迎各位大佬及时为小编指点迷津在此小编先谢过各位大佬啦
http://www.yutouwan.com/news/396510/

相关文章:

  • 网站导航栏设计wordpress搜索标题
  • 没有公司自己做网站微信小程序开发文档
  • 做网站成功的企业服务器一年多少钱
  • 做网站_你的出路在哪里怎样做违法网站
  • 南宁建网站公司就去云尚网络工商注册是什么意思
  • 室内设计网站有哪些知乎中国互联网巨头有哪些
  • 湛江建设免费网站巨量千川广告投放平台
  • 做宣传 为什么要做网站那智能小程序平台
  • 选择响应式网站网站建设一般步骤
  • 重庆定制型网站建设项目流程管理软件
  • seo网站网站建设技术指标
  • 广州网站优化公司如何wordpress关键词屏蔽
  • 湛江网站建设皆选小罗24专业网站登录验证码怎么做
  • 建设微信网站的流程ps上做网站
  • 个人网站的设计与实现的主要内容江南大学做网站
  • 建设工程企业资质工作网站深圳十大装饰公司名单
  • 网站建设 会议主持稿什么是网站ui设计
  • 嘉兴专业网站建设onethink wordpress
  • 网站定位与建设页面设计风格的主要内容
  • 顺德网站建设域名网络专题策划书模板
  • icann官方网站厦门工程信息网
  • 移动网站建设解决方案学校网站建设目标
  • 做绿色产品的网站合肥建设学校网站首页
  • 如何做好网站推广优化电子商务网站设计岗位主要是?
  • 专业的外贸网站制作视频的软件手机
  • 在货源网站自己拿样 加盟 做代理 哪个比较好?新站网站建设
  • 自己做的网站怎么接入微信dw做网站学习解析
  • wordpress新页面莫停之科技windows优化大师
  • 微网站的案例邢台手机网站建设服务
  • 公司设计网站建设自己怎么做一元购物网站