1. 程式人生 > 實用技巧 >【Java】多執行緒

【Java】多執行緒

多執行緒

1).程序

在作業系統中可以併發執行的一個任務,採用分時間片(微觀序列,巨集觀並行),由作業系統排程

2).執行緒

是程序中併發執行的一個順序流程

	執行緒組成:
		a.CPU時間片,由作業系統排程
		b.記憶體(JVM記憶體):堆空間(儲存物件,即例項變數,執行緒共享)、棧空間(儲存區域性變數,執行緒獨立)
		c.程式碼,是由程式設計師決定

3).Thread類

此型別的物件就代表了一個執行緒。執行緒呼叫start啟動後自動執行run方法,run方法是自定義程式碼

	用法:
	使用繼承Thread的類(推薦使用匿名內部類)
		class MyThread extends Thread{public void run(){  該執行緒的自定義流程;}}
		main: Thread t1 = new MyThread();
			 t1.start();  //執行緒啟動
			 t1.run();   //執行緒並未啟動,只是最普通的函式呼叫

	使用實現Runnable介面的類(推薦使用匿名內部類),缺點是不能拋異常,無返回值;可參考下文Callable介面
		class MyThread implements Runnable{public void run() {  該執行緒的自定義流程}}
		main:Runnable rthread = new MyThread();
			 Thread t2 = new Thread(rthread);
		     t2.start();
		
		//匿名內部類:
		Thread t = new Thread(new Runnable() {
		      public void run() {}
                });

4).執行緒同步(最重要,有執行緒池,fork-join框架)

在多執行緒環境下,併發訪問同一個物件(臨界資源),由於彼此間切割此物件的原子性操作,物件的狀態就會處於一個不一致的狀態。在java中,任意物件(Object o)都有一把互斥鎖標記(syjchronized(o))用於分配執行緒

①.synchronized:(同步)互斥鎖
第一種:同步程式碼塊
	Object o = new Object();
	synchronized(o) { 需要同步的程式碼塊1}
synchronized(o) { 需要同步的程式碼塊2}
		
總結:當一個執行緒要執行同步程式碼塊時,必須獲得鎖標記物件的互斥鎖標記,沒有獲取到時就進入執行緒阻塞,知道得到互斥鎖標記。執行緒執行同步程式碼塊,執行完畢後自動釋放互斥鎖標記,歸還給鎖物件。
執行緒是否同步,取決於是否爭用同一鎖物件

	第二種:同步方法
			public synchronized void method() {}
			
總結:當執行緒要執行同步例項方法時,必須獲得當前物件的互斥鎖標記,不能獲得就進入阻塞。當獲取互斥鎖標記執行完同步方法,自動釋放互斥鎖標記,並歸還給物件。
一個執行緒可以在持有互斥鎖標記的前提下獲取其他鎖物件的互斥鎖標記

②.Lock:鎖介面,提供了比synchronized更廣泛的所定義語句(有個實現類為ReentrantLock)
	ReadWriteLock:讀寫鎖,持有一對讀鎖和寫鎖,(有一個實現類ReenTrantReadWriteLock)
				 讀鎖:允許分配給多個執行緒(併發)  readLock();
				 寫鎖:最多隻允許分配給一個執行緒(互斥), writeLock();    讀鎖和寫鎖兩者互斥
	總結:當執行緒持有讀鎖時,不能分配寫鎖給其他執行緒,但可以分配讀鎖;同樣在執行緒持有寫鎖時,也不能分配讀鎖和寫鎖給其他執行緒。(讀讀併發、寫寫互斥、讀寫互斥)	

	用法:ReadWriteLock rwl = new ReentrantReadWriteLock();
		  Lock readLock = rwl.readLock();	 //讀鎖
		  Lock writeLock = rwl.writeLock(); //寫鎖
		  readLock.lock();
			中間為獲得讀鎖的程式碼塊,如果有try/catch時,可將readLock.unlock();寫在finally程式碼塊裡
		  readLock.unlock();
		  
		  writeLock用法與此相同
		  
③.join()方法
	如有執行緒t1,如果有執行緒t2要在t1執行完再執行時,則可在t2的程式碼塊中新增t1.join();語句
	例如:在main函式中有執行緒t1、t2、t3,當執行緒執行完在執行輸出語句時:
		public static void main (String[] args) {
			t1.join();  //將三個執行緒新增到主執行緒,並且先執行這三個執行緒
			t2.join();
			t3.join();
			System.out.println(“輸出語句”);
		}
④.執行緒池ExecutorService es = Executors.newFixedThreadPool(int n);//建立執行緒池,並設定固定併發執行緒的個數n,
	將實現Callable介面(Callable<T>,實現方法public T call(){return null})的執行緒提交(submit)到執行緒池中,
	再用Future<T>非同步接收執行緒結束後的結果,用get()方法獲取
	用法:
Callable<Integer> t1 = new Callable<Integer>(){
	public Integer call() { return null;} //需要實現call方法
};
ExecutorService es = Executors.newFixedThreadPool(2);//可併發兩個執行緒
Future<Integer> f1 = es.submit(t1); //將t1提交到執行緒池,並將執行後的結果給Future物件
Future<Integer> f2 = es.submit(t2); //如果有兩個,那麼f1,f2是非同步執行,互不干擾
System.out.println(f1.get());//將執行的結果通過future物件的get()方法獲取

⑤.fork-join框架(重要,自從jdk1.7),也是執行緒池
	思想:分治-歸併演算法,大任務--->小任務--->將小任務的結果合併成完整結果
	用法:
	class Task extends RecursiveTask<T> {
		protected  T  compute() {
			return T型別資料或null;
		}
	}
	main: ForkJoinPool fjp = new ForkJoinPool();
		 Task t1 = new Task();
		 fjp.invoke(new Task());   //將執行緒放入執行緒池
		 與此同時可以在執行再一個當前的執行緒t2
		 Task t2 = new Task();
		 T tmp2 = t2.compute();  //將執行緒t2執行後的結果返回給tmp2
		 T tmp1 = t1.join();      //線上程t2中將t1執行後的結果返回給tmp1

5).實現執行緒安全的方法

	1).加鎖:synchronized,互斥鎖,對物件加鎖,缺點是併發效率低
	2).鎖分級:ReadWriteLock,分配寫鎖時,不分配讀鎖;寫鎖未分配時,可分配多個讀鎖
	3).鎖分段:ConcurrentHashMap,對整個Map加鎖,分16個片段,分別獨自加鎖(jdk5 – jdk7)
	4).無鎖演算法:CAS比較交換演算法(利用狀態值)
		ConcurrentHashMap,利用CAS演算法,自從jdk8
		詳解:例如對一個變數n=3進行多執行緒訪問
			執行下面程式碼:
				where(true) {
					int status = n;   //舊值
					if(status == n) {  //拿舊值和當前值再次做比較
						//該執行緒可對n進行修改操作;假如在判斷之前n的值被其他執行緒修改了,則從新這個					      迴圈步驟
					} 
				}
	5).儘量迴避臨界資源,儘可能的使用區域性變數

6).執行緒間的通訊(等待 -- 通知 機制)

	例如:有兩個t1,t2執行緒,有物件Object o = new Object();
	t1: 如果使用o.wait();必須出現在對o加鎖的同步程式碼塊中,此時,t1將會釋放它擁有的所有鎖標記,並進入o的等待佇列(阻塞)和釋放CPU資源
		synchronized(o) { o.wait(); }
		t2: 此時使用t1所釋放的鎖標記,利用o.notify() / o.notifyAll(),此方法也有放在對o加鎖的同步程式碼塊中,
	    t2會從o的等待佇列中釋放一個或全部執行緒

wait 和 sleep方法的區別是:wait會失去鎖標記,而sleep不會失去鎖標記

程式碼例子:
/**
		 * 兩個執行緒:	一個輸出1 - 52
		 * 			一個輸出A - Z
		 * 	效果:1 2 A 3 4 B … 52 Z
		 */
final Object o = new Object();
		Thread t1 = new Thread() {
			public void run() {
				synchronized (o) {
					for (int i = 1; i <= 52; i += 2) {
						int tmp = i + 1;
						System.out.print(i + " " + tmp + " ");
						o.notifyAll();
						try {
							o.wait();
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
					}
				}
			}
		};
		t1.start();
		Thread t2 = new Thread() {
			public void run() {
				synchronized (o) {
					for (char i = 'A'; i <= 'Z'; i++) {
						System.out.print(i + " ");
						o.notifyAll();
						try {
							o.wait();
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
					}
				}
			}
		};
		t2.start();
		t1.join();
	 	t2.join();