一、線程通信
1、簡介
確保線程能夠按照預定的順序執行并且能夠安全地訪問共享資源
使多條線程更好的進行協同工作
2、常用方法
void?wait() | |
void?notify(); | |
void notifyAll(); |
這些方法來自Object類,需要使用鎖對象進行調用
3、注意事項
(1)sleep方法和wait方法的區別
sleep是線程休眠,時間到了自動醒來,休眠時不會釋放鎖
wait是線程等待,需要其他線程進行喚醒,等待時會釋放鎖
(2)所有醒著的線程都有概率搶到CPU
(3)線程被喚醒之后(若搶到CPU),從之前進入等待的地方繼續往下執行
4、等待喚醒機制
使用 ReentrantLock 實現同步,并獲取 Condition 對象,使用 Condition 對象調用以下方法
void?await() | |
void?signal(); |
public class AwaitDemo {public static void main(String[] args) {Printer2 p = new Printer2();new Thread(new Runnable() {public void run() {while (true) {try {p.print1();} catch (InterruptedException e) {throw new RuntimeException(e);}}}}).start();new Thread(new Runnable() {public void run() {while (true) {try {p.print2();} catch (InterruptedException e) {throw new RuntimeException(e);}}}}).start();new Thread(new Runnable() {public void run() {while (true) {try {p.print3();} catch (InterruptedException e) {throw new RuntimeException(e);}}}}).start();}
}class Printer2 {int flag = 1;ReentrantLock myLock = new ReentrantLock();Condition c1 = myLock.newCondition();Condition c2 = myLock.newCondition();Condition c3 = myLock.newCondition();public void print1() throws InterruptedException {myLock.lock();if (flag != 1) {c1.await();}System.out.print(1);System.out.println(1);flag = 2;c2.signal();myLock.unlock();}public void print2() throws InterruptedException {myLock.lock();if (flag != 2) {c2.await();}System.out.print(2);System.out.println(2);flag = 3;c3.signal();myLock.unlock();}public void print3() throws InterruptedException {myLock.lock();if (flag != 3) {c3.await();}System.out.print(3);System.out.println(3);flag = 1;c1.signal();myLock.unlock();}
}循環輸出:
11
22
33
Tips:對于一個Condition對象,哪個線程最先使用該對象調用await方法,該對象就綁定到該線程
如果使用一個未綁定線程的Condition對象調用signal方法,將會隨機喚醒一個線程
5、生產者消費者模式
生產者消費者模式是一個十分經典的多線程協作的模式
包含了兩類線程:
生產者線程,用于生產數據
消費者線程,用于消費數據
為了解耦生產者和消費者的關系,通常會采用共享的數據區域 (緩沖區),就像是一個倉庫
生產者生產數據之后直接放置在共享數據區中,并不需要關心消費者的行為
消費者只需要從共享數據區中去獲取數據,并不需要關心生產者的行為
public class ProducerAndConsumer {public static void main(String[] args) {new Thread(new Producer()).start();new Thread(new Consumer()).start();}
}class SharedData {public static boolean flag = false;public static ReentrantLock lock = new ReentrantLock();public static Condition producer = lock.newCondition();public static Condition consumer = lock.newCondition();
}class Producer implements Runnable {@Overridepublic void run() {while (true) {SharedData.lock.lock();if (!SharedData.flag) {System.out.println("produce");try {Thread.sleep(2000);} catch (InterruptedException e) {throw new RuntimeException(e);}SharedData.flag = true;SharedData.consumer.signal();} else {try {SharedData.producer.await();} catch (InterruptedException e) {throw new RuntimeException(e);}}SharedData.lock.unlock();}}
}class Consumer implements Runnable {@Overridepublic void run() {while (true) {SharedData.lock.lock();if (SharedData.flag) {System.out.println("consume");try {Thread.sleep(2000);} catch (InterruptedException e) {throw new RuntimeException(e);}SharedData.flag = false;SharedData.producer.signal();} else {try {SharedData.consumer.await();} catch (InterruptedException e) {throw new RuntimeException(e);}}SharedData.lock.unlock();}}
}
二、線程生命周期
線程被創建并啟動以后,它并不是一啟動就進入了執行狀態,也不是一直處于執行狀態。
線程對象在不同的時期有不同的狀態
NEW(新建) | |
RUNNABLE(就緒) | |
BLOCKED(阻塞) | |
WAITING(等待) | |
TIMED_WAITING(計時等待) | |
TERMINATED(結束狀態) |
三、線程池
1、簡介
系統創建一個線程的成本是比較高的,因為它涉及到與操作系統交互
當程序中需要創建大量生存期很短暫的線程時,頻繁的創建和銷毀線程,就會嚴重浪費系統資源
將線程對象交給線程池維護
可以降低系統成本,提升程序的性能
實際開發中,線程資源必須通過線程池提供,不允許在線程中自行顯示創建線程
2、JDK 提供的線程池(實際開發中不使用)
Executors 中提供靜態方法來創建線程池
static?ExecutorService?newCachedThreadPool?() | |
static?newFixedThreadPool?(int?nThreads) |
3、自定義線程池
(1)ThreadPoolExecutor 類的構造方法:七個參數
ThreadPoolExecutor(int corePoolSize, // 核心線程數int maximumPoolSize, // 最大線程數 = 核心線程數 + 最大臨時線程數long keepAliveTime, // 等待時間,超過該時間就刪除臨時線程TimeUnit unit, // 等待時間的單位BlockingQueue<Runnable> workQueue, // 任務隊列(要指定最大任務數)ThreadFactory threadFactory, // 線程對象任務工廠(用于創建臨時對象)RejectedExecutorHandler handler // 拒絕策略
)
?
(2)拒絕策略
ThreadPoolExecutor.AbortPolicy? | |
ThreadPoolExecutor.DiscardPolicy | |
ThreadPoolExecutor.DiscardOldestPolicy | |
ThreadPoolExecutor.CallerRunsPolicy |
(3)注意事項
臨時線程什么時候創建?
線程任務數 > 核心線程數 + 任務隊列的數量
什么時候會開啟拒絕策略?
線程任務數 > 最大線程數 + 任務隊列的數量
public class ThreadPoolDemo {public static void main(String[] args) {ThreadPoolExecutor pool = new ThreadPoolExecutor(2,5,60,TimeUnit.SECONDS,new ArrayBlockingQueue<>(10),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());for (int i = 0; i < 16; i++) {pool.submit(new Runnable() {@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + " submitted");}});}}
}
四、單例設計模式
單例指單個實例,保證類的對象在內存中只有一份
使用場景:
如果創建一個對象需要消耗的資源過多,比如 I/O 與數據庫的連接
并且這個對象完全是可以復用的, 我們就可以考慮將其設計為單例的對象
class Single1 {private Single1() {}private static Single1 s = new Single1();public static Single1 getInstance() {return s;}
}class Single2 { // 延遲加載模式private Single2() {}private static Single2 s;public static Single2 getInstance() {if (s == null) {synchronized (Single2.class) {if (s == null) {s = new Single2();}}}return s;}
}