walterwing  
          日歷
          <2008年7月>
          293012345
          6789101112
          13141516171819
          20212223242526
          272829303112
          3456789
          統(tǒng)計(jì)
          • 隨筆 - 12
          • 文章 - 1
          • 評(píng)論 - 7
          • 引用 - 0

          導(dǎo)航

          常用鏈接

          留言簿(1)

          隨筆分類

          隨筆檔案

          搜索

          •  

          最新評(píng)論

          閱讀排行榜

          評(píng)論排行榜

           
          看了一陣子java線程方面的知識(shí),《thinking in java 3rd》,《effective java 2nd》,感覺還是霧里看花,難以得其精髓。

          多線程編程本來(lái)就是一門很玄奧的學(xué)問(wèn),不是看一些基礎(chǔ)的語(yǔ)法知識(shí)就能真正掌握的。在實(shí)踐中去揣摩,我想才是最好的方法。奈何我現(xiàn)在沒有這樣的條件,離論文開題的時(shí)間不遠(yuǎn)了,我還沒有摸到頭緒,真不知道是該堅(jiān)持還是放棄。

          扯遠(yuǎn)了,還是回到線程來(lái)吧,雖然不得要領(lǐng),但還是要把一些基礎(chǔ)的東西總結(jié)一下,一旦以后需要用到的時(shí)候,也可以方便地回顧。



          1. 線程的創(chuàng)建

          java中創(chuàng)建一個(gè)線程有兩種方式:

          1.1. 擴(kuò)展Thread類,并重載run()方法

          public class ThreadName extends Thread {
              
          public void run() {
                  
          // do something here
              }

          }


          1.2. 實(shí)現(xiàn)runnable接口,并調(diào)用Thread提供的構(gòu)造函數(shù)

          public class ThreadName implements Runnable {

              
          public void run() {
                  
          // TODO Auto-generated method stub

              }

              
              
          public void main(String args[]) {
                  Thread thread 
          = new Thread(new ThreadName());
                  thread.start();
              }


          }

           

          這兩種方法各有利弊。簡(jiǎn)單來(lái)說(shuō),如果你確定當(dāng)前的類就是作為一個(gè)單純的線程來(lái)實(shí)現(xiàn),不需要再繼承其他任何類的時(shí)候,那么最好就用第一種方式,因?yàn)楹?jiǎn)單,而且可以直接就獲得Thread所提供的各種方法,在類內(nèi)部使用;反之,如果你需要繼承其他類,或者將來(lái)可能會(huì)有這種需要,那么就用第二種方法。

          第二種方法需要注意的地方是,當(dāng)你實(shí)現(xiàn)了Runnable接口后,你僅僅是實(shí)現(xiàn)了該接口而已,你現(xiàn)在所有的只是一個(gè)run()方法,即使你生成一個(gè)對(duì)象來(lái)調(diào)用run()方法,和普通的方法調(diào)用也沒什么兩樣,并不會(huì)創(chuàng)建一個(gè)新的線程。只有當(dāng)你用Thread構(gòu)造函數(shù)創(chuàng)建一個(gè)對(duì)象之后,這才是新創(chuàng)建了一個(gè)線程。

          當(dāng)你使用第二種方法的時(shí)候,可能你也想在類內(nèi)部調(diào)用Thread所提供的一些方法,這時(shí)可以用Thread.currentThread()來(lái)獲得當(dāng)前線程的引用。


          2. 線程的運(yùn)行

          當(dāng)你獲得一個(gè)線程實(shí)例thread后,使他開始運(yùn)行的唯一方法就是thread.start(),由java虛擬機(jī)調(diào)用該線程的run方法。

          注意不是調(diào)用run()方法來(lái)啟動(dòng)線程。當(dāng)你調(diào)用run()方法時(shí),如果該線程是使用獨(dú)立的 Runnable 運(yùn)行對(duì)象構(gòu)造的,則調(diào)用該 Runnable 對(duì)象的 run 方法;否則,該方法不執(zhí)行任何操作并返回。

          另外要注意,多次啟動(dòng)一個(gè)線程是非法的。特別是當(dāng)線程已經(jīng)結(jié)束執(zhí)行后,不能再重新啟動(dòng)。


          3. 結(jié)束線程運(yùn)行

          就我目前所知,有兩種結(jié)束線程的方法(我是指通過(guò)直接操作線程對(duì)象的合法方法)。

          3.1. 在線程內(nèi)部設(shè)置一個(gè)標(biāo)記為volatile的標(biāo)志位

          public class StopThread extends Thread {
              
          public volatile boolean stop = false;
              
              
          private static int i = 0;
              
              
          public void run() {
                  
          while(!stop) {
                      i
          ++;
                  }

                  
                  System.out.println(i);
              }


              
          /**
               * 
          @param args
               
          */

              
          public static void main(String[] args) {
                  StopThread thread 
          = new StopThread();
                  thread.start();
                  
                  
          try {
                      sleep(
          2000);
                  }
           catch (InterruptedException e) {
                      
          // TODO Auto-generated catch block
                      e.printStackTrace();
                  }

                  
                  thread.stop 
          = true;
              }


          }


          關(guān)于volatile的說(shuō)明可能需要先了解JAVA的內(nèi)存模型,簡(jiǎn)單點(diǎn)說(shuō)就是JVM有個(gè)主存,各個(gè)線程再有各自的工作內(nèi)存,兩個(gè)存放的地方帶來(lái)的問(wèn)題就是不一致。volatile就是為了解決這個(gè)不一致出現(xiàn)的。

          使用volatile會(huì)每次修改后及時(shí)的將工作內(nèi)存的內(nèi)容同步回主存。這樣,線程所看到的就是最新的值,而不是被緩存的值。

          注,這里還牽涉到“原子操作”的概念。所謂原子操作,大體上就是指操作只由一個(gè)指令即可完成,不需要上下文切換。在java中,對(duì)除long和double之外的基本類型進(jìn)行簡(jiǎn)單的賦值或者返回值操作的時(shí)候,才是原子操作。然而,只要給long或double加上volatile,就和其他基本類型一樣了。但自增操作并不是原子操作,它牽涉到一次讀一次寫!

          正因?yàn)閷?duì)boolean的操作是原子操作,我們不用擔(dān)心多個(gè)線程同時(shí)對(duì)boolean值進(jìn)行修改而導(dǎo)致不一致的情況,所以在修改、讀取boolean值的時(shí)候不需要加synchronized關(guān)鍵字。

          總結(jié)一下volatile關(guān)鍵字的兩個(gè)作用:
          1) 保證聲明為volatile的64位變量的加載或存儲(chǔ)是個(gè)基本的單元操作
          2) 保證在多處理器計(jì)算機(jī)中,即使是處理高速緩存中的數(shù)據(jù),易失性變量的加載和存儲(chǔ)也能夠正確地進(jìn)行


          3.2. 調(diào)用interrrupt()方法

          有的時(shí)候,線程可能會(huì)阻塞,比如在等待輸入的時(shí)候,并且他也不能輪詢結(jié)束標(biāo)志。這個(gè)時(shí)候,可以用Thread.interrupt()方法來(lái)跳出阻塞代碼。

          public class Blocked extends Thread {
              
          public Blocked() {
                  System.out.println(
          "Starting");
              }

              
              
          public void run() {
                  
          try {
                      
          synchronized(this{
                          wait();
                      }

                  }
           catch(InterruptedException e) {
                      System.out.println(
          "Interrupted");
                  }

                  
                  System.out.println(
          "Exiting run()");
              }

              
              
              
          public static void main(String args[]) {
                  Blocked thread 
          = new Blocked();
                  thread.start();
                  
                  
          try {
                      sleep(
          2000);
                  }
           catch (InterruptedException e) {
                      
          // TODO Auto-generated catch block
                      e.printStackTrace();
                  }

                  
                  thread.interrupt();
                      thread = null;
              }

          }

          注意,我們?cè)谟胕nterrupt終止線程后,最好再將該線程賦為null,這樣垃圾回收器就可以回收該線程了。
          另,使用interrupt()并不需要獲得對(duì)象鎖 - 這與wait()、notify()等不同

          上面例子中有一個(gè)比較tricky的地方:當(dāng)我們使用interrupt()方法中斷線程的運(yùn)行時(shí),線程將拋出InterruptedException,但在拋出exception的同時(shí),他的中斷狀態(tài)將被清除,所以如果我們?cè)赾atch(InterrruptedException e) { }里調(diào)用isInterrupted(),返回的結(jié)果將會(huì)是false。

          Thread類提供了兩種方法來(lái)判斷一共線程是否處于中斷狀態(tài):

          interrupted(): 靜態(tài)方法,用于檢查當(dāng)前進(jìn)程是否已經(jīng)被中斷,同時(shí)清除線程的中斷狀態(tài)
          isInterrupted(): 實(shí)例方法,用于檢查任何一個(gè)線程是否已經(jīng)被中斷,不會(huì)清除中斷狀態(tài)

          在編程實(shí)踐中盡量不要catch InterruptedException,因?yàn)檫@會(huì)清除中斷標(biāo)記。如果要catch,那么在catch塊里重新設(shè)置中斷標(biāo)志:Thread.currentThread().interrupt(); 或者干脆不catch,聲明throws InterruptedException


          當(dāng)你使用Timer類調(diào)度線程的時(shí)候,可以使用Timer類提供的cancel()方法來(lái)終止線程的運(yùn)行。Timer類還是比較好用的,具體參見API doc。


          4. wait(), notify(), notifyAll()

          這三個(gè)方法是線程同步機(jī)制的基礎(chǔ),但這三種方法已經(jīng)被Joshua Bloch視為“low-level”的,“匯編級(jí)別”的代碼,應(yīng)該盡量被JDK 1.5以來(lái)提供的高層次框架類取代。這正是java讓人又愛又恨的地方 - 它總是提供各種方便易用的API供使用者調(diào)用,幫助編程人員提高效率,避免錯(cuò)誤,但與此同時(shí),它也在無(wú)形之間將底層機(jī)制與使用隔離,使相當(dāng)一批編程者“淪為”API的“純”調(diào)用者,只懂得用一堆API來(lái)堆起一個(gè)程序。很不幸,我就是其中之一。但我總算還保留著一點(diǎn)求知的欲望。

          使用wait(),總是要最先想到,一定要用while循環(huán)來(lái)判斷執(zhí)行條件是否滿足:

          synchronized(obj) {
              
          while(conditionIsNotMet) {
                  wait();
              }


               
          // Perform action approriate to condition
          }

          這樣就可以保證在跳出等待循環(huán)之前條件將被滿足,如果你被不相干的條件所通知(比如notifyAll()),或者在你完全退出循環(huán)之前條件已經(jīng)改變,你被確??梢曰貋?lái)繼續(xù)等待。

          有兩個(gè)wait方法帶有超時(shí)參數(shù):
          void wait(long millis);
          void wait(long millis, int nanos);
          但wait方法返回時(shí),無(wú)法確定返回的原因是因?yàn)槌瑫r(shí)還是得到了通知。有兩種方式可以解決這個(gè)問(wèn)題:

          1)
          long before = System.currentTimeMillis();
          wait(delay);
          long after = System.currentTimeMillis();
          if(after - before > delay)
               
          // timeout

          2)可以讓負(fù)責(zé)通知的線程設(shè)置一個(gè)標(biāo)志

          對(duì)于notify(), notifyAll(),Joshua Bloch的建議是盡量使用notifyAll(),以避免出現(xiàn)某些進(jìn)程永遠(yuǎn)沉睡的現(xiàn)象。

          5. synchronized

          synchronized是將對(duì)象中所有加鎖的方法(or代碼塊)鎖定。由于同一時(shí)間只能有一個(gè)線程擁有對(duì)象的鎖,這也就保證了互斥。

          有兩種加鎖形式:

          5.1. 給代碼塊加鎖:

          synchronized(obj) {
               
          // some codes here
          }

          5.2. 給方法加鎖:

          public synchronized void method() {
               
          // some codes here
          }

          注意,java中不允許在重載(重寫?)的時(shí)候改變簽名,但sychronized關(guān)鍵字并不屬于簽名,因此,你可以繼承一個(gè)類,然后重載(重寫?)這個(gè)幾類的方法,加上sychronized關(guān)鍵字來(lái)保證互斥

          如果某個(gè)線程擁有一個(gè)對(duì)象的鎖,并且它調(diào)用了同一個(gè)對(duì)象上的另一個(gè)synchronized方法,那么該線程將自動(dòng)被賦予對(duì)該方法的訪問(wèn)權(quán)。只有當(dāng)該線程退出上一個(gè)synchronized方法時(shí),它才會(huì)釋放該鎖。

          這是因?yàn)槊總€(gè)對(duì)象都有一個(gè)鎖計(jì)數(shù)器,用于計(jì)算鎖的所有者調(diào)用了多少次synchronized方法。當(dāng)鎖計(jì)數(shù)器的值達(dá)到0時(shí),該線程便放棄對(duì)該鎖的所有權(quán)。

          對(duì)于鎖,需要明確下面的關(guān)系:

          你可以擁有同一個(gè)類的不同對(duì)象,每個(gè)對(duì)象被不同的線程鎖定。這些線程甚至可以執(zhí)行同一個(gè)synchronized方法。因?yàn)槲覀?strong>鎖定的是對(duì)象,而不是方法。當(dāng)然,在某個(gè)時(shí)間點(diǎn)上,一個(gè)對(duì)象的鎖只能被一個(gè)線程擁有。不過(guò),一個(gè)線程可以同時(shí)擁有多個(gè)對(duì)象的鎖,只需要在執(zhí)行一個(gè)對(duì)象上的synchronized方法的同時(shí),又執(zhí)行另一個(gè)對(duì)象的synchronized方法即可。

          synchronized一個(gè)較為特殊的應(yīng)用是給靜態(tài)方法加鎖:

          public class Singleton
          {
              
          private static Singleton instance;

              
          private Singleton( }

              
          public static synchronized Singleton getInstance() {
                  
          if(instance == null)
                      instance 
          = ?31span style="color: #0000ff">few
           Singleton();
                  
          return instance;
              }

          }

          當(dāng)一個(gè)線程調(diào)用Synchronized方法時(shí),它便會(huì)獲取對(duì)象的鎖。但是該方法是靜態(tài)方法,當(dāng)調(diào)用Singleton.getInstance()時(shí),哪個(gè)對(duì)象負(fù)責(zé)執(zhí)行線程鎖的操作呢?

          調(diào)用一個(gè)靜態(tài)方法將會(huì)鎖定類對(duì)象(比如Singleton.class)。因此,如果一個(gè)線程調(diào)用一個(gè)類的靜態(tài)synchronized方法,那么該類的所有靜態(tài)synchronized方法均會(huì)被鎖定,直到第一個(gè)調(diào)用返回為止。



          以上介紹了java線程中語(yǔ)法上的一些基礎(chǔ)的東西,下面要介紹的同樣也是基礎(chǔ),但同上面而言還是有些差異,還是分開一段來(lái)介紹的好。

          1. 一些方法

          sleep():
          sleep()方法能迫使線程休眠指定長(zhǎng)的時(shí)間。在調(diào)用sleep()方法的時(shí)候,必須把它放在try塊中,因?yàn)樵谛菝邥r(shí)間到期之前有可能被打斷。如果某人持有對(duì)此線程的引用,并且在此線程上調(diào)用了interrupt()方法,就會(huì)發(fā)生這種情況。

          daemon線程:
          必須在線程啟動(dòng)之前調(diào)用setDaemon()方法,才能把它設(shè)置為后臺(tái)線程。一個(gè)后臺(tái)線程所創(chuàng)建的任何線程都將被自動(dòng)設(shè)置成后臺(tái)線程

          join():
          一個(gè)線程可以在其他線程之上調(diào)用join()方法,其效果是等待一段時(shí)間直到第二個(gè)線程結(jié)束才繼續(xù)執(zhí)行。如果某個(gè)線程在另一個(gè)線程t上調(diào)用t.join(),此線程將被掛起,直到目標(biāo)線程t結(jié)束才恢復(fù)(即t.isAlive()返回為false)
          你也可以在調(diào)用join()時(shí)帶上一個(gè)超時(shí)參數(shù)(單位可以是毫秒或者毫秒+納秒),這樣如果目標(biāo)線程在這段時(shí)間到期還沒結(jié)束的話,join()方法總能返回。
          對(duì)join()方法的調(diào)用可以被中斷,做法是在調(diào)用線程上使用interrupt()方法,這時(shí)需要用到try-catch

          isAlive():
          如果該線程是可運(yùn)行線程或被中斷的線程,那么該方法返回true;如果該線程仍然是個(gè)新線程或尚未成為可運(yùn)行線程,或者該線程是個(gè)死線程,那么該方法返回false
          注:無(wú)法確定一個(gè)“活”線程究竟是處于可運(yùn)行狀態(tài)還是被中斷狀態(tài),也無(wú)法確定一個(gè)運(yùn)行線程釋放正處在運(yùn)行之中。另外,你也無(wú)法對(duì)尚未成為可運(yùn)行的線程與已經(jīng)死掉的線程進(jìn)行區(qū)分。


          2. 線程的四種狀態(tài):創(chuàng)建、就緒、死亡、阻塞。

          線程進(jìn)入阻塞狀態(tài)可能有如下四種原因:

          2.1. 通過(guò)調(diào)用sleep()使線程進(jìn)入休眠狀態(tài)。在這種情況下,線程在指定時(shí)間內(nèi)不會(huì)運(yùn)行

          2.2. 通過(guò)調(diào)用wait()使線程掛起,直到線程得到了notify()或notifyAll()消息,線程才會(huì)進(jìn)入就緒狀態(tài)

          2.3. 線程在等待輸入/輸出操作的完成

          2.4. 線程試圖在某個(gè)對(duì)象上調(diào)用其同步控制方法,但是對(duì)象鎖不可用



          3. 只有當(dāng)下列四個(gè)條件同時(shí)滿足時(shí),才會(huì)發(fā)生死鎖:

          3.1. 互斥條件:線程使用的資源中至少又一個(gè)是不能共享的

          3.2. 至少有一個(gè)進(jìn)程持有一個(gè)資源,并且他在等待獲取一個(gè)當(dāng)前被別的進(jìn)程持有的資源。

          3.3. 資源不能被進(jìn)程搶占。所有的進(jìn)程必須把資源釋放作為普通事件。

          3.4. 必須有循環(huán)等待,即,一個(gè)線程等待其他線程持有的資源,后者又在等待另一個(gè)進(jìn)程持有的資源,這樣一直下去,直到又一個(gè)進(jìn)程在等待第一個(gè)進(jìn)程持有的資源,使得大家都被鎖住。

          要發(fā)生死鎖,必須這四個(gè)條件同時(shí)滿足,所以,只要破壞其中任意一個(gè),就可以打破死鎖。其中第四個(gè)條件是最容易被打破的。


          4. 線程的優(yōu)先級(jí)

          JVM將線程的優(yōu)先級(jí)映射為主機(jī)平臺(tái)的優(yōu)先級(jí)等級(jí)。

          每當(dāng)主機(jī)平臺(tái)使用的優(yōu)先級(jí)低于Java平臺(tái)時(shí),某個(gè)線程的運(yùn)行就可能被另一個(gè)優(yōu)先級(jí)明顯低得多的線程線程搶先。這意味著你不能依靠多線程程序中的優(yōu)先級(jí)等級(jí)。

          另外,調(diào)用yield方法,只會(huì)讓當(dāng)前線程暫時(shí)放棄運(yùn)行,而主機(jī)則始終準(zhǔn)備對(duì)放棄運(yùn)行的線程實(shí)施重新啟動(dòng)。如果當(dāng)前線程優(yōu)先級(jí)較高,則可能主機(jī)一直重啟該線程,而其他低優(yōu)先級(jí)線程將得不到運(yùn)行。為此,yield也靠不住,sleep可能是更好的方式。

          5. java中對(duì)以“管道”形式對(duì)線程的輸入/輸出提供了支持

          PipedWriter類允許線程向管道寫;PipedReader類允許不同線程從一個(gè)管道中讀取?;蚴遣捎肞ipedInputStream和PipedOutputStream提供字節(jié)流支持

          使用管道的主要原因是為了使每個(gè)線程始終能保持簡(jiǎn)單??梢詫⒍鄠€(gè)線程相互連接起來(lái),而不必?fù)?dān)心線程的同步問(wèn)題。

          但要注意,管道式數(shù)據(jù)流只適用于線程在低層次上的通信,在其他情況下,可以使用隊(duì)列。



          下面是對(duì)《effective java 2nd》中Concurrency一章的總結(jié)。感覺這一章并不如我所想象的那樣,對(duì)java的線程機(jī)制有一個(gè)全面透徹的解說(shuō),反而是花了很大力氣宣傳一本書 - 《Java Concurrency in Practice》。好吧,想在一章的內(nèi)容里對(duì)java線程的認(rèn)識(shí)達(dá)到某種高度,怎么想也是不太現(xiàn)實(shí)的。但這本書究竟如何,我還沒看過(guò),不作評(píng)論,但我想肯定是很適合正在用java線程做項(xiàng)目的人的。對(duì)我而言,重要的不是學(xué)會(huì)怎么用java代碼來(lái)寫出多線程程序,而是搞清線程內(nèi)部的機(jī)制。

          下面對(duì)Concurrency一章的重點(diǎn)知識(shí)(我認(rèn)為重要的)進(jìn)行一下總結(jié):

           

          Item 66: Synchronize access to shared mutable data

          • when multiple threads share mutable data, each thread that reads or writes the data must perform synchronization. (讀寫都要加鎖,不能只加一個(gè))
          • If you need only inter-thread communication, and not mutual exclusion, the volatile modifier is an acceptable form of synchronization, but it can be tricky to use correctly.


          Item 67: Avoid excessive synchronization

          • Inside a synchronized region, do not invoke a method that is designed to be overridden, or one provided by a client in the form of a function object
          • As a rule, you should do as little work as possible inside synchronized regions.
          • In summary, to avoid deadlock and data corruption, never call an alien method from within a synchronized region. More generally, try to limit the amount of work that you do from within synchronized regions.


          Item 68: Prefer executors and tasks to threads

          • Executor Framework - refer to Java Concurrency in Practice


          Item 69: Prefer concurrency utilities to wait and notify

          • Given the difficulty of using wait and notify correctly, you should use the higher-level concurrency utilities instead.
          • The higher-level utilities in java.util.concurrent fall into three categories: the Executor Framework; concurrent collections; and synchronizers.
          • Synchronizers are objects that enable threads to wait for one another, allowing them to coordinate their activities. The most commonly used synchronizers are CountDownLatch and Semaphore. Less commonly used are CyclicBarrier and Exchanger.
          • For interval timing, always use System.nanoTime in preference to System.currentTimeMillis.
          • Always use the wait loop idiom to invoke the wait method; never invoke it outside of a loop.
          • The notifyAll method should generally be used in preference to notify. If notify is used, great care must be taken to ensure liveness.


          Item 70: Document thread safety

          • Conditionallythread-safe classes must document which method invocation sequences require external synchronization, and which lock to acquire when executing these sequences.
          • If you write an unconditionally thread-safe class, consider using a private lock object in place of synchronized methods. This protects you against synchronization interference by clients and subclasses and gives you the flexibility to adopt a more sophisticated approach to concurrency control in a later release.


          Item 71: Use lazy initialization judiciously

          • In summary, you should initialize most fields normally, not lazily. If you must initialize a field lazily in order to achieve your performance goals, or to break a harmful initialization circularity, then use the appropriate lazy initialization technique.
          • For instance fields, it is the double-check idiom;
            For static fields, the lazy initialization holder class idiom;
            For instance fields that can tolerate repeated initialization, you may also consider the single-check idiom.


          Item 72: Don’t depend on the thread scheduler

          • When many threads are runnable, the thread scheduler determines which ones get to run, and for how long.
          • In summary, do not depend on the thread scheduler for the correctness of your program. The resulting program will be neither robust nor portable. As a corollary, do not rely on Thread.yield or thread priorities.
          • Thread priorities may be used sparingly to improve the quality of service of an already working program, but they should never be used to “fix” a program that barely works.


          Item 73: Avoid thread groups

          • Thread groups are best viewed as an unsuccessful experiment, and you should simply ignore their existence.

          我個(gè)人最喜歡第73條。。。
           

          posted on 2008-07-16 11:32 This is Wing 閱讀(3364) 評(píng)論(1)  編輯  收藏 所屬分類: Java基礎(chǔ)
          評(píng)論:
           
          Copyright © This is Wing Powered by: 博客園 模板提供:滬江博客
          主站蜘蛛池模板: 峨山| 修水县| 麻阳| 巫山县| 敦化市| 句容市| 曲水县| 长治县| 额尔古纳市| 城市| 绥棱县| 南丰县| 合江县| 平远县| 资溪县| 松原市| 廊坊市| 精河县| 五家渠市| 南岸区| 德保县| 祁东县| 清流县| 柯坪县| 罗城| 常州市| 宜君县| 宜昌市| 军事| 古田县| 达拉特旗| 白银市| 高邮市| 临清市| 巧家县| 曲水县| 全州县| 名山县| 余庆县| 谷城县| 永定县|