<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                企業??AI智能體構建引擎,智能編排和調試,一鍵部署,支持知識庫和私有化部署方案 廣告
                出自 > [Java多線程干貨系列(1):Java多線程基礎](http://www.importnew.com/21136.html) [TOC=1,2] ## 前言 [多線程](http://www.liuhaihua.cn/archives/tag/%e5%a4%9a%e7%ba%bf%e7%a8%8b "View all posts in 多線程")[并發編程](http://www.liuhaihua.cn/archives/tag/%e5%b9%b6%e5%8f%91%e7%bc%96%e7%a8%8b "View all posts in 并發編程")是Java編程中重要的一塊內容,也是面試重點覆蓋區域,所以學好多[線程](http://www.liuhaihua.cn/archives/tag/%e7%ba%bf%e7%a8%8b "View all posts in 線程")并發編程對我們來說極其重要,下面跟我一起開啟本次的學習之旅吧。 ## 正文 ## 線程與[進程](http://www.liuhaihua.cn/archives/tag/%e8%bf%9b%e7%a8%8b "View all posts in 進程") 1 線程:進程中負責程序執行的執行單元 線程本身依靠程序進行運行 線程是程序中的順序控制流,只能使用分配給程序的資源和環境 2 進程:執行中的程序 一個進程至少包含一個線程 3 單線程:程序中只存在一個線程,實際上主方法就是一個主線程 4 多線程:在一個程序中運行多個任務 目的是更好地使用CPU資源 ## 線程的實現 ### 繼承Thread類 在?`<span class="wp_keywordlink_affiliate" style="box-sizing: border-box;">[java](http://www.liuhaihua.cn/archives/tag/java-2 "View all posts in java")</span>.lang`?包中定義, 繼承Thread類必須重寫?`run()`?方法 ~~~ class MyThread extends Thread{ private static int num = 0; public MyThread(){ num++; } @Override public void run() { System.out.println("主動創建的第"+num+"個線程"); } } ~~~ 創建好了自己的線程類之后,就可以創建線程對象了,然后通過s[tar](http://www.liuhaihua.cn/archives/tag/tar "View all posts in tar")t()方法去啟動線程。注意,不是調用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++; } @Override public void run() { System.out.println("主動創建的第"+num+"個線程"); } } ~~~ 在上面[代碼](http://www.liuhaihua.cn/archives/tag/%e4%bb%a3%e7%a0%81 "View all posts in 代碼")中,通過調用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; } @Override public void run() { System.out.println("name:"+name+" 子線程ID:"+Thread.currentThread().getId()); } } ~~~ 運行結果: ![](https://box.kancloud.cn/2016-08-18_57b585d3abecd.png) 從輸出結果可以得出以下結論: 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() { } @Override public void run() { System.out.println("子線程ID:"+Thread.currentThread().getId()); } } ~~~ Runnable的中文意思是“任務”,顧名思義,通過實現Runnable接口,我們定義了一個子任務,然后將子任務交由Thread去執行。注意,這種方式必須將Runnable作為Thread類的[參數](http://www.liuhaihua.cn/archives/tag/%e5%8f%82%e6%95%b0 "View all posts in 參數"),然后通過Thread的start方法來創建一個新線程來執行該子任務。如果調用Runnable的run方法的話,是不會創建新線程的,這根普通的方法調用沒有任何區別。 事實上,查看Thread類的實現源代碼會發現Thread類是實現了Runnable接口的。 在Java中,這2種方式都可以用來創建線程去執行子任務,具體選擇哪一種方式要看自己的[需求](http://www.liuhaihua.cn/archives/tag/%e9%9c%80%e6%b1%82 "View all posts in 需求")。直接繼承Thread類的話,可能比實現Runnable接口看起來更加簡潔,但是由于Java只允許單繼承,所以如果自定義類需要繼承其他類,則只能選擇實現Runnable接口。 ### 使用Executor[Service](http://www.liuhaihua.cn/archives/tag/service "View all posts in Service")、Callable、Future實現有返回結果的多線程 多線程后續會學到,這里暫時先知道一下有這種方法即可。 ExecutorService、Callable、Future這個對象實際上都是屬于Executor框架中的功能類。想要詳細了解Executor框架的可以訪問?[http](http://www.liuhaihua.cn/archives/tag/http "View all posts in http")://www.javaeye.com/topic/366591 ,這里面對該框架做了很詳細的解釋。返回結果的線程是在JDK1.5中引入的新特征,確實很實用,有了這種特征我就不需要再為了得到返回值而大費周折了,而且即便實現了也可能[漏洞](http://www.liuhaihua.cn/archives/tag/%e6%bc%8f%e6%b4%9e "View all posts in 漏洞")百出。 可返回值的任務必須實現Callable接口,類似的,無返回值的任務必須Runnable接口。執行Callable任務后,可以獲取一個Future的對象,在該對象上調用get就可以獲取到Callable任務返回的Object了,再結合[線程池](http://www.liuhaihua.cn/archives/tag/%e7%ba%bf%e7%a8%8b%e6%b1%a0 "View all posts in 線程池")接口ExecutorService就可以實現傳說中有返回結果的多線程了。下面提供了一個完整的有返回結果的多線程[測試](http://www.liuhaihua.cn/archives/tag/test "View all posts in 測試")例子,在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?[core](http://www.liuhaihua.cn/archives/tag/core "View all posts in core")PoolSize) 創建一個支持定時及周期性的任務執行的線程池,多數情況下可用來替代Timer類。 ExecutoreService提供了submit()方法,傳遞一個Callable,或Runnable,返回Future。如果Executor后臺線程池還沒有完成Callable的計算,這調用返回Future對象的get()方法,會阻塞直到計算完成。 ## 線程的狀態 在正式學習Thread類中的具體方法之前,我們先來了解一下線程有哪些狀態,這個將會有助于后面對Thread類中的方法的理解。 * 創建(new)狀態: 準備好了一個多線程的對象 * 就緒(runnable)狀態: 調用了?`start()`?方法, 等待CPU進行調度 * 運行(running)狀態: 執行?`run()`?方法 * 阻塞(blocked)狀態: 暫時停止執行, 可能將資源交給其它線程使用 * 終止(dead)狀態: 線程銷毀 當需要新起一個線程來執行某個子任務時,就創建了一個線程。但是線程創建之后,不會立即進入就緒狀態,因為線程的運行需要一些條件(比如內存資源,在前面的JVM內存區域劃分一篇博文中知道程序計數器、Java棧、本地方法棧都是線程私有的,所以需要為線程分配一定的內存[空間](http://www.liuhaihua.cn/archives/tag/%e7%a9%ba%e9%97%b4 "View all posts in 空間")),只有線程運行需要的所有條件滿足了,才進入就緒狀態。 當線程進入就緒狀態后,不代表立刻就能獲取CPU執行時間,也許此時CPU正在執行其他的事情,因此它要等待。當得到CPU執行時間之后,線程便真正進入運行狀態。 線程在運行狀態過程中,可能有多個原因導致當前線程不繼續運行下去,比如用戶主動讓線程睡眠(睡眠一定的時間之后再重新執行)、用戶主動讓線程等待,或者被[同步](http://www.liuhaihua.cn/archives/tag/%e5%90%8c%e6%ad%a5 "View all posts in 同步")塊給阻塞,此時就對應著多個狀態:time waiting(睡眠或等待一定的事件)、waiting(等待被喚醒)、blocked(阻塞)。 當由于突然中斷或者子任務執行完畢,線程就會被消亡。 下面這副圖描述了線程從創建到消亡之間的狀態: ![](https://box.kancloud.cn/2016-08-18_57b585d405f86.png) 在有些教程上將blocked、waiting、time waiting統稱為阻塞狀態,這個也是可以的,只不過這里我想將線程的狀態和Java中的方法調用聯系起來,所以將waiting和time waiting兩個狀態分離出來。 注:sleep和wait的區別: * `sleep`?是?`Thread`?類的方法,?`wait`?是?`Object`?類中定義的方法. * `Thread.sleep`?不會導致[鎖](http://www.liuhaihua.cn/archives/tag/%e9%94%81 "View all posts in 鎖")行為的改變, 如果當前線程是擁有鎖的, 那么?`Thread.sleep`?不會讓線程釋放鎖. * `Thread.sleep`?和?`Object.wait`?都會暫停當前的線程. OS會將執行時間分配給其它線程. 區別是, 調用?`wait`?后, 需要別的線程執行?`notify/notifyAll`?才能夠重新獲得CPU執行時間. ## 上下文切換 對于單核CPU來說(對于多核CPU,此處就理解為一個核),CPU在一個時刻只能運行一個線程,當在運行一個線程的過程中轉去運行另外一個線程,這個叫做線程上下文切換(對于進程也是類似)。 由于可能當前線程的任務并沒有執行完畢,所以在切換時需要保存線程的運行狀態,以便下次重新切換回來時能夠繼續切換之前的狀態運行。舉個簡單的例子:比如一個線程A正在讀取一個文件的內容,正讀到文件的一半,此時需要暫停線程A,轉去執行線程B,當再次切換回來執行線程A的時候,我們不希望線程A又從文件的開頭來讀取。 因此需要記錄線程A的運行狀態,那么會記錄哪些[數據](http://www.liuhaihua.cn/archives/tag/%e6%95%b0%e6%8d%ae "View all posts in 數據")呢?因為下次恢復時需要知道在這之前當前線程已經執行到哪條指令了,所以需要記錄程序計數器的值,另外比如說線程正在進行某個計算的時候被掛起了,那么下次繼續執行的時候需要知道之前掛起時變量的值時多少,因此需要記錄CPU寄存器的狀態。所以一般來說,線程上下文切換過程中會記錄程序計數器、CPU寄存器狀態等數據。 說簡單點的:對于線程的上下文切換實際上就是?存儲和恢復CPU狀態的過程,它使得線程執行能夠從中斷點恢復執行?。 雖然多線程可以使得任務執行的效率得到提升,但是由于在線程切換時同樣會帶來一定的開銷代價,并且多個線程會導致系統資源占用的增加,所以在進行多線程編程時要注意這些因素。 ## 線程的常用方法 | 編號 | 方法 | 說明 | | --- | --- | --- | | 1 | `public void start()` | 使該線程開始執行;Java 虛擬機調用該線程的 run 方法。 | | 2 | `public void run()` | 如果該線程是使用獨立的 Runnable 運行對象構造的,則調用該 Runnable 對象的 run 方法;否則,該方法不執行任何操作并返回。 | | 3 | `public?<span class="wp_keywordlink_affiliate" style="box-sizing: border-box;">[final](http://www.liuhaihua.cn/archives/tag/final "View all posts in final")</span>?void setName(String name)` | 改變線程名稱,使之與參數 name 相同。 | | 4 | `public final void setPriority(int priority)` | 更改線程的優先級。 | | 5 | `public final void setDaemon(boolean on)` | 將該線程標記為守護線程或用戶線程。 | | 6 | `public final void join(long millisec)` | 等待該線程終止的時間最長為 millis 毫秒。 | | 7 | `public void interrupt()` | 中斷線程。 | | 8 | `public final boolean isAlive()` | 測試線程是否處于活動狀態。 | | 9 | `public static void yield()` | 暫停當前正在執行的線程對象,并執行其他線程。 | | 10 | `public static void sleep(long millisec)` | 在指定的毫秒數內讓當前正在執行的線程休眠(暫停執行),此操作受到系[統計](http://www.liuhaihua.cn/archives/tag/%e7%bb%9f%e8%ae%a1 "View all posts in 統計")時器和調度程序精度和準確性的影響。 | | 11 | `public static Thread currentThread()` | 返回對當前正在執行的線程對象的引用。 | ![](https://box.kancloud.cn/2016-08-18_57b585d424852.png) ### 靜態方法 ### 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{ @Override public 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); } } } } ~~~ 輸出結果: ![](https://box.kancloud.cn/2016-08-18_57b585d446507.png) 從上面輸出結果可以看出,當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{ @Override public 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();`?的[注釋](http://www.liuhaihua.cn/archives/tag/%e6%b3%a8%e9%87%8a "View all posts in 注釋")去掉,執行結果如下: ~~~ 用時: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{ @Override public 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()); ~~~ 雖然上面的[實例](http://www.liuhaihua.cn/archives/tag/%e5%ae%9e%e4%be%8b "View all posts in 實例")中打印的值是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類中的方法調用到底會引起線程狀態發生怎樣的變化呢?下面一幅圖就是在上面的圖上進行改進而來的: ![](https://box.kancloud.cn/2016-08-18_57b585d4608cc.png) ## 停止線程 停止線程是在多線程[開發](http://www.liuhaihua.cn/archives/tag/%e5%bc%80%e5%8f%91 "View all posts in 開發")時很重要的技術點,掌握此技術可以對線程的停止進行有效的處理。 停止一個線程可以使用Thread.stop()方法,但最好不用它。該方法是不[安全](http://www.liuhaihua.cn/archives/tag/%e5%ae%89%e5%85%a8 "View all posts in 安全")的,已被棄用。 在Java中有以下3種方法可以終止正在運行的線程: * 使用退出標志,使線程正常退出,也就是當run方法完成后線程終止 * 使用stop方法強行終止線程,但是不推薦使用這個方法,因為stop和suspend及resume一樣,都是作廢過期的方法,使用他們可能產生不可預料的結果。 * 使用interrupt方法中斷線程,但這個不會終止一個正在運行的線程,還需要加入一個判斷才可以完成線程的停止。 ## 暫停線程 interrupt()方法 ## 線程的優先級 在[操作系統](http://www.liuhaihua.cn/archives/tag/%e6%93%8d%e4%bd%9c%e7%b3%bb%e7%bb%9f "View all posts in 操作系統")中,線程可以劃分優先級,優先級較高的線程得到的CPU資源較多,也就是CPU優先執行優先級較高的線程對象中的任務。 設置線程優先級有助于幫“線程規劃器”確定在下一次選擇哪一個線程來優先執行。 設置線程的優先級使用setPriority()方法,此方法在JDK的[源碼](http://www.liuhaihua.cn/archives/tag/nativecode "View all posts in 源碼")如下: ~~~ 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,當前[目錄](http://www.liuhaihua.cn/archives/tag/directory "View all posts in 目錄")到/”) * 不是所有的應用都可以分配給Daemon線程來進行服務,比如讀寫操作或者計算邏輯。因為在Daemon Thread還沒來的及進行操作時,虛擬機可能已經退出了。 ### 同步與死鎖 1. 同步代碼塊? 在代碼塊上加上”synchronized”關鍵字,則此代碼塊就稱為同步代碼塊 2. 同步代碼塊格式 ~~~ synchronized(同步對象){ 需要同步的代碼塊; } ~~~ 3. 同步方法? 除了代碼塊可以同步,方法也是可以同步的 4. 方法同步格式 ~~~ 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():喚醒所有處于等待狀態的線程,該方法并不是將對象的鎖給所有線程,而是讓它們競爭,只有獲得鎖的線程才能進入就緒狀態; ## [總結](http://www.liuhaihua.cn/archives/tag/%e6%80%bb%e7%bb%93 "View all posts in 總結") 以上就是多線程的一些基礎概念,可能總結的不夠仔細,多多包涵。后續會針對一些比較重要的知識點單獨列出來總結。學好多線程是拿高薪的基礎,小伙伴一起加油吧! ## 參考 該文為本人學習的筆記,方便以后自己跳槽前復習。參考網上各大帖子,取其精華整合自己的理解而成。還有,關注我個人主頁的公眾號,里面電子書資源有《Java多線程編程核心技術》以及《JAVA并發編程實踐》高清版,需要的小伙伴自己取。 《Java多線程編程核心技術》 《JAVA并發編程實踐》 Java并發編程:Thread類的使用 關于Java并發編程的總結和思考 JAVA多線程實現的三種方式 ## 整理的思維導圖 個人整理的多線程基礎的思維導圖,導出的[圖片](http://www.liuhaihua.cn/archives/tag/%e5%9b%be%e7%89%87 "View all posts in 圖片")無法查看備注的一些信息,所以需要源文件的童鞋可以關注我個人主頁上的公眾號,回復?**多線程基礎**?即可獲取源文件。 ![](https://box.kancloud.cn/2016-08-19_57b653d9839bb.png) 一直覺得自己寫的不是技術,而是情懷,一篇篇[文章](http://www.liuhaihua.cn/archives/tag/%e6%96%87%e7%ab%a0 "View all posts in 文章")是自己這一路走來的痕跡。靠專業技能的成功是最具可復制性的,希望我的這條路能讓你少走彎路,希望我能幫你抹去知識的蒙塵,希望我能幫你理清知識的脈絡,希望未來技術之巔上有你也有我。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看