前言
多線程編程的根本目的,就是更好的利用cpu資源,採用多線程的方式去同時完成幾件事情而不互相干擾。
1.多線程的一些基本概念
- **多線程:**指的是這個程序(一個進程)運行時產生了不止一個線程
- **併發:**通過cpu調度算法,讓用戶看上去同時執行,實際上從cpu操作層面不是真正的同時。併發往往在場景中有公用的資源,那麼針對這個公用的資源往往產生瓶頸,我們會用TPS或者QPS來反應這個系統的處理能力。
- **線程安全:**經常用來描繪一段代碼。指在併發的情況之下,該代碼經過多線程使用,線程的調度順序不影響任何結果。這個時候使用多線程,我們只需要關注系統的內存,cpu是不是夠用即可。反過來,線程不安全就意味着線程的調度順序會影響最終結果,如不加事務的轉賬代碼。
- 同步: Java中的同步指的是通過人爲的控制和調度,保證共享資源的多線程訪問成爲線程安全,來保證結果的準確。如上面的代碼簡單加入@synchronized關鍵字。在保證結果準確的同時,提高性能,纔是優秀的程序。線程安全的優先級高於性能。
2. 線程的狀態
線程在Running的過程中可能會遇到阻塞(Blocked)情況:
- 調用join()和sleep()方法,sleep()時間結束或被打斷,join()中斷,IO完成都會回到Runnable狀態,等待JVM的調度。
- 調用wait(),使該線程處於等待池(wait blocked pool),直到notify()/notifyAll(),線程被喚醒被放到鎖定池(lock blocked pool ),釋放同步鎖使線程回到可運行狀態(Runnable)
- 對Running狀態的線程加同步鎖(Synchronized)使其進入(lock blocked pool ),同步鎖被釋放進入可運行狀態(Runnable)。
此外,在runnable狀態的線程是處於被調度的線程,此時的調度順序是不一定的。Thread類中的yield方法可以讓一個running狀態的線程轉入runnable。
3.多線程編程的兩種方式
3.1 繼承Thread類
繼承Thread類可以一種多線程實現方式,但查看Thread類我們可以發現其也是Runnable接口的一個實例,並且,啓動線程的唯一方法就是通過Thread類的start()實例方法。start()方法是一個native方法,它將啓動一個新線程,並執行run()方法。這種方式實現多線程很簡單,通過自己的類直接extend Thread,並複寫run()方法,就可以啓動新線程並執行自己定義的run()方法。例如:
public class ThreadDemo1 extends Thread{
@Override
public void run() {
//輸出當前線程的名稱
String name = Thread.currentThread().getName();
for(int i=0;i<100;i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name+i);
}
}
}
public class ThreadMethod{
public static void main(String[] args) {
new ThreadDemo01().start();
}
}
3.2 實現Runnable接口
如果自己的類已經繼承另一個類,就無法直接extends Thread,此時,必須實現一個Runnable接口,如下:
public class ThreadDemo implements Runnable {
@Override
public void run() {
//輸出當前線程的名稱
String name = Thread.currentThread().getName();
for(int i=0;i<100;i++){
System.out.println(name+i);
}
}
}
//爲了啓動線程必須先實例化一個Thread,並傳入子定義的線程實例
public class ThreadMethod{
public static void main(String[] args) {
new Thread(new ThreadDemo()).start();
}
}
3.3 兩種實現方式區別
- 繼承Thread:直接使用Thread類中的方法,代碼簡單 ,由於子類重寫父類的run(),當調用start()時,直接找子類的run()方法 。但由於單繼承的原因受到限制。
- 實現Runnable接口:不能直接使用Thread類中的方法,需要先獲取到線程對象後,才能得到Thread的方法,代碼複雜。Thread的構造函數中傳入了Runnable引用,成員變量記住它,start()調用Thread中的run()方法時,判斷成員變量Runnable的引用是否爲空,不爲空則在Thread的run()方法中調用Runnable的run()方法。編譯看Runnable的run(),運行看子類run()方法。 由於可以多實現 因此不受限制。
4.主要的方法
- synchronized:給線程加鎖,實現同步
- wait():等待, 必須等到notify/notifyAll 方法才能進入runnable狀態
- notify/notifyAl
- sleep(long l) : 睡眠指定時間
- join() :在一個線程中調用other.join(),將等待other執行完後才繼續本線程。
- interrupte():
- yield():暫停當前正在執行的線程對象,並執行其他線程。
關於中斷:它並不像stop方法那樣會中斷一個正在運行的線程。線程會不時地檢測中斷標識位,以判斷線程是否應該被中斷(中斷標識值是否爲true)。終端只會影響到wait狀態、sleep狀態和join狀態。被打斷的線程會拋出InterruptedException。
Thread.interrupted()檢查當前線程是否發生中斷,返回boolean
synchronized在獲鎖的過程中是不能被中斷的。
中斷是一個狀態!interrupt()方法只是將這個狀態置爲true而已。所以說正常運行的程序不去檢測狀態,就不會終止,而wait等阻塞方法會去檢查並拋出異常。如果在正常運行的程序中添加while(!Thread.interrupted()) ,則同樣可以在中斷後離開代碼體
5. 線程中獲取異常
多線程不能用try,catch來獲取線程中的異常