【黑馬程序員】多線程(一) 第十一天

-------  android培訓java培訓java學習型技術博客、期待與您交流! ----------

知識點

Java的重要功能之一就是內部支持多線程——在一個程序中允許同時運行多個任務。

每個任務都是Runnable接口的實例。線程就是一個便於任務執行的對象。可以通過實現Runnable接口來定義任務類,通過使用Thread構造方法包住一個任務來創建線程。

一個線程對象被創建之後,可以使用start()方法啓動線程,可以使用sleep(long)方法將線程轉入休眠狀態,以便其它線程獲得運行的機會。

線程對象從來不會直接調用run方法。到了執行某個線程的時候,Java虛擬機調用run方法。類必須覆蓋run方法,告訴系統線程運行時將會做什麼。

爲了避免線程破壞貢獻資源,可以使用同步的方法或塊。同步方法在執行前需要獲得一個。當同步方法是實例方法時,鎖是在調用方法的對象上;當同步方法是靜態(類)方法時,鎖是在方法所在的類上。

在執行方法中某個代碼塊時,可以使用同步語句獲得任何對象上的鎖,而不僅是this對象上的鎖。這個代碼塊稱爲同步塊

可以使用顯示鎖和條件以及對象的內置監視器來便於進程之間的通信。

如果兩個或更多的線程獲取多個對象上的鎖時,每個線程都有一個對象上的鎖並等待其他對象上的鎖,這時就有可能發生死鎖現象。使用資源排序技術可以避免死鎖。

可以定義個任務類來擴展SwingWorker,運行費時的任務並且使用任務產生的結果更新GUI。

可以使用JProgressBar來跟蹤線程的進度。


01)多線程(概述)

/*
 * 進程:是一個正在執行的程序。
 * 		每一個進程執行都有一個執行順序。該順序是一個執行路徑,或者叫一個控制單元。
 * 線程:就是進程中的一個獨立的控制單元。線程在控制着進程的執行。
 * 一個進程中至少有一個線程。
 * 
 * Java虛擬機啓動的時候會有一個進程java.exe。
 * 該進程中至少有一個線程在負責java程序的執行,而且這個線程運行的代碼存在於main方法中。
 * 該線程稱之爲主線程。
 * 
 * 擴展:更細節的說明JVM,啓動的不止一個線程,還有負責垃圾回收機制的線程。
 */

02)繼承Thread類

/*
 * 1:如果在自定義的代碼中,自定義一個線程呢?
 * 通過對API的查找,java已經提供了對線程這類事物的描述,就是Thread類。
 * 創建線程的第一種方式:繼承thread類。
 * 步驟:
 * 1:定義類繼承Thread類。
 * 2:複寫Thread類中的run()方法。
 * 3:調用線程的start方法,該方法有兩個作用。(1)啓動線程。(2)調用run方法。
 * 
 * 發現運行結果每次都不同。
 * 因爲多個線程都在獲取CUP的執行權。CUP執行到誰,就執行誰。
 * 明確一點,在某一個時刻,只能有一個線程在運行。(多核除外)
 * cup在做着快速的切換以達到看上去是同時運行的效果。
 * 我們可以形象的把多線程的運行形容爲在互相搶奪CUP的執行權。
 * 
 * 這是多線程的特性:隨機性。誰搶到就執行誰,至於執行多長,cup說了算。
 */
public class ThreadDemo_2 {
	public static void main(String[] args) {
		Demo_2 d = new Demo_2();//創建好一個對象就是創建好一個線程。
		d.run();
		
		for (int i = 0; i < 10 ; i++){
			System.out.println("Hello World: " + i);
		}
	}
}
class Demo_2 extends Thread{
	public void run(){
		for (int i = 0; i < 10; i++){
			System.out.println("Demo run: " + i);
		}
	}
}

03)run和start特點

/*
 * 爲什麼要覆蓋run方法呢?
 * 
 * Thread類用於描述線程。
 * 該類就定義了一個功能,用於存儲線程要運行的代碼,該存儲的功能就是run方法。
 * 也就是說Thread類中的run方法,用於存儲線程要運行的代碼。
 * 
 */
public class ThreadDemo_3 {
	public static void main(String[] args) {
		Demo_2 d = new Demo_2();//創建好一個對象就是創建好一個線程。
		d.start();//開啓線程並執行該線程的run方法。
		//d.run();//僅僅是對象的調用方法。而線程創建了,但並未運行。
		
		for (int i = 0; i < 10 ; i++){
			System.out.println("Hello World: " + i);
		}
	}
}
class Demo_3 extends Thread{
	public void run(){
		for (int i = 0; i < 10; i++){
			System.out.println("Demo run: " + i);
		}
	}
}

04)線程練習

/*
 * 創建兩個線程,和主線程交替運行。
 */
public class ThreadDemoTest {
	public static void main(String[] args) {
		Test t1 = new Test("Run 1");
		Test t2 = new Test("Run 2");
		t1.start();
		t2.start();
		//t1.run();//這行代碼程序將沒有結果。
		//t2.run();//這行代碼程序將沒有結果。
		for (int i = 0; i < 50; i++){
			System.out.println("Main: " + i);
		}
	}
}
class Test extends Thread{//繼承Thread線程類。
	private String name;
	Test(String name){
		this.name = name;
	}
	public void run(){
		for (int i = 0; i < 50; i++){
			System.out.println(name + " Test run: " + i);
		}
	}
}
運行隨機結果如下圖所示:



05)線程運行狀態



06)獲取線程對象及名稱

/*
 * 創建兩個線程,和主線程交替運行。
 * 
 * 原來線程都有自己默認的名稱。
 * Thread-編號 該編號從0開始。
 * Thread.currentThread();獲取當前線程對象。
 * getName();獲取線程的名稱。
 * 設置線程的名稱:setName或者構造函數。
 */
public class ThreadDemo_6 {
	public static void main(String[] args) {
		Demo_6 t1 = new Demo_6("Run 1");
		Demo_6 t2 = new Demo_6("Run 2");
		t1.start();
		t2.start();
		for (int i = 0; i < 50; i++){
			System.out.println("Main: " + i);
		}
	}
}
class Demo_6 extends Thread{//繼承Thread線程類。
//	private String name;
	Demo_6(String name){
//		this.name = name;
		super(name);
	}
	public void run(){
		for (int i = 0; i < 50; i++){
			System.out.println((Thread.currentThread() == this)//Thread.currentThread()是標準通用方法。
					+ "....." + this.getName() + " Test run: " + i);
		}
	}
}
運行隨機結果如下圖所示:



07)多線程(售票的例子。)

/*
 * 需求:簡單的賣票程序。
 * 多個窗口同時賣票。
 */
public class ThreadMaiPiao {
	public static void main(String[] args) {
		Ticket t1 = new Ticket("窗口1: ");
		Ticket t2 = new Ticket("窗口2: ");
		Ticket t3 = new Ticket("窗口3: ");
		Ticket t4 = new Ticket("窗口4: ");
		
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}
}
class Ticket extends Thread{
	Ticket(String name){
		super(name);
	}
	private static int ticket = 10;
	public void run(){
		for (int i = 0; i < ticket; i++){
			System.out.println(Thread.currentThread().getName() + " Sela: " + ticket--);
		}
	}
}

08)創建線程——實現Runnable接口

/*
 * 需求:簡單的賣票程序。
 * 多個窗口同時賣票。
 * 創建第二種方式:實現Runable接口。
 * 步驟:
 * 1:定義類實現Runable接口。
 * 2:覆蓋Runable接口中的run方法。
 * 		將線程要運行的代碼存放在該run方法中。
 * 3:通過Thread類建立線程對象。
 * 4:將Runable接口的子類對象作爲實際參數傳遞給Thread類的構造函數。
 * 		爲什麼,因爲自定義的run方法所屬的對象是Runable接口的子類對象,
 * 		所以要讓線程去指定指定對象的run方法。就必須明確該run方法所屬的對象。
 * 5:調用Thread類中的start方法開啓線程並調用Runable接口子類的run方法。
 * 
 * 實現方式和繼承方式的區別。
 * 實現方式好處在於避免了單繼承的侷限性。在定義線程時,建議使用實現接口方式。
 * 兩種方式的區別:
 * 繼承Thread:線程代碼存放在Thread子類run方法中。
 * 實現Runable:線程代碼存放在Runable接口子類的run方法中。
 */
public class ThreadMaiPiao_2 {
	public static void main(String[] args) {
		Ticket_2 t = new Ticket_2();//
		Thread t1 = new Thread(t);//3:通過Thread類建立線程對象。
		Thread t2 = new Thread(t);//4:將Runable接口的子類對象作爲實際參數傳遞給Thread類的構造函數。
		Thread t3 = new Thread(t);
		Thread t4 = new Thread(t);
		
		t1.start();//5:調用Thread類中的start方法開啓線程並調用Runable接口子類的run方法。
		t2.start();
		t3.start();
		t4.start();
		
	}
}
class Ticket_2 implements Runnable{//extends Thread//1:定義類實現Runable接口。
	private static int ticket = 10;
	public void run(){//2:覆蓋Runable接口中的run方法。
		for (int i = 0; i < ticket; i++){
			System.out.println(Thread.currentThread().getName() + " Sela: " + ticket--);
		}
	}
}

09)多線程的安全問題

/*
 * 需求:簡單的賣票程序。
 * 多個窗口同時賣票。
 * 
 * 通過分析,發現,打印出0、 -1、 -2等錯票。
 * 多線程的運行出現了安全問題。
 * 
 * (重點)問題的原因:當多條語句在操作同一個線程共享數據時,一個線程對多條語句只執行了一部分,
 * 					還沒有執行完,另一個線程就參與了進來。導致了共享數據的錯誤。
 * 解決辦法:對多條操作共享數據的語句,只能讓一個線程都執行完。在執行過程中,其它線程不可以參與執行。
 * 
 * java對於多線程的安全提供了專業的解決方式。
 * 就是同步代碼塊。
 * synchronized(對象){
 * 		需要被同步的代碼。
 * }
 */
public class ThreadMaiPiao_3 {
	public static void main(String[] args) {
		Ticket_3 t = new Ticket_3();//
		Thread t1 = new Thread(t);//3:通過Thread類建立線程對象。
		Thread t2 = new Thread(t);//4:將Runable接口的子類對象作爲實際參數傳遞給Thread類的構造函數。
		Thread t3 = new Thread(t);
		Thread t4 = new Thread(t);
		
		t1.start();//5:調用Thread類中的start方法開啓線程並調用Runable接口子類的run方法。
		t2.start();
		t3.start();
		t4.start();
	}
}
class Ticket_3 implements Runnable{//extends Thread//1:定義類實現Runable接口。
	private int ticket = 100;
	Object obj = new Object();
	public void run(){//2:覆蓋Runable接口中的run方法。
		while(true){
			synchronized(obj){//同步代碼塊。
				if (ticket > 0){
					try{
						Thread.sleep(1);
					}catch(Exception e){
						
					}
					System.out.println(Thread.currentThread().getName() + " Sela: " + ticket--);
				}
			}
		}
	}
}


10)多線程同步代碼塊

/*
 * 需求:簡單的賣票程序。
 * 多個窗口同時賣票。
 * 
 * 同步代碼塊。
 * synchronized(對象){
 * 		需要被同步的代碼。
 * }
 * 對象如同鎖。持有鎖的線程可以在同步中執行。
 * 沒有鎖的線程,即使獲取了CPU的執行權,也進不去,因爲沒有獲取鎖。
 * 
 * 同步的前提:
 * 1:必須要有兩個或兩個以上的線程。
 * 2:必須是多個線程使用同一個鎖。
 * 
 * 好處:解決了多線程的安全問題。
 * 弊端:多個線程每次都要判斷鎖,較爲消耗資源。
 */
public class ThreadMaiPiao_4 {
	public static void main(String[] args) {
		Ticket_4 t = new Ticket_4();//
		Thread t1 = new Thread(t);//3:通過Thread類建立線程對象。
		Thread t2 = new Thread(t);//4:將Runable接口的子類對象作爲實際參數傳遞給Thread類的構造函數。
		Thread t3 = new Thread(t);
		Thread t4 = new Thread(t);
		
		t1.start();//5:調用Thread類中的start方法開啓線程並調用Runable接口子類的run方法。
		t2.start();
		t3.start();
		t4.start();
		
	}
}
class Ticket_4 implements Runnable{//extends Thread//1:定義類實現Runable接口。
	private int ticket = 1000;
	Object obj = new Object();
	public void run(){//2:覆蓋Runable接口中的run方法。
		while(true){
			synchronized(obj){//同步代碼塊。
				if (ticket > 0){
					System.out.println(Thread.currentThread().getName() + " Sela: " + ticket--);
				}else{
					return;
				}
			}
		}
	}
}


11)多線程——同步函數

/*
 * 需求:
 * 銀行有一個金庫。
 * 有兩個儲戶,分別存300元,每次存100元,分三次存儲。
 * 
 * 目的:該程序是否有安全問題?如果有,如何解決?
 * 如何找到問題:**
 * 1:明確哪些代碼是多線程運行代碼。
 * 2:明確共享數據。
 * 3:明確多線程代碼中哪些語句是操作共享數據的。
 */
public class BankDemo {
	public static void main(String[] args) {
		Cus c = new Cus();
		Thread t1 = new Thread(c);
		Thread t2 = new Thread(c);
		t1.start();
		t2.start();
	}
}
class Back{
	private int sum;//2:共享數據
	public synchronized void add(int n){//同步函數。//1:add
			sum = sum + n;//3:
			try{
				Thread.sleep(10);
			}catch(Exception e){
			}
			System.out.println("Sum = " + sum);//3:
	}
}
class Cus implements Runnable{
	private Back b = new Back();//2:共享數據
	public void run(){//1:
		for (int i = 0; i < 3; i++){
			b.add(100);//1:
		}
	}
}

12)this——同步函數的鎖

/*
 * 同步函數用的是哪一個鎖?
 * 函數需要被對象調用。那麼函數都有一個所屬對象的引用。就是this。
 * 所以同步函數使用鎖就是this。
 * 
 * 通過該程序進行驗證。
 * 使用兩個線程來賣票。一個線程在同步代碼塊中,一個線程在同步函數中,都在執行賣票動作。
 */
public class ThreadMaiPiao_5 {
	public static void main(String[] args) {
		Ticket_5 t = new Ticket_5();//
		Thread t1 = new Thread(t);//3:通過Thread類建立線程對象。
		Thread t2 = new Thread(t);//4:將Runable接口的子類對象作爲實際參數傳遞給Thread類的構造函數。
		t1.start();//5:調用Thread類中的start方法開啓線程並調用Runable接口子類的run方法。
		try{Thread.sleep(10);}catch(Exception e){}
		t.flag = false;//
		t2.start();
	}
}
class Ticket_5 implements Runnable{//extends Thread//1:定義類實現Runable接口。
	private int ticket = 1000;
	boolean flag = true;
	public void run(){//2:覆蓋Runable接口中的run方法。
			if (flag){
					while(true){
						synchronized(this){//this是這個同步代碼塊的鎖。
//						this.show();
							if (ticket > 0){try{Thread.sleep(1);}catch(Exception e){}
								System.out.println(Thread.currentThread().getName() + " Run: " + ticket--);
							}
						}
					}
			}else
				while(true)
					show();
	}
	
	public synchronized void show(){//this是這個同步函數的鎖。
		if (ticket > 0){
			try{
				Thread.sleep(1);
			}catch(Exception e){
				
			}
			System.out.println(Thread.currentThread().getName() + " Show: " + ticket--);
		}
	}
}

13)class對象——是靜態同步函數的鎖

/*
 * 如果同步函數被靜態修飾後使用的鎖是什麼呢?
 * 通過驗證發現不在是this了。因爲靜態方法中不可以定義this。
 * 靜態進內存時,內存中沒有本類對象,但是一定有該類對應的字節碼文件對象。
 * 類名.class。
 * 
 * 【總結:靜態的同步方法,使用的鎖是該方法所在類的字節碼文件對象——(類名.class)。】
 */
public class StaticThreadMaiPiao {
	public static void main(String[] args) {
		Ticket_6 t = new Ticket_6();
		Thread t1 = new Thread(t);
		Thread t2 = new Thread(t);
		t1.start();
		try{Thread.sleep(10);}catch(Exception e){}
		t.flag = false;
		t2.start();
	}
}
class Ticket_6 implements Runnable{
	private static int ticket = 100;
	boolean flag = true;
	public void run(){//2:覆蓋Runable接口中的run方法。
			if (flag){
					while(true){
						synchronized(Ticket_6.class){//【(類名.class)是這個同步代碼塊的鎖。】
							if (ticket > 0){try{Thread.sleep(10);}catch(Exception e){}
								System.out.println(Thread.currentThread().getName() + " Run: " + ticket--);
							}
						}
					}
			}else
				while(true)
					show();
	}
	
	public static synchronized void show(){//【(類名.class)是這個同步函數的鎖。】
		if (ticket > 0){
			try{
				Thread.sleep(1);
			}catch(Exception e){
				
			}
			System.out.println(Thread.currentThread().getName() + " Show: ..." + ticket--);
		}
	}
}

14)單設計模式——懶漢式

/*
 * 單設計模式:
 * 惡漢試:
 * class Single{
 * 		private static final Single s = new Single();
 * 		private Single();
 * 		public static Single getInstance(){
 * 			return s;
 * 		}
 * }
 * 
 * 懶漢試:
 * class Single{
 * 		private static final Single s = null;
 * 		private Single();
 * 		public static Single getInstance(){
 * 			if(s == null)
 * 				s = new Single();
 * 			return s;
 * 		}
 * }
 * 懶漢試:特點在於延遲加載。
 * 如果多線程訪問時會出現安全問題。
 * 可以加同步來解決,用雙重判斷能解決低效問題。
 * 加同步時使用的鎖是該類所屬的字節碼文件對象(類名.class)。
 * 
 */

15)多線程——死鎖

/*
 * 死鎖。
 */
public class DeadLockTest {
	public static void main(String[] args) {
		Thread t1 = new Thread(new TestT(true));
		Thread t2 = new Thread(new TestT(false));
		t1.start();
		t2.start();
	}
}
class TestT implements Runnable{
	private boolean flag;
	TestT(boolean flag){
		this.flag = flag;
	}
	
	public void run(){
		if (flag){
			synchronized(MyLock.locka){
				System.out.println("If locka");
				synchronized(MyLock.lockb){
					System.out.println("If lockb");
				}
			}
		}
		else{
			synchronized(MyLock.lockb){
				System.out.println("Else lockb");
				synchronized(MyLock.locka){
					System.out.println("Else locka");
				}
			}
		}
	}
	
}
class MyLock{
	static Object locka = new Object();
	static Object lockb = new Object();
}


附言:我是Java新人,如有錯誤的地方請指出。
                         每天學習一點點,糾錯一點點,進步很大點。


-------  android培訓java培訓java學習型技術博客、期待與您交流! ----------


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