什么是進(jìn)程和線程?
一條線程指的是進(jìn)程中一個(gè)單一順序的控制流,一個(gè)進(jìn)程中可以并發(fā)多個(gè)線程,每條線程并行執(zhí)行不同的任務(wù)。
多線程是多任務(wù)的一種特別的形式,但多線程使用了更小的資源開銷。
一個(gè)進(jìn)程包括由操作系統(tǒng)分配的內(nèi)存空間,包含一個(gè)或多個(gè)線程。一個(gè)線程不能獨(dú)立的存在,它必須是進(jìn)程的一部分。一個(gè)進(jìn)程一直運(yùn)行,直到所有的非守護(hù)線程都結(jié)束運(yùn)行后才能結(jié)束。
為什么使用多線程?
使用多線程可以編寫高效率的程序來(lái)達(dá)到充分利用 CPU,可以大大提高系統(tǒng)整體的并發(fā)能力以及性能.
線程的生命周期
線程是一個(gè)動(dòng)態(tài)執(zhí)行的過(guò)程,它也有一個(gè)從產(chǎn)生到死亡的過(guò)程。
下圖顯示了一個(gè)線程完整的生命周期。
* 新建狀態(tài):
使用 new 關(guān)鍵字和 Thread 類或其子類建立一個(gè)線程對(duì)象后,該線程對(duì)象就處于新建狀態(tài)。它保持這個(gè)狀態(tài)直到程序 start() 這個(gè)線程.
* 就緒狀態(tài):
當(dāng)線程對(duì)象調(diào)用了start()方法之后,該線程就進(jìn)入就緒狀態(tài)。就緒狀態(tài)的線程處于就緒隊(duì)列中,要等待JVM里線程調(diào)度器的調(diào)度。
* 運(yùn)行狀態(tài):
如果就緒狀態(tài)的線程獲取 CPU 資源,就可以執(zhí)行 run(),此時(shí)線程便處于運(yùn)行狀態(tài)。處于運(yùn)行狀態(tài)的線程最為復(fù)雜,它可以變?yōu)樽枞麪顟B(tài)、就緒狀態(tài)和死亡狀態(tài)。
* 阻塞狀態(tài):
如果一個(gè)線程執(zhí)行了sleep(睡眠)、suspend(掛起)等方法,失去所占用資源之后,該線程就從運(yùn)行狀態(tài)進(jìn)入阻塞狀態(tài)。在睡眠時(shí)間已到或獲得設(shè)備資源后可以重新進(jìn)入就緒狀態(tài)??梢苑譃槿N:
* 等待阻塞:運(yùn)行狀態(tài)中的線程執(zhí)行 wait() 方法,使線程進(jìn)入到等待阻塞狀態(tài)。
* 同步阻塞:線程在獲取 synchronized 同步鎖失敗(因?yàn)橥芥i被其他線程占用)。
* 其他阻塞:通過(guò)調(diào)用線程的 sleep() 或 join() 發(fā)出了 I/O 請(qǐng)求時(shí),線程就會(huì)進(jìn)入到阻塞狀態(tài)。當(dāng)sleep() 狀態(tài)超時(shí),join()
等待線程終止或超時(shí),或者 I/O 處理完畢,線程重新轉(zhuǎn)入就緒狀態(tài)。
* 死亡狀態(tài):
一個(gè)運(yùn)行狀態(tài)的線程完成任務(wù)或者其他終止條件發(fā)生時(shí),該線程就切換到終止?fàn)顟B(tài)。
創(chuàng)建線程的方式
java 提供了三種創(chuàng)建線程的方法:實(shí)現(xiàn) Runnable 接口;繼承 Thread 類本身;通過(guò) Callable 和 Future 創(chuàng)建線程。
1.實(shí)現(xiàn) Runnable 接口
創(chuàng)建一個(gè)線程,最簡(jiǎn)單的方法是創(chuàng)建一個(gè)實(shí)現(xiàn) Runnable 接口的類,同時(shí)重寫 run()方法.
然后創(chuàng)建Runnable實(shí)現(xiàn)類的實(shí)例,并以此實(shí)例作為Thread的target對(duì)象,即該Thread對(duì)象才是真正的線程對(duì)象。
新線程創(chuàng)建之后,你調(diào)用它的 start() 方法它才會(huì)運(yùn)行。
package com.example.test; /** * @author ydx */ public class RunnableDemo
implements Runnable{ /** * 線程名稱 */ private String threadName; /** * 構(gòu)造方法 *
@param threadName 線程名稱 */ public RunnableDemo(String threadName) {
this.threadName = threadName; } @Override public void run() {
System.out.println(threadName + " is running"); //業(yè)務(wù) for (int i = 0; i < 5;
i++) { System.out.println(threadName + " 執(zhí)行 " + i); try { Thread.sleep(100); }
catch (InterruptedException e) { e.printStackTrace(); } }
System.out.println(threadName + " is exiting"); } public static void
main(String[] args) { RunnableDemo runnable1 = new RunnableDemo("thread-1");
Thread thread1 = new Thread(runnable1); thread1.start(); RunnableDemo runnable2
= new RunnableDemo("thread-2"); Thread thread2 = new Thread(runnable2);
thread2.start(); } }
第一次運(yùn)行結(jié)果如下:
thread-1 is running thread-1 執(zhí)行 0 thread-2 is running thread-2 執(zhí)行 0 thread-2
執(zhí)行 1 thread-1 執(zhí)行 1 thread-2 執(zhí)行 2 thread-1 執(zhí)行 2 thread-1 執(zhí)行 3 thread-2 執(zhí)行 3
thread-1 執(zhí)行 4 thread-2 執(zhí)行 4 thread-2 is exiting thread-1 is exiting
第二次運(yùn)行結(jié)果如下:
thread-1 is running thread-1 執(zhí)行 0 thread-2 is running thread-2 執(zhí)行 0 thread-1
執(zhí)行 1 thread-2 執(zhí)行 1 thread-1 執(zhí)行 2 thread-2 執(zhí)行 2 thread-1 執(zhí)行 3 thread-2 執(zhí)行 3
thread-2 執(zhí)行 4 thread-1 執(zhí)行 4 thread-1 is exiting thread-2 is exiting
可以看出兩次運(yùn)行結(jié)果是不一樣的,每次兩個(gè)線程的執(zhí)行順序是隨機(jī)的.
2.繼承Thread類
創(chuàng)建一個(gè)線程的第二種方法是創(chuàng)建一個(gè)新的類,該類繼承 Thread 類,然后創(chuàng)建一個(gè)該類的實(shí)例。
繼承類必須重寫 run() 方法,該方法是新線程的入口點(diǎn)。它也必須調(diào)用 start() 方法才能執(zhí)行。
package com.example.test; /** * @author ydx */ public class ThreadDemo extends
Thread { /** * 線程名稱 */ private String threadName; public ThreadDemo(String
threadName) { this.threadName = threadName; } @Override public synchronized
void start() { System.out.println(threadName+ " is starting......");
super.start(); } @Override public void run() { System.out.println(threadName +
" is running"); //業(yè)務(wù) for (int i = 0; i < 3; i++) {
System.out.println(threadName + " 執(zhí)行 " + i); try { Thread.sleep(100); } catch
(InterruptedException e) { e.printStackTrace(); } }
System.out.println(threadName + " is exiting"); } public static void
main(String[] args) { ThreadDemo thread1 = new ThreadDemo("thread-1");
thread1.start(); ThreadDemo thread2 = new ThreadDemo("thread-2");
thread2.start(); } }
運(yùn)行結(jié)果如下:
thread-1 is starting...... thread-2 is starting...... thread-1 is running
thread-1 執(zhí)行 0 thread-2 is running thread-2 執(zhí)行 0 thread-1 執(zhí)行 1 thread-2 執(zhí)行 1
thread-2 執(zhí)行 2 thread-1 執(zhí)行 2 thread-2 is exiting thread-1 is exiting
3.通過(guò) Callable 和 Future 創(chuàng)建線程
* 創(chuàng)建 Callable 接口的實(shí)現(xiàn)類,并實(shí)現(xiàn) call() 方法,該 call() 方法將作為線程執(zhí)行體,并且有返回值。
* 創(chuàng)建 Callable 實(shí)現(xiàn)類的實(shí)例,使用 FutureTask 類來(lái)包裝 Callable 對(duì)象,該 FutureTask 對(duì)象封裝了該
Callable 對(duì)象的 call() 方法的返回值。
* 使用 FutureTask 對(duì)象作為 Thread 對(duì)象的 target 創(chuàng)建并啟動(dòng)新線程。
*
調(diào)用 FutureTask 對(duì)象的 get() 方法來(lái)獲得子線程執(zhí)行結(jié)束后的返回值。
package com.example.test; import java.util.concurrent.Callable; import
java.util.concurrent.ExecutionException; import
java.util.concurrent.FutureTask; /** * @author ydx */ public class CallableTest
implements Callable<Integer> { @Override public Integer call() throws Exception
{ int sum = 0; for (int i = 0; i < 5; i++) { sum += i; System.out.println(i);
//sleep 200ms Thread.sleep(200); } return sum; } public static void
main(String[] args) { long start = System.currentTimeMillis(); CallableTest
callableTest = new CallableTest(); FutureTask<Integer> futureTask = new
FutureTask<>(callableTest); new Thread(futureTask, "thread-1").start();
CallableTest callableTest2 = new CallableTest(); FutureTask<Integer>
futureTask2 = new FutureTask<>(callableTest2); new Thread(futureTask2,
"thread-2").start(); try { System.out.println("thread-1的結(jié)果: " +
futureTask.get()); System.out.println("thread-2的結(jié)果: " + futureTask.get()); }
catch (InterruptedException e) { e.printStackTrace(); } catch
(ExecutionException e) { e.printStackTrace(); } long end =
System.currentTimeMillis(); System.out.println("耗時(shí): " + (end - start) + "ms");
} }
運(yùn)行結(jié)果:
0 0 1 1 2 2 3 3 4 4 thread-1的結(jié)果: 10 thread-2的結(jié)果: 10 耗時(shí): 1004ms
我們創(chuàng)建了兩個(gè)線程, 每個(gè)線程計(jì)算0~4的和,單個(gè)線程耗時(shí)200ms * 5 =
1000ms,而最終兩個(gè)線程的總耗時(shí)約1000ms,由此可見(jiàn)兩個(gè)線程是并發(fā)進(jìn)行.
4.創(chuàng)建線程的三種方式的對(duì)比
* 使用繼承 Thread 類的方式創(chuàng)建多線程時(shí),編寫簡(jiǎn)單,但是不夠靈活
* 采用實(shí)現(xiàn) Runnable、Callable 接口的方式創(chuàng)建多線程時(shí),線程類只是實(shí)現(xiàn)了 Runnable 接口或 Callable
接口,還可以繼承其他類,創(chuàng)建線程比較靈活.
線程管理
Java提供了一些方法用于線程狀態(tài)的控制。具體如下:
1.sleep(線程睡眠)
如果我們需要讓當(dāng)前正在執(zhí)行的線程暫停一段時(shí)間,并進(jìn)入阻塞狀態(tài),則可以通過(guò)調(diào)用Thread的sleep方法。
Thread.sleep(long millis)方法,millis參數(shù)設(shè)定睡眠的時(shí)間,以毫秒為單位。當(dāng)睡眠結(jié)束后,就轉(zhuǎn)為就緒(Runnable)狀態(tài)。
2.wait(線程等待)
Object類中的wait()方法,導(dǎo)致當(dāng)前的線程等待,直到其他線程調(diào)用此對(duì)象的 notify() 方法或 notifyAll() 喚醒方法.
3.yield(線程讓步)
Thread.yield() 方法,暫停當(dāng)前正在執(zhí)行的線程對(duì)象,把執(zhí)行機(jī)會(huì)讓給相同或者更高優(yōu)先級(jí)的線程。
和sleep()方法不同的是,它不會(huì)進(jìn)入到阻塞狀態(tài),而是進(jìn)入到就緒狀態(tài)。
yield()方法只是讓當(dāng)前線程暫停一下,重新進(jìn)入就緒的線程池中,讓系統(tǒng)的線程調(diào)度器重新調(diào)度器重新調(diào)度一次,完全可能出現(xiàn)這樣的情況:當(dāng)某個(gè)線程調(diào)用yield()方法之后,線程調(diào)度器又將其調(diào)度出來(lái)重新進(jìn)入到運(yùn)行狀態(tài)執(zhí)行。
4.join(線程加入)
join()方法,等待其他線程終止。在當(dāng)前線程中調(diào)用另一個(gè)線程的join()方法,則當(dāng)前線程轉(zhuǎn)入阻塞狀態(tài),直到另一個(gè)進(jìn)程運(yùn)行結(jié)束,當(dāng)前線程再由阻塞轉(zhuǎn)為就緒狀態(tài)。
5.notify(線程喚醒)
Object類中的notify()方法,喚醒在此對(duì)象監(jiān)視器上等待的單個(gè)線程。如果所有線程都在此對(duì)象上等待,則會(huì)選擇喚醒其中一個(gè)線程。選擇是任意性的,并在對(duì)實(shí)現(xiàn)做出決定時(shí)發(fā)生。線程通過(guò)調(diào)用其中一個(gè)
wait 方法,在對(duì)象的監(jiān)視器上等待。
直到當(dāng)前的線程放棄此對(duì)象上的鎖定,才能繼續(xù)執(zhí)行被喚醒的線程。被喚醒的線程將以常規(guī)方式與在該對(duì)象上主動(dòng)同步的其他所有線程進(jìn)行競(jìng)爭(zhēng)
線程的優(yōu)先級(jí)
每一個(gè) Java 線程都有一個(gè)優(yōu)先級(jí),這樣有助于操作系統(tǒng)確定線程的調(diào)度順序。
Java 線程的優(yōu)先級(jí)是一個(gè)整數(shù),其取值范圍是 1 (Thread.MINPRIORITY ) - 10 (Thread.MAXPRIORITY )。
默認(rèn)情況下,每一個(gè)線程都會(huì)分配一個(gè)優(yōu)先級(jí) NORM_PRIORITY(5)。
具有較高優(yōu)先級(jí)的線程對(duì)程序更重要,并且應(yīng)該在低優(yōu)先級(jí)的線程之前分配處理器資源。但是,線程優(yōu)先級(jí)不能保證線程執(zhí)行的順序,而且非常依賴于平臺(tái)。
線程池
線程池,其實(shí)就是一個(gè)容納多個(gè)線程的容器,其中的線程可以反復(fù)使用,省去了頻繁創(chuàng)建線程對(duì)象的操作,無(wú)需反復(fù)創(chuàng)建線程而消耗過(guò)多資源。使用線程池的好處:
* 降低資源消耗。通過(guò)重復(fù)利用已創(chuàng)建的線程降低線程創(chuàng)建和銷毀造成的消耗.
* 提高響應(yīng)速度。當(dāng)任務(wù)到達(dá)時(shí),任務(wù)可以不需要的等到線程創(chuàng)建就能立即執(zhí)行。
* 提高線程的可管理性。線程是稀缺資源,如果無(wú)限制的創(chuàng)建,不僅會(huì)消耗系統(tǒng)資源,還會(huì)降低系統(tǒng)的穩(wěn)定性,使用線程池可以進(jìn)行統(tǒng)一的分配,調(diào)優(yōu)和監(jiān)控。
Java通過(guò)Executors提供四種線程池,分別為:
* newCachedThreadPoo 創(chuàng)建一個(gè)可緩存線程池,如果線程池長(zhǎng)度超過(guò)處理需要,可靈活回收空閑線程,若無(wú)可回收,則新建線程。
* newFixedThreadPool 創(chuàng)建一個(gè)定長(zhǎng)線程池,可控制線程最大并發(fā)數(shù),超出的線程會(huì)在隊(duì)列中等待。
* newScheduledThreadPool 創(chuàng)建一個(gè)定長(zhǎng)線程池,支持定時(shí)及周期性任務(wù)執(zhí)行。
* newSingleThreadExecutor 創(chuàng)建一個(gè)單線程化的線程池,它只會(huì)用唯一的工作線程來(lái)執(zhí)行任務(wù),保證所有任務(wù)按照指定順序(FIFO,
LIFO, 優(yōu)先級(jí))執(zhí)行。
熱門工具 換一換
