1. 程式人生 > >Java內容梳理(19)API學習(7)執行緒

Java內容梳理(19)API學習(7)執行緒

目錄:

1、程序和執行緒

2、執行緒的建立

3、執行緒的執行方式和使用場景

4、執行緒的生命週期

5、執行緒優先順序

6、守護執行緒

7、執行緒常用API

8、執行緒安全

9、鎖機制

10、執行緒同步控制(死鎖的介紹)

11、定時器

1、程序和執行緒

什麼是程序?

簡單的說:一個獨立執行的程式對應一個程序,程序程序之間相對獨立,記憶體資料並不共享

什麼是執行緒?

程序由多個執行緒來組成,執行緒共享程序中的資源

執行緒是程序中的邏輯執行單元,當程序啟動時,會立刻啟動一個主執行緒來驅動整個程式邏輯的執行

2、執行緒的建立

(1)利用Runnable介面建立物件

//建立t1執行緒

Thread t1 = new Thread( new Runnable() {

           public void run(){

                  //寫並行邏輯

            }

});

//啟動t1執行緒,讓執行緒進入可執行狀態,等待cpu的選中

t1.start();

(2)利用Thread類來建立物件

//建立t2執行緒

Thread t2 = new Thread(){

          public void run(){

                   //寫並行邏輯

          }

};

//啟動t2執行緒,讓執行緒進入可執行狀態,等待cpu的選中

t2.start();

3、執行緒的執行方式和使用場景

執行緒的執行方式:

同時執行:併發執行

cpu時間片:cpu執行執行緒的單位時間

併發原理:

單核cpu會不停的在多個執行緒中挑選一個執行緒來執行,執行一個時間片,當時間片到期後,cou核心將掛起這個執行緒,再去挑

選其它執行緒佔用自己的時間片。時間片的切換頻率快到使用者無法察覺,從而給使用者一種併發的感覺

執行緒的使用場景:當有多個程式邏輯需要併發執行時,我們需要使用執行緒來實現。

4、執行緒的生命週期

在整個生命週期中,執行緒會有不同的狀態,每個狀態的特點不一樣

生命週期:從new開始,到run方法結束

5、執行緒優先順序

我們不能控制CPU選中具體哪個執行緒來執行, 我們可以通過設定執行緒的優先順序來告知CPU優先執行哪個執行緒.CPU在挑選可執行狀態

下的執行緒時優先考慮選中優先順序高的執行緒.

設定執行緒的優先順序,setPriority(int newPriority)   其中newPriority範圍只能是1-10之間的整數

當我們沒有指定優先順序時,預設採用:NORM_PRIORITY ( 5 )

必須要線上程start()呼叫前執行才有效

package thread;

public class Run {
	public static void main(String[] args) {
		Thread t1 = new Thread( new Runnable() {
				@Override
				public void run() {
					System.out.println("t1執行緒執行了");
				}
		 });
		
		//建立t2執行緒
		Thread t2 = new Thread(){
			@Override
			public void run() {
				System.out.println("t2執行緒執行了");
			}
		};
		t1.setPriority(1);
		t2.setPriority(5);
		
		t1.start();
		t2.start();
		
	}
}

6、守護執行緒

特點:

當程序中普通的執行緒全部執行完畢後,整個程序結束

守護執行緒不影響程序的結束

如:gc執行緒

setDaemon( boolean on )設定當前執行緒物件是否為守護執行緒

必須線上程啟動前呼叫才有效

package thread;

public class Run {
	public static void main(String[] args) {
		Thread t1 = new Thread( new Runnable() {
				@Override
				public void run() {
					System.out.println("t1執行緒執行了");
				}
		 });
		
		Thread t2 = new Thread(){
			@Override
			public void run() {
				
				System.out.println("t2執行緒執行了");
			}
		};
		
		t1.setDaemon(true);
		t1.start();
		t2.start();
		
	}
}

7、執行緒常用API

Thread.currentThread();獲取當前執行緒物件

getId();獲取執行緒ID

getName();獲取當前執行緒的名稱

sleep(long mills);讓執行緒休眠指定的毫秒數;執行緒會進入阻塞狀態,指定時間過後執行緒重新進入可執行狀態

join();讓當前執行緒等待呼叫join方法的執行緒執行完畢後,再執行當前執行緒,讓本來並行的兩條執行緒變為序列執行

package thread;

public class Run2 {
	public static void main(String[] args) {
		Thread t1 = new Thread() {
				public void run() {
					System.out.println("t1執行緒執行");
					
					try {
						sleep(3*1000);
						System.out.println("當前執行緒阻塞3秒");
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
		};
		
		try {
			t1.sleep(2*1000);
			System.out.println("t1執行緒阻塞2秒");
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		t1.start();
		
	}
}

package thread;

public class Run3 {
	public static void main(String[] args) {
		 Thread t1 = new Thread() {
			public void run() {
				System.out.println("t1執行緒執行");
			}
	    };
	    
	    Thread t2 = new Thread() {
			public void run() {
				try {
					t1.join();//保證了t1執行緒始終會在t2執行緒之前執行
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println("t2執行緒執行");
			}
	    };
	    
	    t2.start();
	    t1.start();
	}
}

8、執行緒安全和鎖機制

典型的執行緒安全問題:髒讀

原因:多個執行緒訪問同一資源時,有某些執行緒對該資源進行了修改,其它執行緒再次讀取資源,會與開始讀取的不一致

鎖機制:避免執行緒安全問題的一種手段,synchronized關鍵字,加鎖。

只有先獲得鎖的執行緒A,才能進入synchronized程式碼塊中去執行程式碼,其它執行緒都在等待執行緒A釋放這個鎖後,再去競爭鎖,然後執

行程式碼。

執行緒A釋放鎖的時機:

1. 執行緒A順利執行完synchronized程式碼塊

2. 執行緒A在執行synchronized程式碼塊時出現異常丟擲導致synchronized程式碼塊異常退出時

3. 呼叫鎖物件的wait方法時

注意:

1、若想多個程式碼塊序列執行,必須要讓這幾個程式碼塊被"同一把鎖"鎖住

public void method(){

         synchronized( this ){

             //方法體

        }

}

等同於

public synchronized method(){

          //方法體

}

2、對靜態方法加synchronized鎖的是類

package thread;

public class Run4 {
	private static Long count = 1L;
	public static void main(String[] args) {
		
		Thread t1 = new Thread() {
			public void run() {
				/*對count這個臨界資源加鎖,count也就是"鎖物件"*/
				synchronized (count) {
					count += 1;
					System.out.println("t1執行");
					System.out.println(count);
				}
			}
		};
		
		Thread t2 = new Thread() {
			public void run() {
				/*對count這個臨界資源加鎖*/
				synchronized (count) {
					count += 2;
					System.out.println("t2執行");
					System.out.println(count);
				}
			}
		};
		
		t1.start();
		t2.start();
		
	}
}

舉例:鎖住當前物件

舉例:鎖住其它物件

9、執行緒同步控制(死鎖的介紹)

設計Object類中的三個方法:wait / notify / notifyAll

wait:讓當前執行緒等待(進入阻塞狀態)並釋放其佔用鎖

notify:通知被同個鎖阻塞的執行緒中的某個執行緒恢復可執行狀態,喚醒對應的鎖池中的某個執行緒,進入可執行狀態

notifyAll:喚醒對應鎖池中的全部執行緒

注意:這三個方法必須在synchronized程式碼塊中被使用

死鎖:

什麼是死鎖?

執行緒A等待執行緒B釋放鎖,同時執行緒B也在等待執行緒A釋放鎖,從而形成相互等待的情況,這種情況稱為死鎖

通常情況下死鎖的發生:

多個執行緒需要按照各自的順序來同時使用a鎖和b鎖,但由於這多個執行緒使用a鎖和b鎖的順序不一致,就可能導致死鎖

解決死鎖的思路:

若多個執行緒需要同時使用多個鎖時,我們應該儘量讓這多個執行緒使用這些鎖物件時的順序是一致的.

舉例:生產者與消費者

以生產蛋糕為例應該考慮:

(1)生產速度大於賣出速度

(2)賣出速度大於生產速度

package thread;

import java.util.List;

/**
 * 生產者執行緒
 */
public class Maker extends Thread{
	/*container表示放蛋糕的貨架*/
	private List<String> container;
	private int maxSize;

	/*由外界提供貨架和貨架的最大裝載量*/
	public Maker(List<String> container, int maxSize) {
		this.container = container;
		this.maxSize = maxSize;
	}
	
	public void run() {
		while(true) {
			/*貨架沒滿,就生產蛋糕,往上放*/
			if(container.size() <= maxSize) {
				try {
					System.out.println("生產蛋糕,放入貨架");
					this.sleep(2000);
					container.add("蛋糕");
					System.out.println("貨架上的蛋糕剩餘:"+container.size());
					/*檢測貨架上的蛋糕數,若大於4個,就要去賣蛋糕了;不能等到生產滿了貨架才賣*/
					synchronized (container) {
						if(container.size() >= 4 ) {
							container.notifyAll();
						}
					}
					
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}else {
				/*貨架滿了,等待蛋糕賣出;或者是生產速度大於賣出速度*/
				synchronized (container) {
					try {
						System.out.println("貨架滿了,等待蛋糕賣出");
						//喚醒消費程序,賣蛋糕
						container.notifyAll();
						//讓當前生產蛋糕執行緒等待,再釋放其對container的佔用鎖
						container.wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		}	
	}
	
}
package thread;

import java.util.List;

/**
 *消費者執行緒
 */
public class Saller extends Thread{
	private List<String> container;

	public Saller(List<String> container) {
		super();
		this.container = container;
	}
	
	public void run() {
		while(true) {
			if(container.size() > 0 ) {
				try {
					System.out.println("賣蛋糕,賣一個,貨架減一個");
					Thread.sleep(2000);
					container.remove(0);
					System.out.println("貨架上的蛋糕剩餘:"+container.size());
					
					/*檢測貨架上的蛋糕數,若小於4個,就要去生產蛋糕了;不能等到賣完了才去生產*/
					synchronized (container) {
						if(container.size() <= 4 ) {
							container.notifyAll();
						}
					}
					
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				
			}else {
				/*賣蛋糕賣的快,先賣完的情況*/
				synchronized (container) {
					try {
						System.out.println("蛋糕賣完了,等待生產");
						//喚醒生產蛋糕執行緒
						container.notifyAll();
						//讓賣蛋糕執行緒等待,釋放對container的鎖
						container.wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				
			}
		}
	}
	
}
package thread;

import java.util.ArrayList;
import java.util.List;

public class Run5 {
	public static void main(String[] args) {
		List<String> container = new ArrayList<>();
		int maxSize = 15;
		
		Maker m = new Maker(container, maxSize);
		Saller s = new Saller(container);
		
		m.start();
		s.start();
		
	}
}

10、定時器

它可以週期性的定時執行指定的任務

java.util.Timer類:表示一個計時器(執行者)

schedule(task, 1000):延遲1000毫秒執行task任務(僅執行一次)

schedule(task, 0, 1000):延遲0毫秒執行task任務,每個1000毫秒執行一次這個task任務

cancel():結束計時器

java.util.TimerTask類:表示一個計時器任務(事情)

cancel():結束當前這個任務

package thread;

import java.util.Timer;
import java.util.TimerTask;

public class TimerDemo {
	public static void main(String[] args) {
		/*建立一個計時器*/
		Timer timer = new Timer();
		
		/*建立一個計時器任務:大掃除*/
		TimerTask task1 = new TimerTask() {
			private int day = 1;
			@Override
			public void run() {
				System.out.println("打掃衛生");
			}
		};
		/*延遲2秒執行task1,並且只執行一次,但是計時器不會關閉*/
		//timer.schedule(task1, 2000);
		
		/*每3秒執行一次task1,0表示不會延遲執行*/
		timer.schedule(task1, 0, 3000);
		
		/*取消計時器,計時器立即關閉,不會執行任何任務*/
		timer.cancel();
		
	}
}