進程與線程
最初的計算機只能接受一些特定的指令,用戶每輸入一個指令,計算機就做出一個操作。當用戶在思考或者輸入時,計算機就在等待。這樣效率非常低下,在很多時候,計算機都處在等待狀態(tài)。
后來有了批處理操作系統(tǒng),把一系列需要操作的指令寫下來,形成一個清單,一次性交給計算機。用戶將多個需要執(zhí)行的程序?qū)懺诖艓希缓蠼挥捎嬎銠C去讀取并逐個執(zhí)行這些程序,并將輸出結(jié)果寫在另一個磁帶上。
批處理操作系統(tǒng)在一定程度上提高了計算機的效率,但是由于批處理操作系統(tǒng)的指令運行方式仍然是串行的,內(nèi)存中始終只有一個程序在運行,后面的程序需要等待前面的程序執(zhí)行完成后才能開始執(zhí)行,而前面的程序有時會由于I/O操作、網(wǎng)絡(luò)等原因阻塞,所以批處理操作效率也不高。
人們對于計算機的性能要求越來越高,現(xiàn)有的批處理操作系統(tǒng)并不能滿足人們的需求,而批處理操作系統(tǒng)的瓶頸在于內(nèi)存中只存在一個程序,那么內(nèi)存中能不能存在多個程序呢?這是人們亟待解決的問題。
于是,科學(xué)家們提出了進程的概念。
進程就是應(yīng)用程序在內(nèi)存中分配的空間,也就是正在運行的程序,各個進程之間互不干擾。同時進程保存著程序每一個時刻運行的狀態(tài)。
此時,CPU采用時間片輪轉(zhuǎn)的方式運行進程:CPU為每個進程分配一個時間段,稱作它的時間片。如果在時間片結(jié)束時進程還在運行,則暫停這個進程的運行,并且CPU分配給另一個進程(這個過程叫做上下文切換)。如果進程在時間片結(jié)束前阻塞或結(jié)束,則CPU立即進行切換,不用等待時間片用完。
當進程暫停時,它會保存當前進程的狀態(tài)(進程標識,進程使用的資源等),在下一次切換回來時根據(jù)之前保存的狀態(tài)進行恢復(fù),接著繼續(xù)執(zhí)行。
使用進程+CPU時間片輪轉(zhuǎn)方式的操作系統(tǒng),在宏觀上看起來同一時間段執(zhí)行多個任務(wù),換句話說,進程讓操作系統(tǒng)的并發(fā)成為了可能。雖然并發(fā)從宏觀上看有多個任務(wù)在執(zhí)行,但在事實上,對于單核CPU來說,任意具體時刻都只有一個任務(wù)在占用CPU資源。
雖然進程的出現(xiàn),使得操作系統(tǒng)的性能大大提升,但是隨著時間的推移,人們并不滿足一個進程在一段時間只能做一件事情,如果一個進程有多個子任務(wù)時,只能逐個得執(zhí)行這些子任務(wù),很影響效率。
那么能不能讓這些子任務(wù)同時執(zhí)行呢?于是人們又提出了線程的概念,讓一個線程執(zhí)行一個子任務(wù),這樣一個進程就包含了多個線程,每個線程負責一個單獨的子任務(wù)。
總之,進程和線程的提出極大的提高了操作系統(tǒng)的性能。進程讓操作系統(tǒng)的并發(fā)性成為了可能,而線程讓進程的內(nèi)部并發(fā)成為了可能。
多進程方式確實可以實現(xiàn)并發(fā),但使用多線程,有以下幾個好處:
- 進程間的通信比較復(fù)雜,而線程間的通信比較簡單,通常情況下,我們需要使用共享資源,這些資源在線程間的通信比較容易。
- 進程是重量級的,而線程是輕量級的,故多線程方式的系統(tǒng)開銷更小。
進程和線程的區(qū)別
進程是一個獨立的運行環(huán)境,而線程是在進程中執(zhí)行的一個任務(wù)。
他們兩個本質(zhì)的區(qū)別是是否單獨占有內(nèi)存地址空間及其它系統(tǒng)資源(比如I/O):
- 進程單獨占有一定的內(nèi)存地址空間,所以進程間存在內(nèi)存隔離,數(shù)據(jù)是分開的,數(shù)據(jù)共享復(fù)雜但是同步簡單,各個進程之間互不干擾;而線程共享所屬進程占有的內(nèi)存地址空間和資源,數(shù)據(jù)共享簡單,但是同步復(fù)雜。
- 進程單獨占有一定的內(nèi)存地址空間,一個進程出現(xiàn)問題不會影響其他進程,不影響主程序的穩(wěn)定性,可靠性高;一個線程崩潰可能影響整個程序的穩(wěn)定性,可靠性較低。
- 進程單獨占有一定的內(nèi)存地址空間,進程的創(chuàng)建和銷毀不僅需要保存寄存器和棧信息,還需要資源的分配回收以及頁調(diào)度,開銷較大;線程只需要保存寄存器和棧信息,開銷較小。
另外一個重要區(qū)別是,進程是操作系統(tǒng)進行資源分配的基本單位,而線程是操作系統(tǒng)進行調(diào)度的基本單位,即CPU分配時間的單位 。
上下文切換
上下文切換(有時也稱做進程切換或任務(wù)切換)是指 CPU 從一個進程(或線程)切換到另一個進程(或線程)。上下文是指某一時間點 CPU 寄存器和程序計數(shù)器的內(nèi)容。
-
寄存器是cpu內(nèi)部的少量的速度很快的閃存,通常存儲和訪問計算過程的中間值提高計算機程序的運行速度。
-
程序計數(shù)器是一個專用的寄存器,用于表明指令序列中 CPU 正在執(zhí)行的位置,存的值為正在執(zhí)行的指令的位置或者下一個將要被執(zhí)行的指令的位置,具體實現(xiàn)依賴于特定的系統(tǒng)。
CPU通過為每個線程分配CPU時間片來實現(xiàn)多線程機制。CPU通過時間片分配算法來循環(huán)執(zhí)行任務(wù),當前任務(wù)執(zhí)行一個時間片后會切換到下一個任務(wù)。
但是,在切換前會保存上一個任務(wù)的狀態(tài),以便下次切換回這個任務(wù)時,可以再加載這個任務(wù)的狀態(tài)。所以任務(wù)從保存到再加載的過程就是一次上下文切換。
上下文切換通常是計算密集型的,意味著此操作會消耗大量的 CPU 時間,故線程也不是越多越好。如何減少系統(tǒng)中上下文切換次數(shù),是提升多線程性能的一個重點課題。
線程的創(chuàng)建
1、實現(xiàn)Runnable接口
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("我是實現(xiàn)Runnable接口。。");
}
public static void main(String[] args) {
MyRunnable myRunner = new MyRunnable();
Thread thread = new Thread(myRunner);
thread.start();
}
}
2、繼承Thread類
public class MyThread extends Thread {
@Override
public void run() {
System.out.println("我是繼承Thread類。。");
}
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
}
}
3、實現(xiàn)Callable接口
public class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
return 123;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<Integer> futureTask = new FutureTask<>(new MyCallable());
new Thread(futureTask).start();
System.out.println(futureTask.get());
}
}
4、通過線程池創(chuàng)建線程
看過《阿里Java開發(fā)手冊》的你,應(yīng)該知道
線程池不允許使用Executors去創(chuàng)建,而是通過ThreadPoolExecutor的方式,這樣的處理方式讓寫的同學(xué)更加明確線程池的運行規(guī)則,規(guī)避資源耗盡的風險。
4. 【強制】線程池不允許使用 Executors 去創(chuàng)建,而是通過 ThreadPoolExecutor 的方式,這
樣的處理方式讓寫的同學(xué)更加明確線程池的運行規(guī)則,規(guī)避資源耗盡的風險。
說明:Executors 返回的線程池對象的弊端如下:
1) FixedThreadPool 和 SingleThreadPool:
允許的請求隊列長度為 Integer.MAX_VALUE,可能會堆積大量的請求,從而導(dǎo)致 OOM。
2) CachedThreadPool 和 ScheduledThreadPool:
允許的創(chuàng)建線程數(shù)量為 Integer.MAX_VALUE,可能會創(chuàng)建大量的線程,從而導(dǎo)致 OOM。
ThreadPoolExecutor機制
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
下面是ThreadPoolExecutor最核心的構(gòu)造方法參數(shù):
-
corePoolSize
核心線程池的大??; -
maximumPoolSize
最大線程池大小; -
keepAliveTime
線程池中超過corePoolSize
數(shù)目的空閑線程最大存活時間;可以allowCoreThreadTimeOut(true)
使得核心線程超出有效時間也關(guān)閉; -
TimeUnit
keepAliveTime
的時間單位; -
workQueue
阻塞任務(wù)隊列; -
threadFactory
新建線程工廠; -
RejectedExecutionHandler
當提交任務(wù)數(shù)超過maximumPoolSize+workQueue
之和時,任務(wù)會交給RejectedExecutionHandler
來處理;
重點講解
corePoolSize
,maximumPoolSize
,workQueue
三者之間的關(guān)系:
- 當線程池小于
corePoolSize
時,新提交的任務(wù)會創(chuàng)建一個新線程執(zhí)行任務(wù),即使線程池中仍有空閑線程。 - 當線程池達到
corePoolSize
時,新提交的任務(wù)將被放在workQueue
中,等待線程池中的任務(wù)執(zhí)行完畢。 - 當
workQueue
滿了,并且maximumPoolSize > corePoolSize
時,新提交任務(wù)會創(chuàng)建新的線程執(zhí)行任務(wù)。 - 當提交任務(wù)數(shù)超過
maximumPoolSize
,新任務(wù)就交給RejectedExecutionHandler
來處理。 - 當線程池中超過
corePoolSize
線程,空閑時間達到keepAliveTime
時,關(guān)閉空閑線程。 - 當設(shè)置
allowCoreThreadTimeOut(true)
時,線程池中corePoolSize
線程空閑時間達到keepAliveTime
也將關(guān)閉。
線程的生命周期
- NEW:初始狀態(tài),線程被構(gòu)建,但是還沒有調(diào)用 start 方法。
- RUNNABLED:運行狀態(tài),JAVA 線程把操作系統(tǒng)中的就緒和運行兩種狀態(tài)統(tǒng)一稱為“運行中”。調(diào)用線程的
start()
方法使線程進入就緒狀態(tài)。 - BLOCKED:阻塞狀態(tài),表示線程進入等待狀態(tài),也就是線程因為某種原因放棄了 CPU 使用權(quán)。比如訪問
synchronized
關(guān)鍵字修飾的方法,沒有獲得對象鎖。 - Waiting :等待狀態(tài),比如調(diào)用wait()方法。
- TIME_WAITING:超時等待狀態(tài),超時以后自動返回。比如調(diào)用
sleep(long millis)
方法 - TERMINATED:終止狀態(tài),表示當前線程執(zhí)行完畢。
看下源碼:
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
線程的優(yōu)先級
- 線程的最小優(yōu)先級:1
- 線程的最大優(yōu)先級:10
- 線程的默認優(yōu)先級:5
- 通過調(diào)用
getPriority()
和setPriority(int newPriority)
方法來獲得和設(shè)置線程的優(yōu)先級
看下源碼:
/**
* 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;
看下代碼:
public class MyThread extends Thread {
public static void main(String[] args) {
MyThread a = new MyThread();
System.out.println(a.getPriority());//5
a.setPriority(8);
System.out.println(a.getPriority());//8
}
}
特性:
-
繼承性:比如A線程啟動B線程,則B線程的優(yōu)先級與A是一樣的。
-
規(guī)則性:高優(yōu)先級的線程總是大部分先執(zhí)行完,但不代表高優(yōu)先級線程全部先執(zhí)行完。
-
隨機性:優(yōu)先級較高的線程不一定每一次都先執(zhí)行完。
線程的停止
-
stop()
方法,這個方法已經(jīng)標記為過時了,強制停止線程,相當于kill -9。 -
interrupt()
方法,優(yōu)雅的停止線程。告訴線程可以停止了,至于線程什么時候停止,取決于線程自身。
看下停止線程的代碼:
public class InterruptDemo {
private static int i ;
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
//默認情況下isInterrupted 返回 false、通過 thread.interrupt 變成了 true
while (!Thread.currentThread().isInterrupted()) {
i++;
}
System.out.println("Num:" + i);
}, "interruptDemo");
thread.start();
TimeUnit.SECONDS.sleep(1);
thread.interrupt(); //不加這句,thread線程不會停止
}
}
看上面這段代碼,主線程main方法調(diào)用 thread
線程的 interrupt()
方法,就是告訴 thread
線程,你可以停止了(其實是將 thread
線程的一個屬性設(shè)置為了true),然后 thread
線程通過 isInterrupted()
方法獲取這個屬性來判斷是否設(shè)置為了true。文章來源:http://www.zghlxwxcb.cn/news/detail-471509.html
小結(jié)
以上介紹了一些java線程的一些基本知識,掌握了進程與線程的概念以及線程的創(chuàng)建、生命周期、停止等概念,后面我們再來詳細聊聊java并發(fā)相關(guān)的知識。文章來源地址http://www.zghlxwxcb.cn/news/detail-471509.html
到了這里,關(guān)于java并發(fā)編程:多線程基礎(chǔ)知識介紹的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!