一、線程概念
- 進程:程序運行資源分配的最小單位,每個進程都有自己獨立的代碼和數據空間,操作系統為進程分配各種資源。
- 線程:CPU調度的最小單位,也叫輕量級進程,每個線程都有各自的堆棧、計數器和局部變量等屬性。
- 線程和進程關系:線程依賴于進程而存在,多個線程共享進程的內存空間。
異步和同步:
- 同步:在同一個線程中執行一段業務邏輯時,按順序執行,在前面的結果沒有返回時,后面的程序就不能往下執行,必須等待前一個結果返回時后面的才能往下執行。
- 異步:多線程是實現異步的一個手段,異步是當一個請求發送給被調用者,調用者可以不用等待結果返回而可以做其他的事情。
就像我們每天去到公司一樣,先打開電腦,在電腦開機過程中可以去接點水喝,而不用等待電腦開機再去接水喝。
并發和并行:
- 并行:同一時刻可以處理事情的能力,比如一臺四核的電腦,可以同時運行四個任務,我們就說這臺電腦并行度是4
- 并發:在單位時間內可以處理的事情,主要還是看這臺電腦時間分片的長短,如果這臺電腦的時間分片為100ms,在1s內就可以處理10個任務,那么就說這臺電腦的并發度是40。因為任務是在交替執行的,并發的任務就會讓我們以為這些任務是同時執行的,其實還是順序執行的。
二、線程的優勢
現如今服務器多采用多處理器,CPU的基本調度單位是線程,如果一個程序只有一個線程的話,那么就只能發揮一個CPU的作用,其他CPU的資源將會閑置,這在很大程度上浪費了CPU的資源,如果能夠多個CPU同時發揮作用,在設計正確的情況下,可以通過提高CPU資源的利用率來提高系統的吞吐率。
三、線程狀態
通過Thread.state 可以查看線程的狀態:
- NEW:新建狀態。這種狀態下線程還沒有開始,也就是還沒有調用start方法
- RUNNABLE:可運行狀態。這個狀態下的線程可能處于執行階段,但是也有可能在等待來自操作系統的其他資源,例如等待CPU為其分配時間片。ready和running在這兩種狀態在Java中統稱為RUNNABLE,分開寫是為了更好的理解
- BLOCKED:阻塞狀態。表示線程阻塞于鎖
- WAITING:等待狀態。進入等待狀態的線程需要其他線程做出一些特定動作,例如通知或中斷
- TIMED_WAITING:超時等待狀態。該狀態不同于WAITING狀態,它可以在指定時間內自動返回。
- TERMINATED:終止狀態。表示當前線程已經執行結束
t.start()之后并不代表線程已經啟動,此時它只是在可運行池中,隨時等待被CPU調度,一旦獲取到CPU時間片才真正的處于可運行狀態。
線程的優先級:
1、線程有1-10,10個優先級選擇,最小優先級是1(MIN_PRIORITY),默認優先級5(NORM_PRIORITY),
最大優先級是10(MAX_PRIORITY)
/**
* The minimum priority that a thread can have.
*/
public final static int MIN_PRIORITY = 1;
/**
* The default priority that is assigned to a thread.
*/
public final static int NORM_PRIORITY = 5;
/**
* The maximum priority that a thread can have.
*/
public final static int MAX_PRIORITY = 10;
2、如果沒有指定線程的優先級,默認是普通優先級,也就是NORM_PRIORITY
3、通過t.setPriority(int newPriority)為線程設定優先級
注意:不要試圖通過設定優先級來指定線程的啟動順序,優先級的設定只是說明這個線程在CPU調度時有更大的概率被選中,而且這種優先級設定有不確定性,在某些操作系統中可能優先級的設定并不起作用。
方法介紹:
- join():在線程A中調用B的join()方法,線程B將會先于線程A執行,線程A必須等待線程B執行完成才能執行,有點類似于插隊。同時join()方法還有兩個重載的帶有時間的方法,join(long millis)、join(long millis, int nanos),它表示如果線程B沒有在指定時間內完成,則返回。相當于給插隊線程加了時間限制,超時自動返回。
- yield():Thread的靜態方法。當前線程讓出CPU執行權,當前線程會從運行狀態變為可運行狀態,重新回到可運行線程池,但是他還是會可能被再次選中執行。
- sleep(long millis):Thread的靜態方法,使當前線程t休眠n毫秒,如果當前線程t持有鎖,休眠期間不會釋放鎖,其他線程將無法獲得鎖,當其他線程調用t.interrupt()喚醒休眠線程。當到了休眠時間當前線程t會自動進入RUNNABLE狀態。
- wait():對象方法,使當前線程進入等待狀態,它不會返回,除非我們調用t.notify()或者t.notifyAll()方法喚醒,喚醒之后將和其他線程競爭獲得鎖。調用wait()方法的前提是當前線程必須持有鎖,否則會拋異常,在調用wait()方法之后會釋放鎖,讓其他線程有機會獲得鎖。
sleep()和wait()的區別:
1、sleep()是Thread的靜態方法,wait()是Object的方法。
2、當調用sleep()的線程獲得鎖時不會自動釋放鎖,調用wait()會釋放鎖。
3、sleep()方法需要其他線程調用當前線程的interrupt()方法或者時間過期才能喚醒,wait()方法需要調用t.notify()或者t.notifyAll()方法喚醒。
線程的中斷:
如果線程執行完成或者拋出未處理的異常,線程就會終止。
線程中斷的方法有stop()、resume()、suspend()和interrupt(),但是前三個已經廢棄了,stop()會使線程不正確釋放資源,resume()只是為了suspend()而存在,這兩個方法會導致死鎖。所以最后只剩下interrupt()了。
java線程是協作式的,意思就是當調用當前線程t的interrupt()時,當前線程t并不會立馬終止,而是跟當前線程打個招呼,“兄弟,你死期到了,我跟你說一下,你愛死不死”,同時會把t的中斷標志設置為true。當線程拋出InterruptedException時,線程中斷標志將會清除,調用靜態方法Thread.interrupted()可以判斷線程是否中斷,同時將中斷標志將被清除并設置為false。
實例如下:
主函數
public static void main(String[] args) throws InterruptedException {
//創建兩個線程
InterruptThread interruptThread = new InterruptThread();
interruptThread.setName("thread1");
InterruptThread2 interruptThread2 = new InterruptThread2();
interruptThread2.setName("thread2");
//啟動兩個線程
interruptThread.start();
interruptThread2.start();
//主線程休眠5秒 使兩個線程充分運行
TimeUnit.SECONDS.sleep(5);
//中斷InterruptThread
interruptThread.interrupt();
}
靜態內部線程1:
private static class InterruptThread extends Thread{
@Override
public void run() {
interrupt(); //將中斷標志設置為true
System.err.println("before thread1 interrupt status:"+isInterrupted());
while (true){
try {
sleep(6000);
} catch (InterruptedException e) {
System.err.println("exception thread1 interrupt status:"+isInterrupted());
}
}
}
}
靜態內部線程2:
private static class InterruptThread2 extends Thread{
@Override
public void run() {
long start = System.currentTimeMillis();
while (true){
if (System.currentTimeMillis() - start > 2000){
interrupt(); //將中斷標志設置為true
System.out.println("before thread2 interrupt status:"+isInterrupted());
break;
}
}
Thread.interrupted(); //清除中斷標志,置為false
System.out.println("after thread2 interrupt status:"+isInterrupted());
}
}
1、在InterruptThread 中先把中斷標志設置為true,并打印是否中斷,在主線程中再次將處于休眠狀態中的InterruptThread 再次中斷,此時會拋出異常,再次打印是否中斷。在這個類中我們要證明的是,拋出InterruptedException 線程中斷標志會被清除。
2、在InterruptThread2 中我們讓InterruptThread2 正常運行2s,并在循環中將中斷標志置為true,正常退出循環時,調用Thread.interrupted(); 清除中斷標志,并打印是否中斷。在這個類中我們要證明調用當前線程Thread.interrupted();會將中斷標志清除。
在這兩個類中證明了,java線程中斷是協作式的,將中斷標志置為true并不會立馬就中斷線程。
打印結果:
before thread1 interrupt status:true
exception thread1 interrupt status:false
before thread2 interrupt status:true
after thread2 interrupt status:false
exception thread1 interrupt status:false
四、創建線程
創建線程有三種方法:
- 繼承Thread類
public class CreateThread {
public static void main(String[] args) {
ExtendThread extendThread = new ExtendThread();
extendThread.start();
}
private static class ExtendThread extends Thread{
@Override
public void run() {
System.out.println("class extends thread start");
}
}
}
- 實現Runable接口
public class CreateThread {
public static void main(String[] args) {
Thread thread = new Thread(new ImplRunnable());
thread.start();
}
private static class ImplRunnable implements Runnable{
@Override
public void run() {
System.out.println("class implements Runnable start");
}
}
}
- 實現Callable接口
Callable是可以獲取到返回值, 但是只能通過線程池來調用,它返回的是一個Future對象f,通過f.get()就能獲取到結果。
public class CreateThread {
private static ExecutorService executorService = Executors.newFixedThreadPool(4);
public static void main(String[] args) {
Future<String> submit = executorService.submit(new Callable<String>() {
@Override
public String call() throws Exception {
return "success";
}
});
try {
String s = submit.get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
能力一般,水平有限,如有錯誤,請多指出。
如果對你有用點個關注給個贊唄
更多文章可搜索關注微信公眾號 suncodernote