类似站酷的设计类网站,中国建设会计学会网站,河北省城乡与建设厅网站,品牌微信网站开发一、多线程的相关知识
栈与栈帧
我们都知道 JVM 中由堆、栈、方法区所组成#xff0c;其中栈内存是给谁用的呢#xff1f;其实就是线程#xff0c;每个线程启动后#xff0c;虚拟 机就会为其分配一块栈内存。 每个栈由多个栈帧#xff08;Frame#xff09;组成#xf…一、多线程的相关知识
栈与栈帧
我们都知道 JVM 中由堆、栈、方法区所组成其中栈内存是给谁用的呢其实就是线程每个线程启动后虚拟 机就会为其分配一块栈内存。 每个栈由多个栈帧Frame组成对应着每次方法调用时所占用的内存 每个线程只能有一个活动栈帧对应着当前正在执行的那个方法
线程上下文切换
上下文切换可以更详细地描述为内核(即操作系统的核心)对CPU上的进程(包括线程)执行以下活动:
1. 暂停一个进程的处理并将该进程的CPU状态(即上下文)存储在内存中的某个地方
2. 从内存中获取下一个进程的上下文并在CPU的寄存器中恢复它
3. 返回到程序计数器指示的位置(即返回到进程被中断的代码行)以恢复进程。
从数据来说以程序员的角度来看 是方法调用过程中的各种局部的变量与资源 以线程的角度来看 是方法的调用栈中存储的各类信息。
引发上下文切换的原因一般包括线程、进程切换、系统调用等等。上下文切换通常是计算密集型的因为涉及一系列数据在各种寄存器、 缓存中的来回拷贝。就CPU时间而言一次上下文切换大概需要5000~20000个时钟周期相对一个简单指令几个乃至十几个左右的执行时钟周期可以看出这个成本的巨大。
因为以下一些原因导致 cpu 不再执行当前的线程转而执行另一个线程的代码
线程的 cpu 时间片用完垃圾回收有更高优先级的线程需要运行线程自己调用了 sleep、yield、wait、join、park、synchronized、lock 等方法
当 Context Switch上下文切换 发生时需要由操作系统保存当前线程的状态并恢复另一个线程的状态Java 中对应的概念 就是程序计数器Program Counter Register它的作用是记住下一条 jvm 指令的执行地址是线程私有的 状态包括程序计数器、虚拟机栈中每个栈帧的信息如局部变量、操作数栈、返回地址等 Context Switch 频繁发生会影响性能 并行于并发
并行Parallel在同一时刻有多个指令在多个CPU上同时执行。并发Concurrent在一段时间内有多个指令在单个CPU上交替执行。
进程和线程的区别
进程是正在运行的程序
独立性进程是一个能独立运行的基本单位同时也是系统分配资源和调度的独立单位动态性进程的实质程序的一次执行过程进程是动态产生动态消亡的并发性任何进程都可以同其他进程一起并发执行
线程是进程中的单个顺序控制流是一条执行路径
单线程一个进程如果只有一条执行路径则称为单线程程序多线程一个进程如果有多条执行路径则称为多线程程序
线程状态介绍 当线程被创建并启动以后它既不是一启动就进入了执行状态也不是一直处于执行状态。线程对象在不同的时期有不同的状态。那么Java中的线程存在哪几种状态呢Java中的线程状态被定义在了java.lang.Thread.State枚举类中
State枚举类的源码如下
public class Thread {public enum State {/* 新建 */NEW ,
/* 可运行状态 */RUNNABLE ,
/* 阻塞状态 */BLOCKED ,
/* 无限等待状态 */WAITING ,
/* 计时等待 */TIMED_WAITING ,
/* 终止 */TERMINATED;}// 获取当前线程的状态public State getState() {return jdk.internal.misc.VM.toThreadState(threadStatus);}}
通过源码我们可以看到Java中的线程存在6种状态每种线程状态的含义如下
线程状态具体含义NEW一个尚未启动的线程的状态。也称之为初始状态、开始状态。线程刚被创建但是并未启动。还没调用start方法。MyThread t new MyThread()只有线程象没有线程特征。RUNNABLE当我们调用线程对象的start方法那么此时线程对象进入了RUNNABLE状态。那么此时才是真正的在JVM进程中创建了一个线程线程一经启动并不是立即得到执行线程的运行与否要听令与CPU的调度那么我们把这个中间状态称之为可执行状态(RUNNABLE)也就是说它具备执行的资格但是并没有真正的执行起来而是在等待CPU的度。BLOCKED当一个线程试图获取一个对象锁而该对象锁被其他的线程持有则该线程进入Blocked状态当该线程持有锁时该线程将变成Runnable状态。WAITING一个正在等待的线程的状态。也称之为等待状态。造成线程等待的原因有两种分别是调用Object.wait()、join()方法。处于等待状态的线程正在等待其他线程去执行一个特定的操作。例如因为wait()而等待的线程正在等待另一个线程去调用notify()或notifyAll()一个因为join()而等待的线程正在等待另一个线程结束。TIMED_WAITING一个在限定时间内等待的线程的状态。也称之为限时等待状态。造成线程限时等待状态的原因有三种分别是Thread.sleep(long)Object.wait(long)、join(long)。TERMINATED一个完全运行完成的线程的状态。也称之为终止状态、结束状态
各个状态的转换如下图所示 情况 1 NEW -- RUNNABLE
当调用 t.start() 方法时由 NEW -- RUNNABLE
情况 2 RUNNABLE -- WAITING
t 线程用 synchronized(obj) 获取了对象锁后 调用 obj.wait() 方法时t 线程从 RUNNABLE -- WAITING 调用 obj.notify() obj.notifyAll() t.interrupt() 时 竞争锁成功t 线程从WAITING --RUNNABLE 竞争锁失败t 线程从WAITING -- BLOCKED
情况 3 RUNNABLE -- WAITING
当前线程调用 t.join() 方法时当前线程从 RUNNABLE -- WAITING 注意是当前线程在t 线程对象的监视器上等待 t 线程运行结束或调用了当前线程的 interrupt() 时当前线程从 WAITING -- RUNNABLE
情况 4 RUNNABLE -- WAITING
当前线程调用 LockSupport.park() 方法会让当前线程从 RUNNABLE -- WAITING 调用 LockSupport.unpark(目标线程) 或调用了线程 的 interrupt() 会让目标线程从 WAITING --
RUNNABLE
情况 5 RUNNABLE -- TIMED_WAITING
t 线程用 synchronized(obj) 获取了对象锁后 调用 obj.wait(long n) 方法时t 线程从 RUNNABLE -- TIMED_WAITING t 线程等待时间超过了 n 毫秒或调用 obj.notify() obj.notifyAll() t.interrupt() 时 竞争锁成功t 线程从TIMED_WAITING -- RUNNABLE 竞争锁失败t 线程从
TIMED_WAITING -- BLOCKED
情况 6 RUNNABLE -- TIMED_WAITING
当前线程调用 t.join(long n) 方法时当前线程从 RUNNABLE -- TIMED_WAITING
注意是当前线程在t 线程对象的监视器上等待 当前线程等待时间超过了 n 毫秒或t 线程运行结束或调用了当前线程的 interrupt() 时当前线程从 TIMED_WAITING -- RUNNABLE
情况 7 RUNNABLE -- TIMED_WAITING
当前线程调用 Thread.sleep(long n) 当前线程从 RUNNABLE -- TIMED_WAITING
当前线程等待时间超过了 n 毫秒当前线程从 TIMED_WAITING -- RUNNABLE
情况 8 RUNNABLE -- TIMED_WAITING
当前线程调用 LockSupport.parkNanos(long nanos) 或 LockSupport.parkUntil(long millis) 时当前线 程从 RUNNABLE -- TIMED_WAITING 调用 LockSupport.unpark(目标线程) 或调用了线程 的 interrupt() 或是等待超时会让目标线程从 TIMED_WAITING-- RUNNABLE
情况 9 RUNNABLE -- BLOCKED
t 线程用 synchronized(obj) 获取了对象锁时如果竞争失败从RUNNABLE -- BLOCKED
持 obj 锁线程的同步代码块执行完毕会唤醒该对象上所有 BLOCKED的线程重新竞争如果其中 t 线程竞争 成功从 BLOCKED -- RUNNABLE 其它失败的线程仍然 BLOCKED
情况 10 RUNNABLE -- TERMINATED
当前线程所有代码运行完毕进入 TERMINATED 二、创建线程的几种方式
继承Thread类
方法介绍
实现步骤
定义一个类MyThread继承Thread类在MyThread类中实现run()重写创建MyThread类对象启动线程调用start()放法
代码实现
public class MyThread extends Thread {Overridepublic void run() {for(int i0; i100; i) {System.out.println(i);}}
}
public class MyThreadDemo {public static void main(String[] args) {MyThread my1 new MyThread();MyThread my2 new MyThread();
my1.start();my2.start();}
}
实现Runnable接口
实现步骤
定义一个类MyRunnable实现Runnable接口在MyRunnable类中重写run()方法创建MyRunnable类对象创建Thread对象将创建的MyRunnable类作为Thread对象构造方法的参数传入调用Thread对象的start()方法
代码实现
public class MyRunnable implements Runnable {Overridepublic void run() {for(int i0; i100; i) {System.out.println(Thread.currentThread().getName():i);}}
}
public class MyRunnableDemo {public static void main(String[] args) {//创建MyRunnable类的对象MyRunnable my new MyRunnable();
//创建Thread类的对象把MyRunnable对象作为构造方法的参数//Thread(Runnable target)
// Thread t1 new Thread(my);
// Thread t2 new Thread(my);//Thread(Runnable target, String name)Thread t1 new Thread(my,坦克);Thread t2 new Thread(my,飞机);
//启动线程t1.start();t2.start();}
}
实现callable接口
方法介绍
实现步骤
创建一个类(MyCallable)实现Callable接口重写Callable中的call()方法创建MyCallable对象创建Future实现类FutureTask对象把Mycallable作为构造方法的参数创建Thread对象把FutureTask作为构造方法的参数启动Thread线程调用start()方法调用FutureTask的get()方法获取Mycallable的返回值get方法会一直等待线程执行完获取结果必须要放在start后面否则会一直等待
代码实现
public class MyCallable implements CallableString {Overridepublic String call() throws Exception {for (int i 0; i 100; i) {System.out.println(跟女孩表白 i);}//返回值就表示线程运行完毕之后的结果return 答应;}
}
public class Demo {public static void main(String[] args) throws ExecutionException, InterruptedException {//线程开启之后需要执行里面的call方法MyCallable mc new MyCallable();
//Thread t1 new Thread(mc);
//可以获取线程执行完毕之后的结果.也可以作为参数传递给Thread对象FutureTaskString ft new FutureTask(mc);
//创建线程对象Thread t1 new Thread(ft);//开启线程t1.start();
String s ft.get();//该方法会一直等到线程执行完获取返回值否则会一直等待所以要放在start方法后面System.out.println(s);}
}
三种创建方式的区别
实现Runnable、Callable接口的的优点
好处扩展性强实现该接口的同时还可以继承其他类缺点编程相对复杂不能直接调用Thread类方法
继承Thread类
好处编程相对简单可以直接调用Thread类方法缺点可以拓展性较差不能再继承其他类
Runnable和Callable的区别
Runnable规定的方法是run(),Callable规定的方法Runnable的run()方法不能抛出异常而Callable的call()方法可以抛出异常Runnable执行完毕后没有返回值而Callable执行完毕后有返回值Callable可与通过Future的实现类FutureTask的get()方法计算返回值
Thread和Runnable的区别
Thread才是Java里对线程的唯一抽象Runnable只是对任务业务逻辑的抽象。Thread可以接受任意一个Runnable的实例并执行。
因为Future只是一个接口所以是无法直接用来创建对象使用的因此就有了FutureTask。FutureTask类实现了RunnableFuture接口RunnableFuture继承了Runnable接口和Future接口而FutureTask实现了RunnableFuture接口。所以它既可以作为Runnable被线程执行又可以作为Future得到Callable的返回值。因此我们通过一个线程运行Callable但是Thread不支持构造方法中传递Callable的实例所以我们需要通过FutureTask把一个Callable包装成Runnable然后再通过这个FutureTask拿到Callable运行后的返回值。
新启线程有几种方式
这个问题的答案其实众说纷纭有2种3种4种等等答案建议比较好的回答是
按照Java源码中Thread上的注释 官方说法是在Java中有两种方式创建一个线程用以执行一种是派生自Thread类另一种是实现Runnable接口。
当然本质上Java中实现线程只有一种方式都是通过new Thread()创建线程对象调用Thread#start启动线程。
至于基于callable接口的方式因为最终是要把实现了callable接口的对象通过FutureTask包装成Runnable再交给Thread去执行所以这个其实可以和实现Runnable接口看成同一类。
而线程池的方式本质上是池化技术是资源的复用和新启线程没什么关系。
所以比较赞同官方的说法有两种方式创建一个线程用以执行。
三、多线程的常见方法
start和run
run是多线程的方法体调用多线程时执行的代码块如果直接调用run那么就像直接调用方法一样不会启动多线程正确方法时通过调用start方法启动多线程执行run方法start使用 start 是启动新的线程通过新的线程间接执行 run 中的代码底层会调用native方法通过操作系统进行操作
Thread类是Java里对线程概念的抽象可以这样理解我们通过new Thread()其实只是new出一个Thread的实例还没有操作系统中真正的线程挂起钩来。只有执行了start()方法后才实现了真正意义上的启动线程。
从Thread的源码可以看到Thread的start方法中调用了start0()方法而start0()是个native方法这就说明Thread#start一定和操作系统是密切相关的。
start()方法让一个线程进入就绪队列等待分配cpu分到cpu后才调用实现的run()方法start()方法不能重复调用如果重复调用会抛出异常注意此处可能有面试题多次调用一个线程的start方法会怎么样。
而run方法是业务逻辑实现的地方本质上和任意一个类的任意一个成员方法并没有任何区别可以重复执行也可以被单独调用。
优先级Priority
线程调度两种调度方式
分时调度模型所有线程轮流使用 CPU 的使用权平均分配每个线程占用 CPU 的时间片抢占式调度模型优先让优先级高的线程使用 CPU如果线程的优先级相同那么会随机选择一个优先级高的线程获取的 CPU 时间片相对多一些
Java使用的是抢占式调度模型
随机性假如计算机只有一个 CPU那么 CPU 在某一个时刻只能执行一条指令线程只有得到CPU时间片也就是使用权才可以执行指令。所以说多线程程序的执行是有随机性因为谁抢到CPU的使用权是不一定的
优先级相关方法
代码演示
public class MyCallable implements CallableString {Overridepublic String call() throws Exception {for (int i 0; i 100; i) {System.out.println(Thread.currentThread().getName() --- i);}return 线程执行完毕了;}
}
public class Demo {public static void main(String[] args) {//优先级: 1 - 10 默认值:5MyCallable mc new MyCallable();
FutureTaskString ft new FutureTask(mc);
Thread t1 new Thread(ft);t1.setName(飞机);t1.setPriority(10);//System.out.println(t1.getPriority());//5t1.start();
MyCallable mc2 new MyCallable();
FutureTaskString ft2 new FutureTask(mc2);
Thread t2 new Thread(ft2);t2.setName(坦克);t2.setPriority(1);//System.out.println(t2.getPriority());//5t2.start();}
}
守护线程Daemon
相关方法
代码演示 public class MyThread1 extends Thread {Overridepublic void run() {for (int i 0; i 10; i) {System.out.println(getName() --- i);}}
}
public class MyThread2 extends Thread {Overridepublic void run() {for (int i 0; i 100; i) {System.out.println(getName() --- i);}}
}
public class Demo {public static void main(String[] args) {MyThread1 t1 new MyThread1();MyThread2 t2 new MyThread2();
t1.setName(女神);t2.setName(备胎);
//把第二个线程设置为守护线程//当普通线程执行完之后,那么守护线程也没有继续运行下去的必要了.t2.setDaemon(true);
t1.start();t2.start();}
} sleep
特点
调用 sleep 会让当前线程从 Running 进入 Timed Waiting 状态阻塞 其它线程可以使用 interrupt 方法打断正在睡眠的线程这时 sleep 方法会抛出 InterruptedException睡眠结束后的线程未必会立刻得到执行建议用 TimeUnit 的 sleep 代替 Thread 的 sleep 来获得更好的可读性
代码演示 public static void main(String[] args) {new Thread(()-{try {//方式一Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}try {//方式二TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}}).start();}
yield
特点
调用 yield 会让当前线程从 Running 进入 Runnable 就绪状态然后调度执行其它线程具体的实现依赖于操作系统的任务调度器有可能会出现让出时间片不成功的现象因为当让出时间片后就进入就绪状态很可能会重新再次获得时间片
代码演示 public static void main(String[] args) {Thread thread new Thread();thread.yield();//进入就绪状态让出时间片}
join
特点
当线程调用join方法之后就会让主线程进入阻塞状态直到所有被标记为join方法的线程执行完成后才能开始执行主线程
join的两种调用方式
join 不添加时间参数即等待线程完成后主线程才开始运行joini 添加时间参数主线程最多等待i毫秒超过i毫秒后如果还没完成主线程也开始执行如果参数是0就无限等待相当于无参调用
代码演示 public static void main(String[] args) throws InterruptedException {MyThread myThread1 new MyThread(线程一);myThread1.start();myThread1.join();System.out.println(我主线程先完成了);} parkunpark
通过LockSupport.park可以暂停某个线程。通过LockSupport.unpark可以恢复某个线程运行与 Object 的 wait notify 相比 waitnotify 和 notifyAll 必须配合 Object Monitor 一起使用而 parkunpark 不必LockSupport.unpark(t1);park unpark 是以线程为单位来【阻塞】和【唤醒】线程而 notify 只能随机唤醒一个等待线程notifyAll 是唤醒所有等待线程就不那么【精确】park unpark 可以先 unpark而 wait notify 不能先 notify
原理
每个线程都有自己的一个 Parker 对象由三部分组成 _counter _cond 和 _mutex 打个比喻
线程就像一个旅人Parker 就像他随身携带的背包条件变量就好比背包中的帐篷。_counter 就好比背包中的备用干粮0 为耗尽1 为充足 调用 park 就是要看需不需要停下来歇息 如果备用干粮耗尽那么钻进帐篷歇息 如果备用干粮充足那么不需停留继续前进 调用 unpark就好比令干粮充足 如果这时线程还在帐篷就唤醒让他继续前进如果这时线程还在运行那么下次他调用 park 时仅是消耗掉备用干粮不需停留继续前进因为背包空间有限多次调用 unpark 仅会补充一份备用干粮 1. 当前线程调用 Unsafe.park() 方法
2. 检查 _counter 本情况为 0这时获得 _mutex 互斥锁
3. 线程进入 _cond 条件变量阻塞
4. 设置 _counter 0 1. 调用 Unsafe.unpark(Thread_0) 方法设置 _counter 为 1
2. 唤醒 _cond 条件变量中的 Thread_0
3. Thread_0 恢复运行
4. 设置 _counter 为 0 1. 调用 Unsafe.unpark(Thread_0) 方法设置 _counter 为 1
2. 当前线程调用 Unsafe.park() 方法
3. 检查 _counter 本情况为 1这时线程无需阻塞继续运行
4. 设置 _counter 为 0 interrupt
方法介绍
interrupt中断sleepwaitjoin的线程
会打断当前的等待状态如果需要锁的则会等待获取锁如果不需要所得会进入就绪状态等待获取时间片 interrupt的作用是中断线程如果线程不是处于wait、timewait、block状态的话实际上并不会直接中断线程而是给线程一个打断状态我们需要在代码中判断打断状态是否为true默认值为false如果为true表示按照逻辑是希望线程终止的这时候我们可以写一些善后代码体面终止线程
如果线程是处于wait、timewait、block状态使线程开始运行并且会清空打断状态打断状态还是fasle 方法说明interrupt()打断线程isinterrupted()判断是否被打断不会清空打断标记interrupted()判断是否被打断会清空打断标记
interrupt打断正常运行的线程
表面上并不会出现任何情况但是会将打断状态设置成true用户可以根据实际情况进行处理
interrupt打断park线程
代码演示
private static void test4() {Thread t1 new Thread(() - {for (int i 0; i 5; i) {log.debug(park...);LockSupport.park();log.debug(打断状态{}, Thread.currentThread().isInterrupted());}});t1.start();sleep(1);t1.interrupt();
} park线程只会使打断状态为false的线程进入阻塞第一次park能够成功但是当通过interrupt打断了之后再次park就会失效除非将打断状态设置成false
对于线程的中断除了interrupt还有其他两种方式但是都不推荐使用
暂停、恢复和停止操作对应在线程Thread的API就是suspend()、resume()和stop()。但是这些API是过期的也就是不建议使用的。不建议使用的原因主要有以suspend()方法为例在调用后线程不会释放已经占有的资源比如锁而是占有着资源进入睡眠状态这样容易引发死锁问题。同样stop()方法在终结一个线程时不会保证线程的资源正常释放通常是没有给予线程完成资源释放工作的机会因此会导致程序可能工作在不确定状态下。正因为suspend()、resume()和stop()方法带来的副作用这些方法才被标注为不建议使用的过期方法。 wait、notify
原理 Owner 线程发现条件不满足调用 wait 方法即可进入 WaitSet 变为 WAITING 状态BLOCKED 和 WAITING 的线程都处于阻塞状态不占用 CPU 时间片BLOCKED 线程会在 Owner 线程释放锁时唤醒WAITING 线程会在 Owner 线程调用 notify 或 notifyAll 时唤醒但唤醒后并不意味者立刻获得锁仍需进入 EntryList 重新竞争wait和notify都是object的方法必须要获取对象的锁之后才能使用wait和notify进行等待和唤醒 方法介绍
方法介绍wait无限期等待直到被唤醒waitlong i等待i毫秒如果没被唤醒则停止等待notify随机唤醒一个notifyAll唤醒所有wait线程
wait和sleep的区别
sleep是thread方法而wait是object方法
sleep不需要强制和synchronized配合使用wait需要和synchronized配合使用
sleep在睡眠的同时不会释放锁wait在睡眠的时候会释放锁 文章转载自: http://www.morning.ndcjq.cn.gov.cn.ndcjq.cn http://www.morning.hjjkz.cn.gov.cn.hjjkz.cn http://www.morning.fgxr.cn.gov.cn.fgxr.cn http://www.morning.schwr.cn.gov.cn.schwr.cn http://www.morning.huihuangwh.cn.gov.cn.huihuangwh.cn http://www.morning.pntzg.cn.gov.cn.pntzg.cn http://www.morning.rwqk.cn.gov.cn.rwqk.cn http://www.morning.tckxl.cn.gov.cn.tckxl.cn http://www.morning.mnwb.cn.gov.cn.mnwb.cn http://www.morning.jrbyz.cn.gov.cn.jrbyz.cn http://www.morning.qnxtz.cn.gov.cn.qnxtz.cn http://www.morning.rzcfg.cn.gov.cn.rzcfg.cn http://www.morning.xqcst.cn.gov.cn.xqcst.cn http://www.morning.sjwiki.com.gov.cn.sjwiki.com http://www.morning.jxscp.cn.gov.cn.jxscp.cn http://www.morning.lfgql.cn.gov.cn.lfgql.cn http://www.morning.gsdbg.cn.gov.cn.gsdbg.cn http://www.morning.xltdh.cn.gov.cn.xltdh.cn http://www.morning.ghryk.cn.gov.cn.ghryk.cn http://www.morning.fkgqn.cn.gov.cn.fkgqn.cn http://www.morning.bwqr.cn.gov.cn.bwqr.cn http://www.morning.nqnqz.cn.gov.cn.nqnqz.cn http://www.morning.srnth.cn.gov.cn.srnth.cn http://www.morning.qbccg.cn.gov.cn.qbccg.cn http://www.morning.bpmdh.cn.gov.cn.bpmdh.cn http://www.morning.gdgylp.com.gov.cn.gdgylp.com http://www.morning.tyrlk.cn.gov.cn.tyrlk.cn http://www.morning.thzgd.cn.gov.cn.thzgd.cn http://www.morning.gczzm.cn.gov.cn.gczzm.cn http://www.morning.dmxzd.cn.gov.cn.dmxzd.cn http://www.morning.dwgcx.cn.gov.cn.dwgcx.cn http://www.morning.ynlpy.cn.gov.cn.ynlpy.cn http://www.morning.rykgh.cn.gov.cn.rykgh.cn http://www.morning.ftntr.cn.gov.cn.ftntr.cn http://www.morning.zsgbt.cn.gov.cn.zsgbt.cn http://www.morning.rtkz.cn.gov.cn.rtkz.cn http://www.morning.wjwfj.cn.gov.cn.wjwfj.cn http://www.morning.rnht.cn.gov.cn.rnht.cn http://www.morning.pclgj.cn.gov.cn.pclgj.cn http://www.morning.mfmx.cn.gov.cn.mfmx.cn http://www.morning.wmlby.cn.gov.cn.wmlby.cn http://www.morning.jtnph.cn.gov.cn.jtnph.cn http://www.morning.lkfsk.cn.gov.cn.lkfsk.cn http://www.morning.rrbhy.cn.gov.cn.rrbhy.cn http://www.morning.mbrbk.cn.gov.cn.mbrbk.cn http://www.morning.wwklf.cn.gov.cn.wwklf.cn http://www.morning.xbbrh.cn.gov.cn.xbbrh.cn http://www.morning.klzt.cn.gov.cn.klzt.cn http://www.morning.zffps.cn.gov.cn.zffps.cn http://www.morning.hcwlq.cn.gov.cn.hcwlq.cn http://www.morning.qnftc.cn.gov.cn.qnftc.cn http://www.morning.srjgz.cn.gov.cn.srjgz.cn http://www.morning.rnfn.cn.gov.cn.rnfn.cn http://www.morning.qbrdg.cn.gov.cn.qbrdg.cn http://www.morning.qttg.cn.gov.cn.qttg.cn http://www.morning.jqzns.cn.gov.cn.jqzns.cn http://www.morning.mhcft.cn.gov.cn.mhcft.cn http://www.morning.mqfhy.cn.gov.cn.mqfhy.cn http://www.morning.pmtky.cn.gov.cn.pmtky.cn http://www.morning.bzfld.cn.gov.cn.bzfld.cn http://www.morning.bmjfp.cn.gov.cn.bmjfp.cn http://www.morning.bscsp.cn.gov.cn.bscsp.cn http://www.morning.mtgkq.cn.gov.cn.mtgkq.cn http://www.morning.bhznl.cn.gov.cn.bhznl.cn http://www.morning.mtgkq.cn.gov.cn.mtgkq.cn http://www.morning.tjqcfw.cn.gov.cn.tjqcfw.cn http://www.morning.paoers.com.gov.cn.paoers.com http://www.morning.nfzw.cn.gov.cn.nfzw.cn http://www.morning.ffbp.cn.gov.cn.ffbp.cn http://www.morning.tkflb.cn.gov.cn.tkflb.cn http://www.morning.bnrff.cn.gov.cn.bnrff.cn http://www.morning.xqnzn.cn.gov.cn.xqnzn.cn http://www.morning.mqzcn.cn.gov.cn.mqzcn.cn http://www.morning.hqnsf.cn.gov.cn.hqnsf.cn http://www.morning.ydnx.cn.gov.cn.ydnx.cn http://www.morning.ltffk.cn.gov.cn.ltffk.cn http://www.morning.slzkq.cn.gov.cn.slzkq.cn http://www.morning.bsbcp.cn.gov.cn.bsbcp.cn http://www.morning.wwkdh.cn.gov.cn.wwkdh.cn http://www.morning.hmtft.cn.gov.cn.hmtft.cn