1. 程式人生 > >菜鳥先飛之JAVA_多執行緒

菜鳥先飛之JAVA_多執行緒

多執行緒的概述 執行緒是程式執行的一條路徑, 一個程序中可以包含多條執行緒,多執行緒併發執行可以提高程式的效率, 可以同時完成多項工作。 併發和並行的區別,並行就是兩個任務同時執行,就是甲任務進行的同時,乙任務也在進行。(需要多核CPU);併發是指兩個任務都請求執行,而處理器只能按受一個任務,就把這兩個任務安排輪流進行,由於時間間隔較短,使人感覺兩個任務都在執行。
多執行緒的實現方式 1)繼承Thread,該子類應重寫 Thread 類的 run 方法。接下來可以分配並啟動該子類的例項
/*
 * 開啟執行緒的第一種方法,通過繼承Thread,來開啟執行緒。
 * 1、定義類繼承Thread
 * 2、重寫run方法
 * 3、把執行緒要做的事情寫在run方法中
 * 4、建立執行緒物件
 * 5、開啟新執行緒,內部會自動執行run方法
 * 
 */
public class demo02_thread {

	public static void main(String[] args) {
		// 4、建立自定義的執行緒物件
		MyThread mt = new MyThread();
		// 5、開啟執行緒
		mt.start();
	}

}

// 1、定義一個類繼承Thread
class MyThread extends Thread {

	// 2、重寫run方法
	@Override
	public void run() {
		// 3、將要執行的程式碼,寫在run方法中
		for (int i = 0; i < 1000; i++) {
			System.out.println("aaaaaaaa");
		}
	}

}

2)實現 Runnable 介面。該類然後實現 run 方法。然後可以分配該類的例項,在建立 Thread 時作為一個引數來傳遞並啟動。
/*
 * 開啟執行緒的第二種方法,通過實現Runnable方法。
 * 1、定義類實現Runnable介面
 * 2、實現run方法
 * 3、把新執行緒要做的事寫在run方法中
 * 4、建立自定義的Runnable的子類物件
 * 5、建立Thread物件, 傳入Runnable
 * 6、呼叫start()開啟新執行緒, 內部會自動呼叫Runnable的run()方法
 */
public class demo03_thread {

	public static void main(String[] args) {
		// 4、建立自定義的Runnable的子類物件
		MyRunnable mr = new MyRunnable();
		// 5、建立Thread物件, 傳入Runnable的子物件
		Thread mt = new Thread(mr);
		// 6、開啟執行緒
		mt.start();
		
	}

}

// 1、定義類實現Runnable介面
class MyRunnable implements Runnable {
	// 2、實現run方法
	@Override
	public void run() {
		// 3、把新執行緒要做的事寫在run方法中
		for (int i = 0; i < 1000; i++) {
			System.out.println("aaaaaaa");
		}
	}

}

實現Runnable的原理,看Thread類的建構函式,傳遞了Runnable介面的引用,通過init()方法找到傳遞的target給成員變數的target賦值,檢視run方法,發現run方法中有判斷,如果target不為null就會呼叫Runnable介面子類物件的run方法。 3)兩種方式的區別 a)繼承Thread : 由於子類重寫了Thread類的run(),當呼叫start()時, 直接找子類的run()方法。 b)實現Runnable : 建構函式中傳入了Runnable的引用, 成員變數記住了它, start()呼叫run()方法時內部判斷成員變數Runnable的引用是否為空, 不為空編譯時看的是Runnable的run(),執行時執行的是子類的run()方法。
c)繼承Thread:好處是,可以直接使用Thread類中的方法,程式碼簡單;弊端是,如果已經有了父類,就不能用這種方法。 d)實現Runnable介面:好處是,即使自己定義的執行緒類有了父類也沒關係,因為有了父類也可以實現介面,而且介面是可以多實現的;弊端是,不能直接使用Thread中的方法需要先獲取到執行緒物件後,才能得到Thread的方法,程式碼複雜。
Thread類的成員方法 public final String getName();返回該執行緒的名稱。 public final void setName(String name);改變執行緒名稱,使之與引數 name 相同。 public static Thread currentThread();返回對當前正在執行的執行緒物件的引用。 public static void sleep(long millis)throws InterruptedException;在指定的毫秒數內讓當前正在執行的執行緒休眠(暫停執行),此操作受到系統計時器和排程程式精度和準確性的影響。該執行緒不丟失任何監視器的所屬權。 public final void setDaemon(boolean on);將該執行緒標記為守護執行緒或使用者執行緒。當正在執行的執行緒都是守護執行緒時,Java 虛擬機器退出。該方法必須在啟動執行緒前呼叫。 public final void join()throws InterruptedException;等待該執行緒終止。 public final void join(long millis)throws InterruptedException;等待該執行緒終止的時間最長為 millis 毫秒。超時為 0 意味著要一直等下去。 public static void yield();暫停當前正在執行的執行緒物件,並執行其他執行緒。 public final void setPriority(int newPriority);更改執行緒的優先順序。
同步程式碼塊和同步鎖 當多執行緒併發,有多段程式碼同時執行時, 我們希望某一段程式碼執行的過程中CPU不要切換到其他執行緒工作。這時就需要同步。如果兩段程式碼是同步的,那麼同一時間只能執行一段,在一段程式碼沒執行結束之前,不會執行另外一段程式碼。使用synchronized關鍵字加上一個鎖物件來定義一段程式碼, 這就叫同步程式碼塊;使用synchronized關鍵字修飾一個方法, 該方法中所有的程式碼都是同步的。多個同步程式碼塊如果使用相同的鎖物件, 那麼他們就是同步的。
class Printer {
	public static void print1() {
		//鎖物件可以是任意物件,但是被鎖的程式碼需要保證是同一把鎖,不能用匿名物件
		synchronized(Printer.class){				
			System.out.print("H");
			System.out.print("E");
			System.out.print("L");
			System.out.print("L");
			System.out.print("O");
			System.out.print("\r\n");
		}
	}
	/*
	 * 非靜態同步函式的鎖是:this
	 * 靜態的同步函式的鎖是:位元組碼物件
	 */
	public static synchronized void print2() {	
		System.out.print("W");
		System.out.print("O");
		System.out.print("R");
		System.out.print("D");
		System.out.print("\r\n");
	}
}

兩個執行緒間的通訊 什麼時候需要通訊,當多個執行緒併發執行時,在預設情況下CPU是隨機切換執行緒的,如果我們希望他們有規律的執行,就可以使用通訊,例如每個執行緒執行一次列印。 怎麼通訊,如果希望執行緒等待,就呼叫wait(),如果希望喚醒等待的執行緒,就呼叫notify();這兩個方法必須在同步程式碼中執行,並且使用同步鎖物件來呼叫。 Object類中的方法: 1)public final void wait()throws InterruptedException;在其他執行緒呼叫此物件的 notify() 方法或 notifyAll() 方法前,導致當前執行緒等待。 2)public final void notify();喚醒在此物件監視器上等待的單個執行緒。如果所有執行緒都在此物件上等待,則會選擇喚醒其中一個執行緒。選擇是任意性的,並在對實現做出決定時發生。 3)public final void notifyAll();喚醒在此物件監視器上等待的所有執行緒。
執行緒組的概述和使用 Java中使用ThreadGroup來表示執行緒組,它可以對一批執行緒進行分類管理,Java允許程式直接對執行緒組進行控制。預設情況下,所有的執行緒都屬於主執行緒組。 * public final ThreadGroup getThreadGroup()//通過執行緒物件獲取他所屬於的組 * public final String getName()//通過執行緒組物件獲取他組的名字 我們也可以給執行緒設定分組 * 1,ThreadGroup(String name) 建立執行緒組物件並給其賦值名字 * 2,建立執行緒物件 * 3,Thread(ThreadGroup group,Runnable target,String name) * 4,設定整組的優先順序或者守護執行緒
public class demo21_ThreadGroup {

	public static void main(String[] args) {
//		demo1();
		
		//建立執行緒組
		ThreadGroup tg = new ThreadGroup("執行緒組");
		//建立Runnable的子類物件
		MyRunnable mr = new MyRunnable();
		//建立執行緒例項
		Thread t1 = new Thread(tg, mr, "啊啊啊");
		Thread t2 = new Thread(tg, mr,"bbb");
		//列印執行緒組的名字
		System.out.println(t1.getThreadGroup().getName());
		System.out.println(t2.getThreadGroup().getName());
		
		
	}

	public static void demo1() {
		//建立Runnable的子類物件
		MyRunnable1 mr = new MyRunnable1();
		//建立執行緒例項
		Thread t1 = new Thread(mr,"qq");
		Thread t2 = new Thread(mr,"ww");
		
		ThreadGroup tg1 = t1.getThreadGroup();
		ThreadGroup tg2 = t2.getThreadGroup();
		
		System.out.println(tg1.getName());
		System.out.println(tg2.getName());
	}

}

class MyRunnable1 implements Runnable {

	@Override
	public void run() {
		for (int i = 0; i < 1000; i++) {
			System.out.println(Thread.currentThread().getName()+"..."+i);
		}
	}
	
}

執行緒的五種狀態

執行緒池的概述和使用 程式啟動一個新執行緒成本是比較高的,因為它涉及到要與作業系統進行互動。而使用執行緒池可以很好的提高效能,尤其是當程式中要建立大量生存期很短的執行緒時,更應該考慮使用執行緒池。執行緒池裡的每一個執行緒程式碼結束後,並不會死亡,而是再次回到執行緒池中成為空閒狀態,等待下一個物件來使用。在JDK5之前,我們必須手動實現自己的執行緒池,從JDK5開始,Java內建支援執行緒池。JDK5新增了一個Executors工廠類來產生執行緒池,有如下幾個方法: public static ExecutorService newFixedThreadPool(int nThreads) public static ExecutorService newSingleThreadExecutor() 這些方法的返回值是ExecutorService物件,該物件表示一個執行緒池,可以執行Runnable物件或者Callable物件代表的執行緒。它提供瞭如下方法: Future<?> submit(Runnable task) <T> Future<T> submit(Callable<T> task) 使用步驟:1)建立執行緒池物件;2)建立Runnable例項;3)提交Runnable例項;4)關閉執行緒池。
public class demo22_ExecutorService {

	public static void main(String[] args) {
		
		//建立執行緒池
		ExecutorService pool = Executors.newFixedThreadPool(2);
		
		//將執行緒放入執行緒池並執行
		pool.submit(new MyRunnable1());
		pool.submit(new MyRunnable1());
		//關閉執行緒池
		pool.shutdown();
	}

}