Java多線程和併發編程實踐的學習心得----基礎篇2

Java多線程和併發編程實踐的學習心得----基礎篇2




二  java多線程的基本調度和簡單同步


1、線程的基本調度

線程調度是指系統爲線程分配處理器使用權的過程:協同式、搶佔式。

協同式線程的執行時間由線程本身控制,線程把自己的工作執行完了之後,要主動通知系統切換到另一個線程上。  壞處:線程執行時間不可控制。

搶佔式每個線程將由系統來分配執行時間,線程的切換不由線程本身來決定。Java使用該種調用方式。

線程優先級:在一些平臺上(操作系統線程優先級比Java線程優先級少)不同的優先級實際會變得相同;優先級可能會被系統自行改變。


線程的基本調度主要還是依靠Java線程類 中常用的方法;如 sleep(),Join(),yield(),setPriority()等方法以及和Object類中的wait(),notify(),notifyAll()等方法。
        
線程休眠sleep()顧名思義,線程休眠的目的是使線程讓出CPU的最簡單的做法之一,線程休眠時候,會將CPU資源交給其他線程,以便能輪換執行,當休眠一定時間後,線程會甦醒,進入準備狀態等待執行。線程休眠的方法是Thread.sleep(long millis) 和Thread.sleep(long millis, int nanos) ,均爲靜態方法,哪個線程調用sleep,就休眠哪個線程。


線程優先級setPriority():優先級高的線程獲取CPU資源的概率較大,優先級低的並非沒機會執行。線程的優先級用1-10之間的整數表示,數值越大優先級越高,默認的優先級爲5。子線程初始優先級與父線程相同。
線程的讓步含義就是使當前運行着線程讓出CPU資源,但是然給誰不知道,僅僅是讓出,線程狀態回到可運行狀態。
 


線程的讓步yield()線程的讓步使用Thread.yield()方法,yield() 爲靜態方法,功能是暫停當前正在執行的線程對象,並執行其他線程。



線程的合併join():   
線程的合併的含義就是將幾個並行線程的線程合併爲一個單線程執行,應用場景是當一個線程必須等待另一個線程執行完畢才能執行時可以使用join方法。

Object.wait() :顯而易見,這個和上面方法的區別了。這個方法的Java中的超類Object的靜態方法之一。效果是讓當前對象所在的線程進入等待狀態,就是停在那裏,一值傻等着,啥也不幹,就像睡着了,但不能自己醒來,必須由其他線程來叫醒。


Object.notify/Object.notifyAll() :和wait()方法一樣,該方法也Object的。作用就是叫醒wait()方法所在的線程。其中,Object.notify()是叫醒一個線程,顯然Object.notityAll()是叫醒所有線程,但是還是不能叫醒自己所在的線程。

  



2、線程的同步


首先來搞清楚一個很重要的問題:線程的同步是什麼?

這回不能顧名思義了。線程的同步並不是多個線程同意時間同時訪問資源,恰恰相反,線程同步的真實含義是“排隊”,多線程之間要排隊,一個一個對共享資源進行操作,而不是同時進行操作。 


關於線程同步,需要明白一下幾點:

  • 1 線程同步就是線程排隊。同步就是“排隊”。線程同步的目的就是避免線程“同步”執行。
  • 2 “共享”  :  只有共享資源的讀寫訪問才需要同步。如果不是共享資源,那麼就根本沒有同步的必要。 
  • 3 “變量”:    只有“變量”才需要同步訪問。如果共享的資源是固定不變的,那麼就相當於“常量”,線程同時讀取常量也不需要同步。至少一個線程修改共享資源,這樣的情況下,線程之間就需要同步。
  • 4  同步: 多個線程訪問共享資源的代碼有可能是同一份代碼,也有可能是不同的代碼;無論是否執行同一份代碼,只要這些線程的代碼訪問同一份可變的共享資源,這些線程之間就需要同步。


如何才能線程同步?


線程同步的基本實現思路還是比較容易理解的。我們可以給共享資源加一把鎖,這把鎖只有一把鑰匙。哪個線程獲取了這把鑰匙,纔有權利訪問該共享資源。這把鑰匙在Java中就是線程同步鎖。

那麼問題來了:同步鎖應該加在哪裏?

儘量把同步鎖加在共享資源上。同步鎖應該加在操作共享對象資源的代碼段上的。

在操作系統中,比如,文件系統,數據庫系統等,自身都提供了比較完善的同步鎖機制。我們不用另外給這些資源加鎖,這些資源自己就有鎖。 

但是最重要的問題來了,應該加什麼樣的鎖?

首先我麼知道訪問同一份共享資源的不同代碼段,應該加上同一個同步鎖;如果加的是不同的同步鎖,那麼根本就起不到同步的作用,沒有任何意義。
同時:同步鎖本身也一定是多個線程之間的共享對象。 


synchronized關鍵字:


Java語言裏面用synchronized關鍵字給代碼段加鎖。相當於不管哪一個線程A每次運行到這個方法時,都要檢查有沒有其它正在用這個方法的線程B(或者C D等),有的話要等正在使用這個方法的線程B(或者C D)運行完這個方法後再運行此線程A,沒有的話,直接運行 。

整個語法形式表現爲 :
synchronized(同步鎖) {
  // 訪問共享資源,需要同步的代碼段
}
這裏尤其要注意的就是,同步鎖本身一定要是共享的對象。


volatite 關鍵字:


Java 語言提供了一種稍弱的同步機制,即 volatile 變量.用來確保將變量的更新操作通知到其他線程,保證了新值能立即同步到主內存,以及每次使用前立即從主內存刷新. 當把變量聲明爲volatile類型後,編譯器與運行時都會注意到這個變量是共享的.

簡單一點說:Volatile就是通知編譯器和運行時注意下自己聲明過的變量,這個變量是線程共享的。




synchronized 和volatie的區別和具體用法將在下一節說明!



3 消費者與生產者模型


對於併發協助的多線程的模型來說,“生產者-消費者-倉儲”模型是經典的模型,沒有之一。很多時候都簡稱爲:
生產者和消費者模型。


對於此經典模型,應該知道一下4點:

  • 1、生產者僅僅在倉儲未滿時候生產,倉滿則停止生產。
  • 2、消費者僅僅在倉儲有產品時候才能消費,倉空則等待。
  • 3、當消費者發現倉儲沒產品可消費時候會通知生產者生產。
  • 4、生產者在生產出可消費產品時候,應該通知等待的消費者去消費。
     


實現此模型就要使用上面講的線程的基本調度(如wait(),notityAll()等)和簡單同步(synchronized關鍵字)中的方法了,正好來實踐一下:


package com.qyl.dome1;

public class ProducerConsumer {
	public static void main(String[] args) {
		SyncStack ss = new SyncStack();
		Producer p = new Producer(ss);
		Consumer c = new Consumer(ss);
		new Thread(p).start();
		//new Thread(p).start();
		//new Thread(p).start();
		new Thread(c).start();
	}
}

class Apple {
	int id; 
	Apple(int id) {
		this.id = id;
	}
	public String toString() {
		return "Apple : " + id;
	}
}

class SyncStack {
	int index = 0;
	Apple[] arrapple = new Apple[6];
	
	public synchronized void push(Apple apple) {
		while(index == arrapple.length) {
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		this.notifyAll();		
		arrapple[index] = apple;
		index ++;
	}
	
	public synchronized Apple pop() {
		while(index == 0) {
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		this.notifyAll();
		index--;
		return arrapple[index];
	}
}

class Producer implements Runnable {
	SyncStack ss = null;
	Producer(SyncStack ss) {
		this.ss = ss;
	}
	
	public void run() {
		for(int i=1; i<20; i++) {
			Apple apple = new Apple(i);
			ss.push(apple);
			System.out.println("生產了:" + apple);
			try {
				Thread.sleep((int)(Math.random() * 200));
			} catch (InterruptedException e) {
				e.printStackTrace();
			}			
		}
	}
}

class Consumer implements Runnable {
	SyncStack ss = null;
	Consumer(SyncStack ss) {
		this.ss = ss;
	}
	
	public void run() {
		for(int i=1; i<20; i++) {
			Apple apple = ss.pop();
			System.out.println("消費了: " + apple);
			try {
				Thread.sleep((int)(Math.random() * 1000));
			} catch (InterruptedException e) {
				e.printStackTrace();
			}			
		}
	}
}

上面的例子中:Apple是商品,SyncStack是倉庫,Consumer是消費者,Producer是生產者,他們協作共同完成生產和消費的供應鏈。
程序執行結果如下:
生產了:Apple : 1
消費了: Apple : 1
生產了:Apple : 2
消費了: Apple : 2
生產了:Apple : 3
生產了:Apple : 4
生產了:Apple : 5
生產了:Apple : 6
消費了: Apple : 6
生產了:Apple : 7
生產了:Apple : 8
生產了:Apple : 9
消費了: Apple : 9
生產了:Apple : 10
生產了:Apple : 11
消費了: Apple : 10
消費了: Apple : 11
生產了:Apple : 12
消費了: Apple : 12
生產了:Apple : 13
消費了: Apple : 13
生產了:Apple : 14
消費了: Apple : 14
生產了:Apple : 15
消費了: Apple : 15
生產了:Apple : 16
生產了:Apple : 17
消費了: Apple : 16
生產了:Apple : 18
消費了: Apple : 17
消費了: Apple : 18
生產了:Apple : 19
消費了: Apple : 19






 未完待續.......

在下一篇中將會比較深入的探討線程同步中的有關術語和概念。  


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