想要設計一個程序,邊打遊戲邊聽歌,怎麼設計?java
要解決上述問題,得使用多進程或者多線程來解決.編程
注意:單核處理器的計算機確定是不能並行的處理多個任務的,只能是多個任務在單個CPU上併發運行。同安全
理,線程也是同樣的,從宏觀角度上理解線程是並行運行的,可是從微觀角度上分析倒是串行運行的,即一個服務器
線程一個線程的去運行,當系統只有一個CPU時,線程會以某種順序執行多個線程,咱們把這種狀況稱之爲多線程
線程調度。併發
進程:是指一個內存中運行的應用程序,每一個進程都有一個獨立的內存空間,一個應用程序能夠同時運行多ide
個進程;進程也是程序的一次執行過程,是系統運行程序的基本單位;系統運行一個程序便是一個進程從創函數
建、運行到消亡的過程。
工具
線程:線程是進程中的一個執行單元,負責當前進程中程序的執行,一個進程中至少有一個線程。一個進程學習
中是能夠有多個線程的,這個應用程序也能夠稱之爲多線程程序。
簡而言之:一個程序運行後至少有一個進程,一個進程中能夠包含多個線程
分時調度
全部線程輪流使用 CPU 的使用權,平均分配每一個線程佔用 CPU 的時間。
搶佔式調度
優先讓優先級高的線程使用 CPU,若是線程的優先級相同,那麼會隨機選擇一個(線程隨機性),Java使用的爲搶佔式調度。
多線程能夠提升cpu的利用率
大部分操做系統都支持多進程併發運行,如今的操做系統幾乎都支持同時運行多個程序。在同時運行的程序,」感受這些軟件好像在同一時刻運行着「。
實際上,CPU(中央處理器)使用搶佔式調度模式在多個線程間進行着高速的切換。對於CPU的一個核而言,某個時刻,只能執行一個線程,而 CPU的在多個線程間切換速度相對咱們的感受要快,看上去就是在同一時刻運行。 其實,多線程程序並不能提升程序的運行速度,但可以提升程序運行效率,讓CPU的使用率更高。
Java使用 java.lang.Thread 類表明線程,全部的線程對象都必須是Thread類或其子類的實例。每一個線程的做用是 完成必定的任務,實際上就是執行一段程序流即一段順序執行的代碼。Java使用線程執行體來表明這段程序流。 Java中經過繼承Thread類來建立並啓動多線程的步驟以下:
步驟:
代碼:
/*測試類中的代碼*/ public class DemoThread { public static void main(String[] args) { MyThread mt = new MyThread("線程1"); mt.start(); // 啓動線程1的任務 MyThread mt2 = new MyThread("線程2"); mt2.start(); // 啓動線程1的任務 } } /*定義的線程類代碼*/ public class MyThread extends Thread { public MyThread(String name) { super(name); } @Override public void run() { for (int i = 0; i < 20; i++) { System.out.println(getName() + "線程執行" + i); } } }
多個線程之間的程序不會影響彼此(好比一個線程崩潰了並不會影響另外一個線程)。
在Java中,main方法是程序執行的入口,也是Java程序的主線程。當在程序中開闢新的線程時,執行過程是這樣的。
執行過程
圖解執行過程:(上述代碼爲例)
構造方法
經常使用方法
代碼:
//【代碼測試類】 public class Main01 { public static void main(String[] args) { MyThread mt = new MyThread("線程1"); mt.start(); // 打印線程名稱 System.out.println(mt.getName()); System.out.println("當前線程是" + Thread.currentThread().getName()); // 每間隔一秒鐘打印一個數字 for (int i = 0; i < 60; i++) { System.out.println(i); try { // sleep拋出了異常,須要處理異常 Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } } //【MyThread類】 public class MyThread extends Thread{ public MyThread(){ super(); } // 構造函數中調用父類構造函數傳入線程名稱 public MyThread(String name) { super(name); } @Override public void run() { // 打印線程名稱 System.out.println(this.getName()); System.out.println("當前線程是" + Thread.currentThread().getName()); } }
翻閱API後得知建立線程的方式總共有兩種,一種是繼承Thread類方式,一種是實現Runnable接口方式 。
Runnable使用步驟
代碼
// 測試類 public class Main01 { public static void main(String[] args) { // 建立Runnable對象 RunnableImpl ra = new RunnableImpl(); // 建立線程對象並傳入Runnable對象 Thread th = new Thread(ra); // 啓動並執行線程任務 th.start(); } } // 【Runnable實現類】 public class RunnableImpl implements Runnable { @Override public void run() { System.out.println("線程任務1"); } }
總結
經過實現Runnable接口,使得該類有了多線程類的特徵。run()方法是多線程程序的一個執行目標。全部的多線程代碼都在run方法裏面。Thread類實際上也是實現了Runnable接口的類。
在啓動的多線程的時候,須要先經過Thread類的構造方法Thread(Runnable target) 構造出對象,而後調用Thread對象的start()方法來運行多線程代碼。
實際上全部的多線程代碼都是經過運行Thread的start()方法來運行的。所以,無論是繼承Thread類仍是實現Runnable接口來實現多線程,最終仍是經過Thread的對象的API來控制線程的,熟悉Thread類的API是進行多線程編程的基礎。
Runnable對象僅僅做爲Thread對象的target,Runnable實現類裏包含的run()方法僅做爲線程執行體。
而實際的線程對象依然是Thread實例,只是該Thread線程負責執行其target的run()方法。
建立線程方式2好像比建立線程方式1操做要麻煩一些,爲什麼要畫蛇添足呢?
由於若是一個類繼承Thread,則不適合資源共享。可是若是實現了Runable接口的話,則很容易的實現資源共享。
總結:實現Runnable接口比繼承Thread類所具備的優點:
擴展:
在java中,每次程序運行至少啓動2個線程。一個是main線程,一個是垃圾收集線程。由於每當使用
java命令執行一個類的時候,實際上都會啓動一個JVM,每個JVM其實在就是在操做系統中啓動了一個進程。
使用線程的內匿名內部類方式,能夠方便的實現每一個線程執行不一樣的線程任務操做。
簡而言之,使用匿名內部類能夠簡化代碼。
格式:new 類名/接口名(){ //重寫父類/接口中的方法 }
代碼
// 匿名內部類建立線程方式1 new Thread(){ @Override public void run() { System.out.println(Thread.currentThread().getName()); } }.start(); // 匿名內部類建立線程方式2 new Thread(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()); } }).start();
多個線程執行同一個任務並操做同一個數據時,就會形成數據的安全問題。咱們經過如下案例來看線程安全問題。
案例需求:
電影院要賣票,咱們模擬電影院的賣票過程。假設要播放的電影是 「皮卡丘大戰葫蘆娃」,本次電影的座位共100個 (本場電影只能賣100張票)。
咱們來模擬電影院的售票窗口,實現多個窗口同時賣 「葫蘆娃大戰奧特曼」這場電影票(多個窗口一塊兒賣這100張票) 須要窗口,採用線程對象來模擬;須要票,Runnable接口子類來模擬 。
案例代碼實現:
//【操做票的任務代碼類】 public class RunnableImpl implements Runnable { // 線程任務要操做的數據 private int ticket = 100; @Override public void run() { while (true){ if(ticket>0){ System.out.println(Thread.currentThread().getName() + "正在賣第" + ticket+"張票"); ticket--; } } } } //【測試類】 public class Main01 { public static void main(String[] args) { // 建立線程任務 RunnableImpl ra = new RunnableImpl(); // 建立第一個線程執行線程任務 new Thread(ra).start(); // 建立第二線程執行線程任務 new Thread(ra).start(); // 建立第三個線程執行線程任務 new Thread(ra).start(); } }
執行結果及問題
緣由
總結:這種問題,幾個窗口(線程)票數不一樣步了,這種問題稱爲線程不安全。
線程安全問題都是由全局變量及靜態變量引發的。若每一個線程中對全局變量、靜態變量只有讀操做,而無寫 操做,通常來講,這個全局變量是線程安全的;如有多個線程同時執行寫操做,通常都須要考慮線程同步, 不然的話就可能影響線程安全。
上述咱們知道,線程安全問題是由於線程在操做數據時不一樣步形成的,因此只要可以實現操做數據同步,就能夠解決線程安全問題。
同步指的就是,當一個線程執行指定同步的代碼任務時,其餘線程必須等該線程操做完畢後再執行。
根據案例描述:窗口1線程進入操做的時候,窗口2和窗口3線程只能在外等着,窗口1操做結束,窗口1和窗口2和窗口3纔有機會進入代碼 去執行。也就是說在某個線程修改共享資源的時候,其餘線程不能修改該資源,等待修改完畢同步以後,才能去搶奪CPU 資源,完成對應的操做,保證了數據的同步性,解決了線程不安全的現象。
爲了保證每一個線程都能正常執行原子操做,Java引入了線程同步機制(synchronize)。
那麼怎麼去使用呢?有三種方式完成同步操做:
同步代碼塊:synchronized關鍵字能夠用於方法中的某個區塊中,表示只對這個區塊的資源實行互斥訪問。
格式:synchronized(同步鎖){ 須要同步操做的代碼 }
代碼:
//【測試類】 public class Main01 { public static void main(String[] args) { // 建立線程任務 RunnableImpl ra = new RunnableImpl(); // 建立第一個線程執行線程任務 new Thread(ra).start(); // 建立第二線程執行線程任務 new Thread(ra).start(); // 建立第三個線程執行線程任務 new Thread(ra).start(); } } //【線程任務類】 public class RunnableImpl implements Runnable { // 線程任務要操做的數據 private int ticket = 100; // 定義線程鎖對象(任意對象) Object obj = new Object(); @Override public void run() { while (true){ synchronized (obj){ if(ticket>0){ try { Thread.sleep(10); System.out.println(Thread.currentThread().getName()+"在售賣第" + ticket + "張票"); ticket--; } catch (InterruptedException e) { e.printStackTrace(); } } } } } }
同步方法:使用synchronized修飾的方法,就叫作同步方法,保證A線程執行該方法的時候,其餘線程只能在方法外等着
格式:public synchronized void method(){ // 可能會產生線程安全問題的代碼 }
代碼:
//【測試類】 public class Main01 { public static void main(String[] args) { // 建立線程任務 RunnableImpl ra = new RunnableImpl(); // 建立第一個線程執行線程任務 new Thread(ra).start(); // 建立第二線程執行線程任務 new Thread(ra).start(); // 建立第三個線程執行線程任務 new Thread(ra).start(); } } //【線程任務類】 public class RunnableImpl implements Runnable { // 線程任務要操做的數據 private int ticket = 100; @Override public void run() { while (true) { func(); } } public synchronized void func() { if (ticket > 0) { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "在售賣第" + ticket + "張票"); ticket--; } } }
Lock:java.util.concurrent.locks.Lock 機制提供了比synchronized代碼塊和synchronized方法更普遍的鎖定操做, 同步代碼塊/同步方法具備的功能Lock都有,除此以外更強大,更體現面向對象。
方法:Lock鎖也稱同步鎖,加鎖與釋放鎖方法化了
代碼:
//【測試類】 public class Main01 { public static void main(String[] args) { // 建立線程任務 RunnableImpl ra = new RunnableImpl(); // 建立第一個線程執行線程任務 new Thread(ra).start(); // 建立第二線程執行線程任務 new Thread(ra).start(); // 建立第三個線程執行線程任務 new Thread(ra).start(); } } //【線程任務類】 public class RunnableImpl implements Runnable { // 線程任務要操做的數據 private int ticket = 100; // 建立鎖對象 Lock lock = new ReentrantLock(); @Override public void run() { while (true) { // 開啓同步鎖 lock.lock(); if (ticket > 0) { try { Thread.sleep(10); System.out.println(Thread.currentThread().getName() + "在售賣第" + ticket + "張票"); ticket--; } catch (InterruptedException e) { e.printStackTrace(); }finally { // 釋放同步鎖 lock.unlock(); } } } } }
當線程被建立並啓動之後,它既不是一啓動就進入了執行狀態,也不是一直處於執行狀態。在線程的生命週期中, 有幾種狀態呢?在API中 java.lang.Thread.State 這個枚舉中給出了六種線程狀態:
咱們不須要去研究這幾種狀態的實現原理,咱們只需知道在作線程操做中存在這樣的狀態。那咱們怎麼去理解這幾 個狀態呢,新建與被終止仍是很容易理解的,咱們就研究一下線程從Runnable(可運行)狀態與非運行狀態之間 的轉換問題。
Timed Waiting在API中的描述爲:一個正在限時等待另外一個線程執行一個(喚醒)動做的線程處於這一狀態。
單獨 的去理解這句話,真是玄之又玄,其實咱們在以前的操做中已經接觸過這個狀態了,在哪裏呢? 在咱們寫賣票的案例中,爲了減小線程執行太快,現象不明顯等問題,咱們在run方法中添加了sleep語句,這樣就 強制當前正在執行的線程休眠(暫停執行),以「減慢線程」。
其實當咱們調用了sleep方法以後,當前執行的線程就進入到「休眠狀態」,其實就是所謂的Timed Waiting(計時等 待),那麼咱們經過一個案例加深對該狀態的一個理解。
案例:實現一個計數器,計數到100,在每一個數字之間暫停1秒,每隔10個數字輸出一個字符串 。
代碼:
public class MyThread extends Thread { public void run() { for (int i = 0; i < 100; i++) { if ((i) % 10 == 0) { System.out.println("‐‐‐‐‐‐‐" + i); } System.out.print(i); try { Thread.sleep(1000); System.out.print(" 線程睡眠1秒!\n"); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) { new MyThread().start(); } }
總結:經過案例能夠發現,sleep方法的使用仍是很簡單的。咱們須要記住下面幾點:
注意:sleep()中指定的時間是線程不會運行的最短期。所以,sleep()方法不能保證該線程睡眠到期後就 開始馬上執行。
圖解:
Blocked狀態在API中的介紹爲:一個正在阻塞等待一個監視器鎖(鎖對象)的線程處於這一狀態 。
咱們已經學完同步機制,那麼這個狀態是很是好理解的了。好比,線程A與線程B代碼中使用同一鎖,若是線程A獲 取到鎖,線程A進入到Runnable狀態,那麼線程B就進入到Blocked鎖阻塞狀態。
這是由Runnable狀態進入Blocked狀態。除此Waiting以及Time Waiting狀態也會在某種狀況下進入阻塞狀態。
Wating狀態在API中介紹爲:一個正在無限期等待另外一個線程執行一個特別的(喚醒)動做的線程處於這一狀態。
咱們經過一段代碼來 學習一下:
代碼:
// 鎖對象 public static Object obj = new Object(); public static void main(String[] args) { // 消費者線程 new Thread(new Runnable() { @Override public void run() { while (true) { synchronized (obj) { System.out.println(Thread.currentThread().getName() + "-顧客1:老闆包子好了嗎?"); try { // 等待 obj.wait(); } catch (InterruptedException e) { e.printStackTrace(); } // 喚醒以後要執行的代碼 System.out.println(Thread.currentThread().getName() + "顧客1:能夠吃包子了。"); System.out.println("--------------------------------------"); } } } }).start(); // 生產者線程 new Thread(new Runnable() { @Override public void run() { while (true) { try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (obj) { System.out.println("等待3秒後..."); System.out.println(Thread.currentThread().getName() + "老闆說:包子好了!"); // 喚醒 obj.notify(); } } } }).start(); }
分析:
經過上述案例咱們會發現,一個調用了某個對象的 Object.wait 方法的線程會等待另外一個線程調用此對象的 Object.notify()方法 或 Object.notifyAll()方法 。
其實waiting狀態並非一個線程的操做,它體現的是多個線程間的通訊,能夠理解爲多個線程之間的協做關係, 多個線程會爭取鎖,同時相互之間又存在協做關係。就比如在公司裏你和你的同事們,大家可能存在晉升時的競 爭,但更多時候大家更可能是一塊兒合做以完成某些任務。
當多個線程協做時,好比A,B線程,若是A線程在Runnable(可運行)狀態中調用了wait()方法那麼A線程就進入 了Waiting(無限等待)狀態,同時失去了同步鎖。假如這個時候B線程獲取到了同步鎖,在運行狀態中調用了 notify()方法,那麼就會將無限等待的A線程喚醒。注意是喚醒,若是獲取到鎖對象,那麼A線程喚醒後就進入 Runnable(可運行)狀態;若是沒有獲取鎖對象,那麼就進入到Blocked(鎖阻塞狀態)。
圖解:
概念:多個線程在處理同一個資源,可是處理的動做(線程的任務)卻不相同。
好比:線程A用來生成包子的,線程B用來吃包子的,包子能夠理解爲同一資源,線程A與線程B處理的動做,一個是生產,一個是消費,那麼線程A與線程B
爲何要處理線程間的通訊
多個線程併發執行時, 在默認狀況下CPU是隨機切換線程的,當咱們須要多個線程來共同完成一件任務,而且咱們 但願他們有規律的執行, 那麼多線程之間須要一些協調通訊,以此來幫咱們達到多線程共同操做一份數據。
如何保證線程間通訊有效利用資源
多個線程在處理同一個資源,而且任務不一樣時,須要線程通訊來幫助解決線程之間對同一個變量的使用或操做。 就是多個線程在操做同一份數據時, 避免對同一共享變量的爭奪。也就是咱們須要經過必定的手段使各個線程能有效的利用資源。而這種手段即—— 等待喚醒機制。
這是多個線程間的一種協做機制。談到線程咱們常常想到的是線程間的競爭(race),好比去爭奪鎖,但這並非故事的所有,線程間也會有協做機制。就比如在公司裏你和你的同事們,大家可能存在在晉升時的競爭,但更多時候大家更可能是一塊兒合做以完成某些任務。
就是在一個線程進行了規定操做後,就進入等待狀態(wait()), 等待其餘線程執行完他們的指定代碼事後 再將其喚醒(notify());在有多個線程進行等待時, 若是須要,可使用 notifyAll()來喚醒全部的等待線程。
wait/notify 就是線程間的一種協做機制。
等待喚醒機制就是用於解決線程間通訊的問題的,使用到的3個方法的含義以下:
wait
線程再也不活動,再也不參與調度,進入 wait set 中,所以不會浪費 CPU 資源,也不會去競爭鎖了,這時的線程狀態便是 WAITING。它還要等着別的線程執行一個特別的動做,也便是「通知(notify)」在這個對象上等待的線程從wait set 中釋放出來,從新進入到調度隊列(ready queue)中。
notify
則選取所通知對象的 wait set 中的一個線程釋放;例如,餐館有空位置後,等候就餐最久的顧客最早
入座。
notifyAll
則釋放所通知對象的 wait set 上的所有線程。
注意事項
注意1:
哪怕只通知了一個等待的線程,被通知線程也不能當即恢復執行,由於它當初中斷的地方是在同步塊內,而此刻它已經不持有鎖,因此她須要再次嘗試去獲取鎖(極可能面臨其它線程的競爭),成功後才能在當初調用 wait 方法以後的地方恢復執行。
總結以下:
若是能獲取鎖,線程就從 WAITING 狀態變成 RUNNABLE 狀態;
不然,從 wait set 出來,又進入 entry set,線程就從 WAITING 狀態又變成 BLOCKED 狀態
注意2:
wait方法與notify方法必需要由同一個鎖對象調用。由於:對應的鎖對象能夠經過notify喚醒使用同一個鎖對象調用的wait方法後的線程。
wait方法與notify方法是屬於Object類的方法的。由於:鎖對象能夠是任意對象,而任意對象的所屬類都是繼承了Object類的。
wait方法與notify方法必需要在同步代碼塊或者是同步函數中使用。由於:必需要經過鎖對象調用這2個方 法。
等待喚醒機制其實就是經典的「生產者與消費者」的問題。
就拿生產包子消費包子來講等待喚醒機制如何有效利用資源。
包子鋪線程生產包子,吃貨線程消費包子。當包子沒有時(包子狀態爲false),吃貨線程等待,包子鋪線程生產包子(即包子狀態爲true),並通知吃貨線程(解除吃貨的等待狀態),由於已經有包子了,那麼包子鋪線程進入等待狀態。接下來,吃貨線程可否進一步執行則取決於鎖的獲取狀況。若是吃貨獲取到鎖,那麼就執行吃包子動做,包子吃完(包子狀態爲false),並通知包子鋪線程(解除包子鋪的等待狀態),吃貨線程進入等待。包子鋪線程可否進一步執行則取決於鎖的獲取狀況。
包子類代碼
public class BaoZi { // 包子皮 String pi; // 包子餡 String xian; // 包子的狀態 true有包子,false沒有包子,默認是false boolean flag = false; }
生產者代碼
public class BaoZiPu extends Thread { // 包子 BaoZi bz; public BaoZiPu(BaoZi bz){ this.bz = bz; } // 線程任務 @Override public void run() { int i = 0; // 用來交替生產不一樣的包子 while (true){ synchronized (bz){ // 判斷是否有包子 if(bz.flag){ // 有包子,則進入wait狀態 try { bz.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } // 沒有有包子,則等待3秒鐘 try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } // 生產包子 if(i%2==0){ bz.pi="蔬菜麪皮"; bz.xian="三鮮"; }else if(i%2==1) { bz.pi = "米麪皮"; bz.xian = "牛肉"; } bz.flag = true; i++; System.out.println("包子鋪線程" + Thread.currentThread().getName() + ":生產好了" + bz.pi+bz.xian + "包子" ); // 喚醒相同鎖的其餘線程 bz.notify(); } } } }
消費者代碼
public class ChiHuo extends Thread{ // 包子 BaoZi bz; public ChiHuo(BaoZi bz) { this.bz = bz; } // 線程任務 @Override public void run() { while (true){ synchronized (bz){ if(!bz.flag){ // 沒有包子,則等待 try { bz.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } // 有包子,則吃包子 System.out.println("吃貨線程" + Thread.currentThread().getName() + ":正在吃" + bz.pi + bz.xian + "包子"); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } // 三秒鐘後,吃完包子,並喚醒其餘同步鎖線程 System.out.println("吃貨線程" + Thread.currentThread().getName() +"吃完了包子"); bz.flag = false; // 沒有包子了 System.out.println("-----------------------------------------------------"); bz.notify(); } } } }
測試代碼
public class Test { public static void main(String[] args) { // 建立包子對象 BaoZi bz = new BaoZi(); // 建立生產者 new BaoZiPu(bz).start(); // 建立消費者 new ChiHuo(bz).start(); } }
咱們使用線程的時候就去建立一個線程,這樣實現起來很是簡便,可是就會有一個問題:
若是併發的線程數量不少,而且每一個線程都是執行一個時間很短的任務就結束了,這樣頻繁建立線程就會大大下降系統的效率,由於頻繁建立線程和銷燬線程須要時間。
那麼有沒有一種辦法使得線程能夠複用,就是執行完一個任務,並不被銷燬,而是能夠繼續執行其餘的任務?
在Java中能夠經過線程池來達到這樣的效果。
其實就是一個容納多個線程的容器,其中的線程能夠反覆使用,省去了頻繁建立線程對象的操做,無需反覆建立線程而消耗過多資源。
Java裏面線程池的頂級接口是 java.util.concurrent.Executor
,可是嚴格意義上講 Executor 並非一個線程 池,而只是一個執行線程的工具。真正的線程池接口是 java.util.concurrent.ExecutorService
。
要配置一個線程池是比較複雜的,尤爲是對於線程池的原理不是很清楚的狀況下,頗有可能配置的線程池不是較優的,所以在 java.util.concurrent.Executors 線程工廠類裏面提供了一些靜態工廠,生成一些經常使用的線程池。官方建議使用Executors工程類來建立線程池對象。
Executors類中有個建立線程池的方法以下:
獲取到了一個線程池ExecutorService 對象,那麼怎麼使用呢,在這裏定義了一個使用線程池對象的方法以下:
使用線程池中線程對象的步驟:
代碼
public class Test { public static void main(String[] args) { // 線程任務對象1 Runnable task = new Runnable(){ @Override public void run() { System.out.println(Thread.currentThread().getName() + ":執行了任務1"); } }; // 線程任務對象2 Runnable task2 = new Runnable(){ @Override public void run() { System.out.println(Thread.currentThread().getName() + ":執行了任務2"); } }; // 建立線程池 ExecutorService pool = Executors.newFixedThreadPool(2); // 提交任務 pool.submit(task); pool.submit(task2); pool.submit(task); pool.submit(task2); pool.submit(task); pool.submit(task2); // 關閉線程池,不建議關閉 // pool.shutdown(); } }