1. 程式人生 > 實用技巧 >Callable介面及執行緒池

Callable介面及執行緒池

執行緒池

執行緒容器,可設定執行緒分配的數量上限。

將預先建立執行緒物件存入池中,並重用執行緒池中的執行緒物件。

避免頻繁的的建立和銷燬。

常用的執行緒池介面和類

Executor :執行緒池的頂級介面

ExecutorService :執行緒池介面,可通過submit(Runnable task)提交任務程式碼。

Executors工廠類:通過此類可獲得一個執行緒池。

​ 通過newFixedThreadPool(int nThreads)獲取固定數量的執行緒池。

​ 通過newCachedThreadPool()獲得動態數量的執行緒池,如果不夠 則建立新的,沒有上限。

public class TestThreadPool {
	public static void main(String[] args) {
		//執行緒池介面(引用)    --->     Executors工具類(工廠類)
		ExecutorService es = Executors.newFixedThreadPool(4);
		
		Runnable task = new MyTask();
		
		es.submit(task); //submit()提交任務
		es.submit(task);
		es.submit(task);
		es.submit(task);
	}
}
//建立介面實現類
class MyTask implements Runnable{
	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			System.out.println(Thread.currentThread().getName() + " - " +i);
		}
	}
}

Callable介面

JDK5加入,與Runnable介面類似,實現之後代表一個執行緒任務。

有泛型返回值,可以宣告異常。

Future介面

概念:

​ 非同步接收ExecutorService.submit()所返回的狀態結果,當中包含了call()的返回值。

方法:

​ V get()以阻塞形式等待Future的非同步處理結果(call()的返回值)

使用

執行緒池執行Callable介面,Callable返回值由Future接收,Future當中的get() 方法就可以得到非同步返回值。

可以解決併發下的一些統計工作。

public class TestCallable {
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		System.out.println("程式開始");
		//建立執行緒池
		ExecutorService ex = Executors.newFixedThreadPool(3);
		//介面引用指向實現介面
		Callable<Integer> task1 = new MyTask1();
		Callable<Integer> task2 = new MyTask2();
		
		Future<Integer> f1 = ex.submit(task1);	//得到Callable執行緒介面的返回值
		Future<Integer> f2 = ex.submit(task2);
		
		Integer result1 = f1.get(); //以阻塞形式等待Future中的非同步處理結果(call的返回值)
		Integer result2 = f2.get(); //在沒有返回值以前,get無限期等待
		
		System.out.println(result1 + result2); //輸出5050
	}
}
//Mytask1類遵從Callable介面實現call()方法
class MyTask1 implements Callable<Integer>{
	@Override
	public Integer call() throws Exception {
		Thread.sleep(1000); //休眠1秒,觀察Future介面get方法
		Integer sum = 0;
		for (int i = 1; i <= 50; i++) {
			sum += i;
		}
		return sum; //返回計算的sum值
	}
}
//Mytask2類遵從Callable介面實現call()方法
class MyTask2 implements Callable<Integer>{
	@Override
	public Integer call() throws Exception {
		Thread.sleep(1000);
		Integer sum = 0;
		for (int i = 51; i <= 100; i++) {
			sum += i;
		}
		return sum; //返回計算的sum值
	}
}

同步和非同步的區別:

同步:

形容一次方法呼叫,同步一旦開始,呼叫者必須等待該方法返回,才能繼續。

非同步:

形容一次方法呼叫,非同步一旦開始,像是一次訊息傳遞,呼叫者告知之後立刻返回。二者競爭時間片,併發執行。

Lock介面

提供更多實用性的方法,功能更強大,效能更優越。

常用方法:

void lock() - 獲取鎖,如鎖被佔用,則等待。

boolean tryLock() - 嘗試獲取鎖(成功返回true,失敗返回false,不阻塞)

void unlock() - 釋放鎖

重入鎖

ReentrantLock:Lock介面的實現類,與synchronized一樣具有互斥鎖

功能。

Lock locker = new ReentrantLock(); //建立重入鎖物件
locker.lock();	//開啟鎖
try{
    鎖住程式碼塊
}finally{
    //考慮可能會出現異常,釋放鎖必須放入finally程式碼塊中,避免無法釋放
    locker.unlock();	//釋放鎖
}

讀寫鎖

ReentrantReadWriteLock:

  • 一種支援一寫多讀的同步鎖,讀寫分離,可分別分配讀鎖,寫鎖。

  • 支援多次分配讀鎖,使多個讀操作可以併發執行。

互斥規則:

寫 - 寫:互斥,阻塞。

讀 - 寫:互斥,讀阻塞寫,寫阻塞讀。

讀 - 讀:不互斥,不阻塞。

在讀操作遠遠高於寫操作的環境中,可在保障執行緒安全的情況下,提高執行效率。

//讀寫鎖
class Student {
    //建立讀寫鎖物件
    ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    //建立讀鎖
    ReentrantReadWriteLock.ReadLock readLock =rwl.readLock();
    //建立寫鎖
    ReentrantReadWriteLock.WriteLock writeLock = rwl.writeLock();

    private int value;  //屬性
    //讀方法
    public int getValue() throws InterruptedException {
        readLock.lock();    //開啟讀鎖
        try {
            Thread.sleep(1000); //休眠1秒,觀察讀的時間
            return value;
        } finally {
            readLock.unlock();  //關閉讀鎖
        }
    }
    //寫方法
    public void setValue(int value) throws InterruptedException {
        writeLock.lock();   //開啟寫鎖
        try {
            Thread.sleep(1000);
            this.value = value;
        } finally {
            writeLock.unlock(); //關閉寫鎖
        }
    }
}

最後總結:多執行緒相關的新舊對比