## 一、概述
世間萬物都可以同時完成很多工作。例如,人體可以同時進行呼吸、血液循環、思考問題等活動。用戶既可以使用計算機聽歌,也可以編寫文檔和發送郵件,而這些活動的完成可以同時進行。這種同時執行多個操作的“思想”在 Java 中被稱為并發,而將并發完成的每一件事稱為線程。
在 Java 中,并發機制非常重要,但并不是所有程序語言都支持線程。在以往的程序中,多以一個任務完成以后再進行下一個任務的模式進行,這樣下一個任務的開始必須等待前一個任務的結束。Java 語言提供了并發機制,允許開發人員在程序中執行多個線程,每個線程完成一個功能,并與其他線程并發執行。這種機制被稱為多線程。
> Windows 系統是多任務操作系統,它以進程為單位。一個進程是一個包含有自身地址的程序,每個獨立執行的程序都稱為進程,也就是正在執行的程序。
> 系統可以分配給每個進程一段有限的執行 CPU 的時間(也稱為 CPU 時間片),CPU 在這段時間中執行某個進程,然后下一個時間段又跳到另一個進程中去執行。由于 CPU 切換的速度非常快,給使用者的感受就是這些任務似乎在同時運行,所以使用多線程技術后,可以在同一時間內運行更多不同種類的任務。
線程可以理解成是在進程中獨立運行的子任務。比如,QQ.exe 運行時就有很多的子任務在同時運行。像好友視頻、下載文件、傳輸數據、發送表情等,這些不同的任務或者說功能都可以同時運行,其中每一項任務完全可以理解成是“線程”在工作,傳文件、聽音樂、發送圖片表情等功能都有對應的線程在后臺默默地運行。
## 二、線程的創建
### 2.1 通過 Thread 類創建線程
```
public class MyThread extends Thread {
public void run() {
System.out.println(getName() + " 線程正在運行");
}
}
```
當一個類繼承 Thread 類后,就可以在該類中覆蓋 run() 方法,將實現線程功能的代碼寫入 run() 方法中,然后調用 Thread 類的 start() 方法啟動線程。
```
public class ThreadTest {
public static void main(String[] args) {
System.out.println("主線程開始運行");
Thread thread = new MyThread();
thread.start();
// thread.run();
System.out.println("主線程運行結束");
}
}
```
> 如果 start() 方法調用一個已經啟動的線程,系統將會拋出 IllegalThreadStateException 異常。
```
主線程開始運行
主線程運行結束
Thread-0 線程正在運行
```
MyThread 類中的 start() 方法通知“線程規劃器”此線程已經準備就緒,等待調用線程對象的 run() 方法。這個過程其實就是讓系統安排一個時間來調用 Thread 中的 run() 方法,也就是使線程得到運行,啟動線程,具有異步執行的效果。
```
主線程開始運行
Thread-0 線程正在運行
主線程運行結束
```
如果調用代碼 thread.run() 就不是異步執行了,而是同步,那么此線程對象并不交給“線程規劃器”來進行處理,而是由 main 主線程來調用 run() 方法,也就是必須等 run() 方法中的代碼執行完后才可以執行后面的代碼。
```
public class MyThread extends Thread {
public MyThread() {}
public MyThread(String name) {
super(name);
}
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + " 線程運行第" + i + "次");
}
}
}
public class ThreadTest {
public static void main(String[] args) {
System.out.println("主線程開始運行");
MyThread mt = new MyThread("myThread");
t.start();
for (int i = 0; i < 100; i++) {
System.out.println("主線程運行第" + i + "次");
}
System.out.println("主線程運行結束");
}
}
```
從上面的運行結果來看,MyThread 類中 run() 方法執行的時間要比主線程晚。這也說明在使用多線程技術時,代碼的運行結果與代碼執行順序或調用順序是無關的。同時也驗證了線程是一個子任務,CPU 以不確定的方式,或者說以隨機的時間來調用線程中的 run() 方法。
```
public class MyThread extends Thread {
private int i;
public MyThread3(int i) {
super();
this.i = i;
}
public void run() {
System.out.println("當前數字:" + i);
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
MyThread m = new MyThread(i);
m.start();
}
}
}
```
從運行結果中可以看到,雖然調用時數字是有序的,但是由于線程執行的隨機性,導致輸出的數字是無序的,而且每次順序都不一樣。除了異步調用之外,同步執行線程 start() 方法的順序不代表線程啟動的順序。
*****
【選擇】下列選項中,哪兩句的說法是錯誤的()(選擇兩項)
```
A 線程是比進程還要小的運行單位
B Thread 類位于 java.thread 包下
C run() 方法用于啟動線程
D CPU 使用時間片輪轉的工作方法,可以讓多個程序輪流占用 CPU,達到同時運行的效果
```
【閱讀】以下代碼中,在(1)處加入哪條語句能成功啟動線程()
```
public class ThreadOne extends Thread {
public void run() {
System.out.println("執行 ThreadOne 進程");
}
}
public class ThreadTest {
public static void main(String[] args) {
ThreadOne one = new ThreadOne();
// (1)
}
}
```
【選擇】通過 Thread 類創建線程時要()(選擇三項)
```
A 繼承 Thread 類
B 在子類中重寫 run() 方法
C 實現 Runnable 接口
D 調用 start() 方法啟動線程
```
【編程】通過繼承 Thread 類的方式創建線程,并在線程體中通過循環打印輸出如演示所示的內容。
```
打印機正在打印1
打印機正在打印2
打印機正在打印3
打印機正在打印4
打印機正在打印5
打印機正在打印6
打印機正在打印7
打印機正在打印8
打印機正在打印9
打印機正在打印10
```
### 2.2 實現 Runnable 接口創建線程
如果要創建的線程類已經有一個父類,這時就不能再繼承 Thread 類,因為 Java 不支持多繼承,所以可以通過實現 Runnable 接口來應對這樣的情況。
> 從 JDK 的 API 中可以發現,實質上 Thread 類實現了 Runnable 接口,其中的 run() 方法正是對 Runnable 接口中 run() 方法的具體實現。
實現 Runnable 接口的程序會創建一個 Thread 對象,并將 Runnable 對象與 Thread 對象相關聯。Thread 類有如下兩個與 Runnable 有關的構造方法:
```
1. public Thread(Runnable r)
2. public Thread(Runnable r, String name)
```
使用 Runnable 接口啟動線程的基本步驟如下:
```
1. 創建一個 Runnable 對象。
2. 使用參數帶 Runnable 對象的構造方法創建 Thread 實例。
3. 調用 start() 方法啟動線程。
```
:-: 
【例題】案例演示如何實現 Runnable 接口,以及如何啟動線程。
```
public class MyRunnable implements Runnable {
public void run() {
System.out.println(Thread.currentThread().getName() + "運行中!");
}
public static void main(String[] args) {
Runnable rn = new MyRunnable();
Thread th1 = new Thread(rn);
th1.start();
Thread th2 = new Thread(rn);
th2.start();
}
}
```
```
public class MyRunnable implements Runnable {
int i = 0; // 此時多個線程共用一個成員變量
public void run() {
while(i < 10) {
System.out.println(Thread.currentThread().getName() + "運行第" + (i++) + "次");
}
}
public static void main(String[] args) {
Runnable rn = new MyRunnable();
Thread th1 = new Thread(rn);
th1.start();
Thread th2 = new Thread(rn);
th2.start();
}
}
```
*****
【選擇】用 Runnable 接口創建線程的主要工作如下,它們正確的先后順序為()(選擇一項)
```
1) 通過實現類的對象創建線程類的對象
2) 聲明實現 Runnable 接口的類
3) 調用 start() 方法啟動線程
4) 創建實現類的對象
5) 在實現類內實現 run() 方法
```
```
A 1-4-2-5-3
B 2-1-4-5-3
C 2-5-4-1-3
D 1-5-2-4-3
```
【編程】編寫代碼完成以下任務:
1. 通過實現 Runnable 接口的方式創建線程類 Cat 和 Dog,run() 方法實現的功能為:加入一個循環長度為3的循環,分別循環輸出信息`A cat`和`A dog`。
2. 在測試類中分別創建 Cat 和 Dog 類的對象,啟動兩個線程。
3. 在測試類中創建一個循環長度為3的for循環,打印輸出信息`main thread`。
```
main thread
main thread
main thread
Thread-1A dog
Thread-1A dog
Thread-1A dog
Thread-0A cat
Thread-0A cat
Thread-0A cat
```
## 三、線程的生命周期
### 3.1 線程的狀態和生命周期
線程具有生命周期,主要包括 7 種狀態,分別是出生狀態、就緒狀態、運行狀態、等待狀態、休眠狀態、阻塞狀態和死亡狀態。
1. 出生狀態:用戶在創建線程時所處的狀態,在用戶使用該線程實例調用`start()`方法之前,線程都處于出生狀態。
2. 就緒狀態:也稱可執行狀態,當用戶調用`start()`方法之后,線程處于就緒狀態。
3. 運行狀態:當線程得到系統資源后進入運行狀態。
4. 等待狀態:當處于運行狀態下的線程調用 Thread 類的`wait()`方法時,該線程就會進入等待狀態。進入等待狀態的線程必須調用 Thread 類的`notify()`方法才能被喚醒。`notifyAll()`方法是將所有處于等待狀態下的線程喚醒。
5. 休眠狀態:當線程調用 Thread 類中的`sleep()`方法時,則會進入休眠狀態。
6. 阻塞狀態:如果一個線程在運行狀態下發出輸入/輸出請求,該線程將進入阻塞狀態,在其等待輸入/輸出結束時,線程進入就緒狀態。對阻塞的線程來說,即使系統資源關閉,線程依然不能回到運行狀態。
7. 死亡狀態:當線程的`run()`方法執行完畢,線程進入死亡狀態。
> 提示:一旦線程進入可執行狀態,它會在就緒狀態與運行狀態下輾轉,同時也可能進入等待狀態、休眠狀態、阻塞狀態或死亡狀態。
*****
【選擇】關于線程的狀態和生命周期的說法,正確的是()(選擇兩項)
```
A 只有獲取到 CPU 的使用權,線程才能從可運行狀態轉為運行狀態
B 調用 start() 方法可以使線程處于可運行狀態
C 如果正在運行的線程異常終止,則線程會處于阻塞狀態
D 一個正在運行的線程,調用 join() 方法,則會處于終止狀態
```
### 3.2 sleep 方法的使用
```
public static void sleep(long millis) { ... }
```
* 指定的毫秒數內讓當前正在執行的線程休眠(暫停執行)。
* 這個正在執行的線程是指 Thread.currentThread() 返回的線程。
* 參數為休眠的時間,單位是毫秒。
```
public class MyRunnable implements Runnable {
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "運行第" + i + "次");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class SleepTest {
public static void main(String[] args) {
Runnable r = new MyRunnable();
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
t1.start();
t2.start();
}
}
```
*****
【選擇】以下說法錯誤的是()(選擇一項)
```
A sleep() 方法的參數是以毫秒為單位的
B 使用實現 Runnable 接口的方式創建線程,一定要重寫 run 方法
C 當創建線程對象,線程即進入創建狀態
D 調用 sleep 方法時,不需要處理異常
```
【閱讀】關于下列代碼,說法正確的是()
```
public class MyRunnable implements Runnable {
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println("正在運行" + i);
Thread.sleep(500);
}
}
}
public class Test {
public static void main(String[] args) {
Runnable m = new MyRunnable();
Thread th = new Thread(m);
th.start();
}
}
```
【編程】利用線程輸出`a~z`的26個字母(橫向輸出),要求每隔一秒鐘輸出一個字母。
```
abcdefghijklmnopqrstuvwxyz
```
### 3.3 join 方法的使用
```
1. public final void join() 等待調用該方法的線程結束后才能執行。
2. public final void join(long millis) 等待該線程終止的最長時間為 millis 毫秒。
```
```
public class MyRunnable implements Runnable {
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "正在執行第" + i + "次");
}
}
}
public class JoinTest {
public static void main(String[] args) {
Runnable r = new MyRunnable();
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
t1.start();
try {
t1.join();
} catch(InterruptedException e) {
e.printStackTrace();
}
t2.start();
}
}
```
*****
【選擇】下列關于 Thread 類中的 join() 方法說法錯誤的是()(選擇兩項)
```
A 調用 join() 方法可以使其他線程由正在運行狀態變成阻塞狀態
B join() 方法可以通過 Thread 類名直接訪問
C 子類中可以重寫 join() 方法
D join() 方法的作用是等待調用該方法的線程結束后才能執行
```
### 3.4 線程的優先級
> Java 為線程類提供了10個優先級,優先級可以用整數1~10表示,超過范圍會拋出異常。主線程優先級的默認值為 5。
* 優先級常量
```
MAX_PRIORITY:線程的最高優先級 10
MIN_PRIORITY:線程的最低優先級 1
NORM_PRIORITY:線程的默認優先級 5
```
* 使用 Thread 類中的 setPriority() 方法來設置線程的優先級。語法如下:
```
public final void setPriority(int newPriority);
```
* 使用 Thread 類中的 getPriority() 方法來獲取線程的優先級。語法如下:
```
public final int getPriority();
```
```
public class MyRunnable implements Runnable {
public void run() {
System.out.println("優先級為" + Thread.currentThread().getPriority());
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "正在運行" + i + "次");
}
}
public static void main(String[] args) {
int mainPriority = Thread.currentThread().getPriority(); // 獲取主線程的優先級
System.out.println("主線程的優先級為" + mainPriority);
Runnable r = new MyRunnable();
Thread t1 = new Thread(r, "線程1");
Thread t2 = new Thread(r, "線程2");
t1.setPriority(10);
t2.setPriority(Thread.MIN_PRIORITY);
t1.start();
t2.start();
}
}
```
*****
【選擇】下列說法正確的是()(選擇一項)
```
A 設置優先級的方法為 public int setPriority(int n)
B 優先級可用1-10的整數表示
C 獲取優先級的方法是 public void getPriorty()
D 在 Java 中,優先級高的線程一定會比優先級低的線程先運行
```
## 四、線程同步
* 各個線程是通過競爭 CPU 時間而獲得運行機會的
* 各個線程什么時候得到 CPU 時間,占用多久,是不可預測的
* 一個正在運行著的線程在什么地方被暫停是不確定的
```
public class Bank {
private int balance;
public Bank(int balance) {
this.balance = balance;
}
// getter setter ...
@Override
public String toString() {
return "Bank{balance=" + balance + "}";
}
// 存款
public void saveBalance(int value) {
// 獲取當前的賬號余額
int balance = getBalance();
try {
Thread.sleep(1000);
} catch(InterruptedException e) {
e.printStackTrace();
}
setBalance(balance + value);
// 輸出存款后的賬戶余額
System.out.println("存款后的賬戶余額為" + getBalance());
}
// 取款
public void drawBalance(int value) {
int balance = getBalance();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
setBalance(balance - value);
System.out.println("取款后的賬戶余額為" + getBalance());
}
}
public class SaveBalance implements Runnable {
Bank bank;
public SaveBalance(Bank bank) {
this.bank = bank;
}
public void run() {
bank.saveBalance(100);
}
}
public class DrawBalance implements Runnable {
Bank bank;
public DrawBalance(Bank bank) {
this.bank = bank;
}
public void run() {
bank.drawBalance(200);
}
}
public class BankTest {
public static void main(String[] args) {
Bank bank = new Bank(1000);
Runnable sb = new SaveBalance(bank);
Runnable db = new DrawBalance(bank);
Thread save = new Thread(sb);
Thread draw = new Thread(db);
save.start();
draw.start();
try {
save.join();
draw.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(bank);
}
}
```
為了處理共享資源競爭,可以使用同步機制。所謂同步機制,指的是兩個線程同時作用在一個對象上,應該保持對象數據的統一性和整體性。[](http://c.biancheng.net/java/)Java 提供 synchronized 關鍵字,為防止資源沖突提供了內置支持。
* 在一個類中,用 synchronized 關鍵字聲明的方法為同步方法。格式如下:
~~~
public [static] synchronized 類型名稱 方法名稱() { ... }
~~~
* synchronized 也可以作用于代碼塊。對于同步塊,synchronized 獲取的是參數中的對象鎖。格式如下:
```
synchronized(obj) { ... }
```
Java 有一個專門負責管理線程對象中同步方法訪問的工具——同步模型監視器,它的原理是為每個具有同步代碼的對象準備唯一的一把“鎖”。當多個線程訪問對象時,只有取得鎖的線程才能進入同步方法,其他訪問共享對象的線程停留在對象中等待。
```
public synchronized void saveBalance(int value) { ... }
public void drawBalance(int value) {
synchronized(this) { ... }
}
```
*****
【選擇】運行下列代碼,結果是()(選擇兩項)
```
// Barber(理發師) 類
public class Barber {
public void wash() {
synchronized(this) {
System.out.println("打濕頭發");
System.out.println("洗頭水");
System.out.println("沖洗");
}
}
public synchronized void cut() {
System.out.println("剪短");
System.out.println("燙發");
}
}
public class Cut extends Thread {
Barber b;
public Cut(Barber b) {
this.b = b;
}
public void run() {
b.cut();
}
}
public class Wash extends Thread {
Barber b;
public Wash(Barber b) {
this.b = b;
}
public void run() {
b.wash();
}
}
public class Test {
public static void main(String[] args) {
Barber b = new Barber();
Cut cut = new Cut(b);
Wash wash = new Wash(b);
cut.start();
wash.start();
}
}
```
```
A 剪短 打濕頭發 燙頭 洗頭水 沖洗
B 剪短 燙發 打濕頭發 洗頭水 沖洗
C 打濕頭發 洗頭水 沖洗 剪短 燙發
D 剪短 打濕頭發 洗頭水 沖洗 燙發
```
【選擇】下列對關鍵字 synchronized 說法不正確的是()(選擇一項)
```
A synchronized(同步),即協調不同線程之間的工作
B synchronized 關鍵字可以用在成員方法中
C 保證多個線程可以同時執行和結束
D 保證共享對象在同一時刻只能被一個線程訪問
```
## 五、線程間通信
```
wait(): 中斷方法的執行,使線程等待
notify(): 喚醒處于等待的某一個線程,使其結束等待
notifyAll(): 喚醒所有處于等待的線程,使他們結束等待
```
```
public class Count {
private int n;
boolean flag = false;
public synchronized int getN() throws InterruptedException{
if (!flag) wait();
System.out.println("消費:" + n);
flag = false;
notifyAll();
return n;
}
public synchronized void setN(int n) throws InterruptedException {
if (flag) wait();
System.out.println("生產:" + n);
this.n = n;
flag = true;
notifyAll();
}
}
public class Producer implements Runnable {
private Count count;
public Producer(Count count) {
this.count = count;
}
public void run() {
int i = 0;
while (true) {
try {
count.setN(i++);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Consumer implements Runnable {
private Count count;
public Consumer(Count count) {
this.count = count;
}
public void run() {
while (true) {
try {
count.getN();
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class CountTest {
public static void main(String[] args) {
Count count = new Count();
new Thread(new Producer(count)).start();
new Thread(new Consumer(count)).start();
}
}
```
*****
【選擇】以下說法錯誤的是()(選擇一項)
```
A wait() 方法用于使線程等待
B notify() 方法用于喚醒一個線程
C notifyAll() 方法用于喚醒多個線程
D 使用 wait() 方法阻塞的線程,可以不用喚醒
```
- 階段一 Java 零基礎入門
- 步驟1:基礎語法
- 第01課 初識
- 第02課 常量與變量
- 第03課 運算符
- 第04課 選擇結構
- 第05課 循環結構
- 第06課 一維數組
- 第08課 方法
- 第09課 數組移位與統計
- 第10課 基礎語法測試
- 第09課 基礎語法測試(含答案)
- 步驟2:面向對象
- 第01課 類和對象
- 第02課 封裝
- 第03課 學生信息管理
- 第04課 繼承
- 第05課 單例模式
- 第06課 多態
- 第07課 抽象類
- 第08課 接口
- 第09課 內部類
- 第10課 面向對象測試
- 第10課 面向對象測試(含答案)
- 步驟3:常用工具類
- 第01課 異常
- 第02課 包裝類
- 第03課 字符串
- 第04課 集合
- 第05課 集合排序
- 第06課 泛型
- 第07課 多線程
- 第08課 輸入輸出流
- 第09課 案例:播放器
- 第10課 常用工具測試(一)
- 第10課 常用工具測試(一)(答案)
- 第10課 常用工具測試(二)
- 第10課 常用工具測試(二)(答案)
- 階段二 從網頁搭建入門 JavaWeb
- 步驟1:HTML 與 CSS
- 第01課 HTML 入門
- 第01課 HTML 入門(作業)
- 第02課 CSS 入門
- 第02課 CSS 入門(作業)
- 第03課 CSS 布局
- 第03課 CSS 布局(作業)
- 步驟2:JavaScript 與前端案例
- 第01課 JavaScript 入門
- 第01課 JavaScript 入門(作業)
- 第02課 仿計算器
- 第03課 前端油畫商城案例
- 第04課 輪播圖
- 第05課 網頁搭建測試
- 第05課 網頁搭建測試(含答案)
- 步驟3:JavaScript 教程
- 入門
- 概述
- 基本語法
- 數據類型
- 概述
- 數值
- 字符串
- undefined, null 和布爾值
- 對象
- 函數
- 數組
- 運算符
- 算術運算符
- 比較運算符
- 布爾運算符
- 位運算符
- 運算順序
- 語法專題
- 數據類型的轉換
- 錯誤處理機制
- 標準庫
- String
- Date
- Math
- DOM
- 概述
- Document 節點
- 事件
- EventTarget 接口
- 事件模型
- 常見事件
- 階段三 數據庫開發與實戰
- 步驟1:初始數據庫操作
- 第01課 數據類型
- 第02課 表的管理
- 第03課 數據管理
- 第04課 常用函數
- 第05課 JDBC 入門
- 第06課 Java 反射
- 第07課 油畫商城
- 第08課 數據庫基礎測試
- 步驟2:MyBatis 從入門到進階
- 第01課 IntelliJ IDEA 開發工具入門
- 第02課 Maven 入門
- 第03課 工廠模式
- 第04課 MyBatis 入門
- 第05課 MyBatis 進階
- 第06課 商品信息管理
- 第07課 MyBatis 基礎測試
- 步驟3:Redis 數據庫與 Linux 下項目部署
- 第01課 Linux 基礎
- 第02課 Linux 下 JDK 環境搭建及項目部署
- 第03課 Redis 入門
- 階段四 SSM 到 Spring Boot 入門與綜合實戰
- 步驟1:Spring 從入門到進階
- 第01課 Spring 入門
- 第02課 Spring Bean 管理
- 第03課 Spring AOP
- 第04課 基于 AspectJ 的 AOP 開發
- 第05課 JDBC Template
- 第06課 Spring 事務管理
- 第07課 人員管理系統開發
- 第08課 Spring 從入門到進階測試
- 步驟2:Spring MVC 入門與 SSM 整合開發
- 第01課 Spring MVC 入門與數據綁定
- 第02課 Restful 風格的應用
- 第03課 SpringMVC 攔截器
- 第04課 辦公系統核心模塊
- 步驟3:Spring Boot 實戰
- 第01課 Spring Boot 入門
- 第02課 校園商鋪項目準備
- 第03課 校園商鋪店鋪管理
- 第04課 校園商鋪商品管理及前臺展示
- 第05課 校園商鋪框架大換血
- 步驟4:Java 面試
- 第01課 面試準備
- 第02課 基礎面試技巧
- 第03課 Web基礎與數據處理
- 第04課 主流框架