Java多線程(三) 五、線程的通信 5.1 wait() 與 notify() 和 notifyAll() 介紹: wait():令當前線程掛起並放棄CPU、同步資源並等待,使別的線程可訪問並修改共用資源,而當前線程排隊等候其他線程調用notify() 或 notifyAll() 方法喚醒,喚醒後 ...
Java多線程(三)
目錄五、線程的通信
5.1 wait() 與 notify() 和 notifyAll() 介紹:
- wait():令當前線程掛起並放棄CPU、同步資源並等待,使別的線程可訪問並修改共用資源,而當前線程排隊等候其他線程調用notify() 或 notifyAll() 方法喚醒,喚醒後等待重新獲得對監視器的所有權後才能繼續執行。
- notify():喚醒正在排隊等待同步資源的線程中優先順序最高者結束等待。
- notifyAll ():喚醒正在排隊等待資源的所有線程結束等待。
-
這三個方法只有在 synchronized 方法 或 synchronized 代碼塊中才能使用,否則會報 java.lang.IllegalMonitorStateException異常。
-
因為這三個方法必須有鎖對象調用,而任意對象都可以作為 synchronized 的同步鎖, 因此這三個方法只能在Object類中聲明。
5.2 wait() 的使用:
- 在當前線程中調用方法: 對象名.wait() 。
- 使當前線程進入等待(某對象)狀態 ,直到另一線程對該對象發出 notify (或notifyAll) 為止。
- 調用方法的必要條件:當前線程必須具有對該對象的監控權(加鎖)。
- 調用此方法後,當前線程將釋放對象監控權 ,然後進入等待。
- 在當前線程被 notify 後,要重新獲得監控權,然後從斷點處繼續代碼的執行。
5.3 notify() / notifyAll() 的使用
- 在當前線程中調用方法: 對象名.notify()。
- 功能:喚醒等待該對象監控權的一個/所有線程。
- 調用方法的必要條件:當前線程必須具有對該對象的監控權(加鎖)。
5.4 經典例題:生產者/消費者問題
題目描述:
生產者(Productor)將產品交給店員(Clerk),而消費者(Customer)從店員處取走產品,店員一次只能持有固定數量的產品(比如:20),如果生產者試圖生產更多的產品,店員會叫生產者停一下,如果店中有空位放產品了再通知生產者繼續生產;如果店中沒有產品了,店員會告訴消費者等一下,如 果店中有產品了再通知消費者來取走產品。
class Clerk{
private int productCount = 0;
//生產產品
public synchronized void produceProduct() {
if(productCount < 20){
productCount++;
System.out.println(Thread.currentThread().getName() + ":開始生產第" + productCount + "個產品");
notify();
}else{
//等待
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//消費產品
public synchronized void consumeProduct() {
if(productCount > 0){
System.out.println(Thread.currentThread().getName() + ":開始消費第" + productCount + "個產品");
productCount--;
notify();
}else{
//等待
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Producer extends Thread{//生產者
private Clerk clerk;
public Producer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
System.out.println(getName() + ":開始生產產品.....");
while(true){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.produceProduct();
}
}
}
class Consumer extends Thread{//消費者
private Clerk clerk;
public Consumer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
System.out.println(getName() + ":開始消費產品.....");
while(true){
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.consumeProduct();
}
}
}
public class ProductTest {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Producer p1 = new Producer(clerk);
p1.setName("生產者1");
Consumer c1 = new Consumer(clerk);
c1.setName("消費者1");
Consumer c2 = new Consumer(clerk);
c2.setName("消費者2");
p1.start();
c1.start();
c2.start();
}
}
六、JDK5.0新增多線程創建方式
6.1 多線程的創建方式之三:實現Callable介面
- 創建一個實現 Callable 的實現類
- 實現call方法,將此線程需要執行的操作聲明在call() 中。
- 創建 Callable 介面實現類的對象。
- 將此 Callable 介面實現類的對象作為傳遞到 FutureTask 構造器中,創建 FutureTask 的對象。
- 將 FutureTask 的對象作為參數傳遞到 Thread 類的構造器中,創建 Thread 對象,並調用 start() 。
- 獲取 Callable 中 call 方法的返回值。(可選)
//1.創建一個實現Callable的實現類
class NumThread implements Callable{
//2.實現call方法,將此線程需要執行的操作聲明在call()中
@Override
public Object call() throws Exception {
int sum = 0;
for (int i = 1; i <= 100; i++) {
if(i % 2 == 0){
System.out.println(i);
sum += i;
}
}
return sum;
}
}
public class ThreadNew {
public static void main(String[] args) {
//3.創建Callable介面實現類的對象
NumThread numThread = new NumThread();
//4.將此Callable介面實現類的對象作為傳遞到FutureTask構造器中,創建FutureTask的對象
FutureTask futureTask = new FutureTask(numThread);
//5.將FutureTask的對象作為參數傳遞到Thread類的構造器中,創建Thread對象,並調用start()
new Thread(futureTask).start();
try {
//6.獲取Callable中call方法的返回值
//get()返回值即為FutureTask構造器參數Callable實現類重寫的call()的返回值。
Object sum = futureTask.get();
System.out.println("總和為:" + sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
-
與使用 Runnable 相比, Callable功能更強大些:
- 相比run()方法,可以有返回值。
- 方法可以拋出異常。
- 支持泛型的返回值。
- 需要藉助 FutureTask 類,比如獲取返回結果。
-
Future 介面:
- 可以對具體Runnable、Callable任務的執行結果進行取消、查詢是否完成、獲取結果等。
- FutrueTask 是 Futrue 介面的唯一的實現類。
- FutureTask 同時實現了Runnable, Future介面。它既可以作為 Runnable 被線程執行,又可以作為 Future 得到 Callable 的返回值。
6.2 多線程的創建方式之四:使用線程池
-
線程池的引入:
經常創建和銷毀、使用量特別大的資源,比如併發情況下的線程, 對性能影響很大。
-
線程池的思路:
提前創建好多個線程,放入線程池中,使用時直接獲取,使用完放回池中。可以避免頻繁創建銷毀、實現重覆利用。
-
使用線程池的好處:
-
提高響應速度(減少了創建新線程的時間)
-
降低資源消耗(重覆利用線程池中線程,不需要每次都創建)
-
便於線程管理
corePoolSize:核心池的大小
maximumPoolSize:最大線程數
keepAliveTime:線程沒有任務時最多保持多長時間後會終止
-
-
線程池的使用:
(1)提供指定線程數量的線程池。
(2)執行指定的線程的操作,需要提供實現 Runnable 介面或 Callable 介面實現類的對象。
(3)關閉連接池。
class NumberThread implements Runnable{
@Override
public void run() {
for(int i = 0;i <= 100;i++){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
}
class NumberThread1 implements Runnable{
@Override
public void run() {
for(int i = 0;i <= 100;i++){
if(i % 2 != 0){
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
}
public class ThreadPool {
public static void main(String[] args) {
//1. 提供指定線程數量的線程池
ExecutorService service = Executors.newFixedThreadPool(10);
//2.執行指定的線程的操作。需要提供實現Runnable介面或Callable介面實現類的對象
service.execute(new NumberThread());//適合適用於Runnable
service.execute(new NumberThread1());//適合適用於Runnable
// service.submit(Callable callable);//適合使用於Callable
//3.關閉連接池
service.shutdown();
}
}
- 線程池相關API
- JDK 5.0 起提供了線程池相關API:ExecutorService 和 Executors 。
- ExecutorService:真正的線程池介面。常見子類 ThreadPoolExecutor。
// 執行任務/命令,沒有返回值,一般用來執行 Runnable
void execute(Runnable command)
// 執行任務,有返回值,一般又來執行 Callable
Future submit(Callable task)
// 關閉連接池
void shutdown()
- Executors:工具類、線程池的工廠類,用於創建並返回不同類型的線程池。
// 創建一個可根據需要創建新線程的線程池
Executors.newCachedThreadPool()
// 創建一個可重用固定線程數的線程池
Executors.newFixedThreadPool(n)
// 創建一個只有一個線程的線程池
Executors.newSingleThreadExecutor()
// 創建一個線程池,它可安排在給定延遲後運行命令或者定期地執行。
Executors.newScheduledThreadPool(n)
-
線程池的屬性設置
-
ExecutorService 是一個介面,裡面並沒有設置線程池屬性的方法,故不能用該介面的對象來對線程池進行屬性設置。
-
設置線程池屬性需要通過創建 ExecutorService 介面的實現類 ThreadPoolExecutor 的對象,調用該實現類的方法進行設置。
-
ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
//設置線程池的屬性
service1.setCorePoolSize(15);
service1.setKeepAliveTime();