Java-thread多執行緒
程序與執行緒
程序是作業系統資源分配的基本單位,每個程序都有獨立的程式碼和資料空間(程式上下文),程式之間的切換會有較大的開銷;在作業系統中能同時執行多個程序(程式);系統在執行的時候會為每個程序分配不同的記憶體空間;沒有執行緒的程序可以看做是單執行緒的,如果一個程序內有多個執行緒,則執行過程不是一條線的,而是多條線(執行緒)共同完成的。
執行緒是任務排程和執行的基本單位,執行緒可以看做輕量級的程序,同一類執行緒共享程式碼和資料空間,每個執行緒都有自己獨立的執行棧和程式計數器(PC),執行緒之間切換的開銷小。在同一個程序(程式)中有多個執行緒同時執行(通過CPU排程,在每個時間片中只有一個執行緒執行);對執行緒而言,除了CPU外,系統不會為執行緒分配記憶體(執行緒所使用的資源來自其所屬程序的資源),執行緒組之間只能共享資源。執行緒是程序的一部分,所以執行緒也被稱為輕權程序或者輕量級程序。
執行緒和程序一樣分為五個階段:建立、就緒、執行、阻塞、終止。
執行緒相關基本概念
多程序:是指作業系統能同時執行多個任務(程式)。
多執行緒:是指在同一程式中有多個順序流在執行。
主執行緒:當Java程式啟動時,一個執行緒立刻執行,該執行緒通常叫做程式的主執行緒,使用java命令就啟動一個虛擬機器程序,java虛擬機器會建立一個主執行緒,該程式入口main()方法執行
主執行緒特點:
- 是產生其他子執行緒的執行緒
- 不一定是最後執行的執行緒,子執行緒可能在主執行緒結束後執行
子執行緒:在一個執行緒中開啟另外一個新執行緒,則新開執行緒稱為該執行緒的子執行緒,子執行緒初始優先順序與父執行緒相同,子執行緒執行順序不受程式設計師控制、JVM自行調動
普通執行緒(使用者執行緒):只完成使用者自己想要完成的任務,不提供公共服務
守護執行緒(後臺執行緒):只要當前JVM例項中尚存在任何一個非守護執行緒沒有結束,守護執行緒就全部工作;只有當最後一個非守護執行緒結束時,守護執行緒隨著JVM一同結束工作。
執行緒組:在Java中,執行緒組是指java.lang.ThreadGroup類的物件,每個執行緒都隸屬於唯一的一個執行緒組,如果沒有定義會預設一個執行緒組,也可以指定執行緒屬於某個執行緒組,每一個應用都至少有一個執行緒屬於系統執行緒組,這個執行緒組線上程建立時指定並在執行緒的整個生命週期內都不能更改。可以通過呼叫包含ThreadGroup型別引數的Thread類構造方法來指定執行緒所屬執行緒組。若沒有指定,則執行緒預設的隸屬於名為main的系統執行緒組。除了預建的系統執行緒外,所以執行緒組都必須顯式建立。
執行緒的生命週期、狀態(重點)
新建狀態(New):新建立了一個執行緒物件。使用 new 關鍵字和 Thread 類或其子類建立一個執行緒物件後,該執行緒物件就處於新建狀態。它保持這個狀態直到程式 start() 這個執行緒。
就緒狀態(Runnable):執行緒物件建立後,其他執行緒呼叫了該物件的start()方法,該執行緒就進入就緒狀態。該狀態的執行緒位於可執行執行緒池中,就緒狀態的執行緒處於就緒佇列中,變得可執行,等待獲取CPU的使用權,要等待JVM裡執行緒排程器的排程。
執行狀態(Running):如果就緒狀態的執行緒獲取 CPU 資源,就可以執行 run(),此時執行緒便處於執行狀態。處於執行狀態的執行緒最為複雜,它可以變為阻塞狀態、就緒狀態和死亡狀態。
阻塞狀態(Blocked):阻塞狀態是執行緒因為某種原因放棄CPU使用權,暫時停止執行。直到執行緒進入就緒狀態,才有機會轉到執行狀態。阻塞的情況分三種:
(一)、等待阻塞:執行的執行緒執行wait()方法,JVM會把該執行緒放入等待池中。
(二)、同步阻塞:執行的執行緒在獲取物件的同步鎖時,若該同步鎖被別的執行緒佔用,則JVM會把該執行緒放入鎖池中。
(三)、其他阻塞:執行的執行緒執行sleep()或join()方法,或者發出了I/O請求時,JVM會把該執行緒置為阻塞狀態。當sleep()狀態超時、join()等待執行緒終止或者超時、或者I/O處理完畢時,執行緒重新轉入就緒狀態。
死亡狀態(Dead):執行緒執行完了或者因異常退出了run()方法,該執行緒結束生命週期。
1、新建狀態(New):新建立了一個執行緒物件。
2、就緒狀態(Runnable):執行緒物件建立後,其他執行緒呼叫了該物件的start()方法。該狀態的執行緒位於可執行執行緒池中,變得可執行,等待獲取CPU的使用權。
3、執行狀態(Running):就緒狀態的執行緒獲取了CPU,執行程式程式碼。
4、阻塞狀態(Blocked):阻塞狀態是執行緒因為某種原因放棄CPU使用權,暫時停止執行。直到執行緒進入就緒狀態,才有機會轉到執行狀態。阻塞的情況分三種:
(一)、等待阻塞:執行的執行緒執行wait()方法,JVM會把該執行緒放入等待池中。
(二)、同步阻塞:執行的執行緒在獲取物件的同步鎖時,若該同步鎖被別的執行緒佔用,則JVM會把該執行緒放入鎖池中。
(三)、其他阻塞:執行的執行緒執行sleep()或join()方法,或者發出了I/O請求時,JVM會把該執行緒置為阻塞狀態。當sleep()狀態超時、join()等待執行緒終止或者超時、或者I/O處理完畢時,執行緒重新轉入就緒狀態。
5、死亡狀態(Dead):執行緒執行完了或者因異常退出了run()方法,該執行緒結束生命週期。
執行緒的建立
建立執行緒的方法有兩種:
- 繼承Thread類,重寫該類的run()方法
- 實現Runnable介面,重寫該介面的run()方法
執行緒的啟動:呼叫執行緒物件的start()方法。執行緒執行的是建立執行緒類中重寫的run()方法。
public class CreateThread {
// 繼承Thread類本身
static class extsThread extends Thread {
@Override
public void run() {
}
}
// 實現Runnable介面
static class implRunnable implements Runnable {
@Override
public void run() {
}
}
public static void main(String[] args) {
// 方法一:繼承Thread類,建立執行緒物件
Thread thread1 = new extsThread();
// 啟動執行緒
thread1.start();
// 方法二:實現Runnable介面,建立實現類物件
Runnable runnable = new implRunnable();
// 再將實現類物件作為引數,傳遞打炮Thread建立執行緒物件
Thread thread2 = new Thread(runnable);
// 啟動執行緒
thread2.start();
}
}
執行緒的優先順序
每一個Java執行緒都有一個優先順序,Java執行緒的優先順序用整數表示,執行緒的優先級別不會決定哪個執行緒先啟動,只能建議JVM哪個執行緒先執行,高優先順序的執行緒比低優先順序的執行緒有更高的機率得到執行,分佈在Thread.MIN_PRIORITY和Thread.MAX_PRIORITY之間(分別為1和10)
預設情況下,新建立的執行緒都擁有和建立它的執行緒相同的優先順序。main方法所關聯的初始化執行緒擁有一個預設的優先順序,這個優先順序是Thread.NORM_PRIORITY (5),執行緒的當前優先順序可以通過getPriority方法獲得,執行緒的優先順序可以通過setPriority方法來動態的修改,一個執行緒的最高優先順序由其所在的執行緒組限定
Thread類有以下三個靜態常量:
public final static int MIN_PRIORITY = 1;
public final static int NORM_PRIORITY = 5;
public final static int MAX_PRIORITY = 10;
Thread類的setPriority()和getPriority()方法分別用來設定和獲取執行緒的優先順序
/**
* thread是一個執行緒物件
*/
// 設定執行緒優先順序
thread.setPriority(Thread.MIN_PRIORITY);
thread.setPriority(8);
// 獲得執行緒優先順序
thread.getPriority();
執行緒的排程
1、執行緒睡眠(sleep):Thread.sleep(long millis)方法,使執行緒轉到阻塞狀態,等待執行緒睡眠設定時間後進入就緒狀態,不釋放鎖。millis引數設定睡眠的時間,以毫秒為單位sleep()平臺移植性好。
try {
// 當前執行緒睡眠1秒
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
2、執行緒讓步(yield):Thread.yield() 方法,暫停當前正在執行的執行緒物件,使執行緒轉到就緒狀態,不釋放鎖,把執行機會讓給相同或者更高優先順序的執行緒。
public class ThreadYield extends Thread{
@Override
public void run() {
for (int i = 1; i <= 10; i++) {
System.out.println("Thread Name : " + Thread.currentThread().getName() + " " + i);
}
}
public static void main(String[] args) {
// 互動執行
ThreadYield threadYield1 = new ThreadYield();
ThreadYield threadYield2 = new ThreadYield();
Thread thread = new Thread("Thread Yield") {
@Override
public void run() {
for (int i = 1; i <= 10; i++) {
System.out.println("Thread Name : " + Thread.currentThread().getName() + " " + i);
if (i == 4) {
// 當前執行緒讓步
this.yield();
}
}
}
};
thread.start();
threadYield1.start();
threadYield2.start();
}
}
3、執行緒加入(join):Thread.join()方法,在當前執行緒中呼叫另一個執行緒的join()方法,則使當前執行緒轉入阻塞狀態,直到另一個執行緒執行結束,當前執行緒再由阻塞轉為就緒狀態。join()方法底層用wait()實現,所以會使當前執行緒釋放鎖。
public class ThreadJoin extends Thread {
private Thread thread;
public ThreadJoin() {
}
public ThreadJoin(Thread thread) {
this.thread = thread;
}
@Override
public void run() {
for (int i = 1; i < 10; i++) {
if (i == 4) {
thread.start();
try {
// 加入一個執行緒myThread,等myThread,執行完threadJoin2才繼續執行
thread.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println(this.currentThread().getName() + " : " + i);
}
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
ThreadJoin threadJoin2 = new ThreadJoin(myThread);
threadJoin2.start();
}
}
4、執行緒等待(wait):Object.wait()方法,導致當前的執行緒等待,進入阻塞狀態,直到其他執行緒呼叫此物件的 notify() 方法或 notifyAll() 喚醒方法,使該執行緒進入就緒狀態,釋放鎖。
5、執行緒喚醒(notify):Object.notify()方法和Object.notifyAll()方法,喚醒在此物件監視器上等待的單個執行緒或所有執行緒,使執行緒從阻塞狀態變成就緒狀態。如果所有執行緒都在此物件上等待,則會選擇喚醒其中一個執行緒。選擇是任意性的,並在對實現做出決定時發生。執行緒通過呼叫其中一個 wait 方法,在物件的監視器上等待。 直到當前的執行緒放棄此物件上的鎖定,才能繼續執行被喚醒的執行緒。被喚醒的執行緒將以常規方式與在該物件上主動同步的其他所有執行緒進行競爭;例如,喚醒的執行緒在作為鎖定此物件的下一個執行緒方面沒有可靠的特權或劣勢。
public class ThreadWait {
public static void main(String[] args) {
final Test t = new Test();
Thread t1 = new Thread("A") {
@Override
public void run() {
try {
for (int i = 0; i < 10; i++) {
synchronized (t) {
if(i!=0)
// 執行緒的喚醒
t.notify();
System.out.println("A");
t.wait();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
Thread t2 = new Thread("B") {
@Override
public void run() {
try {
for (int i = 0; i < 10; i++) {
synchronized (t) {
t.notify();
System.out.println("B");
if(i!=9)
// 執行緒等待
t.wait();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
t1.start();
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
}
}
注意:Thread中suspend()和resume()兩個方法在JDK1.5中已經廢除,不再介紹。因為有死鎖傾向。
執行緒的同步
執行緒同步:保證多個執行緒同時讀取一個類中的共享資料的執行緒安全
Java所有物件都有一個內建鎖,使用 synchronized 關鍵字修飾方法或程式碼塊時將為當前物件加鎖,一個執行緒獲取鎖後,其他執行緒需要等待該執行緒執行完畢後解鎖。
有兩種同步的方法:
- synchronized修飾方法
- synchronized修飾程式碼塊
// 修飾方法
private synchronized void function() {
}
// 修飾程式碼塊
private void function() {
synchronized (object) {
}
}
參考:
https://www.cnblogs.com/Qian123/p/5670304.html
https://www.cnblogs.com/yjd_hycf_space/p/7526608.html
http://www.runoob.com/java/java-multithreading.html