2017 - 10 -30 多線程 Lock鎖 線程組 線程池 設計模型 簡單工廠 工廠方法 單例設計

1 Lock鎖的概述和使用
雖然我們可以理解同步代碼塊和同步方法的鎖對象問題,但是我們並沒有直接看到在哪裏加上了鎖,在哪裏釋放了鎖,
爲了更清晰的表達如何加鎖和釋放鎖,JDK5以後提供了一個新的鎖對象Lock。
Lock:
    void lock():   獲取鎖。
    void unlock():  釋放鎖。  
ReentrantLock是Lock的實現類
----------------------------------------------------------
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class SellTicket implements Runnable {
// 定義票
private int tickets = 100;
// 定義鎖對象
private Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
try {
// 加鎖
lock.lock();
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "正在出售第" + (tickets--) + "張票");
}
} finally {
// 釋放鎖
lock.unlock();
                     }
              }
       }
}
public class SellTicketDemo {
public static void main(String[] args) {
// 創建資源對象
SellTicket st = new SellTicket();
// 創建三個窗口
Thread t1 = new Thread(st, "窗口1");
Thread t2 = new Thread(st, "窗口2");
Thread t3 = new Thread(st, "窗口3");
// 啓動線程
t1.start();
t2.start();
t3.start();
        }
}

2 死鎖問題概述和使用
同步的弊端:
     A:效率低
     B:容易產生死鎖
死鎖:
     兩個或兩個以上的線程在爭奪資源的過程中,發生的一種相互等待的現象。
----------------------------------------------------------------------
public class MyLock {
// 創建兩把鎖對象
public static final Object objA = new Object();
public static final Object objB = new Object();
}
public class DieLock extends Thread {
private boolean flag;
public DieLock(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
if (flag) {
synchronized (MyLock.objA) {
System.out.println("if objA");
synchronized (MyLock.objB) {
System.out.println("if objB");
}
}
} else {
synchronized (MyLock.objB) {
System.out.println("else objB");
synchronized (MyLock.objA) {
System.out.println("else objA");
                              }
                       }
               }
      }
}
public class DieLockDemo {
public static void main(String[] args) {
DieLock dl1 = new DieLock(true);
DieLock dl2 = new DieLock(false);
dl1.start();
dl2.start();
       }
}
//運氣好,理想狀態 輸出:if objA  if objB  else objB   else objA
//一般情況    輸出:if objA   else objB


3 線程間通信


---------------------------------------------
分析:
     資源類:Student
     設置學生數據:SetThread(生產者)
     獲取學生數據:GetThread(消費者)
     測試類:StudentDemo
 
問題1:按照思路寫代碼,發現數據每次都是:null---0
原因:我們在每個線程中都創建了新的資源,而我們要求的時候設置和獲取線程的資源應該是同一個
如何實現呢?
       在外界把這個數據創建出來,通過構造方法傳遞給其他的類。

public class Student {
String name;
int age;
}
public class StudentDemo {
public static void main(String[] args) {
//創建資源
Student s = new Student();
//設置和獲取的類
SetThread st = new SetThread(s);
GetThread gt = new GetThread(s);
//線程類
Thread t1 = new Thread(st);
Thread t2 = new Thread(gt);
//啓動線程
t1.start();
t2.start(); //t1和t2都會搶,有可能t2先得到資源
        }
}
public class SetThread implements Runnable {
private Student s;
public SetThread(Student s) {
this.s = s; //把引用給this.s 可以修改堆裏面的東西
        }
@Override
public void run() {
// Student s = new Student();
s.name = "林青霞";
s.age = 27;
      }
}
public class GetThread implements Runnable {
private Student s;
public GetThread(Student s) {
this.s = s;
}
@Override
public void run() {
// Student s = new Student();
System.out.println(s.name + "---" + s.age);
     }
}
------------------------------------------------------
問題2:爲了數據的效果好一些,我加入了循環和判斷,給出不同的值,這個時候產生了新的問題
        A:同一個數據出現多次
        B:姓名和年齡不匹配
原因:
A:同一個數據出現多次
              CPU的一點點時間片的執行權,就足夠你執行很多次。
                 (SetThread 一次還沒走完,GetThread就走了多次)
        B:姓名和年齡不匹配
              線程運行的隨機性
                (例如:s.name = "林青霞";//剛走到這裏,就被別人搶到了執行權,輸出時,age還是上一個30.)
                (s.name = "劉意"; //剛走到這裏,就被別人搶到了執行權,age還是上一個27)
------------------------------------------------------
線程安全問題:
        A:是否是多線程環境
        B:是否有共享數據
        C:是否有多條語句操作共享數據
解決方案:
      加鎖。
        注意:
          A:不同種類的線程都要加鎖。
          B:不同種類的線程加的鎖必須是同一把。
public class Student {
String name;
int age;
}
public class StudentDemo {
public static void main(String[] args) {
//創建資源
Student s = new Student();
//設置和獲取的類
SetThread st = new SetThread(s);
GetThread gt = new GetThread(s);
//線程類
Thread t1 = new Thread(st);
Thread t2 = new Thread(gt);
//啓動線程
t1.start();
t2.start();
       }
}
public class SetThread implements Runnable {
private Student s;
private int x = 0;
public SetThread(Student s) {
this.s = s;
}
@Override
public void run() {
while (true) {
synchronized (s) {
if (x % 2 == 0) {
s.name = "林青霞";//剛走到這裏,就被別人搶到了執行權
s.age = 27;
} else {
s.name = "劉意"; //剛走到這裏,就被別人搶到了執行權
s.age = 30;
                            }
x++;
                 }
           }
     }
}

public class GetThread implements Runnable {
private Student s;
public GetThread(Student s) {
this.s = s;
}
@Override
public void run() {
while (true) {
synchronized (s) {
System.out.println(s.name + "---" + s.age);
                   }
           }
     }
}

4 喚醒機制
(1) 生產者消費者之等待喚醒機制思路圖解


(2)問題3:雖然數據安全了,但是呢,一次一大片不好看,我就想依次的一次一個輸出。
如何實現呢?
            通過Java提供的等待喚醒機制解決。
等待喚醒:
            Object類中提供了三個方法:
                      wait():等待
                      notify():喚醒單個線程
                      notifyAll():喚醒所有線程
            爲什麼這些方法不定義在Thread類中呢?
                      這些方法的調用必須通過鎖對象調用,而我們剛纔使用的鎖對象是任意鎖對象。
                      所以,這些方法必須定義在Object類中。
-----------------------------------------------------------------------
public class Student {
String name;
int age;
boolean flag; // 默認情況是沒有數據,如果是true,說明有數據
}
public class StudentDemo {
public static void main(String[] args) {
//創建資源
Student s = new Student();
//設置和獲取的類
SetThread st = new SetThread(s);
GetThread gt = new GetThread(s);
//線程類
Thread t1 = new Thread(st);
Thread t2 = new Thread(gt);
//啓動線程
t1.start();
t2.start();
          }
}
public class SetThread implements Runnable {
private Student s;
private int x = 0;
public SetThread(Student s) {
this.s = s;
         }
@Override
public void run() {
while (true) {
synchronized (s) {
//判斷有沒有
if(s.flag){
try {
s.wait(); //t1等着,釋放鎖
} catch (InterruptedException e) {
e.printStackTrace();
}
                            }
if (x % 2 == 0) {
s.name = "林青霞";
s.age = 27;
} else {
s.name = "劉意";
s.age = 30;
}
x++; //x=1
//修改標記
s.flag = true;
//喚醒線程
s.notify(); //喚醒t2,喚醒並不表示你立馬可以執行,必須還得搶CPU的執行權。
                          }
//t1有,或者t2有
                }
         }
}
public class GetThread implements Runnable {
private Student s;
public GetThread(Student s) {
this.s = s;
}
@Override
public void run() {
while (true) {
synchronized (s) {
if(!s.flag){
try {
s.wait(); //t2就等待了。立即釋放鎖。將來醒過來的時候,是從這裏醒過來的時候
} catch (InterruptedException e) {
e.printStackTrace();
                                   }
                            }
System.out.println(s.name + "---" + s.age);
//林青霞---27
//劉意---30
//修改標記
s.flag = false;
//喚醒線程
s.notify(); //喚醒t1
                      }
                }
        }
}

5 線程的狀態轉換圖



6 線程組的概述和使用
線程組:把多個線程組合到一起。
它可以對一批線程進行分類管理,Java允許程序直接對線程組進行控制。
-----------------------------------------------------
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(Thread.currentThread().getName() + ":" + x);
               }
       }
}
public class ThreadGroupDemo {
public static void main(String[] args) {
// method1();
// 我們如何修改線程所在的組呢?
// 創建一個線程組
// 創建其他線程的時候,把其他線程的組指定爲我們自己新建線程組
method2();
// t1.start();
// t2.start();
}
private static void method2() {
// ThreadGroup(String name)
ThreadGroup tg = new ThreadGroup("這是一個新的組");
MyRunnable my = new MyRunnable();
// Thread(ThreadGroup group, Runnable target, String name)
Thread t1 = new Thread(tg, my, "林青霞");
Thread t2 = new Thread(tg, my, "劉意");
System.out.println(t1.getThreadGroup().getName());
System.out.println(t2.getThreadGroup().getName());
//通過組名稱設置後臺線程,表示該組的線程都是後臺線程
tg.setDaemon(true);
     }

private static void method1() {
MyRunnable my = new MyRunnable();
Thread t1 = new Thread(my, "林青霞");
Thread t2 = new Thread(my, "劉意");
// 我不知道他們屬於那個線程組,我想知道,怎麼辦
// 線程類裏面的方法:public final ThreadGroup getThreadGroup()
ThreadGroup tg1 = t1.getThreadGroup();
ThreadGroup tg2 = t2.getThreadGroup();
// 線程組裏面的方法:public final String getName()
String name1 = tg1.getName();
String name2 = tg2.getName();
System.out.println(name1);
System.out.println(name2);
// 通過結果我們知道了:線程默認情況下屬於main線程組
// 通過下面的測試,你應該能夠看到,默任情況下,所有的線程都屬於同一個組
System.out.println(Thread.currentThread().getThreadGroup().getName());
       }
}
6 生產者消費者之等待喚醒機制代碼優化
最終版代碼中:
          把Student的成員變量給私有的了。
          把設置和獲取的操作給封裝成了功能,並加了同步。
          設置或者獲取的線程裏面只需要調用方法即可。
public class StudentDemo {
public static void main(String[] args) {
//創建資源
Student s = new Student();
//設置和獲取的類
SetThread st = new SetThread(s);
GetThread gt = new GetThread(s);
//線程類
Thread t1 = new Thread(st);
Thread t2 = new Thread(gt);
//啓動線程
t1.start();
t2.start();
       }
}
public class Student {
private String name;
private int age;
private boolean flag; // 默認情況是沒有數據,如果是true,說明有數據
public synchronized void set(String name, int age) {
// 如果有數據,就等待
if (this.flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 設置數據
this.name = name;
this.age = age;
// 修改標記
this.flag = true;
this.notify();
        }
public synchronized void get() {
// 如果沒有數據,就等待
if (!this.flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
            }
// 獲取數據
System.out.println(this.name + "---" + this.age);
// 修改標記
this.flag = false;
this.notify();
       }
}

public class SetThread implements Runnable {
private Student s;
private int x = 0;
public SetThread(Student s) {
this.s = s;
}
@Override
public void run() {
while (true) {
if (x % 2 == 0) {
s.set("林青霞", 27);
} else {
s.set("劉意", 30);
}
x++;
               }
      }
}
public class GetThread implements Runnable {
        private Student s;
        public GetThread(Student s) {
                 this.s = s;
}
@Override
public void run() {
while (true) {
s.get();
           }
      }
}
7 線程池
程序啓動一個新線程成本是比較高的,因爲它涉及到要與操作系統進行交互。而使用線程池可以很好的提高性能,尤其是當程序中要創建大量生存期很短的線程時,更應該考慮使用線程池。
線程池的好處:線程池裏的每一個線程代碼結束後,並不會死亡,而是再次回到線程池中成爲空閒狀態,等待下一個對象來使用。
如何實現線程的代碼呢?
        A:創建一個線程池對象,控制要創建幾個線程對象。
            public static ExecutorService newFixedThreadPool(int nThreads)
        B:這種線程池的線程可以執行:
            可以執行Runnable對象或者Callable對象代表的線程
            做一個類實現Runnable接口。
        C:調用如下方法即可
            Future<?> submit(Runnable task)
            <T> Future<T> submit(Callable<T> task)
        D:我就要結束,可以嗎?
            可以。
-------------------------------------------------
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(Thread.currentThread().getName() + ":" + x);
}
}
}
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ExecutorsDemo {
public static void main(String[] args) {
// 創建一個線程池對象,控制要創建幾個線程對象。
// public static ExecutorService newFixedThreadPool(int nThreads)
ExecutorService pool = Executors.newFixedThreadPool(2);
// 可以執行Runnable對象或者Callable對象代表的線程
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
//結束線程池
pool.shutdown();
}
}

8 多線程實現方式3(瞭解就行)
多線程實現的方式3:
        A:創建一個線程池對象,控制要創建幾個線程對象。
          public static ExecutorService newFixedThreadPool(int nThreads)
        B:這種線程池的線程可以執行:
                  可以執行Runnable對象或者Callable對象代表的線程
                  做一個類實現Runnable接口。
        C:調用如下方法即可
                  Future<?> submit(Runnable task)
                  <T> Future<T> submit(Callable<T> task)
        D:我就要結束,可以嗎?
                     可以。

//Callable:是帶泛型的接口。
//這裏指定的泛型其實是call()方法的返回值類型。
public class MyCallable implements Callable {
@Override
public Object call() throws Exception {
for (int x = 0; x < 100; x++) {
System.out.println(Thread.currentThread().getName() + ":" + x);
                   }
return null;
         }
}
public class CallableDemo {
public static void main(String[] args) {
//創建線程池對象
ExecutorService pool = Executors.newFixedThreadPool(2);
//可以執行Runnable對象或者Callable對象代表的線程
pool.submit(new MyCallable());
pool.submit(new MyCallable());
//結束
pool.shutdown();
         }
}
9 匿名內部類的方式實現多線程
匿名內部類的格式:
      new 類名或者接口名() {
              重寫方法;
        };
      本質:是該類或者接口的子類匿名對象
public class ThreadDemo {
public static void main(String[] args) {
// 繼承Thread類來實現多線程
new Thread() {
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(Thread.currentThread().getName() + ":"
+ x);
                                }
                        }
             }.start();
// 實現Runnable接口來實現多線程
new Thread(new Runnable() {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(Thread.currentThread().getName() + ":"
+ x);
                              }
                     }
             }) {
}.start();
// 更有難度的
new Thread(new Runnable() {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println("hello" + ":" + x);
                             }
                      }
}) {
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println("world" + ":" + x);
                          }
                   }
            }.start();
       }
         //輸出 world....而不是hello....
}

10 定時器
定時器:可以讓我們在指定的時間做某件事情,還可以重複的做某件事情。
依賴Timer和TimerTask這兩個類:
Timer:定時
public Timer()
public void schedule(TimerTask task,long delay) :安排在指定延遲後執行指定的任務 
public void schedule(TimerTask task,long delay,long period):安排指定的任務從指定的延遲後開始進行重複的
public void cancel()

TimerTask:任務
public abstract void run()
public boolean cancel()
--------------------------------------------------
public class TimerDemo {
public static void main(String[] args) {
// 創建定時器對象
Timer t = new Timer();
// 3秒後執行爆炸任務
// t.schedule(new MyTask(), 3000);
//結束任務
t.schedule(new MyTask(t), 3000);
}
}

// 做一個任務
class MyTask extends TimerTask {
private Timer t;
public MyTask(){}
public MyTask(Timer t){
this.t = t;
}
@Override
public void run() {
System.out.println("beng,爆炸了");
t.cancel();
}
}
-------------------------------------------------------
public class TimerDemo2 {
public static void main(String[] args) {
// 創建定時器對象
Timer t = new Timer();
// 3秒後執行爆炸任務第一次,如果不成功,每隔2秒再繼續炸
t.schedule(new MyTask2(), 3000, 2000);
       }
}
// 做一個任務
class MyTask2 extends TimerTask {
@Override
public void run() {
System.out.println("beng,爆炸了");
      }
}

***11 多線程常見的面試題
(1)多線程有幾種實現方案,分別是哪幾種?
       兩種。
       繼承Thread類
       實現Runnable接口
    擴展一種:實現Callable接口。這個得和線程池結合。(不會的話,還是別說算了)
(2)同步有幾種方式,分別是什麼?
       兩種。
       同步代碼塊
       同步方法
(3)啓動一個線程是run()還是start()?它們的區別?
       start();
       run():封裝了被線程執行的代碼,直接調用僅僅是普通方法的調用
       start():啓動線程,並由JVM自動調用run()方法
(4)sleep()和wait()方法的區別
       sleep():必須指時間;不釋放鎖。(Thread類中)
       wait():可以不指定時間,也可以指定時間;釋放鎖。(Object類中)
(5)爲什麼wait(),notify(),notifyAll()等方法都定義在Object類中
       因爲這些方法的調用是依賴於鎖對象的,而同步代碼塊的鎖對象是任意鎖。
       而Object代碼任意的對象,所以,定義在這裏面。
(6)線程的生命週期圖
        新建 -- 就緒 -- 運行 -- 死亡
        新建 -- 就緒 -- 運行 -- 阻塞 -- 就緒 -- 運行 -- 死亡
        建議:畫圖解釋。

12 設計模式
(1)設計模式
    就是經驗的總結。

        A:創建型       創建對象
        B:結構型       對象的組成
        C:行爲型       對象的功能

創建型模式:
        1:簡單工廠模式
        2:工廠方法模式
        3:設計模式
----------------------------------------
(2)簡單工廠模式
通過使用工廠,來創建對象
-------------------------------------------------
簡單工廠模式概述
又叫靜態工廠方法模式,它定義一個具體的工廠類負責創建一些類的實例
優點:
客戶端不需要在負責對象的創建,從而明確了各個類的職責
缺點:
這個靜態工廠類負責所有對象的創建,如果有新的對象增加,或者某些對象的創建方式不同,就需要不斷的修改工廠類,不利於後期的維護
--------------------------------------------------
public abstract class Animal {
public abstract void eat();
}
public class Cat extends Animal {
@Override
public void eat() {
System.out.println("貓吃魚");
        }
}
public class Dog extends Animal {
@Override
public void eat() {
System.out.println("狗吃肉");
       }
}
public class AnimalDemo {
public static void main(String[] args) {
// 具體類調用
Dog d = new Dog();
d.eat();
Cat c = new Cat();
c.eat();
System.out.println("------------");
// 工廠有了後,通過工廠給造
                //私有化構造方法後,不能創建新對象了,只能通過靜態
// Dog dd = AnimalFactory.createDog();
// Cat cc = AnimalFactory.createCat();
// dd.eat();
// cc.eat();
// System.out.println("------------");
// 工廠改進後
Animal a = AnimalFactory.createAnimal("dog");
a.eat();
a = AnimalFactory.createAnimal("cat");
a.eat();
// NullPointerException
a = AnimalFactory.createAnimal("pig");
if (a != null) {
a.eat();
} else {
System.out.println("對不起,暫時不提供這種動物");
             }
       }
}

public class AnimalFactory {
        //私有化,只提供靜態方法
private AnimalFactory() {
}
// public static Dog createDog() {
// return new Dog();
// }
//
// public static Cat createCat() {
// return new Cat();
// }
        //通過名字創建目標對象
public static Animal createAnimal(String type) {
if ("dog".equals(type)) {
return new Dog();
} else if ("cat".equals(type)) {
return new Cat();
} else {
return null;
}
}
}
(3)工廠方法模型
工廠方法模式概述
工廠方法模式中抽象工廠類負責定義創建對象的接口,具體對象的創建工作由繼承抽象工廠的具體類實現。
優點:
客戶端不需要在負責對象的創建,從而明確了各個類的職責,如果有新的對象增加,只需要增加一個具體的類和具體的工廠類即可,不影響已有的代碼,後期維護容易,增強了系統的擴展性
缺點:
需要額外的編寫代碼,增加了工作量
--------------------------------------------------
public interface Factory {
public abstract Animal createAnimal();
}
public abstract class Animal {
public abstract void eat();
}
public class Dog extends Animal {
@Override
public void eat() {
System.out.println("狗吃肉");
}
}
public class DogFactory implements Factory {
@Override
public Animal createAnimal() {
return new Dog();
}
}
public class Cat extends Animal {
@Override
public void eat() {
System.out.println("貓吃魚");
}
}
public class CatFactory implements Factory {
@Override
public Animal createAnimal() {
return new Cat();
}
}
public class AnimalDemo {
public static void main(String[] args) {
// 需求:我要買只狗
Factory f = new DogFactory();
Animal a = f.createAnimal();
a.eat();
System.out.println("-------");
//需求:我要買只貓
f = new CatFactory();
a = f.createAnimal();
a.eat();
}
}

***(4) 單例設計模式
單例設計模式概述
單例模式就是要確保類在內存中只有一個對象,該實例必須自動創建,並且對外提供。
優點:
在系統內存中只存在一個對象,因此可以節約系統資源,對於一些需要頻繁創建和銷燬的對象單例模式無疑可以提高系統的性能。
缺點:
沒有抽象層,因此擴展很難。
職責過重,在一定程序上違背了單一職責
------------------------------------
單例模式:保證類在內存中只有一個對象。


如何保證類在內存中只有一個對象呢?
            A:把構造方法私有
            B:在成員位置自己創建一個對象
            C:通過一個公共的方法提供訪問
--------------------------------------------
餓漢式:類一加載就創建對象------------------
--------------------------------------------
public class Student {
// 構造私有
private Student() {
}
// 自己造一個
// 靜態方法只能訪問靜態成員變量,加靜態
// 爲了不讓外界直接訪問修改這個值,加private
private static Student s = new Student();
// 提供公共的訪問方式
// 爲了保證外界能夠直接使用該方法,加靜態
public static Student getStudent() {
return s;
}
}
public class StudentDemo {
public static void main(String[] args) {
// Student s1 = new Student();
// Student s2 = new Student();
// System.out.println(s1 == s2); // false
// 通過單例如何得到對象呢?
// Student.s = null;
Student s1 = Student.getStudent();
Student s2 = Student.getStudent();
System.out.println(s1 == s2);
System.out.println(s1); // null,cn.itcast_03.Student@175078b
System.out.println(s2);// null,cn.itcast_03.Student@175078b
}
}
--------------------------------------------
懶漢式:用的時候,纔去創建對象--------------
--------------------------------------------
public class Teacher {
private Teacher() {
}
private static Teacher t = null;
public synchronized static Teacher getTeacher() {
// t1,t2,t3
if (t == null) {
//t1,t2,t3
t = new Teacher();
}
return t;
}
}
public class TeacherDemo {
public static void main(String[] args) {
Teacher t1 = Teacher.getTeacher();
Teacher t2 = Teacher.getTeacher();
System.out.println(t1 == t2);
System.out.println(t1); // cn.itcast_03.Teacher@175078b
System.out.println(t2);// cn.itcast_03.Teacher@175078b
}
}
***面試題:單例模式的思想是什麼?請寫一個代碼體現。
   思想:單例模式:保證類在內存中只有一個對象。
   代碼:寫懶漢式
          開發:餓漢式(是不會出問題的單例模式)
          面試:懶漢式(可能會出問題的單例模式)
                 A:懶加載(延遲加載)
                 B:線程安全問題
                         a:是否多線程環境               是
                         b:是否有共享數據               是
                         c:是否有多條語句操作共享數據   是
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章