多執行緒學習總結(一)
一、程序和執行緒的定義
程序:程序是資源(CPU、記憶體等)分配的基本單位,它是程式執行時的一個例項。程式執行時系統就會建立一個程序,併為它分配資源,然後把該程序放入程序就緒佇列,程序排程器選中它的時候就會為它分配CPU時間,程式開始真正執行。
執行緒:執行緒是程式執行時的最小單位,它是程序的一個執行流,是CPU排程和分派的基本單位,一個程序可以由很多個執行緒組成,執行緒間共享程序的所有資源,每個執行緒有自己的堆疊和區域性變數。
例:程序 ->車間,執行緒->車間工人
二、程序和執行緒的區別
- 程序是資源分配的最小單位,執行緒是程式執行的最小單位。
- 地址空間:同一程序的執行緒共享本程序的地址空間,而程序之間則是獨立的地址空間。
- 資源擁有:同一程序內的執行緒共享本程序的資源如記憶體、I/O、cpu等,但是程序之間的資源是獨立的
-
執行緒之間的通訊更方便,同一程序下的執行緒共享全域性變數、靜態變數等資料,而程序之間的通訊需要以通訊的方式(IPC)進行。不過如何處理好同步與互斥是編寫多執行緒程式的難點。
-
但是多程序程式更健壯,多執行緒程式只要有一個執行緒死掉,整個程序也死掉了,而一個程序死掉並不會對另外一個程序造成影響,因為程序有自己獨立的地址空間。
三、執行緒的七種狀態和其相互間的轉換
1.實現執行緒的方式主要有兩種方式:繼承Thread類和實現Runnable介面;但只要我們建立(new)物件,這個執行緒就進入了初始化狀態;
2.呼叫這份執行緒的start()方法,執行緒就進入執行緒池中等待CUP的使用權,就處於就緒狀態;
3.處於就緒狀態的執行緒的得到獲取CUP時間片,就進入到執行狀態;
4.處於執行狀態中:
- run()方法結束獲取main方法結束後,程序就進入到了終止狀態;
- 當執行緒呼叫了自身的sleep()方法或其他執行緒的join()方法,程序讓出CUP,然後就會進入到阻塞狀態(該狀態既會停止當前執行緒,但並不釋放所佔有的資源,即呼叫sleep()函式後,執行緒不會釋放它的“鎖標誌”)。當sleep()結束或者join()方法結束後,該執行緒進入可以執行狀態,繼續的部分帶OS分貝CPU時間片。
- 執行緒呼叫了yield()方法,意思是放棄當前獲得的CPU時間片,回到就緒狀態,這時與其他程序處於同等競爭狀態,OS有可能會接著又讓這個程序進入執行狀態; 呼叫 yield() 的效果等價於排程程式認為該執行緒已執行了足夠的時間片從而需要轉到另一個執行緒。yield()只是使當前執行緒重新回到可執行狀態,所以執行yield()的執行緒有可能在進入到可執行狀態後馬上又被執行。
- 當執行緒剛進入可執行狀態(注意,還沒執行),發現將要呼叫的資源被synchroniza(同步),獲取不到鎖標記,將會立即進入鎖池狀態,等待獲取鎖標記(這時的鎖池裡也許已經有了其他執行緒在等待獲取鎖標記,這時它們處於佇列狀態,既先到先得),一旦執行緒獲得鎖標記後,就轉入就緒狀態,等待OS分配CPU時間片;
-
suspend() 和 resume()方法:兩個方法配套使用,suspend()使得執行緒進入阻塞狀態,並且不會自動恢復,必須其對應的resume()被呼叫,才能使得執行緒重新進入可執行狀態。典型地,suspend()和 resume() 被用在等待另一個執行緒產生的結果的情形:測試發現結果還沒有產生後,讓執行緒阻塞,另一個執行緒產生了結果後,呼叫 resume()使其恢復。
-
wait()和 notify() 方法:當執行緒呼叫wait()方法後會進入等待佇列(進入這個狀態會釋放所佔有的所有資源,與阻塞狀態不同),進入這個狀態後,是不能自動喚醒的,必須依靠其他執行緒呼叫notify()或notifyAll()方法才能被喚醒(由於notify()只是喚醒一個執行緒,但我們由不能確定具體喚醒的是哪一個執行緒,也許我們需要喚醒的執行緒不能夠被喚醒,因此在實際使用時,一般都用notifyAll()方法,喚醒有所執行緒),執行緒被喚醒後會進入鎖池,等待獲取鎖標記。
四、在java中實現多執行緒的幾種方式:
(一)繼承Thread類的方式:
package com.wcg.thread.t1;
public class Demo1 extends Thread{
public Demo1(String name) {
super(name);
}
@Override
public void run() {
while(true) {
System.out.println(this.getName()+"當前執行緒執行了");
}
}
public static void main(String[] args) {
Demo1 demo1 = new Demo1("first_thread");
Demo1 demo2 = new Demo1("second_thread");
demo1.start();
demo2.start();
}
}
說明:
程式啟動執行main時候,java虛擬機器啟動一個程序,主執行緒main在main()呼叫時候被建立。隨著呼叫MitiSay的兩個物件的start方法,另外兩個執行緒也啟動了,這樣,整個應用就在多執行緒下執行。
注意:start()方法的呼叫後並不是立即執行多執行緒程式碼,而是使得該執行緒變為可執行態(Runnable),什麼時候執行是由作業系統決定的。
從程式執行的結果可以發現,多執行緒程式是亂序執行。因此,只有亂序執行的程式碼才有必要設計為多執行緒。Thread.sleep()方法呼叫目的是不讓當前執行緒獨自霸佔該程序所獲取的CPU資源,以留出一定時間給其他執行緒執行的機會。實際上所有的多執行緒程式碼執行順序都是不確定的,每次執行的結果都是隨機的。但是start方法重複呼叫的話,會出現java.lang.IllegalThreadStateException異常。
(二)實現Runnabale的方式:
package com.wcg.thread.t1;
public class Demo2 implements Runnable{
@Override
public void run() {
while(true) {
System.out.println("Thread Running");
}
}
public static void main(String[] args) {
//例項化一個Thread,並傳入例項物件
Thread thread = new Thread(new Demo2());
thread.run();
}
}
(三)實現Callable介面(帶有返回值)通過FutureTask包裝器來建立Thread執行緒:
public class Demo4 implements Callable<Integer>{
@Override
public Integer call() throws Exception {
System.out.println("正在計算中....");
Thread.sleep(3000);
return 1;
}
public static void main(String[] args) throws Exception{
Demo4 d = new Demo4();
FutureTask<Integer> task = new FutureTask<>(d);
Thread thread = new Thread(task);
thread.start();
System.out.println("哈哈哈哈");
Integer result = task.get();
System.out.println(result);
}
}
(四)通過定時器Timer的方式實現:
public class Demo5 {
public static void main(String[] args) {
Timer timer = new Timer();
//TimerTask繼承了Runable介面
timer.schedule(new TimerTask() {
@Override
public void run() {
//實現定時任務
System.out.println("timerTask is running");
}
}, 0, 1000);
}
}
(注:只做個人學習總結,如有侵權請聯絡我,有的資料是網上獲取的。。。。。)