一、基本概念
1、進程:是執(zhí)行中一段程序,即一旦程序被載入到內(nèi)存中并準備執(zhí)行,它就是一個進程。進程是表示資源分配的的基本概念,又是調(diào)度運行的基本單位,是系統(tǒng)中的并發(fā)執(zhí)行的單位。
2、線程:單個進程中執(zhí)行中每個任務就是一個線程。線程是進程中執(zhí)行運算的最小單位。
3、線程是一種輕量級的進程,與進程相比,線程給操作系統(tǒng)帶來側(cè)創(chuàng)建、維護、和管理的負擔要輕,意味著線程的代價或開銷比較小。
4、線程沒有地址空間,線程包含在進程的地址空間中。線程上下文只包含一個堆棧、一個寄存器、一個優(yōu)先權(quán),線程文本包含在他的進程 的文本片段中,進程擁有的所有資源都屬于線程。所有的線程共享進程的內(nèi)存和資源。 同一進程中的多個線程共享代碼段(代碼和常量),數(shù)據(jù)段(全局變量和靜態(tài)變量),擴展段(堆存儲)。但是每個線程擁有自己的棧段, 寄存器的內(nèi)容,棧段又叫運行時段,用來存放所有局部變量和臨時變量。
5、父和子進程使用進程間通信機制,同一進程的線程通過讀取和寫入數(shù)據(jù)到進程變量來通信。
6、進程內(nèi)的任何線程都被看做是同位體,且處于相同的級別。不管是哪個線程創(chuàng)建了哪一個線程,進程內(nèi)的任何線程都可以銷毀、掛起、恢復和更改其它線程的優(yōu)先權(quán)。線程也要對進程施加控制,進程中任何線程都可以通過銷毀主線程來銷毀進程,銷毀主線程將導致該進程的銷毀,對主線程的修改可能影響所有的線程。
7、子進程不對任何其他子進程施加控制,進程的線程可以對同一進程的其它線程施加控制。子進程不能對父進程施加控制,進程中所有線程都可以對主線程施加控制。
總結(jié):進程是所有線程的集合,每一個線程是進程中的一條執(zhí)行路徑。
多線程的目的是為了提高程序效率
可以通過繼承Thread或Runnable接口來創(chuàng)建進程
public class ThreadDemo1 extends Thread {
@Override
public void run() {
System.out.println("extends Thread to do Something");
}
}
public class ThreadDemo2 implements Runnable {
@Override
public void run() {
System.out.println("implements Runnable to do Something");
}
}
public class main {
public static void main(String[] args) {
//1.繼承Thread類創(chuàng)建
new ThreadDemo1().start();
//2.實現(xiàn)Runnable接口創(chuàng)建
new Thread(new ThreadDemo2()).start();
//3。使用匿名內(nèi)部類創(chuàng)建
new Thread(() -> {
System.out.println("Anonymous inner class to do Something");
}).start();
}
}
運行得到結(jié)果
extends Thread to do Something
implements Runnable to do Something
Anonymous inner class to do Something
二、多線程運行狀態(tài)
線程從創(chuàng)建、運行到結(jié)束總是處于下面五個狀態(tài)之一:新建狀態(tài)、就緒狀態(tài)、運行狀態(tài)、阻塞狀態(tài)及死亡狀態(tài)
1. 新建狀態(tài)
當用new操作符創(chuàng)建一個線程時,例如new Thread(r),線程還沒有開始運行,此時線程處在新建狀態(tài)。當一個線程處于新生狀態(tài)時,程序還沒有開始運行線程中的代碼
2. 就緒狀態(tài)
一個新創(chuàng)建的線程并不自動開始運行,要執(zhí)行線程,必須調(diào)用線程的start()方法。當線程對象調(diào)用start()方法即啟動了線程,start()方法創(chuàng)建線程運行的系統(tǒng)資源,并調(diào)度線程運行run()方法。當start()方法返回后,線程就處于就緒狀態(tài)。 處于就緒狀態(tài)的線程并不一定立即運行run()方法,線程還必須同其他線程競爭CPU時間,只有獲得CPU時間才可以運行線程。因為在單CPU的計算機系統(tǒng)中,不可能同時運行多個線程,一個時刻僅有一個線程處于運行狀態(tài)。因此此時可能有多個線程處于就緒狀態(tài)。對多個處于就緒狀態(tài)的線程是由Java運行時系統(tǒng)的線程調(diào)度程序(thread scheduler)來調(diào)度的。
3. 運行狀態(tài)
當線程獲得CPU時間后,它才進入運行狀態(tài),真正開始執(zhí)行run()方法.
4. 阻塞狀態(tài)
線程運行過程中,可能由于各種原因進入阻塞狀態(tài):
- 線程通過調(diào)用sleep方法進入睡眠狀態(tài);
- 線程調(diào)用一個在I/O上被阻塞的操作,即該操作在輸入輸出操作完成之前不會返回到它的調(diào)用者;
- 線程試圖得到一個鎖,而該鎖正被其他線程持有;
- 線程在等待某個觸發(fā)條件;
5. 死亡狀態(tài)
有兩個原因會導致線程死亡:
- run方法正常退出而自然死亡,
- 一個未捕獲的異常終止了run方法而使線程猝死。
為了確定線程在當前是否存活著(就是要么是可運行的,要么是被阻塞了),需要使用isAlive方法。如果是可運行或被阻塞,這個方法返回true;如果線程仍舊是new狀態(tài)且不是可運行的,或者線程死亡了,則返回false.
三、多線程之間實現(xiàn)同步
1. 什么是多線程安全?
當多個線程同時共享,同一個全局變量或靜態(tài)變量,做寫的操作時,可能會發(fā)生數(shù)據(jù)沖突問題,也就是線程安全問題。做讀操作是不會發(fā)生數(shù)據(jù)沖突問題。
2. 如何解決多線程之間線程安全問題?
答:使用多線程之間同步或使用鎖(lock)。
3. 為什么使用線程同步或使用鎖能解決線程安全問題呢?
將可能會發(fā)生數(shù)據(jù)沖突問題(線程不安全問題),只能讓當前一個線程進行執(zhí)行。被包裹的代碼執(zhí)行完成后釋放鎖,讓后才能讓其他線程進行執(zhí)行。這樣的話就可以解決線程不安全問題。
4. 什么是多線程之間同步?
當多個線程共享同一個資源,不會受到其他線程的干擾。
5. 多線程同步的分類?
- 使用同步代碼塊(使用自定鎖)
synchronized(同一個數(shù)據(jù)){}
- 使用同步函數(shù)(使用this鎖)
public synchronized void func() {}
- 靜態(tài)同步函數(shù)(使用該函數(shù)所屬字節(jié)碼文件對象鎖)
public static synchronized void func() {}
四、多線程之間的死鎖
死鎖的四個必要條件
1)互斥條件,即某個資源在一段時間內(nèi)只能由一個線程占有,不能同時被兩個或兩個以上的線程占有
2)不可搶占條件,線程所獲得的資源在未使用完畢之前,資源申請者不能強行地從資源占有者手中奪取資源,而只能由該資源的占有者線程自行釋放
3)占有且申請條件,線程至少已經(jīng)占有一個資源,但又申請新的資源;由于該資源已被另外線程占有,此時該線程阻塞;但是,它在等待新資源之時,仍繼續(xù)占用已占有的資源。
4)循環(huán)等待條件,存在一個線程等待序列{P1,P2,...,Pn},其中P1等待P2所占有的某一資源,P2等待P3所占有的某一源,......,而Pn等待P1所占有的的某一資源,形成一個線程循環(huán)等待環(huán)
解決死鎖的辦法:加鎖順序,死鎖檢測
下面通過代碼實例來講解一下如何去寫一個死鎖代碼&如何去解決死鎖問題
public class DeadLockTest {
static class MyTask implements Runnable {
Object obj1 = "obj1";
Object obj2 = "obj2";
int flag;
private void setFlag(int flag) {
this.flag = flag;
}
@Override
public void run() {
if (flag == 1) {
synchronized (obj1) {
System.out.println("locking "+obj1); //占用obj1
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj2) {
System.out.println("使用順序 obj1 -> obj2");
}
}
} else if (flag == 2) {
synchronized (obj2) {
System.out.println("locking "+obj2); //占用obj2
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj1) {
System.out.println("使用順序 obj2 -> obj1");
}
}
}
}
}
public static void main(String[] args) throws InterruptedException {
MyTask myTask = new MyTask();
myTask.setFlag(1);
Thread thread1 = new Thread(myTask);
thread1.start();
//保證線程thread1優(yōu)先執(zhí)行
Thread.sleep(100);
myTask.setFlag(2);
Thread thread2 = new Thread(myTask);
thread2.start();
}
}
通過兩個線程去競爭資源從而達到死鎖目的
解決方案
MyTask myTask1 = new MyTask();
myTask1.setFlag(2);
Thread thread2 = new Thread(myTask1);
thread2.start();
理論上是可以解決死鎖,但是并沒有成功,想了好久原來是字符串常量的問題,需要通過new String()方式解決,即
Object obj1 = new String("obj1");
Object obj2 = new String("obj2");