java基礎-多線程


一、多線程引入

1、我們在之前寫的代碼程序只有一個執行流程,這樣的程序就是單線程程序。
2、假如一個程序有多條執行流程,那麼,該程序就是多線程程序。
二、進程和線程的概念:
1、進程:應用程序在內存中運行的空間
2、線程:是進程中的一個執行單元,負責執行進程中的代碼。
3、一個進程如果只有一條執行路徑,則稱爲單線程程序。
4、一個進程如果有多條執行路徑,則稱爲多線程程序。
三、多線程的存在解決什麼問題?
1、多部分代碼同時執行的問題。
2、傳統的單個主線程從頭執行到尾的運行安排,當遇到較多的操作次數時,效率偏低。
四、多線程的弊端:
1、開啓過多會降低效率,多線程的原理其實是多個執行單元執行一個程序,CPU高速在多個線程之間進行切換,當線程個數過多時,CPU切換一個循環的時間過長,相應的降低了程序運行的效率。
五、多線程的特性:
1、隨機性,因爲CPU的快速切換造成的。
六、java程序運行原理
1、java 命令會啓動 java 虛擬機,啓動 JVM,等於啓動了一個應用程序,也就是啓動了一個進程。該進程會自動啓動一個 “主線程” ,然後主線程去調用某個類的 main 方法。
所以 main方法運行在主線程中。在此之前的所有程序都是單線程的。
2、思考:jvm虛擬機的啓動是單線程的還是多線程的?
jvm的啓動是多線程的,因爲它最低有兩個線程啓動了,主線程和垃圾回收線程。
七、舉例:
1、金山衛士進行電腦體檢的同時,還可以清理垃圾,木馬查殺等,這就是多線程。
第二節:創建線程的兩種方式
一、方式一:
1、創建方式:繼承Thread類。
(3).1 定義一個類繼承Thread。原因:Thread類描述了線程的任務存在的位置:run方法。
(3).2 重寫run方法。原因:爲了定義線程需要運行的任務代碼。
(3).3 創建子類對象,就是創建線程對象。目的:爲了執行線程任務,而是任務都定義在run方法中。run方法結束了,線程任務就是結束了,線程也就是結束了
(線程任務:每一個線程都有自己執行的代碼,主線程的代碼都定義在主函數中,自定義線程的代碼都定義在run方法中。)
(3).4 調用start方法,原因:開啓線程並讓線程執行,同時還會告訴jvm去調用run方法。
2、調用run和調用start的區別?
調用run方法僅僅是主線程執行run方法中的代碼;調用start方法是開啓線程,讓新建的線程與主線程同時執行。
3、多線程的運行原理
(3).1 如果多部分代碼同時執行,那麼在棧內存方法執行是怎麼完成的呢?
 其實是,每一個線程在棧內存中都有自己的棧內存空間,在自己的棧空間中
 進行壓棧和彈棧。
(3).2 主線程結束,程序就結束嗎?
 主線程結束,如果其他線程還在執行,進程是不會結束了,當所有的線程都結束了,進程纔會結束。
3、代碼體現
  1. //創建線程的第一種方式 繼承Thread  
  2. //開啓兩個窗口進行賣票 第一種方式  
  3. /* 
  4. 思路: 
  5. 1 創建一個類 繼承thread類並覆蓋run方法 
  6. 2 創建子類對象 
  7. 3 調用start方法 
  8.  
  9. */  
  10. class Ticket extends Thread  
  11. {//自定義變量並給一定的值  
  12.     private int ticket=100;  
  13.     //覆蓋run方法  
  14.     public void run()  
  15.     {  
  16.         //進行循環  
  17.         while (true)  
  18.         {  
  19.             //判斷票的數量  
  20.             if (ticket>0)  
  21.             {  
  22.                 //打印輸出  
  23.                 System.out.println(Thread.currentThread().getName()+".............................."+ticket--);  
  24.             }  
  25.             else  
  26.             {  
  27.                 break;  
  28.             }  
  29.         }  
  30.     }  
  31. }  
  32. class Demo  
  33. {  
  34.     public static void main(String[] args)  
  35.     {  
  36.         //創建Thread子類的對象  
  37.         Ticket t=new Ticket();  
  38.         Ticket t1=new Ticket();  
  39.         //並調用start的方法  
  40.         t.start();  
  41.         t1.start();  
  42.       
  43.     }  
  44. }  


二、方式二:
1、方式二步驟:
(1).1,定義類實現Runnable接口。原因:避免了繼承Thread類的單繼承侷限性。
(1).2,覆蓋接口中的run方法。原因:將線程任務代碼定義到run方法中。
(1).3,創建Thread類的對象。原因:只有創建Thread類的對象纔可以創建線程。
(1).4,將Runnable接口的子類對象作爲參數傳遞給Thread類的構造函數。
原因:線程已被封裝到Runnable接口的run方法中,而這個run方法所屬於Runnable接口的子類對象,
所以將這個子類對象作爲參數傳遞給Thread的構造函數,這樣,線程對象創建時就可以明確要運行的線程的任務。
(1).5,調用Thread類的start方法開啓線程。
2、方式二和方式一的區別:
(2).1 第二種方式實現Runnable接口避免了單繼承的侷限性,所以較爲常用。
(2).2 實現Runnable接口的方式,更加的符合面向對象,線程分爲兩部分,一部分線程對象,一部分線程任務。
 繼承Thread類:線程對象和線程任務耦合在一起。一旦創建Thread類的子類對象,既是線程對象,有又有線程任務。
 實現runnable接口:將線程任務單獨分離出來封裝成對象,類型就是Runnable接口類型。
 Runnable接口對線程對象和線程任務進行解耦。
3、代碼體現
  1. //創建兩個線程去賣票,用種第二方式  
  2. /* 
  3. 思路: 
  4. 1 創建 一個類 去實現 Runable接口 並覆蓋run方法 
  5. 2 創建一個類的對象 並把這個對象作爲參數傳遞給Thread類的構造函數中 
  6. 3 調用start方法 
  7. */  
  8. //實現Runnable接口  
  9. class Ticket implements Runnable  
  10. {  
  11.     //定義一定量的票數  
  12.     private int ticket=200;  
  13.     //覆蓋run方法  
  14.     public void run()  
  15.     {  
  16.         //進行循環  
  17.         while (true)  
  18.         {  
  19.             //判斷票的數量  
  20.             if (ticket>0)  
  21.             {  
  22.                 //進行打印  
  23.                 System.out.println(Thread.currentThread().getName()+".............."+ticket--);  
  24.             }  
  25.             else  
  26.             {  
  27.               
  28.             break;  
  29.             }  
  30.         }  
  31.       
  32.     }  
  33. }  
  34.   
  35. class Demo1  
  36. {  
  37.     public static void main(String[] args)  
  38.     {  
  39.         //把Ticket()的類的對象傳入到Thread類對象的構造方法當中去,並調用start方法。  
  40.     new Thread(new Ticket()).start();  
  41.     new Thread(new Ticket()).start();  
  42.       
  43.     }  
  44. }  


第三節:線程的運行的狀態
一、線程運行是有多種狀態的:
1、創建  new Thread類或者其子類對象。
2、運行 start()具備者CPU的執行資格和CPU的執行權。
3、凍結 釋放了CPU的執行資格和CPU的執行權。比如執行到sleep(time)  wait() 導致線程凍結。
4、臨時阻塞狀態 具備者CPU的執行資格,不具備CPU的執行權。
5、消亡 線程結束。run方法結束。
第四節:線程的安全問題
一、多線程的安全問題:
1、安全問題產生的原因:
(1).1線程任務中有共享的數據。
(1).2線程任務中有多條操作共享數據的代碼。
當線程在操作共享數據時,其他線程參與了運算導致了數據的錯誤(安全問題)
2、安全問題的解決思路:
保證在線程執行共享數據代碼時,其他線程無法參與運算。
3、安全問題的解決代碼體現:
同步代碼塊。synchronized(obj){需要被同步的代碼}  
4、同步的好處:
同步的出現解決了多線程的安全問題。
5、同步的弊端:
當線程相當多時,因爲每個線程都會去判斷同步上的鎖,這是很耗費資源的,無形中會降低程序的運行效率。
6、同步的前提:
必須保證多個線程在同步中使用的是同一個鎖。
解決了什麼問題?
當多線程安全問題發生時,加入了同步後,
問題依舊,就要通過這個同步的前提來判斷同步是否寫正確。
7、代碼體現
  1. //出現了線程的安全問題  
  2. //解決安全方式之一同步函數 賣票問題  
  3. //定義一個類實現Runnable接口  
  4. class Ticket implements Runnable  
  5. {  
  6.     //定義一個私有的變量票的數量  
  7.     private int ticket=10;  
  8.     //覆蓋run方法  
  9.     public void run()  
  10.     {  
  11.         //進行循環  
  12.         while (true)  
  13.         {       //加上一個鎖  
  14.             synchronized(this)  
  15.             {  
  16.                 //判斷票的數量大於零  
  17.                 if (ticket>0)  
  18.                 {  
  19.                     //讓線程進行睡眠100毫秒,並進行異常的處理  
  20.                     try{Thread.sleep(100);}catch(InterruptedException ie){}  
  21.                     //進行打印  
  22.                     System.out.println(Thread.currentThread().getName()+"................"+ticket--);  
  23.                 }  
  24.                 else  
  25.                 {  
  26.                 break;  
  27.                   
  28.                 }         
  29.             }  
  30.         }  
  31.       
  32.     }  
  33. }  
  34. class Demo2  
  35. {  
  36.     public static void main(String[] args)  
  37.     {  
  38.         //創建Ticket類的對象  
  39.         Ticket t=new Ticket();  
  40.         //並把對象傳入到Thread類對象的構造函數當中去  
  41.         Thread tt=new Thread(t);  
  42.         Thread ttt=new Thread(t);  
  43.         //調用start的方法。  
  44.         tt.start();  
  45.         ttt.start();  
  46.       
  47.     }  
  48. }  


二、同步函數
1、同步函數的表現形式。就是讓一個封裝體具備了同步性。
其實就是在函數上加上同步關鍵字。
    2、同步函數的鎖用的哪個呢?
(2).1 非靜態的同步函數使用的鎖是: this
(2).2 靜態的同步函數使用的鎖時:類名.class 
3、同步函數和同步代碼塊的區別。
(3).1同步函數使用的鎖是固定的。
(3).2同步代碼塊使用的鎖是任意對象。
(3).3如果線程任務中僅使用一個同步,可以簡化成同步函數的形式。
(3).4如果使用多個同步(多個鎖),必須使用同步代碼塊。
三、單例模式懶漢式中併發訪問
1、懶漢式並併發訪問,容易出現線程安全問題,而導致對象的不唯一。
2、解決的方式,加上同步關鍵字。如果使用同步代碼塊,使用的鎖是 類名.class
3、但是效率會降低,所以可以使用同步代碼塊,加上雙重對引用變量的判斷。
public static Single getInstance()
{
if(s==null)
{
synchronized(Single.class)
{
if(s==null)
s = new Single();
}
}
return s;
}
四、死鎖:
1、死鎖:線程都爲結束,都處於不執行狀態,這時就稱爲死鎖。
2、常見場景:
(2).1 同步嵌套。
(2).2 都處於凍結狀態。
3、儘量避免死鎖。
4、代碼體現
  1. //寫一個死鎖  
  2. /* 
  3. 思路: 
  4. 1 所謂的死鎖 就是嵌套循環 相互抱着不是自己的鎖不放就導致了死鎖 
  5. 2 定義一個類 定義兩個鎖 
  6. 3 定義一個類 實現Runnable接口 覆蓋run方法 在寫入一個嵌套循環 再定義一個標記 
  7. 4 開啓線程 
  8.  
  9. */  
  10. class Demo4  
  11. {  
  12.     public static void main(String[] args)  
  13.     {  
  14.     //創建Ticket類的兩個對象  
  15.     Ticket t=new Ticket();  
  16.     Ticket t1=new Ticket();  
  17.     //分別傳入到兩個Thread類對象的構造函數當中去  
  18.     Thread tt=new Thread(t);  
  19.     Thread ttt=new Thread(t1);  
  20.     //調用start方法  
  21.     tt.start();  
  22.     //把標記進行轉換  
  23.     t.bug=true;  
  24.     //再進行調用start方法  
  25.     ttt.start();  
  26.       
  27.       
  28.     }  
  29. }  
  30. //定義了鎖的類  
  31. class MyLock  
  32. {  
  33.     //並定義了兩個鎖  
  34.     public static final Object locka=new Object();  
  35.     public static final Object lockb=new Object();  
  36. }  
  37. //自定義一個Ticket類並實現Runnabale接口  
  38. class Ticket implements Runnable   
  39. {  
  40.     //定義一個變量,來記住票的數量  
  41.     private int ticket;  
  42.     //定義一個變量標記  
  43.     boolean bug=false;  
  44.     //覆蓋run方法  
  45.     public void run()  
  46.     {  
  47.         //進行判斷標記  
  48.         if (bug)  
  49.         {  
  50.             //進行循環  
  51.             while (true)  
  52.             {  
  53.                 //定義一同步代碼塊並傳入MyLock.locka鎖  
  54.                 synchronized(MyLock.locka)  
  55.                 {  
  56.                     //進行對線程sleep100毫秒,並對異常進行處理  
  57.                                 try{Thread.sleep(100);}catch(InterruptedException ie){}  
  58.                                 //打印票的數量  
  59.                     System.out.println("......................run1111111,,,,,,,,,locka................"+ticket++);  
  60.                     //在定義一個同步代碼塊這時是MyLock.lockb鎖  
  61.                         synchronized(MyLock.lockb)  
  62.                         {  
  63.                         //進行打印  
  64.                         System.out.println(".......1111111lockb................");  
  65.                           
  66.                         }  
  67.                 }  
  68.             }  
  69.         }//當標記變化時,就執行下列代碼  
  70.             else  
  71.             {  
  72.                 while (true)  
  73.                 {  
  74.                     //定義一同步代碼塊並傳入MyLock.lockb鎖  
  75.                     synchronized(MyLock.lockb)  
  76.                     {  
  77.                         //進行對線程sleep100毫秒,並對異常進行處理  
  78.                     try{Thread.sleep(100);}catch(InterruptedException ie){}  
  79.                     //打印票的數量  
  80.                     System.out.println("...............,,lockb22222222222222................"+ticket++);  
  81.                     //在定義一個同步代碼塊這時是MyLock.locka鎖  
  82.                         synchronized(MyLock.locka)  
  83.                         {  
  84.                         //進行打印  
  85.                         System.out.println(".....................22222222222run...locka................");  
  86.                           
  87.                         }  
  88.                       
  89.                     }  
  90.               
  91.                 }  
  92.               
  93.             }     
  94.     }  
  95. }  


第五節:線程間的通信
一、簡述:
1、多線程間通信:多個線程執行任務不同,但是處理資源相同。
2、等待喚醒機制涉及的方法: 
(1)、wait(): 讓線程處於凍結狀態,被wait的線程會被存儲到線程池中。 
(2)、notify():喚醒線程池中一個線程(任意)。
(3)、notifyAll():喚醒線程池中的所有線程。 
3、這些方法都必須定義在同步中? 
因爲這些方法是用於操作線程狀態的方法,必須要明確到底操作的是哪個鎖上的線程。 
4、爲什麼操作線程的方法wait notify notifyAll定義在了Object類中?
因爲這些方法是監視器的方法。監視器其實就是鎖,鎖可以是任意的對象,任意的對象調用的方式一定定義在Object類中。 
5、一對一 代碼體現
  1. //線程間的通信 例如生產者與消費者 一對一的情況  
  2. /* 
  3. 思路: 
  4. 1 資源是唯一的 所以創造一個類用來描述這個類 
  5. 2 用一個類來描述生產者生產者生產一個 
  6. 3 用一個類來描述消費者消費者就消費一個 
  7.  
  8.  
  9. */  
  10. class Demo5  
  11. {  
  12.     public static void main(String[] args)  
  13.     {  
  14.         Socure s=new Socure();  
  15.         Procity p=new Procity(s);  
  16.         Take t=new Take(s);  
  17.         new Thread(p).start();  
  18.         new Thread(t).start();  
  19.       
  20.     }  
  21. }  
  22. //自定義一個資源類  
  23. class Socure   
  24. {  
  25.     //私有變量標記和生產和消費的數量  
  26.     private boolean balg;  
  27.     private int count;  
  28.     //定義一個方法用來生產的  
  29.     public void add()  
  30.     {  
  31.         //定義一個同步代碼塊  
  32.         synchronized(this)  
  33.         {  
  34.             //循環標記   
  35.             while(balg)  
  36.                 //進行等待,並對異常進行處理  
  37.             try{this.wait();}catch(InterruptedException io){}  
  38.             //數量進行加一  
  39.             count++;  
  40.             //打印所生產的數量  
  41.             System.out.println(Thread.currentThread().getName()+"........生產者............."+count);  
  42.             //標記進行改變  
  43.             balg=true;  
  44.             //進行釋放  
  45.             this.notify();  
  46.         }  
  47.     }  
  48.     //定義一個方法進行消費  
  49.     public void get()  
  50.     {  
  51.         //定義一個同步代碼塊  
  52.         synchronized(this)  
  53.         {  
  54.             //循環標記  
  55.             while(!balg)  
  56.                 //進行等待,並對異常進行處理  
  57.             try{this.wait();}catch(InterruptedException ie){}  
  58.             //打印出消費者消費的數量  
  59.             System.out.println(Thread.currentThread().getName()+"...............消費者.........."+count);  
  60.             //標記進行轉變  
  61.             balg=false;  
  62.             //進行釋放  
  63.             this.notify();  
  64.         }  
  65.     }  
  66. }  
  67. //定義一個生產者類去實現Runnable接口  
  68. class Procity implements Runnable   
  69. {  
  70.     //定義資源的變量  
  71.     private Socure s;  
  72.     Procity(Socure s)  
  73.     {  
  74.     this.s=s;  
  75.     }  
  76.     //覆蓋run方法  
  77.     public void run()  
  78.     {  
  79.       while (true)  
  80.       {  
  81.           //進行生產  
  82.           s.add();  
  83.       }  
  84.     }  
  85. }  
  86. //定義一個消費者類實現Runnavale接口  
  87. class Take implements Runnable  
  88. {  
  89.     //定義資源的變量  
  90.     private Socure s;  
  91.     Take(Socure s)  
  92.     {  
  93.     this.s=s;  
  94.     }  
  95.     //覆蓋run方法  
  96.     public void run()  
  97.     {  
  98.         while (true)  
  99.         {  
  100.             //進行消費  
  101.             s.get();  
  102.         }  
  103.     }  
  104. }  


6、多對多 代碼體現
  1. //線程之間的通信 多對多的情況  
  2. class Demo6  
  3. {  
  4.     public static void main(String[] args)  
  5.     {  
  6.         //創建子類類的對象  
  7.         Recour r=new Recour();  
  8.         //定義生產者,並把資源進行傳入構造函數當中去  
  9.         Procity p=new Procity(r);  
  10.         //定義生消費者,並把資源進行傳入構造函數當中去  
  11.         Take t=new Take(r);  
  12.         //把生產者傳入到Thread類對象中的構造方法並調用start方法  
  13.         new Thread(p).start();  
  14.         new Thread(p).start();  
  15.         //把消費者傳入到Thread類對象中的構造方法並調用start方法  
  16.         new Thread(t).start();  
  17.         new Thread(t).start();  
  18.       
  19.     }  
  20. }  
  21. //自定義一個資源類  
  22. class Recour  
  23. {  
  24.     //定義一個變量用來記住生產和消費的個數  
  25.     private int count;  
  26.     //定義一個標記  
  27.     private boolean flag;  
  28.     //定義一個方法 進行生產的  
  29.     public void add()  
  30.     {  
  31.         //同步代碼塊  
  32.         synchronized(this)  
  33.         {  
  34.             //定義一個循環  
  35.             while(flag)  
  36.                 //如果符合進行等待,並對異常進行處理  
  37.             try{this.wait();}catch(InterruptedException io){}  
  38.             //生產的個數  
  39.             count++;  
  40.             //打印出生產的個數  
  41.             System.out.println(Thread.currentThread().getName()+"......生產者......."+count);  
  42.             //轉換標記  
  43.             flag=true;  
  44.             //進行釋放  
  45.             this.notifyAll();  
  46.   
  47.         }  
  48.     }  
  49.     public void get()  
  50.     {  
  51.         //同步代碼塊  
  52.         synchronized(this)  
  53.         {  
  54.             //定義一個循環  
  55.             while (!flag)  
  56.                 //如果符合進行等待,並對異常進行處理  
  57.             try{this.wait();}catch(InterruptedException ioe){}  
  58.             //打印出消費的個數  
  59.             System.out.println(Thread.currentThread().getName()+"..消費者...."+count);  
  60.             //轉換標記  
  61.             flag=false;  
  62.             //進行釋放  
  63.             this.notifyAll();  
  64.         }  
  65.       
  66.     }  
  67. }  
  68. //自定義一個生產類去實現Runnable接口  
  69. class Procity implements Runnable  
  70. {  
  71.     //定義一個資源類的變量  
  72.     private Recour s;  
  73.     Procity(Recour s)  
  74.     {  
  75.     this.s=s;     
  76.     }  
  77.     //覆蓋run方法  
  78.     public void run()  
  79.     {  
  80.         while (true)  
  81.         {  
  82.             //進行生產  
  83.             s.add();  
  84.         }  
  85.       
  86.     }  
  87.   
  88. }  
  89. //定義一個消費類並且去實現Runnable接口  
  90. class Take implements Runnable  
  91. {  
  92.     //定義一個資源類的變量  
  93.     private Recour s;  
  94.     Take(Recour s)  
  95.     {  
  96.     this.s=s;  
  97.     }  
  98.     //覆蓋run方法  
  99.     public void run()  
  100.     {  
  101.         while (true)  
  102.         {  
  103.             //進行消費  
  104.             s.get();  
  105.         }  
  106.     }  
  107. }  


二、jdk1.5中提供了多線程升級解決方案:
1、jdk1.5以後將同步和鎖封裝成了對象。並將操作鎖的隱式方式定義到了該對象中,將隱式動作變成了顯示動作。  
2、Lock接口: 出現替代了同步代碼塊或者同步函數。將同步的隱式鎖操作變成現實鎖操作。同時更爲靈活。可以一個鎖上加上多組監視器。
3、Lock接口中的方法:
(1)、lock():獲取鎖。 
(2)、unlock():釋放鎖,通常需要定義finally代碼塊中。 
4、Condition接口:出現替代了Object中的wait notify notifyAll方法。 
(1)、將這些監視器方法單獨進行了封裝,變成Condition監視器對象。可以任意鎖進行組合。
5、Condition接口中的方法:
(1)、await():讓線程處於凍結狀態
(2)、signal():喚醒線程池中一個線程(任意)。
(3)、signalAll(): 喚醒線程池中的所有線程。
6、代碼體現三、停止線程:
    1、stop方法:此方法以過時,通過該方法的描述得到解決辦法。 
2、run方法結束:任務中都會有循環結構,只要控制住循環就可以結束任務,控制循環通常就用定義標記來完成。
3、如果線程處於了凍結狀態,無法讀取標記。如何結束呢?
(1)、可以使用Thread類中的interrupt()方法將線程從凍結狀態強制恢復到運行狀態中來,讓線程具備cpu的執行資格。
注:當時強制動作會發生了InterruptedException,記得要處理 。
4、sleep和wait方法的異同點:
(1)、相同點:可以讓線程處於凍結狀態。
(2)、不同點:
(2).1:
     sleep必須指定時間。
     wait可以指定時間,也可以不指定時間。
(2).2:
 sleep時間到,線程處於臨時阻塞或者運行。
 wait如果沒有時間,必須要通過notify或者notifyAll喚醒。
(2).3:
     sleep不一定非要定義在同步中。
     wait必須定義在同步中。
(2).4:
     都定義在同步中,
     線程執行到sleep,不會釋放鎖。
     線程執行到wait,會釋放鎖。
 四、多線程中的其他方法:
1、setDaemon: 將線程設置爲守護線程,必須在開啓前設置,當進程中的線程都是守護線程時,進程結束。
2、setPriority()方法用來設置優先級
(2).1 MAX_PRIORITY 最高優先級10。
        (2).2 MIN_PRIORITY   最低優先級。
        (2).3 NORM_PRIORITY 分配給線程的默認優先級。
  3、join方法:加入一個執行線程。
當a線程執行到了b線程的.join()方法時,a線程就會等待,等b線程都執行完,a線程纔會執行。(此時b和其他線程交替運行。)join可以用來臨時加入線程執行。

4、yield方法:暫停線程,釋放執行權。



轉自:http://blog.csdn.net/bei__kou/article/details/46455597?ref=myread

發佈了29 篇原創文章 · 獲贊 9 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章