[Java] 16.1. 執行緒

執行緒,打破程式執行的順序。

 

image/svg+xml16.1. 執⾏緒 – Thread class Employee { String name; public Employee( String name){ this .name = name; } void doWork (){ System.out.println(name + 開始⼯作。 " ); } } class Main{ public static void main( String [] args){ Employee e1 = new Employee ( " Jack " ); Employee e2 = new Employee ( " Eric " ); Employee e3 = new Employee ( " Mary " ); e1 . doWork (); e2 . doWork (); e3 . doWork (); } } 執⾏結果 Jack 開始⼯作 Eric 開始⼯作 Mary 開始⼯作 class Employee extends Thread { String name; public Employee( String name){ this .name = name; } } class Employee extends Thread { String name; public Employee( String name){ this .name = name; } public void run (){ System.out.println(name+ " 正在畫第 '1' 張圖。 " ); System.out.println(name+ " 正在畫第 '2' 張圖。 " ); System.out.println(name+ " 正在畫第 '3' 張圖。 " ); System.out.println(name+ " 正在畫第 '4' 張圖。 " ); System.out.println(name+ " 正在畫第 '5' 張圖。 " ); System.out.println(name+ " 正在畫第 '6' 張圖。 " ); System.out.println(name+ " 正在畫第 '7' 張圖。 " ); System.out.println(name+ " 正在畫第 '8' 張圖。 " ); System.out.println(name+ " 正在畫第 '9' 張圖。 " ); } } class Main{ public static void main( String [] args){ Employee e1 = new Employee ( " Jack " ); Employee e2 = new Employee ( " Eric " ); Employee e3 = new Employee ( " Mary " ); e1 . start (); e2 . start (); e3 . start (); } } 執⾏結果之⼀: Jack 正在畫第 '1' 張圖。 Jack 正在畫第 '2' 張圖。 Jack 正在畫第 '3' 張圖。 Jack 正在畫第 '4' 張圖。 Jack 正在畫第 '5' 張圖。 Jack 正在畫第 '6' 張圖。 Jack 正在畫第 '7' 張圖。 Jack 正在畫第 '8' 張圖。 Mary 正在畫第 '1' 張圖。 Mary 正在畫第 '2' 張圖。 Mary 正在畫第 '3' 張圖。 Mary 正在畫第 '4' 張圖。 Mary 正在畫第 '5' 張圖。 Mary 正在畫第 '6' 張圖。 Mary 正在畫第 '7' 張圖。 Mary 正在畫第 '8' 張圖。 Eric 正在畫第 '1' 張圖。 Eric 正在畫第 '2' 張圖。 Eric 正在畫第 '3' 張圖。 Eric 正在畫第 '4' 張圖。 Eric 正在畫第 '5' 張圖。 Eric 正在畫第 '6' 張圖。 Eric 正在畫第 '7' 張圖。 Mary 正在畫第 '9' 張圖。 Eric 正在畫第 '8' 張圖。 Eric 正在畫第 '9' 張圖。 Jack 正在畫第 '9' 張圖。 class Main{ public static void main( String [] args){ Employee e1 = new Employee ( " Jack " ); Employee e2 = new Employee ( " Eric " ); Employee e3 = new Employee ( " Mary " ); e1 . start (); e2 . start (); e3 . start (); e1 . run (); e2 . run (); e3 . run (); } } 執⾏結果: Jack 正在畫第 '1' 張圖。 Jack 正在畫第 '2' 張圖。 Jack 正在畫第 '3' 張圖。 Jack 正在畫第 '4' 張圖。 Jack 正在畫第 '5' 張圖。 Jack 正在畫第 '6' 張圖。 Jack 正在畫第 '7' 張圖。 Jack 正在畫第 '8' 張圖。 Jack 正在畫第 '9' 張圖。 Eric 正在畫第 '1' 張圖。 Eric 正在畫第 '2' 張圖。 Eric 正在畫第 '3' 張圖。 Eric 正在畫第 '4' 張圖。 Eric 正在畫第 '5' 張圖。 Eric 正在畫第 '6' 張圖。 Eric 正在畫第 '7' 張圖。 Eric 正在畫第 '8' 張圖。 Eric 正在畫第 '9' 張圖。 Mary 正在畫第 '1' 張圖。 Mary 正在畫第 '2' 張圖。 Mary 正在畫第 '3' 張圖。 Mary 正在畫第 '4' 張圖。 Mary 正在畫第 '5' 張圖。 Mary 正在畫第 '6' 張圖。 Mary 正在畫第 '7' 張圖。 Mary 正在畫第 '8' 張圖。 Mary 正在畫第 '9' 張圖。 class Person { } class Employee extends Person implements Runnable { String name; public Employee( String name){ this .name = name;} public void run (){ for ( int i= 1 ; i<= 10 ; i++) System.out.println(name+ " 正在畫第 '" +i+ "' 張圖。 " ); } } class Main{ public static void main( String [] args){ Employee e = new Employee ( " Jack " ); Thread t = new Thread ( e ); t.start (); } } class Main{ public static void main( String [] args){ Runnable r1 = new Runnable () { public void run (){ for ( int i= 0 ; i< 10 ; i++) System.out.println( "Runnable:" +i); } } ; Thread t1 = new Thread ( r1 ); t1 . start (); Thread t2 = new Thread () { public void run (){ for ( int i= 0 ; i< 10 ; i++){ System.out.println( "Thread:" +i); } } } ; t2 . start (); } } class Main{ public static void main( String [] args){ Employee e1 = new Employee ( Jack " ); e1 . start (); } } 執⾏結果: Jack 正在畫第 '1' 張圖。 Jack 正在畫第 '2' 張圖。 Jack 正在畫第 '3' 張圖。 Jack 正在畫第 '4' 張圖。 Jack 正在畫第 '5' 張圖。 Jack 正在畫第 '6' 張圖。 Jack 正在畫第 '7' 張圖。 Jack 正在畫第 '8' 張圖。 Jack 正在畫第 '9' 張圖。 class Employee extends Thread { String name; public Employee( String name){ this .name = name;} public void run (){ System.out.println(name+ " 正在畫第 '1' 張圖。 " ); System.out.println(name+ " 正在畫第 '2' 張圖。 " ); System.out.println(name+ " 正在畫第 '3' 張圖。 " ); System.out.println(name+ " 正在畫第 '4' 張圖。 " ); try { Thread.sleep ( 1000 ); } catch ( InterruptedException e){} System.out.println(name+ " 正在畫第 '5' 張圖。 " ); System.out.println(name+ " 正在畫第 '6' 張圖。 " ); System.out.println(name+ " 正在畫第 '7' 張圖。 " ); System.out.println(name+ " 正在畫第 '8' 張圖。 " ); try { Thread.sleep ( 1000 ); } catch ( InterruptedException e){} System.out.println(name+ " 正在畫第 '9' 張圖。 " ); } } class Main{ public static void main( String [] args){ Employee e1 = new Employee ( Jack " ); e1 . start (); } } 執⾏結果: Jack 正在畫第 '1' 張圖。 Jack 正在畫第 '2' 張圖。 Jack 正在畫第 '3' 張圖。 Jack 正在畫第 '4' 張圖。 Jack 正在畫第 '5' 張圖。 Jack 正在畫第 '6' 張圖。 Jack 正在畫第 '7' 張圖。 Jack 正在畫第 '8' 張圖。 Jack 正在畫第 '9' 張圖。 class Employee extends Thread { String name; public Employee( String name){ this .name = name;} public void run (){ for ( int i= 1 ; i<= 10 ; i++) System.out.println(name+ " 正在畫第 '" +i+ "' 張圖。 " ); } } class Main{ public static void main( String [] args){ Employee e1= new Employee ( " Jack " ); e1 . start (); try { e1 . sleep ( 1000 ); } catch ( InterruptedException e){} System.out.println( " main() ⽅法中的程式。 " ); } } 執⾏結果: Jack 正在畫第 '1' 張圖。 Jack 正在畫第 '2' 張圖。 Jack 正在畫第 '3' 張圖。 Jack 正在畫第 '4' 張圖。 Jack 正在畫第 '5' 張圖。 Jack 正在畫第 '6' 張圖。 Jack 正在畫第 '7' 張圖。 Jack 正在畫第 '8' 張圖。 Jack 正在畫第 '9' 張圖。 main() ⽅法中的程式。 建立⼀個系統就像建房⼦⼀樣是分⼯合作的,就像運⽤執⾏緒的功能是⼀樣的,每位員⼯都是分別進 ⾏⼯作的 class Employee extends Thread { String name; String task ; public Employee( String name, String task ){ this .name = name; this .task = task ; } public void run (){ for ( int i= 1 ; i<= 5 ; i++){ try { Thread.sleep ( 100 ); } catch ( InterruptedException e){} System.out.println(name + " 進⾏⼯作 -" + task + " ,完成進⾏ " + i* 20 + "%" ); } } } class Main{ public static void main( String [] args){ Employee e1 = new Employee ( "Jack" , " 畫圖 " ); Employee e2 = new Employee ( "Eric" , " 程式 " ); Employee e3 = new Employee ( "Mary" , " 設計 畫⾯ " ); e1 . start (); e2 . start (); e3 . start (); try { e1 . join (); e2 . join (); e3 . join (); } catch ( InterruptedException e){} System.out.println( " 系統已開發完成 ! " ); } } } 執⾏結果之⼀: Jack 進⾏⼯作 - 畫圖,完成進⾏ 20% Eric 進⾏⼯作 - 寫程式,完成進⾏ 20% Mary 進⾏⼯作 - 設計畫⾯,完成進⾏ 20% Jack 進⾏⼯作 - 畫圖,完成進⾏ 40% Eric 進⾏⼯作 - 寫程式,完成進⾏ 40% Mary 進⾏⼯作 - 設計畫⾯,完成進⾏ 40% Jack 進⾏⼯作 - 畫圖,完成進⾏ 60% Eric 進⾏⼯作 - 寫程式,完成進⾏ 60% Mary 進⾏⼯作 - 設計畫⾯,完成進⾏ 60% Jack 進⾏⼯作 - 畫圖,完成進⾏ 80% Eric 進⾏⼯作 - 寫程式,完成進⾏ 80% Mary 進⾏⼯作 - 設計畫⾯,完成進⾏ 80% Jack 進⾏⼯作 - 畫圖,完成進⾏ 100% Eric 進⾏⼯作 - 寫程式,完成進⾏ 100% Mary 進⾏⼯作 - 設計畫⾯,完成進⾏ 100% 系統已開發完成 ! class Employee extends Thread { String name; String task; public Employee( String name, String task){ this .name = name; this .task=task; } public void run (){ for ( int i= 1 ; i<= 5 ; i++){ Thread . yield (); System.out.println(name + " 進⾏⼯作 -" + task + " ,完成進⾏ " + i* 20 + "%" ); } } } class Main{ public static void main( String [] args){ Employee e1= new Employee( "Jack" , " 畫圖 " ); Employee e2= new Employee( "Eric" , " 寫程式 " ); Employee e3= new Employee( "Mary" , " 設計畫⾯ " ); e1.start(); e2.start(); e3.start(); } } 執⾏結果: Jack 進⾏⼯作 - 畫圖,完成進⾏ 20% Eric 進⾏⼯作 - 寫程式,完成進⾏ 20% Mary 進⾏⼯作 - 設計畫⾯,完成進⾏ 20% Jack 進⾏⼯作 - 畫圖,完成進⾏ 40% Eric 進⾏⼯作 - 寫程式,完成進⾏ 40% Mary 進⾏⼯作 - 設計畫⾯,完成進⾏ 40% Jack 進⾏⼯作 - 畫圖,完成進⾏ 60% Eric 進⾏⼯作 - 寫程式,完成進⾏ 60% Mary 進⾏⼯作 - 設計畫⾯,完成進⾏ 60% Jack 進⾏⼯作 - 畫圖,完成進⾏ 80% Eric 進⾏⼯作 - 寫程式,完成進⾏ 80% Mary 進⾏⼯作 - 設計畫⾯,完成進⾏ 80% Jack 進⾏⼯作 - 畫圖,完成進⾏ 100% Eric 進⾏⼯作 - 寫程式,完成進⾏ 100% Mary 進⾏⼯作 - 設計畫⾯,完成進⾏ 100% 1 J16_1_1 Employee.java 1. 這是員⼯類別( Employee )。 J16_1_3 Employee.java J16_1_1 Main.java 2. 進⾏⼯作⽅法( doWork() ),讓員⼯開始⼯作。 3. 建立 3 名⼯作的員⼯ Jack Eric 、與 Mary . 進⾏⼯作⽅法( doWork() )讓員⼯開始⼯作,但是⼯作進⾏的順序,卻⼀定會 是在 main() 程式中執⾏的順序,不論執⾏多少次,結果都會是 Jack Eric 然後是 Mary 的順序進⾏⼯作。 Java 是否有提供模擬同時進⾏⼯作的功能,好讓員⼯在進⾏ ⼯作的時候,可以各⾃進⾏⼯作,⽽不⽤按照特定順序來進⾏⼯作呢? J16_1_2 Employee.java 1. 這是⼀個簡單的員⼯類別( Employee ),沒有繼承⾃ ⼈員類別,⽬的只是想讓之後的範例變的更簡單⽽已。 2. 讓員⼯繼承⾃ Thread 類別, 以擁有獨立運作的可能。 3. Thread 類別上的 run() ⽅法,其實是 實作⾃ Runnable 介⾯時所提供的。 4. 執⾏緒上還有⼀些⽅法,可以控制 執⾏緒執⾏的⾏為。後⾯會詳加說明。 1. 簡單的員⼯類別( Employee )繼承⾃ Thread 類別,以擁有獨立運作的可能。 2. 改寫從 Thread 中繼承下來的 run() ⽅法。並將員⼯所要 進⾏的⼯作寫在這個 run() ⽅法中。 3. run() ⽅法是⼀個 public 存取權限。不要忘了加上去。 4. 這是每位員⼯需要進⾏的⼯作。 J16_1_3 Main.java 1. 建立 3 名⼯作的員⼯ Jack Eric 、與 Mary 2. start() ⽅法實際啟動了執⾏緒。讓這些執⾏緒 可以獨立於 main() 程式之外,以跳脫執⾏的順序。 3. Jack Eric Mary 在進⾏⼯作的時,並不會因為 Jack 先呼叫 start() ⽅法,就必須先讓 Jack 先完成所有的⼯作。 執⾏緒會讓每個員⼯都是平等的,⼤家可以同時執⾏。因此 執⾏的結果是不⼀定的,因為您無法預測哪位員⼯會先進⾏ ⼯作,就好像現實⽣活中,並沒有誰應該先⼯作的問題。 J16_1_4 Main.java 1. 建立 3 名⼯作的員⼯ Jack Eric 、與 Mary 2. 現在我們不透過呼叫 start() ⽅法的⽅式,啟動執⾏緒。 . 執⾏的結果會依照在 main() 程式中所寫的順序來執⾏。 3. ⽽是直接呼叫 run() ⽅法,但 這並不會得到執⾏緒獨立 運作的功能,因此會以呼叫 run() ⽅法的的先後順序來執 ⾏,員⼯無法享有同時⼯作的能⼒。 J16_1_5 Person.java 記住,員⼯物件的執⾏緒,是從 main() ⽅法的執⾏緒中獨立出來的,⽽ main() ⽅法是由 JVM 啟動⼀個執⾏緒來執⾏的,因此整個 Java 在執⾏程式 的時候,皆是以執⾏緒為基礎在運作的。 1. 簡單的⼈員類別( Person ),為後續的繼承作準備。 2. 員⼯類別( Employee )已經繼承⾃⼈員類別。 3. 這時只好實作 Runnable 介⾯, 以取得獨立運作的可能。 4. 改寫 run ⽅法,以描述執⾏緒啟動時所需進⾏的⼯作。 5. 建立員⼯物件。 6. e 變數中的員⼯物件,因為有實作 Runnable 介⾯, 因此可以交給 Thread 的建構式,並建立 Thread 物件。 7. 最後在經由 Thread 物件的 start() ⽅法來啟動執⾏緒。當然,執 ⾏緒所執⾏的 run() ⽅法,會是在員⼯物件上的 run() ⽅法,並不是 Thread 物件本⾝的 run() ⽅法。原因是我們已經透過 Thread 的建構 式,指明所要執⾏的 run() ⽅法是員⼯物件上的 run() ⽅法。 J16_1_5 Employee.java J16_1_5 Main.java J16_1_6 Main.java . 建立⼀個實作 Runnable 的匿名類別, 同時建立此匿名類別的物件。 2. 改寫 Runnable 介⾯上的 run() ⽅法, 以描述執⾏緒啟動時,所要執⾏的程式。 3. 將這個匿名的類別的物件作為 Thread 建構式的參數,如此只要在呼叫 start() ⽅法,即可啟動執⾏緒,以執⾏匿名類別中的 run() ⽅法。 4. 當然也可以建立繼承⾃ Thread 類別的匿名類別,並放寫 run() ⽅法,以描述執⾏緒啟動時,所要執⾏的程式。 5. 因為這個匿名類別是繼承⾃ Thread 類別,因此本⾝就有個 start() ⽅法可以啟動 執⾏緒,所以並不需要透過另⼀個 Thread 物件來啟動執⾏緒。當然若想要透過另⼀個 Thread 物件啟動執⾏緒也是可以的,因為 Thread 本⾝是實作 Runnable 介⾯的類別, 因此可以轉型成 Runnable 型別後,作為 Thread 建構式的參數來建立 Thread 物件。 J16_1_7 Main.java 1. 建立員⼯物件( Employee ),此員⼯類別繼承⾃ Thread 類別,可透過呼叫 start() ⽅法啟動執⾏緒。 . 在呼叫 start() ⽅法,啟動執⾏後 J16_1_8 Employee.java 1. 員⼯類別( Employee )繼承⾃ Thread 類別。 3. 透過 Thread 類別直接呼叫靜態的 sleep() ⽅法,以進入 休眠狀態, sleep() ⽅法會傳入⼀個數值,以表⽰要休眠的時 間,單位是 1/1000 秒,因此給定 1000 即表⽰休息⼀秒。 4. 員⼯物件也可以呼叫 sleep() ⽅法,因為員⼯類別繼承了 Thread 類別,因此將 sleep() ⽅法也繼承下來了。 5. 呼叫 sleep() ⽅法以進入休眠狀態後,有可能被中斷,因此要使⽤ try/catch 來捕捉被中斷的異常物件 -InterruptedException 5. 建立員⼯物件。 6. 員⼯物件還是⼀如往常的使⽤ start() ⽅法,來啟動執⾏緒。 7. 在這個時間點,員⼯會停⽌⼯作,並休息 1 秒鐘, 此為程式 Thread.sleep(1000) 所產⽣的效過。 8. 在這個時間點,員⼯會停⽌⼯作並休息 1 秒鐘, 此為程式 this.sleep(1000) 所產⽣的效過。 2. 改寫 run() ⽅法。 J16_1_8 Main.java J16_1_9 Employee.java J16_1_9 Main.java 1. 員⼯類別( Employee )繼承⾃ Thread 類別。 3. 建立員⼯物件。 4. 透過 start() ⽅法啟動員⼯物件的執⾏緒。 5. 千萬別被這⾏程式給騙了, e1 變數中雖然是⼀個有繼承 Thread 類別的員⼯類別物件,但這⾏程式 “e1.sleep()” 最後還是會被編譯器 轉換為 ”Employee.sleep()” ,也因為 Employee 是繼承⾃ Thread 因此⼜可視為 ”Thread.sleep()” ;所以最後造成的結果,就是讓啟動 main ⽅法的執⾏緒休眠,並不會讓員⼯物件的執⾏緒休眠。 6. “main() ⽅法中的程式 ⼀定會在最後出現, 原因是 “e1.sleep()” 這⾏程式讓執⾏ main() 法的執⾏緒進入休眠狀態,在這休眠1秒的過程 式,員⼯物件已經順利完成他的⼯作,所以 “main() ⽅法中的程式 ⼀定會在最後出現。 2. 注意, run() ⽅法中並沒有使⽤到 sleep() ⽅法。 J16_1_10 Main.java J16_1_10 Employee.java 1. 建屋項,⽬前進度 100% 2. 調⽔泥,⽬前進度 100% 3. 鋪地板進⾏中,⽬前進度 80% 。如 何確保每個⼯作皆進⾏完成了,才能 確定系統已建立完成了呢?  1. 員⼯類別( Employee )已經繼承⾃ Thread 類別。 2. 為員⼯加入⼯作項⽬,並透過建構式來指定員⼯的⼯作項⽬。 3. 改寫 run() ⽅法。 4. 使⽤ sleep() ⽅法模擬⼯作是需要花時間來完成的。 5. 建立 3 名員⼯物件並透過建構式指定⼯作項⽬。 7. 但因為我們在各個員⼯物件上呼叫 join() ⽅法 ( 這是 因為繼承⾃ Thread 才有的⽅法 ) ,因此使得⽬前程式所在 的執⾏緒 -main() 執⾏緒必須停下來,直到這 3 個員⼯執 ⾏緒完成他們的⼯作 ( 執⾏完畢 ) ,才能在往下執⾏。請注 意,只有 main() 執⾏緒會等待這 3 個員⼯執⾏緒執⾏完 畢,但這 3 個員⼯執⾏緒沒有相互等待的問題,還是可以 獨立的以競爭的⽅式來執⾏執⾏緒中的⼯作。 6. ⼀如往常的啟動執⾏緒,讓每個員⼯都可以獨立進⾏⼯作。同時, 這三個執⾏緒也是從 main() 執⾏緒中獨立出來的新執⾏緒。 8. 這⾏程式是在 main() 執⾏緒中的,因為它必須 等待 3 個員⼯執⾏緒完成他們的⼯作 ( 執⾏完畢 ) ,才 能在往下執⾏,所以這⾏程式⼀定會在最後執⾏。 9. 需使⽤ try/catch 捕抓 InterruptedException 型別 的異常物件。 10. 這是 3 名員⼯的執⾏緒在獨立作業下 所產⽣的結果,這⼀部份的結果是不⼀定 的,因為執⾏緒在獨立作業的情況下是相 互競爭的,無法確保執⾏的順序。 11. 這是在 main() 執⾏緒中輸出的結果,因為 3 個員⼯執⾏緒呼 叫了 join() ⽅法,造成 main() 執⾏緒必須等待完成他們的⼯作 ( 執⾏完畢 ) 才能在往下執⾏,所以這⾏程式⼀定會在最後執⾏。 J16_1_11 Employee.java 執⾏緒 B 執⾏緒 A 執⾏緒 C ⽬前市⾯上有 1 CPU 但卻是雙核⼼或四核⼼的 CPU ,這表⽰在作業系統作業 的時候,會將此所未的核⼼看作為實際存在的獨立 CPU ,雙核⼼即 2 CPU 四核⼼即 4 CPU 共同分享 CPU 的資源, 以執⾏每個執⾏緒上 run() ⽅法中的程式。 1. 員⼯類別( Employee )已經繼承⾃ Thread 類別。 2. 改寫 run() ⽅法,並使⽤ yeild() ⽅法讓出 CPU 的使⽤權,就像員⼯讓出這 唯⼀電腦的使⽤權是⼀樣的。 3. 因為員⼯在完成⼀個進度時,就會使⽤ yeild() ⽅法 讓唯⼀⼀台電腦的使⽤權(意指唯⼀ CPU 的使⽤權),因 此看起來⼤家是很平均的完成⼯作的進度。 J16_1_11 Main.java 為什麼員⼯在 main() 程式的順序,就是進⾏⼯作的順序? 1 步,讓員⼯類別繼承⾃ Thread 以擁有獨⾃⼯作的能⼒ 2 步,將員⼯要進⾏的⼯作放在 run() ⽅法中 3 步, start() ⽅法是讓員⼯開始獨立作業的指令 1 2 3 1 注意,員⼯物件直接呼叫 run() ⽅法時, 並不會得到獨立運作的功能 1 2 3 2 3 1 2 3 1 2 3 實作 Runnable 介⾯的員⼯物件,也可透過 Thread 啟動執⾏緒 匿名類別與 Thread 類別的合作關係 1 1 員⼯在獨立作業的過程中, 可否稍微休息⼀下? 使⽤ sleep() ⽅法,讓員⼯休息⼀下 不要 sleep() 放在員⼯的執⾏緒外, 否則將造成其他執⾏緒睡著 1 1 2 2 1 使⽤ join() ⽅法, 保證所有員⼯的建置⼯作在系統運作前完成 1 2 3 1 1 1 1 1 2 3 2 3 2 3 2 3 2 3 4 4 您知道 1 台電腦只有 1 CPU 的情況下, 就像是 1 家公司只有 1 台電腦的狀況是⼀樣的! yield() ⽅法讓員⼯立即讓出電腦的使⽤權 1 1 2 3 2 3 1 1 2 3 2 3 1 2 3 1 2 3 ⼀個完整的系統, 是需要所有員⼯建置完⼯後才可以算完成

留言