併發程式設計之多執行緒基礎

蔣老溼發表於2018-10-31

併發程式設計之多執行緒基礎

執行緒與程式區別

每個正在系統上執行的程式都是一個程式。每個程式包含一到多個執行緒。執行緒是一組指令的集合,或者是程式的特殊段,它可以在程式裡獨立執行。也可以把它理解為程式碼執行的上下文。所以執行緒基本上是輕量級的程式,它負責在單個程式裡執行多工。通常由作業系統負責多個執行緒的排程和執行。

使用執行緒可以把佔據時間長的程式中的任務放到後臺去處理,程式的執行速度可能加快,在一些等待的任務實現上如使用者輸入、檔案讀寫和網路收發資料等,執行緒就比較有用了。在這種情況下可以釋放一些珍貴的資源如記憶體佔用等等。

如果有大量的執行緒,會影響效能,因為作業系統需要在它們之間切換,更多的執行緒需要更多的記憶體空間,執行緒的中止需要考慮其對程式執行的影響。通常塊模型資料是在多個執行緒間共享的,需要防止執行緒死鎖情況的發生。

總結: 程式是所有執行緒的集合,每一個執行緒是程式中的一條執行路徑。

多執行緒應用場景?

答:主要能體現到多執行緒提高程式效率。
舉例: 迅雷多執行緒下載、資料庫連線池、分批傳送簡訊等。

多執行緒建立方式

第一種繼承Thread類 重寫run方法

//1. 繼承thread類,重寫run方法,run方法中,需要執行緒執行程式碼
class ThreadDemo01 extends Thread {

	// run方法中,需要執行緒需要執行程式碼
	@Override
	public void run() {
		for (int i = 0; i < 10; i++) {
			System.out.print("子id:" + this.getId() + ",");
			System.out.print("name:" + getName());
			System.out.println("..i:" + i);
			System.out.println();
		}
	}

}

// 1.什麼是執行緒 執行緒是一條執行路徑,每個執行緒都互不影響。
// 2.什麼是多執行緒,多執行緒在一個程式中,有多條不同的執行路徑,並行執行。目的為了提高程式效率。
// 3.在一個程式中,一定會主執行緒。
// 4.如果連執行緒主執行緒都沒有,怎麼執行程式。
// 執行緒幾種分類 使用者執行緒、守護執行緒
// 主執行緒 子執行緒 GC執行緒
public class Test001 {

	// 交替執行
	public static void main(String[] args) {
		System.out.println("main... 主執行緒開始...");
		// 1.建立執行緒
		ThreadDemo01 threadDemo01 = new ThreadDemo01();
		// 2.啟動執行緒
		threadDemo01.start();
		for (int i = 0; i < 10; i++) {
			System.out.println("main..i:" + i);
		}
		System.out.println("main... 主執行緒結束...");
	}

}
複製程式碼

第二種實現Runnable介面,重寫run方法

class ThreadDemo02 implements Runnable {

	public void run() {
		for (int i = 0; i < 10; i++) {
			System.out.println(" 子 i:" + i);
		}
	}
}

// 1.實現runable介面,重寫run方法
public class Thread002 {
	public static void main(String[] args) {
		System.out.println("main... 主執行緒開始...");
	
		// 建立執行緒
		ThreadDemo02 threadDemo02 = new ThreadDemo02();
		Thread t1= new Thread(threadDemo02);
		// 啟動執行緒
		t1.start();
		for (int i = 0; i <10; i++) {
			System.out.println("main..i:"+i);
		}
		System.out.println("main... 主執行緒結束...");
	}

}
複製程式碼

第三種使用匿名內部類方式


public class Thread003 {

	public static void main(String[] args) {
		System.out.println("main... 主執行緒開始...");

		Thread t1 = new Thread(new Runnable() {

			public void run() {
				for (int i = 0; i < 10; i++) {
					System.out.println(" 子 i:" + i);
				}
			}
		});
		t1.start();
		for (int i = 0; i < 10; i++) {
			System.out.println("main..i:" + i);
		}
		System.out.println("main... 主執行緒結束...");
	}
}
複製程式碼

使用繼承Thread類還是使用實現Runnable介面好?

使用實現實現Runnable介面好,原因實現了介面還可以繼續繼承,繼承了類不能再繼承。

啟動執行緒是使用呼叫start方法還是run方法?

開始執行執行緒 注意 開啟執行緒不是呼叫run方法,而是start方法。

獲取執行緒物件以及名稱

常用執行緒api方法
start() 啟動執行緒
currentThread() 獲取當前執行緒物件
getID() 獲取當前執行緒ID Thread-編號,該編號從0開始
getName() 獲取當前執行緒名稱
sleep(long mill) 休眠執行緒
Stop() 停止執行緒,
常用執行緒建構函式
Thread() 分配一個新的 Thread 物件
Thread(String name) 分配一個新的 Thread物件,具有指定的 name正如其名。
Thread(Runable r) 分配一個新的 Thread物件
Thread(Runable r, String name) 分配一個新的 Thread物件

守護執行緒

Java中有兩種執行緒,一種是使用者執行緒,另一種是守護執行緒。
使用者執行緒是指使用者自定義建立的執行緒,主執行緒停止,使用者執行緒不會停止
守護執行緒當程式不存在或主執行緒停止,守護執行緒也會被停止。
使用setDaemon(true)方法設定為守護執行緒

public class Test005 {

	public static void main(String[] args) throws InterruptedException {
		Thread t1 = new Thread(new Runnable() {

			public void run() {
				while (true) {
					try {
						Thread.sleep(1000);

					} catch (Exception e) {
						// TODO: handle exception
					}
					System.out.println("我是子執行緒(使用者執行緒)");
				}
			}
		});
		// 標識當前方法為守護執行緒
		t1.setDaemon(true);
		t1.start();
		for (int i = 0; i < 10; i++) {
			Thread.sleep(300);
			System.out.println("main:i:" + i);
		}
		System.out.println("主執行緒執行完畢...");

	}

}

複製程式碼

多執行緒執行狀態

併發程式設計之多執行緒基礎
執行緒從建立、執行到結束總是處於下面五個狀態之一:新建狀態、就緒狀態、執行狀態、阻塞狀態及死亡狀態。

新建狀態

當用new操作符建立一個執行緒時, 例如new Thread(r),執行緒還沒有開始執行,此時執行緒處在新建狀態。 當一個執行緒處於新生狀態時,程式還沒有開始執行執行緒中的程式碼

就緒狀態

一個新建立的執行緒並不自動開始執行,要執行執行緒,必須呼叫執行緒的start()方法。當執行緒物件呼叫start()方法即啟動了執行緒,start()方法建立執行緒執行的系統資源,並排程執行緒執行run()方法。當start()方法返回後,執行緒就處於就緒狀態。

處於就緒狀態的執行緒並不一定立即執行run()方法,執行緒還必須同其他執行緒競爭CPU時間,只有獲得CPU時間才可以執行執行緒。因為在單CPU的計算機系統中,不可能同時執行多個執行緒,一個時刻僅有一個執行緒處於執行狀態。因此此時可能有多個執行緒處於就緒狀態。對多個處於就緒狀態的執行緒是由Java執行時系統的執行緒排程程式(thread scheduler)來排程的。

執行狀態

當執行緒獲得CPU時間後,它才進入執行狀態,真正開始執行run()方法.

阻塞狀態

執行緒執行過程中,可能由於各種原因進入阻塞狀態:

  1. 執行緒通過呼叫sleep方法進入睡眠狀態;
  2. 執行緒呼叫一個在I/O上被阻塞的操作,即該操作在輸入輸出操作完成之前不會返回到它的呼叫者;
  3. 執行緒試圖得到一個鎖,而該鎖正被其他執行緒持有;
  4. 執行緒在等待某個觸發條件;

死亡狀態

有兩個原因會導致執行緒死亡:

  1. run方法正常退出而自然死亡,
  2. 一個未捕獲的異常終止了run方法而使執行緒猝死。

為了確定執行緒在當前是否存活著(就是要麼是可執行的,要麼是被阻塞了),需要使用isAlive方法。如果是可執行或被阻塞,這個方法返回true; 如果執行緒仍舊是new狀態且不是可執行的, 或者執行緒死亡了,則返回false.

join()方法作用

當在主執行緒當中執行到t1.join()方法時,就認為主執行緒應該把執行權讓給t1
例子:建立一個執行緒,子執行緒執行完畢後,主執行緒才能執行。

//join
public class Test006 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new Runnable() {
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println("子執行緒,i:" + i);
                }
            }
        });
        t1.start();
        // 當在主執行緒當中執行到t1.join()方法時,就認為主執行緒應該把執行權讓給t1
        t1.join();
        for (int i = 0; i < 10; i++) {
            System.out.println("main執行緒,i:" + i);
        }
    }
}
複製程式碼

優先順序

現代作業系統基本採用時分的形式排程執行的執行緒,執行緒分配得到的時間片的多少決定了執行緒使用處理器資源的多少,也對應了執行緒優先順序這個概念。在JAVA執行緒中,通過一個int priority來控制優先順序,範圍為1-10,其中10最高,預設值為5。下面是原始碼(基於1.8)中關於priority的一些量和方法。

class PrioritytThread implements Runnable {
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().toString() + "---i:" + i);
        }
    }
}
public class Test008 {
    public static void main(String[] args) {
        PrioritytThread prioritytThread = new PrioritytThread();
        Thread t1 = new Thread(prioritytThread);
        Thread t2 = new Thread(prioritytThread);
        t1.start();
        // 注意設定了優先順序, 不代表每次都一定會被執行。 只是CPU排程會有限分配
        t2.start();
        t1.setPriority(10);
    }
}
複製程式碼

Yield方法

Thread.yield()方法的作用:暫停當前正在執行的執行緒,並執行其他執行緒。(可能沒有效果) yield()讓當前正在執行的執行緒回到可執行狀態,以允許具有相同優先順序的其他執行緒獲得執行的機會。因此,使用yield()的目的是讓具有相同優先順序的執行緒之間能夠適當的輪換執行。但是,實際中無法保證yield()達到讓步的目的,因為,讓步的執行緒可能被執行緒排程程式再次選中。
結論: 大多數情況下,yield()將導致執行緒從執行狀態轉到可執行狀態,但有可能沒有效果。

例項

現… 在有T1、T2、T3三個執行緒,你怎樣保證T2在T1執行完後執行,T3在T2執行完後執行

class T1 implements Runnable {
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().toString() + "---i:" + i);
        }
    }
}

class T2 implements Runnable {
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().toString() + "---i:" + i);
        }
    }
}

class T3 implements Runnable {
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().toString() + "---i:" + i);
        }
    }
}

public class JoinThreadDemo02 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new T1(), "T1");
        Thread t2 = new Thread(new T2(), "T2");
        Thread t3 = new Thread(new T3(), "T3");
        t1.start();
        t1.join();
        t2.start();
        t2.join();
        t3.start();
        t3.join();
    }
}
複製程式碼

git文章所有程式碼

面試題

  1. 程式與執行緒的區別?
    答:程式是所有執行緒的集合,每一個執行緒是程式中的一條執行路徑,執行緒只是一條執行路徑。
  2. 為什麼要用多執行緒?
    答:提高程式效率
  3. 多執行緒建立方式?
    答:繼承Thread或Runnable 介面。
  4. 是繼承Thread類好還是實現Runnable介面好?
    答:Runnable介面好,因為實現了介面還可以繼續繼承。繼承Thread類不能再繼承。
  5. 你在哪裡用到了多執行緒?
    答:主要能體現到多執行緒提高程式效率。
    舉例:分批傳送簡訊、迅雷多執行緒下載等。

相關文章