Java 多執行緒入門
基本概念
- 程式 :是指為完成特定任務 ,用某種程式語言編寫的一組指令的集合 ,一段靜態程式碼 。
- 程序 :是指程式的一次執行過程 ,或正在執行的一個程式 。出生 -> 存在 -> 消亡 》生命週期
- 執行緒 :程式內部的執行路徑 。
例如 :360 木馬查殺 、磁碟清理 、修復 。可以同時進行 支援多 執行緒 。
每個執行緒用於獨立的 虛擬機器棧 和 程式計數器 (pc)
一個 Java應用程式 java.exe 至少有三個執行緒 ,主執行緒 gc 垃圾回收執行緒,異常處理執行緒
並行與併發
- 並行 :多個 CPU 同時執行多個任務 。例如 :多個人同時做不同的事。
- 併發 :一個 CPU(採用時間片) 同時執行多個任務 。例如 :秒殺 ,多個人做同一件事 。
多執行緒優點等
1、提高應用程式的相應 。對圖形化介面更有意義 ,可增強使用者體驗 。
2、提高計算機系統 CPU 的利用率
3、改善程式結構 ,將既長又複雜的程序分為多個執行緒 ,獨立執行 ,利於理解和修改 。
何時需要多執行緒
- 程式需要同時執行兩個或多個任務
- 程式需要實現一些需要等待的任務時,如使用者輸入 、檔案讀寫 操作 、網路操作 、搜尋等 。
- 需要一些後臺執行的程式時
建立多執行緒方式一
多執行緒的建立 ,方式一 :繼承於Thread 類
1、建立一個繼承於 Thread類 的子類
MyThread extends Thread
2、重寫 Thread 類的 run() 方法 , 將此執行緒執行的操作宣告在 run() 方法中 。
@Override public void run() {
3、建立 Thread類的子類的物件 例項
MyThread thread = new MyThread();
4、通過此物件呼叫 start() 方法
1、啟動當前執行緒 ,2、呼叫當前執行緒的 run() 方法
thread.start();
執行緒的常用方法
方法名稱 | 作用 |
---|---|
void start() | 啟動執行緒 ,並執行物件的 run() 方法 |
run() | 執行緒在被排程時執行的操作 |
String getName() | 返回執行緒的名稱 |
void setName(String name) | 設定執行緒名稱 |
static Thread currentThread() | 返回當前執行緒 。 |
static void yield() | 執行緒讓步 暫停當前正在執行的執行緒 ,把執行機會讓給優先順序更高(或相同)的執行緒 |
join() | 當某個程式執行流中呼叫其他執行緒的 join() 方式時,呼叫執行緒將被阻塞,直到 join() 方法加入的join執行緒執行完為止 。 線上程a中呼叫 執行緒b的join(),此時執行緒b進入阻塞狀態 ,直到 執行緒b完全執行完以後 ,執行緒a才結束阻塞狀態 |
static void sleep(long millis) | 等待時間 (指定時間 :毫秒)令當前活動執行緒在指定時間段內放棄對 CPU 的控制 ,時間到後重新排隊 。 |
boolean isAlive() | 返回 boolean ,判斷執行緒是否存活 |
stop() | 強制執行緒生命週期結束 (不推薦使用 ) |
執行緒優先順序的設定
-
執行緒的優先順序等級
-
MAX_PRIORITY = 10
-
MIN_PRIORITY = 1
-
NORM_PRIORITY = 5
-
-
涉及的方法
- getPriority() :返回執行緒優先值
- setPriority(int newPriority) :改變執行緒的優先順序
-
注意事項 :
- 執行緒建立時 ,繼承父執行緒的優先順序 。
- 低優先順序只是獲得排程的概率低 ,並非一定 高優先順序之後才被呼叫 。
建立多執行緒方式二
建立多執行緒的方式二 :實現 Runnable 介面
- 1、建立一個實現了 Runnable 介面的類
- 2、實現類去 重寫 Runnable 中的抽象方法 :run()
- 3、建立實現類的物件 (例項化)
- 4、將此物件引數傳遞到 Thread 類的構造器中 ,建立 Thread 類的物件
- 5、通過 Thread 類的物件呼叫 start()
兩種方式的比較 :
1、實現Runnable 沒有類的單繼承的侷限性
2、實現 Runnable 更適合來處理多個執行緒有共享資料的情況 。
public class Thread implements Runnable { // Thread類 實際上也是 實現 Runnable 介面
執行緒的生命週期
- JDK 中 用 Thread.State 類定義了執行緒的幾種狀態 。
完整的生命週期 經歷五種狀態 :
1、新建狀態(NEW):執行緒被建立後 ,就進入了新建狀態 。 比如 : Thread t1 = new Thread();
2、就緒狀態(Runnable):也被稱為 “可執行狀態” 。執行緒被建立後 ,其他執行緒呼叫 start()
方法來啟動執行緒 。
例如 : t1.start()
處於就緒狀態的執行緒 ,隨時可能被CPU排程執行 。
3、執行狀態(Running):執行緒獲取CPU許可權進行執行任務 。但需要注意 :執行緒只能從就緒狀態進入到執行狀態 。
4 、阻塞狀態(Blocked):阻塞狀態是因為執行緒因為某種原因放棄CPU使用權 ,暫時停止執行 。直到執行緒進入就緒狀態,才有機會轉到執行狀態 。阻塞的情況分為三種 :
- 1、等待阻塞 :通過呼叫執行緒的
wait()
方法 ,讓執行緒等待某工作的完成 。 - 2、同步阻塞 :執行緒在獲取
synchronized
同步鎖失敗(因為鎖被其他執行緒所佔用) ,它會進入到同步阻塞狀態 。 - 3、其他阻塞 :通過呼叫執行緒的
sleep()
或join()
或發出了 I/O請求時 ,執行緒會進入到阻塞狀態 。當sleep()
狀態超時、join()
等待執行緒終止或者超時、或者 I/O 處理完畢時 ,執行緒重新轉入就緒狀態 。
5、死亡狀態(Dead):執行緒執行完了或者因異常退出了 run()
方法 ,該執行緒結束生命週期 。
理解執行緒的安全問題
執行緒的同步
方式一 :同步程式碼塊
synchronized (同步監視器) {
// 需要被同步的程式碼
}
說明 :
1、操作共享資料的程式碼 ,即視為需要被同步的程式碼
2、共享資料 :多個執行緒共同操作的變數 。例如 > ticket(車票) 就是共享資料
3、同步監視器 ,俗稱 鎖 ,任何一個類的物件都可以充當鎖 。
要求 :多個執行緒必須共有同一把鎖 。
補充 :
1、實現 Runnable ,同步監視器 可以使用 this
充當鎖 ,代表當前呼叫run方法的物件 。
2、繼承 Thread ,同步監視器 可以使用 當前類 充當 鎖 ,類 也是物件 當前類.class
方式二 :同步方法
public synchronized void show() {
// 需要被同步的程式碼
}
同步方法總結 :
1、同步方法依然涉及到同步監視器 ,但是不需要我們顯式的宣告
2、非靜態的同步方法 ,同步監視器為 this
靜態的同步方法 ,同步監視器 為 當前類本身
實現 Runnable ,同步方法不需要使用 static 關鍵字
繼承 Thread ,同步方法 需要使用 static 關鍵字
死鎖的問題
Lock鎖方式解決執行緒安全問題
JDK 5.0新增
-
Lock 鎖 是介面 ,是控制多個執行緒對共享資源進行訪問的工具 。
-
ReentrantLock 類實現了 Lock ,可以顯式加鎖、釋放鎖 。【常用】與 synchronized相同的併發性 。
-
1、例項化
ReentrantLock
Lock lock = new ReentrantLock();
- 2、呼叫鎖的反法 lock()
try{
lock.lock()
// 需要被同步的程式碼
}
- 3、呼叫解鎖方法 :unlock()
finally {
lock.unlock();
}
面試題 :
1、synchronized 與 lock 的異同 ?
· synchronized 自動釋放鎖 ,程式碼塊鎖 和方法鎖
· lock 手動釋放鎖 ,只有程式碼塊鎖 ,
· 使用順序:
Lock --》同步程式碼塊 --》 同步方法 。
同步機制練習
class Account{
private double balance;
public Account(double balance) {
this.balance = balance;
}
public synchronized void deposit(double amt) {
if (amt > 0) {
balance += amt;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "存錢成功:賬戶餘額:" + balance);
}
}
}
class MyThreadRunnable implements Runnable{
private Account account;
public MyThreadRunnable(Account account) {
this.account = account;
}
@Override
public void run() {
for (int i = 0; i < 3; i++) {
account.deposit(1000);
}
}
}
public class ThreadRunnableTest {
public static void main(String[] args) {
Account account = new Account(0);
MyThreadRunnable runnable = new MyThreadRunnable(account);
Thread thread01 = new Thread(runnable);
Thread thread02 = new Thread(runnable);
thread01.setName("甲");
thread02.setName("乙");
thread01.start();
thread02.start();
}
}
執行緒的通訊
涉及執行緒通訊的 方法 ,只能夠在 同步程式碼塊 或者 同步方法中 使用 。
方法名稱 | 作用 |
---|---|
wait() | 一旦執行此方法,當前執行緒就進入阻塞狀態 ,並釋放同步監視器 |
notify() | 一旦執行此方法 ,就會喚醒被 wait 的一個執行緒 。如果有多個執行緒被 wait ,就喚醒優先順序高的那個執行緒 |
notifyAll() | 一旦執行此方法 ,就會喚醒所有被 wait 的執行緒 |
上述方法必須使用在 同步程式碼塊 或 同步方法中 。
上訴方法的呼叫者 必須是同步程式碼程式碼塊或同步方法中的同步監視器,否則會出現異常 IllegalMonitorStateException
都定義在 Object 類 中 。
面試題 :sleep() 和 wait() 的異同 ?
1、宣告位置不同 。Thread類中 宣告 sleep() ,Object類中宣告 wait()
2、呼叫要求不同 。sleep() 可以在任何需要出現的地方呼叫 。wait() 只能在同步程式碼塊或者同步方法 中使用 。
3、是否釋放同步鎖 。如果都在 同步程式碼塊或同步方法中執行 ,sleep 不會釋放鎖 ,wait 會釋放鎖
生產者消費者 例題
Resource.java 【工廠】
public class Resource {
int c = 0;
public synchronized void t1() {
if (c < 20) {
c++;
System.out.println("開始生產第" + c + "個商品");
} else {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.notify();
}
public synchronized void put() {
if (c > 0) {
System.out.println("開始消費第" + c + "個商品");
c--;
} else {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.notify();
}
}
producerExample.java 【生產者】
public class producerExample implements Runnable{
private Resource resource;
public producerExample(Resource resource) {
this.resource = resource;
}
@Override
public void run() {
while (true) {
resource.t1();
}
}
}
consumerExample.java 【消費者】
public class consumerExample implements Runnable{
private Resource resource;
public consumerExample(Resource resource) {
this.resource = resource;
}
@Override
public void run() {
while (true) {
resource.put();
}
}
}
Cashier.java 【超市 ,收銀員】
public class Cashier {
private int number;
private String merchandise;
public void setMerchandise(String merchandise) {
this.merchandise = merchandise;
}
public String getMerchandise() {
return merchandise;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
}
測試類
public class Test01 {
public static void main(String[] args) {
Resource resource = new Resource();
producerExample producerExample = new producerExample(resource);
consumerExample consumerExample = new consumerExample(resource);
new Thread(producerExample).start();
new Thread(consumerExample).start();
}
}
建立多執行緒方式三 :實現 Callable
JDK 5.0 新增
- 1、建立一個實現 Callable 的實現類
class MyCallableTest implements Callable {
- 2、Callable 類的 call() 方法 ,將此執行緒需要執行的操作宣告在 call() 中 。
@Override
public Object call() throws Exception {
- 3、建立 Callable 介面實現類的物件
MyCallableTest myCallableTest = new MyCallableTest();
- 4、建立 FutureTask 物件 ,將 Callable 介面的實現類物件作為引數傳遞到 FutureTask 構造器中
FutureTask task = new FutureTask(myCallableTest);
- 5、建立 Thread物件 ,將 FutureTask 的物件作為引數傳遞到 Thread 類的構造器中 ,並呼叫 start() 。
new Thread(task).start();
- 6、如果需要獲取返回值 ,使用 FutureTask 物件 呼叫 get() 方法 。【可選 】
Object o = task.get();
如何理解 Callable 與 Runnable 的區別 ?
1、call() 可以有返回值
2、call() 可以丟擲異常 ,可以被捕獲異常 ,獲取異常資訊 。
3、Callable 支援泛型 。
使用執行緒池的好處
使用執行緒池
-
背景 :經常建立和銷燬 ,使用量特別大的資源 。比如併發情況下的執行緒 ,對效能影響很大 。
-
思路 :提前建立號多個執行緒 ,放入執行緒池中 ,使用時直接獲取 ,用完放回池中 。可以避免頻繁地建立和銷燬 ,實現重複利用 。
-
好處 :
- 提高響應速度 (減少建立執行緒的時間)
- 降低資源消耗 (重複利用執行緒 )
- 便於執行緒管理
- CorePoolSize :核心池的大小
- MaximumPoolSize :最大執行緒數
- KeepAliveTim :執行緒沒有任務時 最多保持多長時間後會終止 。
-
JDK 5.0 起提供了 執行緒池相關的API :
ExecutorService
、Executors
-
ExecutorService :執行緒池介面 ,常見子類
ThreadPoolExecutor
-
<T> Future<T> submit(Callable<T> task);// 執行任務/命令 有返回值 。 適用於 實現Callable介面
-
void execute(Runnable command); // 執行任務 ,無返回值 適用於 實現Runnable介面
-
void shutdown(); // 關閉連線池
-
-
Executors :工具類 、執行緒池的工廠類 。用於建立並返回不同型別的執行緒池 。
-
Executors.newFixedThreadPool(int nThreads) //建立一個可重用固定執行緒數的執行緒池
-
Executors.newCachedThreadPool() // 建立一個可根據需要建立新執行緒的執行緒池
-
Executors.newSingleThreadExecutor() // 建立一個只有一個執行緒的執行緒池
-
Executors.newScheduledThreadPool(int corePoolSize) // 建立一個執行緒池 ,它可安排在給定延遲後執行命令或者定期地執行
-
使用 :
- 1、提供指定執行緒數量的執行緒池
ExecutorService service = Executors.newFixedThreadPool(10);
- 2、執行指定的執行緒的操作 ,需要提供實現 Runnable介面 或Callable 介面的實現類的物件
service.execute(myCallableTest); // 適合 實現Runnable介面
service.submit(myCallableTest); // 適合 實現Callable介面
- 關閉連線池
service.shutdown();
寫一個執行緒安全的懶漢式 【使用同步機制 修改為執行緒安全 。】餓漢式
class Bank{
private Bank(){}
private static Bank instance = null;
private static Bank getInstance() {
if (instance == null) {
synchronized (Bank.class) {
if (instance == null) {
instance = new Bank();
}
}
}
return instance;
}
}