天天看點

java基礎——程序和線程

1.并行和并發 在學習線程之前,先了解一下并行和并發的概念 并發(concurrency)的實質是一個實體CPU(也可以多個實體CPU) 在若幹道程式之間多路複用,并發性是對有限實體資源強制行使多使用者共享以提高效率。 并行性(parallelism)指兩個或兩個以上事件或活動在同一時刻發生。在多道程式環境下,并行性使多個程式同一時刻可在不同CPU上同時執行。

java基礎——程式和線程

并發,是在同一個cpu上同時(不是真正的同時,而是看來是同時,因為cpu要在多個程式間切換)運作多個程式。

java基礎——程式和線程

并行,是每個cpu運作一個程式。

------------------------------------------------------ 【程序和線程】 程序是指一個記憶體中運作的應用程式。每個應用程式都有自己的一塊記憶體空間(記憶體空間是用來存放資料的),一個應用程式可以同時啟用多個程序。比如在Windows系統中,一個exe檔案就是一個程序(程序之間的通信很不友善)。 線程是指程序中一個執行任務(控制單元),一個程序可以同時并發運作多個線程,如:多線程下載下傳軟體。 多任務系統,該系統可以運作多個程序,一個程序也可以執行多個任務,一個程序可以包含多個線程。 一個程序至少有一個線程,為了提高效率,可以在一個程序中開啟多個執行任務,即多線程。

多程序:作業系統中同時運作的多個程式。 多線程:在同一個程序中同時運作的多個任務。

Windows環境下的任務管理器:在作業系統系統中允許多個任務,每一個任務就是一個程序,每一個程序也可以同時執行多個任務,每一個任務就是線程。 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ 程序與線程的差別: 程序: 有獨立的記憶體空間,程序中的資料存放空間(堆空間和棧空間)是獨立的,至少有一個線程。 線程 : 堆空間是共享的 ,棧空間是獨立的, 線程消耗的資源也比程序小 ,互相之間可以影響,又稱為輕型程序或程序元。 線程的先後執行取決于CPU排程器( JVM來排程 )。 多線程并發性可看作是多個線程在瞬間搶CPU資源,誰搶到資源誰就運作,這也造就了多線程的随機性。 Java程式的程序裡至少包含主線程和垃圾回收線程(背景線程) --------------------------------------------------------------------------------------------------------------------------------------------------------- 多線程的優勢 【 多線程優勢】 工作方式:多任務、并發的。 ①程序之間不能共享記憶體,而線程之間 共享記憶體 (堆記憶體)則很簡單。 ②系統建立程序時需要為該程序重新配置設定系統資源, 建立線程則代價小很多 ,是以實作多任務并發時,多線程效率更高。 ③ Java語言本身内置多線程功能的支援 ,而不是單純的作為底層系統的排程方式,進而簡化了多線程程式設計。

多線程下載下傳: 可以了解為一個線程就是一個檔案的下載下傳通道, 多線程也就是同時開起好幾個下載下傳通道 。當伺服器提供下載下傳服務時,使用下載下傳者是共享寬帶的,在優先級相同的情況下,總伺服器會對總下載下傳線程進行平均配置設定。(線程多的話,下載下傳快;現流行的下載下傳軟體都支援多線程) 多線程是為了同步完成多項任務,不是為了提供程式運作效率,而是通過 提高資源使用效率來提高系統的效率 。(是以買電腦的時候,也應該看看CPU的線程數)

寬帶帶寬:是以為(bit)計算,而下載下傳速度是以位元組(Byte)計算,1位元組(Byte)等于8位(bit),是以1024kb/s是代表上網帶寬為1024千位(1M),而下載下傳速度需要1024千位/秒(1024kb/s)帶寬除以8,得出128千位元組/秒(128KB/s)。

-------------------------------------------------------------------------------------------------------------------------------------------------------- 建立程序操作 【Java操作程序】 在Java代碼中如何去運作一個程序。(後面會講擷取程序中的資料(IO))。 ①Runtime類的exec()方法 ②ProcessBuilder的start()方法 command參數包含程式及其參數的字元串數組。

//java中如何開啟一個程序:運作一個記事本程式
public class ProcessDemo {
	public static void main(String[] args) throws Exception {
		//方式1 使用 runtime類的exec方法
		Runtime  runtime = Runtime.getRuntime();
		runtime.exec("notepad");
		//方式2:processbuilder
		ProcessBuilder pb= new ProcessBuilder("notepad");
		pb.start();
	}
}           

【建立和啟動線程】(傳統有兩種方式) ①繼承Thread類 ②實作Runnable接口 線程類(java.lang.Thread):Thread類和Thread的子類才能稱之為線程類。 别忘了主線程(main方法運作,表示主線程)。

①繼承Thread類: 步驟: 1.定義一個類A 繼承 于java.lang.Thread類 2.在A類中 覆寫 Thread類的run方法 3.在run方法中編寫需要執行的操作(run方法裡的代碼是 線程執行體 ) 4.在main方法(線程)中,建立線程對象,并啟動線程。 建立線程類的格式:A類 a=new A類(); 調用線程對象的start方法:a.start(); //啟動一個線程 注:千萬不要調用run方法,如果調用run方法等于是對象調用方法,依然還是隻有一個線程,并沒有開啟新的線程。

//播放音樂的線程類
class MusicTread extends java.lang.Thread{
	public void run() {
		for(int i = 0; i < 50; i++ )
		{
			System.out.println("播放音樂"+ i);
		}
	}
}
//方式1:繼承thread類
public class ThreadDemo {
	public static void main(String[] args) {
	//運作遊戲
		for(int i = 0; i < 50; i++ )
		{
			System.out.println("運作遊戲"+ i);
			if(i == 10)
			{
				MusicTread mic = new MusicTread();
				mic.start();//啟動線程,不能用run方法
				
			}
		}
	}
}           

步驟:②實作Runnable接口: 1.定義一個類A實作java.lang.Runnable接口, 注意A類不是線程類 2.在A類中覆寫Runnable接口中的run方法 3.在run方法中編寫需要執行的操作(run方法裡的代碼是 線程執行體 ) 4.在main方法(線程)中,建立線程對象,并啟動線程。 建立線程類對象:Thread t=new Thread(new A()); 調用線程對象的start方法:t.start(); //啟動一個線程 Thread構造器,需要Runnable對象/Runnable實作類的對象

java基礎——程式和線程
class Game implements Runnable {
	public void run() {
		for (int i = 0; i < 50; i++) {
			playgame();
		}
	}

	private void playgame() {
		System.out.println("playgame...");
	}
}

public class ThreadCreate {
	public static void main(String[] args) throws Exception {
		// 通過實作runnable接口方式
		Game gm = new Game();
		Thread gmtd = new Thread(gm, "playgames,hh..");
		gmtd.start();
		System.out.println(gmtd.currentThread().getName());
		
	}
}           

注意:子線程一旦建立成功,擁有和主線程一樣的級别,主線程執行完成,子線程仍然可以繼續執行

runnable類 currentThread()傳回目前線程對象的引用

并行和并發 ------------------------------------------------------------------------------------------------ 【繼承方式和實作方式的差別】 繼承方式: 1)Java中類是單繼承的,如果繼承了Thread,該類就不能再有其他的直接父類了。 2)從操作上分析,繼承方式更簡單,擷取線程名字也簡單( 操作上 ,更簡單)。 3)從多線程共享同一個資源上分析, 繼承方式不能做到 。 實作方式: 1)Java中類可以實作接口,此時還可以繼承其他類,并且還可以實作其他接口(設計上更優雅)。 2)從操作上分析,實作方式稍微複雜點,擷取線程名字也比較複雜, 要使用Thread.currentThread() 來擷取目前線程的引用。 3)從多線程共享同一個資源上分析, 實作方式能做到(可以共享同一個資源 )。

------------------------------------------------------ 【線程不安全的問題】 當多線程并發通路同一資源對象的時候,可能出現線程不安全的問題。 讓問題更明顯(并不是使用該方法之後才出現問題):Thread.sleep(10); //目前線程睡10毫秒,使目前線程休息,讓其他線程去搶資源。經常用來模拟網絡延遲。 注:線上程的run方法上不能使用throws來聲明抛出異常,隻能在方法中使用try-catch來處理異常。 原因:子類覆寫父類方法的原則,子類不能抛出新的異常。在Runnable接口中的run方法,都沒有聲明抛出異常。

原子操作:不分割,必須保證同步進行。 1.先展示自己拿到受傷蘋果的編号。 2.在吃掉蘋果,意味着蘋果的總數少一個。

要解決上述多線程并發通路同一個資源的安全性問題: 解決方案: ①同步代碼塊 ②同步方法 ③鎖機制(Lock) ----------------------------------------------------------------

【同步代碼塊】 文法: synchronized (同步鎖) { 需要同步操作的代碼 } 【同步鎖】 為了保證每個線程都能正常執行原子操作,Java引入了線程同步機制。 同步監聽對象/同步鎖/同步監聽器/互斥鎖:對象的同步鎖隻是一個概念,可以想象為在對象上标記了一個鎖。 Java程式運作使用任何對象作為同步監聽對象,但是一般的,我們把目前并發通路的共同資源作為同步監聽對象(如:this,該對象屬于多線程共享的資源)。 注意:在任何時候,最多 允許一個線程擁有同步鎖 ,誰拿到鎖就進入代碼塊,其他線程隻能在外等着。

【同步方法】 使用synchronized修飾的方法。 對應的同步鎖:對于非static方法,同步鎖就是this;對于static方法,使用目前方法所在類的位元組碼對象。(Apple2.class) 不要使用synchronized修飾run方法 ,修飾之後,某一個線程就執行完了所有功能,好比是多個線程出現串行。解決辦法: 把需要同步執行代碼放在一個新方法中用synchronized修飾,在run中調用新方法即可。 -------------------------------------------------------------------------------------- 【synchronized】 好處:保證了多線程并發通路的同步操作,避免線程的安全性問題。 缺點:使用synchronized的方法/代碼塊的性能要低一些。

StringBuilder和StringBuffer的差別: StringBuilder:不安全、性能高(無synchronized修飾) StringBuffer:安全、性能低(有synchronized修飾) ArrayList和Vector的差別: ArrayList(無synchronized修飾) Vector(有synchronized修飾) HashMap和Hashtable的差別: ------------------------------------------------------------------------------------------------------------------------------------------- 同步鎖(Lock): lock機制提供了比synchronized代碼塊和synchronized方法更為廣泛的鎖操作,同步代碼塊/同步方法具有的功能lock都有,除此之外更強大,更展現面向對象. 在JDK7 API中可以看到如下的說明:

java基礎——程式和線程
public class SingletonDemo{
	class  Apple implements Runnable {
		private int num = 50;//蘋果總數
		private final Lock lock = new ReentrantLock();//建立一個鎖對象
		public void run() {
			for (int i=0;i<50;i++){
				eat();
			}
		}
		private void eat() {
			//進入方法立馬加鎖
			lock.lock();
			try{
				if(num>0){
					System.out.println(Thread.currentThread().getName()+"吃了的編号"+num);
					Thread.sleep(100);
					num--;
				}
			}catch(InterruptedException e){
				e.printStackTrace();
			}finally{
				lock.unlock();
			}
		}
	}
}