java多線程併發(一)(線程基礎)

以前一直搞不懂多線程、併發是什麼,今天看到了一篇文章,然後百度、谷歌了好些資料,總算有點頭緒了。

最近開始覺得寫技術博客是很好的一個習慣了,有些東西在網上看了之後,只看了一遍,過了陣子,基本上又忘了,這時如果整理下記下來,就算以後忘記了,還能在博客裏面找到。好記性不如爛博客(在一篇技術博文上看到的)。

以前對JVM內存結構分配不懂,一直以爲多個客戶端進行訪問服務器就是多線程併發,原來我錯了,熟悉了JVM內存結構之後,再回來想想,原來如此。。。


言歸正傳:

先熟悉下,一個java程序對應一個JVM實例,一個JVM只有一個堆空間,所以,每個java程序都有獨立的堆空間,不會彼此干擾。一個java程序的所有線程共享一個堆空間(應該不繞吧?如果一個java程序有多個線程運行(併發訪問靜態屬性、單例),就得考慮多線程併發訪問對象(堆數據)的問題。

先了解下java內存模型,看完之後再繼續看吧:http://blog.csdn.net/a_yyc1990/article/details/11479583


什麼是併發?

跟程序順序沒有關係,併發使得程序在同一時刻可以執行多個操作。


爲什麼需要併發?

併發可以提高程序的運行效率一個線程可能服務不了多個用戶,就可以用多個線程來解決。提升效率。


如果是單個線程的話,不用擔心出現同時操作資源出現錯誤的情況。多個線程操作的話,可能會相互影響(競爭或合作),比如同時訪問(修改)同一個資源(對象)。這個時候要考慮併發訪問的情況,如果不考慮,會引發難以預料的錯誤。多線程操作的是同一個堆區域裏面的數據。爲了避免出現數據的異常,我們要在程序裏面對併發訪問做出控制。


舉一個《thinking in java》第四版中的例子。有一個EvenGenerator類,它的next()方法用來生成偶數。如下:

public class EvenGenerator {
	private int currentValue = 0;
	private boolean cancled = false;

	public int next() {
		++currentValue; // 危險!
		++currentValue;
		return currentValue;
	}

	public boolean isCancled() {
		return cancled;
	}

	public void cancle() {
		cancled = true;
	}

}

另外有一個EvenChecker類,用來不斷地檢驗EvenGenerator的next()方法產生的是不是一個偶數,它實現了Runnable接口。

public class EvenChecker implements Runnable {

	private EvenGenerator generator;

	public EvenChecker(EvenGenerator generator) {
		this.generator = generator;
	}

	@Override
	public void run() {
		int nextValue;
		while (!generator.isCancled()) {
			nextValue = generator.next();
			if (nextValue % 2 != 0) {
				System.out.println(nextValue + "不是一個偶數!");
				generator.cancle();
			}
		}
	}

}

然後創建兩個EvenChecker來併發地對同一個EvenGenerator對象產生的數字進行檢驗。

public class Test {

	public static void main(String[] args) {
		EvenGenerator generator = new EvenGenerator();
		Thread t1 = new Thread(new EvenChecker(generator));
		Thread t2 = new Thread(new EvenChecker(generator));

		t1.start();
		t2.start();
	}

}

顯然,在一般情況下,EvenGenerator的next()方法產生的數字肯定是一個偶數,因爲在方法體裏進行兩次”++currentValue”的操作。但是運行這個程序,輸出的結果竟然像下面這樣(並不是每次都是這個一樣的結果,但是程序總會因這樣的情況而終止):

849701不是一個偶數!

錯誤出在哪裏呢?程序中有“危險”註釋的哪一行便可能引發潛在的錯誤。因爲很可能某個線程在執行完這一行只進行了一次遞增之後,CPU時間片被另外一個線程奪去,於是就生產出了奇數。

解決的辦法,就是給EvenGenerator的next()方法加上synchronized關鍵字,像這樣:

public synchronized int next() {
		++currentValue;
		++currentValue;
		return currentValue;
	}

這個時候這個方法就不會在併發環境下生產出奇數了。因爲synchronized關鍵字保證了一個對象在同一時刻,最多只有一個synchronized方法在執行。

synchronized

每一個對象本身都隱含着一個鎖對象,這個鎖對象就是用來解決併發問題的互斥量(mutex)。要調用一個對象的synchronized方法的線程,必須持有這個對象的鎖對象,在執行完畢之後,必須釋放這個鎖對象,以讓別的線程得到這個鎖對象。因爲一個對象僅有一個鎖對象,這就保證了在同一時刻,最多隻有一個線程能夠調用並執行這個對象的synchronized方法。其他想調用這個對象的synchronized方法的線程必須等待當前線程釋放鎖。就像上面舉的例子,在同一時刻,最多隻有一個EvenChecker能調用EvenGenerator的next()方法,這就保證了不會出現currentValue只遞增一次,CPU時間片就被別的線程奪去的情況。


參考自:http://blog.psjay.com/posts/summary-of-java-concurrency-two-synchronized-and-atomicity/


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章