一、并發和并行
并行:指在同一時刻,有多條指令在多個處理器上同時執行。所以無論從微觀還是從宏觀來看,二者都是一起執行的。
并發:指在同一時刻只能有一條指令執行,但多個進程指令被快速的輪換執行,使得在宏觀上具有多個進程同時執行的效果,但在微觀上并不是同時執行的,只是把時間分成若干段,使多個進程快速交替的執行。
二、進程和線程
概念
一個程序就是一個進程,而一個程序中的多個任務則被稱為線程。
進程是表示資源分配的基本單位,線程是程序執行(CPU調度)的最小單位。
舉個例子:
打開你的計算機上的任務管理器,會顯示出當前機器的所有進程,QQ,360等,當QQ運行時,就有很多子任務在同時運行。比如,當你邊打字發送表情,邊好友視頻時這些不同的功能都可以同時運行,其中每一項任務都可以理解成“線程”在工作。
區別
地址空間:同一進程的線程共享本進程的地址空間,而進程之間則是獨立的地址空間。
資源擁有:同一進程內的線程共享本進程的資源,但是進程之間的資源是獨立的。
一個進程崩潰后,在保護模式下不會對其他進程產生影響;但是一個線程崩潰整個進程都死掉。所以多進程要比多線程健壯。
進程切換要比線程切換慢。每個進程都有自己的虛擬地址空間,而線程是共享所在進程的虛擬地址空間的,因此同一個進程中的線程進行線程切換時不涉及虛擬地址空間的轉換。(進程切換與線程切換的區別參考這篇博文:進程切換與線程切換的區別?)
執行過程:每個獨立的進程有一個程序運行的入口、順序執行序列和程序出口。但是線程不能獨立執行,必須依存在應用程序中,由應用程序提供多個線程執行控制。
線程是處理器調度的基本單位,但是進程不是。
兩者均可并發執行。
?
三、線程的狀態圖
線程的5種狀態?
1. 新建狀態(New):
用new語句創建的線程處于新建狀態,此時它和其他Java對象一樣,僅僅在堆區中被分配了內存。
2. 就緒狀態(Runnable):
當一個線程對象創建后,其他線程調用它的start()方法,該線程就進入就緒狀態,Java虛擬機會為它創建方法調用棧和程序計數器。處于這個狀態的線程位于可運行池中,等待獲得CPU的使用權。
3. 運行狀態(Running):
處于這個狀態的線程占用CPU,執行程序代碼。只有處于就緒狀態的線程才有機會轉到運行狀態。
4. 阻塞狀態(Blocked):
阻塞狀態是指線程因為某些原因放棄CPU,暫時停止運行。當線程處于阻塞狀態時,Java虛擬機不會給線程分配CPU。直到線程重新進入就緒狀態,它才有機會轉到運行狀態。
阻塞狀態可分為以下3種:
位于對象等待池中的阻塞狀態(Blocked in object’s wait pool):
當線程處于運行狀態時,如果執行了某個對象的wait()方法,Java虛擬機就會把線程放到這個對象的等待池中,這涉及到“線程通信”的內容。
位于對象鎖池中的阻塞狀態(Blocked in object’s lock pool):
當線程處于運行狀態時,試圖獲得某個對象的同步鎖時,如果該對象的同步鎖已經被其他線程占用,Java虛擬機就會把這個線程放到這個對象的鎖池中,這涉及到“線程同步”的內容。
其他阻塞狀態(Otherwise Blocked):
當前線程執行了sleep()方法,或者調用了其他線程的join()方法,或者發出了I/O請求時,就會進入這個狀態。
5. 死亡狀態(Dead):
當線程退出run()方法時,就進入死亡狀態,該線程結束生命周期。
?涉及到的方法:
sleep(long millis):
屬于Thread類,主要的作用是讓當前線程停止執行millis毫秒,把cpu讓給其他線程執行,但不會釋放對象鎖和監控的狀態,到了指定時間后線程自動蘇醒,并返回到可運行狀態,不是運行狀態。
wait() notify():
wait() 屬于Object類,與sleep()的區別是當前線程會釋放鎖,進入等待此對象的等待池。比方說,線程A調用Obj.wait(),線程A就會停止運行,而轉為等待狀態。至于等待多長時間,那就看其他線程是否調用Obj.notify()。
注意:它必須包含在Synchronzied語句中,無論是wait()還是notify()都需要首先獲得目標的對象的一個監視器。
先來解釋一下?"Synchronzied" :
它是一種同步鎖。作用是實現線程間同步。對同步的代碼加鎖,使得每一次,只能有一線程進入同步塊,從而保證線程間的安全性。
它修飾的對象有以下幾種:
修飾一個代碼塊,被修飾的代碼塊稱為同步語句塊,其作用的范圍是大括號{}括起來的部分,進入同步代碼前要獲得給定對象的鎖
修飾一個實例方法,進入同步代碼前要獲得當前實例的鎖
修飾一個靜態方法,進入同步代碼前要獲得當前類的鎖
(具體可參考我的博客:)
join():
使用線程類的join()方法在一個線程中啟動另一個線程,另外一個線程完成該線程繼續執行。如:讓三個線程T1,T2,T3順序執行,T3調用T2,T2調用T1。
package zl;
public class JoinTest {
public static void main(String[] args) {
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("我是t1線程");
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
try {
thread1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我是t2線程");
}
});
Thread thread3 = new Thread(new Runnable() {
@Override
public void run() {
try {
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我是t3線程");
}
});
thread1.start();
thread2.start();
thread3.start();
}
}
輸出結果:
yield():
yield方法可以暫停當前正在執行的線程對象,讓其它有相同優先級的線程執行。它是一個靜態方法而且只保證當前線程放棄CPU占用而不能保證使其它線程一定能占用CPU。因為當前線程讓出cpu后,還會進行cpu資源的爭奪,所以有可能在進入到暫停狀態后馬上又被執行。?在大多數情況下,yield()將導致線程從運行狀態轉到可運行狀態,但有可能沒有效果。
package zl;
public class YiledTest implements Runnable {
private String name;
public YiledTest(String name) {
this.name = name;
}
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(name + ":" + i);
//t1線程到5的時候暫停,讓出cpu,但此時t1和t2都可搶奪cpu,所以結果不確定
if ("t1".equals(name) && i == 5) {
System.out.println(name + ":" + i +"......yield.............");
Thread.yield();
}
}
}
public static void main(String[] args) throws Exception {
Thread t1 = new Thread(new YiledTest("t1"));
Thread t2 = new Thread(new YiledTest("t2"));
t1.start();
t2.start();
}
}
運行結果:?
start()方法和run()方法的區別
定義:start()方法在java.lang.Thread類中定義;run()方法在java.lang.Runnable接口中定義,必須在實現類中重寫。
是否創建新線程:當程序調用start()方法時,會創建一個新線程并啟動進入可運行狀態(runnable)(此時線程要等待CPU調度,不同的JVM有不同的調度算法,線程何時被調度是未知的。因此,start()方法的被調用順序不能決定線程的執行順序),線程進入運行狀態時(running)執行run()方法。但是如果我們直接調用run()方法,會創建新的線程但不會啟動,run()方法將作為當前調用線程本身的常規方法調用執行,并且不會發生多線程。
舉例:
public class ThreadTest extends Thread{
@Override
public void run() {
System.out.println("當前線程的名稱:"+ Thread.currentThread().getName());
System.out.println("run()");
}
public static void main(String[] args) {
//Thread-0 Thread-0
ThreadTest threadTest = new ThreadTest();
//當調用線程類實例的start()方法時,會創建一個新的線程,默認名稱為Thread-0,并啟動該線程進入可運行狀態,然后調用run()方法,并在其中執行所有內容。
threadTest.start();
}
}
輸出結果:
一個線程的創建肯定是由另一個線程完成的,如:main方法中創建線程"Thread-0"??
public class ThreadTest extends Thread{
@Override
public void run() {
System.out.println("當前線程的名稱:"+ Thread.currentThread().getName());
System.out.println("run()");
}
public static void main(String[] args) {
ThreadTest threadTest = new ThreadTest();
//當調用ThreadTest類的run()方法時,雖然創建了新線程,但并沒有啟動該進程。在當前線程即主線程main上執行run()方法。因此,沒有發生多線程。run()方法是作為正常函數被調用。
threadTest.run();
}
}
輸出結果:
參考鏈接:線程狀態轉換圖及各部分介紹
java多線程面試題整理及答案(2019年)
join()方法作用
java中yield()方法如何使用
Java中Thread.start和Thread.run是什么?有什么區別