日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

Java多线程干货系列(1):Java多线程基础

發布時間:2023/12/3 java 49 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java多线程干货系列(1):Java多线程基础 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

轉載自??Java多線程干貨系列(1):Java多線程基礎

前言

多線程并發編程是Java編程中重要的一塊內容,也是面試重點覆蓋區域,所以學好多線程并發編程對我們來說極其重要,下面跟我一起開啟本次的學習之旅吧。

正文

線程與進程

1 線程:進程中負責程序執行的執行單元
線程本身依靠程序進行運行
線程是程序中的順序控制流,只能使用分配給程序的資源和環境

2 進程:執行中的程序
一個進程至少包含一個線程

3 單線程:程序中只存在一個線程,實際上主方法就是一個主線程

4 多線程:在一個程序中運行多個任務
目的是更好地使用CPU資源

線程的實現

繼承Thread類

在java.lang包中定義, 繼承Thread類必須重寫run()方法

class MyThread extends Thread{private static int num = 0;public MyThread(){num++;}@Overridepublic void run() {System.out.println("主動創建的第"+num+"個線程");} }

創建好了自己的線程類之后,就可以創建線程對象了,然后通過start()方法去啟動線程。注意,不是調用run()方法啟動線程,run方法中只是定義需要執行的任務,如果調用run方法,即相當于在主線程中執行run方法,跟普通的方法調用沒有任何區別,此時并不會創建一個新的線程來執行定義的任務。

public class Test {public static void main(String[] args) {MyThread thread = new MyThread();thread.start();} } class MyThread extends Thread{private static int num = 0;public MyThread(){num++;}@Overridepublic void run() {System.out.println("主動創建的第"+num+"個線程");} }

在上面代碼中,通過調用start()方法,就會創建一個新的線程了。為了分清start()方法調用和run()方法調用的區別,請看下面一個例子:

public class Test {public static void main(String[] args) {System.out.println("主線程ID:"+Thread.currentThread().getId());MyThread thread1 = new MyThread("thread1");thread1.start();MyThread thread2 = new MyThread("thread2");thread2.run();} }class MyThread extends Thread{private String name;public MyThread(String name){this.name = name;}@Overridepublic void run() {System.out.println("name:"+name+" 子線程ID:"+Thread.currentThread().getId());} }

運行結果:

從輸出結果可以得出以下結論:

1)thread1和thread2的線程ID不同,thread2和主線程ID相同,說明通過run方法調用并不會創建新的線程,而是在主線程中直接運行run方法,跟普通的方法調用沒有任何區別;

2)雖然thread1的start方法調用在thread2的run方法前面調用,但是先輸出的是thread2的run方法調用的相關信息,說明新線程創建的過程不會阻塞主線程的后續執行。

實現Runnable接口

在Java中創建線程除了繼承Thread類之外,還可以通過實現Runnable接口來實現類似的功能。實現Runnable接口必須重寫其run方法。

下面是一個例子:

public class Test {public static void main(String[] args) {System.out.println("主線程ID:"+Thread.currentThread().getId());MyRunnable runnable = new MyRunnable();Thread thread = new Thread(runnable);thread.start();} } class MyRunnable implements Runnable{public MyRunnable() {}@Overridepublic void run() {System.out.println("子線程ID:"+Thread.currentThread().getId());} }

Runnable的中文意思是“任務”,顧名思義,通過實現Runnable接口,我們定義了一個子任務,然后將子任務交由Thread去執行。注意,這種方式必須將Runnable作為Thread類的參數,然后通過Thread的start方法來創建一個新線程來執行該子任務。如果調用Runnable的run方法的話,是不會創建新線程的,這根普通的方法調用沒有任何區別。

事實上,查看Thread類的實現源代碼會發現Thread類是實現了Runnable接口的。

在Java中,這2種方式都可以用來創建線程去執行子任務,具體選擇哪一種方式要看自己的需求。直接繼承Thread類的話,可能比實現Runnable接口看起來更加簡潔,但是由于Java只允許單繼承,所以如果自定義類需要繼承其他類,則只能選擇實現Runnable接口。

使用ExecutorService、Callable、Future實現有返回結果的多線程

多線程后續會學到,這里暫時先知道一下有這種方法即可。

ExecutorService、Callable、Future這個對象實際上都是屬于Executor框架中的功能類。想要詳細了解Executor框架的可以訪問http://www.javaeye.com/topic/366591?,這里面對該框架做了很詳細的解釋。返回結果的線程是在JDK1.5中引入的新特征,確實很實用,有了這種特征我就不需要再為了得到返回值而大費周折了,而且即便實現了也可能漏洞百出。

可返回值的任務必須實現Callable接口,類似的,無返回值的任務必須Runnable接口。執行Callable任務后,可以獲取一個Future的對象,在該對象上調用get就可以獲取到Callable任務返回的Object了,再結合線程池接口ExecutorService就可以實現傳說中有返回結果的多線程了。下面提供了一個完整的有返回結果的多線程測試例子,在JDK1.5下驗證過沒問題可以直接使用。代碼如下:

/** * 有返回值的線程 */ @SuppressWarnings("unchecked") public class Test { public static void main(String[] args) throws ExecutionException, InterruptedException { System.out.println("----程序開始運行----"); Date date1 = new Date(); int taskSize = 5; // 創建一個線程池 ExecutorService pool = Executors.newFixedThreadPool(taskSize); // 創建多個有返回值的任務 List<Future> list = new ArrayList<Future>(); for (int i = 0; i < taskSize; i++) { Callable c = new MyCallable(i + " "); // 執行任務并獲取Future對象 Future f = pool.submit(c); // System.out.println(">>>" + f.get().toString()); list.add(f); } // 關閉線程池 pool.shutdown(); // 獲取所有并發任務的運行結果 for (Future f : list) { // 從Future對象上獲取任務的返回值,并輸出到控制臺 System.out.println(">>>" + f.get().toString()); } Date date2 = new Date(); System.out.println("----程序結束運行----,程序運行時間【" + (date2.getTime() - date1.getTime()) + "毫秒】"); } } class MyCallable implements Callable<Object> { private String taskNum; MyCallable(String taskNum) { this.taskNum = taskNum; } public Object call() throws Exception { System.out.println(">>>" + taskNum + "任務啟動"); Date dateTmp1 = new Date(); Thread.sleep(1000); Date dateTmp2 = new Date(); long time = dateTmp2.getTime() - dateTmp1.getTime(); System.out.println(">>>" + taskNum + "任務終止"); return taskNum + "任務返回運行結果,當前任務時間【" + time + "毫秒】"; } }

代碼說明:
上述代碼中Executors類,提供了一系列工廠方法用于創先線程池,返回的線程池都實現了ExecutorService接口。
public static ExecutorService newFixedThreadPool(int nThreads)
創建固定數目線程的線程池。

public static ExecutorService newCachedThreadPool()
創建一個可緩存的線程池,調用execute 將重用以前構造的線程(如果線程可用)。如果現有線程沒有可用的,則創建一個新線程并添加到池中。終止并從緩存中移除那些已有 60 秒鐘未被使用的線程。

public static ExecutorService newSingleThreadExecutor()
創建一個單線程化的Executor。

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
創建一個支持定時及周期性的任務執行的線程池,多數情況下可用來替代Timer類。

ExecutoreService提供了submit()方法,傳遞一個Callable,或Runnable,返回Future。如果Executor后臺線程池還沒有完成Callable的計算,這調用返回Future對象的get()方法,會阻塞直到計算完成。

線程的狀態

在正式學習Thread類中的具體方法之前,我們先來了解一下線程有哪些狀態,這個將會有助于后面對Thread類中的方法的理解。

  • 創建(new)狀態: 準備好了一個多線程的對象
  • 就緒(runnable)狀態: 調用了start()方法, 等待CPU進行調度
  • 運行(running)狀態: 執行run()方法
  • 阻塞(blocked)狀態: 暫時停止執行, 可能將資源交給其它線程使用
  • 終止(dead)狀態: 線程銷毀

當需要新起一個線程來執行某個子任務時,就創建了一個線程。但是線程創建之后,不會立即進入就緒狀態,因為線程的運行需要一些條件(比如內存資源,在前面的JVM內存區域劃分一篇博文中知道程序計數器、Java棧、本地方法棧都是線程私有的,所以需要為線程分配一定的內存空間),只有線程運行需要的所有條件滿足了,才進入就緒狀態。

當線程進入就緒狀態后,不代表立刻就能獲取CPU執行時間,也許此時CPU正在執行其他的事情,因此它要等待。當得到CPU執行時間之后,線程便真正進入運行狀態。

線程在運行狀態過程中,可能有多個原因導致當前線程不繼續運行下去,比如用戶主動讓線程睡眠(睡眠一定的時間之后再重新執行)、用戶主動讓線程等待,或者被同步塊給阻塞,此時就對應著多個狀態:time waiting(睡眠或等待一定的事件)、waiting(等待被喚醒)、blocked(阻塞)。

當由于突然中斷或者子任務執行完畢,線程就會被消亡。

下面這副圖描述了線程從創建到消亡之間的狀態:

在有些教程上將blocked、waiting、time waiting統稱為阻塞狀態,這個也是可以的,只不過這里我想將線程的狀態和Java中的方法調用聯系起來,所以將waiting和time waiting兩個狀態分離出來。

注:sleep和wait的區別:

  • sleep是Thread類的方法,wait是Object類中定義的方法.
  • Thread.sleep不會導致鎖行為的改變, 如果當前線程是擁有鎖的, 那么Thread.sleep不會讓線程釋放鎖.
  • Thread.sleep和Object.wait都會暫停當前的線程. OS會將執行時間分配給其它線程. 區別是, 調用wait后, 需要別的線程執行notify/notifyAll才能夠重新獲得CPU執行時間.

上下文切換

對于單核CPU來說(對于多核CPU,此處就理解為一個核),CPU在一個時刻只能運行一個線程,當在運行一個線程的過程中轉去運行另外一個線程,這個叫做線程上下文切換(對于進程也是類似)。

由于可能當前線程的任務并沒有執行完畢,所以在切換時需要保存線程的運行狀態,以便下次重新切換回來時能夠繼續切換之前的狀態運行。舉個簡單的例子:比如一個線程A正在讀取一個文件的內容,正讀到文件的一半,此時需要暫停線程A,轉去執行線程B,當再次切換回來執行線程A的時候,我們不希望線程A又從文件的開頭來讀取。

因此需要記錄線程A的運行狀態,那么會記錄哪些數據呢?因為下次恢復時需要知道在這之前當前線程已經執行到哪條指令了,所以需要記錄程序計數器的值,另外比如說線程正在進行某個計算的時候被掛起了,那么下次繼續執行的時候需要知道之前掛起時變量的值時多少,因此需要記錄CPU寄存器的狀態。所以一般來說,線程上下文切換過程中會記錄程序計數器、CPU寄存器狀態等數據。

說簡單點的:對于線程的上下文切換實際上就是?存儲和恢復CPU狀態的過程,它使得線程執行能夠從中斷點恢復執行

雖然多線程可以使得任務執行的效率得到提升,但是由于在線程切換時同樣會帶來一定的開銷代價,并且多個線程會導致系統資源占用的增加,所以在進行多線程編程時要注意這些因素。

線程的常用方法

編號方法說明
1public void start()使該線程開始執行;Java 虛擬機調用該線程的 run 方法。
2public void run()如果該線程是使用獨立的 Runnable 運行對象構造的,則調用該 Runnable 對象的 run 方法;否則,該方法不執行任何操作并返回。
3public final void setName(String name)改變線程名稱,使之與參數 name 相同。
4public final void setPriority(int priority)更改線程的優先級。
5public final void setDaemon(boolean on)將該線程標記為守護線程或用戶線程。
6public final void join(long millisec)等待該線程終止的時間最長為 millis 毫秒。
7public void interrupt()中斷線程。
8public final boolean isAlive()測試線程是否處于活動狀態。
9public static void yield()暫停當前正在執行的線程對象,并執行其他線程。
10public static void sleep(long millisec)在指定的毫秒數內讓當前正在執行的線程休眠(暫停執行),此操作受到系統計時器和調度程序精度和準確性的影響。
11public static Thread currentThread()返回對當前正在執行的線程對象的引用。

靜態方法

currentThread()方法

currentThread()方法可以返回代碼段正在被哪個線程調用的信息。

public class Run1{public static void main(String[] args){ System.out.println(Thread.currentThread().getName());} }

sleep()方法

方法sleep()的作用是在指定的毫秒數內讓當前“正在執行的線程”休眠(暫停執行)。這個“正在執行的線程”是指this.currentThread()返回的線程。

sleep方法有兩個重載版本:

sleep(long millis) //參數為毫秒 sleep(long millis,int nanoseconds) //第一參數為毫秒,第二個參數為納秒

sleep相當于讓線程睡眠,交出CPU,讓CPU去執行其他的任務。

但是有一點要非常注意,sleep方法不會釋放鎖,也就是說如果當前線程持有對某個對象的鎖,則即使調用sleep方法,其他線程也無法訪問這個對象。看下面這個例子就清楚了:

public class Test {private int i = 10;private Object object = new Object();public static void main(String[] args) throws IOException {Test test = new Test();MyThread thread1 = test.new MyThread();MyThread thread2 = test.new MyThread();thread1.start();thread2.start();}class MyThread extends Thread{@Overridepublic void run() {synchronized (object) {i++;System.out.println("i:"+i);try {System.out.println("線程"+Thread.currentThread().getName()+"進入睡眠狀態");Thread.currentThread().sleep(10000);} catch (InterruptedException e) {// TODO: handle exception}System.out.println("線程"+Thread.currentThread().getName()+"睡眠結束");i++;System.out.println("i:"+i);}}} }

輸出結果:

從上面輸出結果可以看出,當Thread-0進入睡眠狀態之后,Thread-1并沒有去執行具體的任務。只有當Thread-0執行完之后,此時Thread-0釋放了對象鎖,Thread-1才開始執行。

注意,如果調用了sleep方法,必須捕獲InterruptedException異常或者將該異常向上層拋出。當線程睡眠時間滿后,不一定會立即得到執行,因為此時可能CPU正在執行其他的任務。所以說調用sleep方法相當于讓線程進入阻塞狀態。

yield()方法

調用yield方法會讓當前線程交出CPU權限,讓CPU去執行其他的線程。它跟sleep方法類似,同樣不會釋放鎖。但是yield不能控制具體的交出CPU的時間,另外,yield方法只能讓擁有相同優先級的線程有獲取CPU執行時間的機會。

注意,調用yield方法并不會讓線程進入阻塞狀態,而是讓線程重回就緒狀態,它只需要等待重新獲取CPU執行時間,這一點是和sleep方法不一樣的。

代碼:

public class MyThread extends Thread{@Overridepublic void run() {long beginTime=System.currentTimeMillis();int count=0;for (int i=0;i<50000000;i++){count=count+(i+1);//Thread.yield();}long endTime=System.currentTimeMillis();System.out.println("用時:"+(endTime-beginTime)+" 毫秒!");} }public class Run {public static void main(String[] args) {MyThread t= new MyThread();t.start();} }

執行結果:

用時:3 毫秒!

如果將?//Thread.yield();的注釋去掉,執行結果如下:

用時:16080 毫秒!

對象方法

start()方法

start()用來啟動一個線程,當調用start方法后,系統才會開啟一個新的線程來執行用戶定義的子任務,在這個過程中,會為相應的線程分配需要的資源。

run()方法

run()方法是不需要用戶來調用的,當通過start方法啟動一個線程之后,當線程獲得了CPU執行時間,便進入run方法體去執行具體的任務。注意,繼承Thread類必須重寫run方法,在run方法中定義具體要執行的任務。

getId()

getId()的作用是取得線程的唯一標識

代碼:

public class Test {public static void main(String[] args) {Thread t= Thread.currentThread();System.out.println(t.getName()+" "+t.getId());} }

輸出:

main 1

isAlive()方法

方法isAlive()的功能是判斷當前線程是否處于活動狀態

代碼:

public class MyThread extends Thread{@Overridepublic void run() {System.out.println("run="+this.isAlive());} } public class RunTest {public static void main(String[] args) throws InterruptedException {MyThread myThread=new MyThread();System.out.println("begin =="+myThread.isAlive());myThread.start();System.out.println("end =="+myThread.isAlive());} }

程序運行結果:

begin ==false

run=true end ==false

方法isAlive()的作用是測試線程是否偶處于活動狀態。什么是活動狀態呢?活動狀態就是線程已經啟動且尚未終止。線程處于正在運行或準備開始運行的狀態,就認為線程是“存活”的。

有個需要注意的地方

System.out.println("end =="+myThread.isAlive());

雖然上面的實例中打印的值是true,但此值是不確定的。打印true值是因為myThread線程還未執行完畢,所以輸出true。如果代碼改成下面這樣,加了個sleep休眠:

public static void main(String[] args) throws InterruptedException {MyThread myThread=new MyThread();System.out.println("begin =="+myThread.isAlive());myThread.start();Thread.sleep(1000);System.out.println("end =="+myThread.isAlive());}

則上述代碼運行的結果輸出為false,因為mythread對象已經在1秒之內執行完畢。

join()方法

在很多情況下,主線程創建并啟動了線程,如果子線程中藥進行大量耗時運算,主線程往往將早于子線程結束之前結束。這時,如果主線程想等待子線程執行完成之后再結束,比如子線程處理一個數據,主線程要取得這個數據中的值,就要用到join()方法了。方法join()的作用是等待線程對象銷毀。

public class Thread4 extends Thread{public Thread4(String name) {super(name);}public void run() {for (int i = 0; i < 5; i++) {System.out.println(getName() + " " + i);}}public static void main(String[] args) throws InterruptedException {// 啟動子進程new Thread4("new thread").start();for (int i = 0; i < 10; i++) {if (i == 5) {Thread4 th = new Thread4("joined thread");th.start();th.join();}System.out.println(Thread.currentThread().getName() + " " + i);}} }

執行結果:

main??0 main??1 main??2 main??3 main??4 new thread??0 new thread??1 new thread??2 new thread??3 new thread??4 joined thread??0 joined thread??1 joined thread??2 joined thread??3 joined thread??4 main??5 main??6 main??7 main??8 main??9

由上可以看出main主線程等待joined thread線程先執行完了才結束的。如果把th.join()這行注釋掉,運行結果如下:

main??0 main??1 main??2 main??3 main??4 main??5 main??6 main??7 main??8 main??9 new thread??0 new thread??1 new thread??2 new thread??3 new thread??4 joined thread??0 joined thread??1 joined thread??2 joined thread??3 joined thread??4

getName和setName

用來得到或者設置線程名稱。

getPriority和setPriority

用來獲取和設置線程優先級。

setDaemon和isDaemon

用來設置線程是否成為守護線程和判斷線程是否是守護線程。

守護線程和用戶線程的區別在于:守護線程依賴于創建它的線程,而用戶線程則不依賴。舉個簡單的例子:如果在main線程中創建了一個守護線程,當main方法運行完畢之后,守護線程也會隨著消亡。而用戶線程則不會,用戶線程會一直運行直到其運行完畢。在JVM中,像垃圾收集器線程就是守護線程。

在上面已經說到了Thread類中的大部分方法,那么Thread類中的方法調用到底會引起線程狀態發生怎樣的變化呢?下面一幅圖就是在上面的圖上進行改進而來的:

用時:3 毫秒!

停止線程

停止線程是在多線程開發時很重要的技術點,掌握此技術可以對線程的停止進行有效的處理。
停止一個線程可以使用Thread.stop()方法,但最好不用它。該方法是不安全的,已被棄用。
在Java中有以下3種方法可以終止正在運行的線程:

  • 使用退出標志,使線程正常退出,也就是當run方法完成后線程終止
  • 使用stop方法強行終止線程,但是不推薦使用這個方法,因為stop和suspend及resume一樣,都是作廢過期的方法,使用他們可能產生不可預料的結果。
  • 使用interrupt方法中斷線程,但這個不會終止一個正在運行的線程,還需要加入一個判斷才可以完成線程的停止。

暫停線程

interrupt()方法

線程的優先級

在操作系統中,線程可以劃分優先級,優先級較高的線程得到的CPU資源較多,也就是CPU優先執行優先級較高的線程對象中的任務。
設置線程優先級有助于幫“線程規劃器”確定在下一次選擇哪一個線程來優先執行。

設置線程的優先級使用setPriority()方法,此方法在JDK的源碼如下:

public final void setPriority(int newPriority) {ThreadGroup g;checkAccess();if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {throw new IllegalArgumentException();}if((g = getThreadGroup()) != null) {if (newPriority > g.getMaxPriority()) {newPriority = g.getMaxPriority();}setPriority0(priority = newPriority);}}

在Java中,線程的優先級分為1~10這10個等級,如果小于1或大于10,則JDK拋出異常throw new IllegalArgumentException()。

JDK中使用3個常量來預置定義優先級的值,代碼如下:

public final static int MIN_PRIORITY = 1; public final static int NORM_PRIORITY = 5; public final static int MAX_PRIORITY = 10;

線程優先級特性:

  • 繼承性
    比如A線程啟動B線程,則B線程的優先級與A是一樣的。
  • 規則性
    高優先級的線程總是大部分先執行完,但不代表高優先級線程全部先執行完。
  • 隨機性
    優先級較高的線程不一定每一次都先執行完。

守護線程

在Java線程中有兩種線程,一種是User Thread(用戶線程),另一種是Daemon Thread(守護線程)。
Daemon的作用是為其他線程的運行提供服務,比如說GC線程。其實User Thread線程和Daemon Thread守護線程本質上來說去沒啥區別的,唯一的區別之處就在虛擬機的離開:如果User Thread全部撤離,那么Daemon Thread也就沒啥線程好服務的了,所以虛擬機也就退出了。

守護線程并非虛擬機內部可以提供,用戶也可以自行的設定守護線程,方法:public final void setDaemon(boolean on) ;但是有幾點需要注意:

  • thread.setDaemon(true)必須在thread.start()之前設置,否則會跑出一個IllegalThreadStateException異常。你不能把正在運行的常規線程設置為守護線程。 (備注:這點與守護進程有著明顯的區別,守護進程是創建后,讓進程擺脫原會話的控制+讓進程擺脫原進程組的控制+讓進程擺脫原控制終端的控制;所以說寄托于虛擬機的語言機制跟系統級語言有著本質上面的區別)
  • 在Daemon線程中產生的新線程也是Daemon的。 (這一點又是有著本質的區別了:守護進程fork()出來的子進程不再是守護進程,盡管它把父進程的進程相關信息復制過去了,但是子進程的進程的父進程不是init進程,所謂的守護進程本質上說就是“父進程掛掉,init收養,然后文件0,1,2都是/dev/null,當前目錄到/”)
  • 不是所有的應用都可以分配給Daemon線程來進行服務,比如讀寫操作或者計算邏輯。因為在Daemon Thread還沒來的及進行操作時,虛擬機可能已經退出了。

同步與死鎖

  • 同步代碼塊
    在代碼塊上加上”synchronized”關鍵字,則此代碼塊就稱為同步代碼塊
  • 同步代碼塊格式synchronized(同步對象){需要同步的代碼塊; }
  • 同步方法
    除了代碼塊可以同步,方法也是可以同步的
  • 方法同步格式synchronized void 方法名稱(){}

    synchronized后續會單獨來學習。(●’?’●)

  • 面試題

    線程和進程有什么區別?
    答:一個進程是一個獨立(self contained)的運行環境,它可以被看作一個程序或者一個應用。而線程是在進程中執行的一個任務。線程是進程的子集,一個進程可以有很多線程,每條線程并行執行不同的任務。不同的進程使用不同的內存空間,而所有的線程共享一片相同的內存空間。別把它和棧內存搞混,每個線程都擁有單獨的棧內存用來存儲本地數據。

    如何在Java中實現線程?
    答:
    創建線程有兩種方式:
    一、繼承 Thread 類,擴展線程。
    二、實現 Runnable 接口。

    啟動一個線程是調用run()還是start()方法?
    答:啟動一個線程是調用start()方法,使線程所代表的虛擬處理機處于可運行狀態,這意味著它可以由JVM 調度并執行,這并不意味著線程就會立即運行。run()方法是線程啟動后要進行回調(callback)的方法。

    Thread類的sleep()方法和對象的wait()方法都可以讓線程暫停執行,它們有什么區別?
    答:sleep()方法(休眠)是線程類(Thread)的靜態方法,調用此方法會讓當前線程暫停執行指定的時間,將執行機會(CPU)讓給其他線程,但是對象的鎖依然保持,因此休眠時間結束后會自動恢復(線程回到就緒狀態,請參考第66題中的線程狀態轉換圖)。wait()是Object類的方法,調用對象的wait()方法導致當前線程放棄對象的鎖(線程暫停執行),進入對象的等待池(wait pool),只有調用對象的notify()方法(或notifyAll()方法)時才能喚醒等待池中的線程進入等鎖池(lock pool),如果線程重新獲得對象的鎖就可以進入就緒狀態。

    線程的sleep()方法和yield()方法有什么區別?
    答:
    ① sleep()方法給其他線程運行機會時不考慮線程的優先級,因此會給低優先級的線程以運行的機會;yield()方法只會給相同優先級或更高優先級的線程以運行的機會;
    ② 線程執行sleep()方法后轉入阻塞(blocked)狀態,而執行yield()方法后轉入就緒(ready)狀態;
    ③ sleep()方法聲明拋出InterruptedException,而yield()方法沒有聲明任何異常;
    ④ sleep()方法比yield()方法(跟操作系統CPU調度相關)具有更好的可移植性。

    請說出與線程同步以及線程調度相關的方法。
    答:

    • wait():使一個線程處于等待(阻塞)狀態,并且釋放所持有的對象的鎖;
    • sleep():使一個正在運行的線程處于睡眠狀態,是一個靜態方法,調用此方法要處理InterruptedException異常;
    • notify():喚醒一個處于等待狀態的線程,當然在調用此方法的時候,并不能確切的喚醒某一個等待狀態的線程,而是由JVM確定喚醒哪個線程,而且與優先級無關;
    • notityAll():喚醒所有處于等待狀態的線程,該方法并不是將對象的鎖給所有線程,而是讓它們競爭,只有獲得鎖的線程才能進入就緒狀態;

    總結

    以上就是多線程的一些基礎概念,可能總結的不夠仔細,多多包涵。后續會針對一些比較重要的知識點單獨列出來總結。學好多線程是拿高薪的基礎,小伙伴一起加油吧!

    參考

    該文為本人學習的筆記,方便以后自己跳槽前復習。參考網上各大帖子,取其精華整合自己的理解而成。還有,關注我個人主頁的公眾號,里面電子書資源有《Java多線程編程核心技術》以及《JAVA并發編程實踐》高清版,需要的小伙伴自己取。

    《Java多線程編程核心技術》
    《JAVA并發編程實踐》
    Java并發編程:Thread類的使用
    關于Java并發編程的總結和思考
    JAVA多線程實現的三種方式

    整理的思維導圖

    個人整理的多線程基礎的思維導圖,導出的圖片無法查看備注的一些信息,所以需要源文件的童鞋可以關注我個人主頁上的公眾號,回復多線程基礎即可獲取源文件。


    一直覺得自己寫的不是技術,而是情懷,一篇篇文章是自己這一路走來的痕跡。靠專業技能的成功是最具可復制性的,希望我的這條路能讓你少走彎路,希望我能幫你抹去知識的蒙塵,希望我能幫你理清知識的脈絡,希望未來技術之巔上有你也有我。

    用時:3 毫秒!

    總結

    以上是生活随笔為你收集整理的Java多线程干货系列(1):Java多线程基础的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。