多線程
- 在傳統的操作系統中,程序並不能獨立運行,作爲資源分配和獨立運行的基本單位都是進程。 在未配置 OS
- 的系統中,程序的執行方式是順序執行,即必須在一個程序執行完後,才允許另一個程序執行;在多道程序環境下,則允許多個程序併發執行。程序的這兩種執行方式間有着顯著的不同。也正是程序併發執行時的這種特徵,才導致了在操作系統中引入進程的概念。
- 自從在 20 世紀 60 年代人們提出了進程的概念後,在 OS 中一直都是以進程作爲能擁有資源和獨立運行的基本單位的。直到 20 世紀 80年代中期,人們又提出了比進程更小的能獨立運行的基本單位——線程(Thread),試圖用它來提高系統內程序併發執行的程度,從而可進一步提高系統的吞吐量。特別是在進入20 世紀 90年代後,多處理機系統得到迅速發展,線程能比進程更好地提高程序的並行執行程度,充分地發揮多處理機的優越性,因而在近幾年所推出的多處理機 OS中也都引入了線程,以改善 OS 的性能。
- ----------------以上摘自《計算機操作系統-湯小丹等編著-3 版》
進程的概念
- 就是正在運行的程序。也就是代表了程序鎖佔用的內存區域。
特點
-
獨立性:進程是系統中獨立存在的實體,它可以擁有自己的獨立的資源,每一個進程都擁有自己私有的地址空間。在沒有經過進程本身允許的情況下,一個用戶進程不可以直接訪問其他進程的地址空間。
-
動態性:進程與程序的區別在於,程序只是一個靜態的指令集合,而進程是一個正在系統中活動的指令集合。在進程中加入了時間的概念,進程具有自己的生命週期和各種不同的狀態,這些概念在程序中都是不具備的。
-
併發性:多個進程可以在單個處理器上併發執行,多個進程之間不會互相影響。
線程的概念
-
線程(thread)是操作系統能夠進行運算調度的最小單位。它被包含在進程之中,是進程中的實際運作單位。一個進程可以開啓多個線程。
-
多線程擴展了多進程的概念,使得同一個進程可以同時併發處理多個任務。
-
簡而言之,一個程序運行後至少一個進程,一個進程裏包含多個線程。
-
如果一個進程只有一個線程,這種程序被稱爲單線程。
-
如果一個進程中有多條執行路徑被稱爲多線程程序。
進程和線程的關係
從上圖中可以看出一個操作系統中可以有多個進程,一個進程中可以有多個線程,每個進程有自己獨立的內存,每個線程共享一個進程中的內存,每個線程又有自己獨立的內存。(記清這個關係,非常重要!) 所以想使用線程技術,得先有進程,進程的創建是OS創建的,你能實現嗎?不能,一般都是c或者c++語言完成的。
多線程的特性
-
隨機性
線程狀態
線程生命週期,總共有五種狀態: -
1) 新建狀態(New):當線程對象對創建後,即進入了新建狀態,如:Thread t = new MyThread();
-
2) 就緒狀態(Runnable):當調用線程對象的start()方法(t.start();),線程即進入就緒狀態。處於就緒狀態的線程,只是說明此線程已經做好了準備,隨時等待CPU調度執行,並不是說執行了t.start()此線程立即就會執行;
-
3) 運行狀態(Running):當CPU開始調度處於就緒狀態的線程時,此時線程才得以真正執行,即進入到運行狀態。注:就緒狀態是進入到運行狀態的唯一入口,也就是說,線程要想進入運行狀態執行,首先必須處於就緒狀態中;
-
4) 阻塞狀態(Blocked):處於運行狀態中的線程由於某種原因,暫時放棄對CPU的使用權,停止執行,此時進入阻塞狀態,直到其進入到就緒狀態,纔有機會再次被CPU調用以進入到運行狀態;
-
5) 根據阻塞產生的原因不同,阻塞狀態又可以分爲三種:
- a) 等待阻塞:運行狀態中的線程執行wait()方法,使本線程進入到等待阻塞狀態;
- b) 同步阻塞:線程在獲取synchronized同步鎖失敗(因爲鎖被其它線程所佔用),它會進入同步阻塞狀態;
- c) 其他阻塞:通過調用線程的sleep()或join()或發出了I/O請求時,線程會進入到阻塞狀態。當sleep()狀態超時、join()等待線程終止或者超時、或者I/O處理完畢時,線程重新轉入就緒狀態。
- 6) 死亡狀態(Dead):線程執行完了或者因異常退出了run()方法,該線程結束生命週期。
多線程創建1:繼承Thread
Thread類本質上是實現了Runnable接口的一個實例,代表一個線程的實例。啓動線程的唯一方法就是通過Thread類的start()實例方法。Start()方法是一個native方法,它將通知底層操作系統,最終由操作系統啓動一個新線程,操作系統將執行run()方法。這種方式實現多線程很簡單,通過自己的類直接extend Thread,並複寫run()方法,就可以啓動新線程並執行自己定義的run()方法。(模擬開啓多個線程,每個線程調用run()方法.)
常用方法
String getName()
返回該線程的名稱。
static Thread currentThread()
返回對當前正在執行的線程對象的引用。
void setName(String name)
改變線程名稱,使之與參數 name 相同。
static void sleep(long millis)
在指定的毫秒數內讓當前正在執行的線程休眠(暫停執行),此操作受到系統計時器和調度程序精度和準確性的影響。
void start()
使該線程開始執行;Java 虛擬機調用該線程的 run 方法。
Thread(String name)
分配新的 Thread 對象。
測試
public class Test1 {
public static void main(String[] args) {
//3、創建線程對象
ThreadDemo t1 = new ThreadDemo("鋼鐵俠");
ThreadDemo t2 = new ThreadDemo("美隊");
//4、開啓線程:誰搶到資源誰就先執行
t1.start();
t2.start();
//t1.run();//當做常規方法調用,且 不會發生多線程現象
}
}
//1、作爲Thread的子類,並重寫run方法。把多線程的業務寫在run方法中
class ThreadDemo extends Thread{
public ThreadDemo() {}
public ThreadDemo(String name) {
super(name);
}
@Override
public void run() {
//2、默認實現是super.run();
for (int i = 0; i < 10; i++) {
System.out.println(getName()+i);
}
}
}
多線程創建2:實現Runnable接口
如果自己的類已經extends另一個類,就無法多繼承,此時,可以實現一個Runnable接口。
常用方法
void run()
使用實現接口 Runnable 的對象創建一個線程時,啓動該線程將導致在獨立執行的線程中調用對象的 run 方法。
測試
public class Test2 {
public static void main(String[] args) {
MyThread t = new MyThread ();
//2,構造創建對象,傳入Runnable子類
Thread target = new Thread(t);
Thread target2 = new Thread(t);
//開啓線程
target.start();
target2.start();
}
}
//1,實現Runnable接口,重寫run()
class MyThread implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
注意:可以看到執行順序是亂的,我們已經知道start()方法只是通知操作系統線程就緒,具體什麼時間執行,操作系統來決定,我們JVM已經控制不了了。這就是亂序的原因,也是正常的。
比較
同步鎖:synchronized
把有可能出現問題的代碼包起來,一次只讓一個線程執行。通過sychronized關鍵字實現同步。當多個對象操作共享數據時,可以使用同步鎖解決線程安全問題。
synchronized(對象){
需要同步的代碼;
}
特點
1、 前提1,同步需要兩個或者兩個以上的線程。
2、 前提2,多個線程間必須使用同一個鎖。
3、 同步的缺點是會降低程序的執行效率, 爲了保證線程安全,必須犧牲性能。
4、 可以修飾方法稱爲同步方法,使用的鎖對象是this。
5、 可以修飾代碼塊稱爲同步代碼塊,鎖對象可以任意。