Java基礎--併發實用工具(1)

1.簡介

從一開始,Java就對多線程提供了內置支持。例如,可以通過實現Runnable接口或者擴展Thread類來創建新的線程;可以通過使用synchronized關鍵字來獲得同步支持;並且Object類庫定義了wait()和notify()方法支持線程間通信。總之,這種對多線程的內置支持是Java重要的革新之一,並且仍然是Java的主要優勢之一。
但是,因爲Java對多線程的原始支持在概念上仍然是簡單的,並不是對所有應用來說都是理想選擇,特別是對於大量使用多線程的應用。例如,原始的多線程支持並沒有提供一些高級特性,比如信號量、線程池以及執行管理器,而這些特性有助於創建功能強大的併發程序。
爲了滿足處理併發程序的需要,JDK5增加了併發實用工具,通常也被稱爲併發API。最初的併發實用工具提供了開發併發應用程序的許多特性,這些特性是程序員期待已久的。例如:提供了同步器(比如信號量)、線程池、執行管理器、鎖、一些併發集合以及使用線程獲取計算結果的流線化方式。
原始的併發API相當大,JDK7和JDK8的新增特性更是顯著增加了這一API的大小。正如您所期望的,圍繞併發實用工具的許多問題都很複雜。儘管如此,對於所有程序員而言,大致掌握併發API的工作原理是很重要的。即使在沒有嚴重依賴並行處理的程序中,同步器、可調用線程以及執行器這類特性,依然可以廣泛應用於各種情況。

2.同步器之Semaphore

在java.util.concurrent包中,提供了5個同步對象(器)。這裏將一個個地介紹,通過它們可以比較容易地處理一些以前比較難處理的情況。同步器本身並不和任何資源相關聯,也不應該和任何資源相關聯,同步器應該和需要同步的線程相關聯。對於需要同步的線程來說,資源只不過是操作數而已,對這些線程來說,重要的是知道能同時對資源能進行訪問的線程的數量,即資源信號量所持有的許可證數量。對同步器使用都是讓需要同步的線程使用同一個同步器,然後根據同步器本身的特性在不同情景下進行線程同步。
Semaphore類的中文翻譯爲信號量。其實現了經典的信號量。信號量通過其持有的許可證的數量的多少來決定是否讓線程訪問該資源,信號量維護的許可證的數量爲0時,禁止線程訪問,大於0時,允許線程訪問,並且許可證數量減一。
使用Semaphore的方式爲:創建一個信號量同步器Semaphore(int num),可以指定該信號量持有的初始許可證的數量(其實指不指定都沒有關係,畢竟可以在程序運行過程中以釋放許可證的方式增加許可證的數量,在下面的實例代碼中有體現);在對資源進行訪問之前先獲取許可證,可以獲取一個acquire(),也可以獲取多個acquier(int num),獲取幾個,信號量手中的許可證就要減少幾個;對資源進行訪問之後釋放許可證,可以釋放一個release(),也可以釋放多個release(int num),如果在線程終結之前沒有釋放的許可證就算沒了(下面的有關代碼有體現)。
Semaphore的簡單使用方式如下:
import java.util.concurrent.Semaphore;

public class SemaphoreTest {
	//創建控制線程訪問的一個信號量
	static Semaphore semaphore = new Semaphore(1);
	public static void main(String[] args) {
		//線程1用來給公共資源中的count加一
		new Thread(()->{
			try {
				semaphore.acquire();
			} catch (Exception e) {
				e.printStackTrace();
			}
			for(int i = 0;i<5;i++){
				CommonResource.count++;
				System.out.println("Set: "+CommonResource.count);
				//通常的哈,這裏睡上一會兒,給了另外一個線程運行的機會
				try {
					Thread.sleep(10);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
			semaphore.release();
		}).start();
		//線程2用來給公共資源中的count減一
		new Thread(()->{
			try {
				semaphore.acquire();
			} catch (Exception e) {
				e.printStackTrace();
			}
			for(int i = 0;i<5;i++){
				CommonResource.count--;
				System.out.println("Got: "+CommonResource.count);
				try {
					Thread.sleep(10);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
			semaphore.release();
		}).start();
//		運行結果
//		Set: 1
//		Set: 2
//		Set: 3
//		Set: 4
//		Set: 5
//		Got: 4
//		Got: 3
//		Got: 2
//		Got: 1
//		Got: 0
		//如果沒有加信號量,幾乎不可能出現這種情況,而加了信號量後,就肯定是這種情況咯
	}

}
//公共資源,即兩個線程(不一定是兩個可以是多個)要操作的資源
class CommonResource{
	static int count = 0;
}
以信號量的方式實現生產者-消費者案例的同步:
import java.util.concurrent.Semaphore;

public class ProducerCustomerWithSemaphore {
	/*
	 * 需要注意的是:
	 * 1.信號量初始許可證的個數僅僅是初始許可證個數
	 * 2.實際過程中可用的許可證的個數可以一直通過釋放來增加,就如本例
	 * 3.如果一個線程拿到了許可證,但是在終結之前沒有釋放該許可證,那許可證的總個數就少了一個,就如本例
	 */
	public static void main(String[] args) {
		Queue3 queue3 = new Queue3();
		//生產者
		new Thread(()->{
			while(queue3.n<6){
				queue3.set();
			}
		}).start();
		//消費者
		new Thread(()->{
			while(queue3.n<6){
				queue3.get();
			}
		}).start();
	}
//	運行結果:
//	Set: 1
//	Got: 1
//	Set: 2
//	Got: 2
//	Set: 3
//	Got: 3
//	Set: 4
//	Got: 4
//	Set: 5
//	Got: 5
//	Set: 6
//	Got: 6

}
class Queue3{
	int n;
	Semaphore semaphoreForCon;
	Semaphore semaphoreForPro;

	public Queue3(){
		semaphoreForCon = new Semaphore(0);
		semaphoreForPro = new Semaphore(1);
	}
	public void get(){
		//消費者
		try {
			semaphoreForCon.acquire();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("Got: "+n);
		semaphoreForPro.release();
	}
	public void set(){
		//生產者
		try {
			semaphoreForPro.acquire();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		n++;
		System.out.println("Set: "+n);
		semaphoreForCon.release();
	}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章