移動端網(wǎng)站設(shè)計制作企業(yè)網(wǎng)絡(luò)搭建方案
目錄
- 什么是線程?什么是進(jìn)程?為什么要有線程?有什么關(guān)系與區(qū)別?
- 什么是守護(hù)線程?
- 如何創(chuàng)建、啟動 Java 線程?
- 線程池參數(shù)
- 詳細(xì)解釋Callable接口和Future類
- 偏向鎖 / 輕量級鎖 / 重量級鎖
- synchronized 和 java.util.concurrent.lock.Lock 之間的區(qū)別
- synchronized 和 java.util.concurrent.lock.Lock 之間的區(qū)別
- java.util.concurrent.lock.Lock 與 java.util.concurrent.lock.ReadWriteLock 之間的區(qū)別
- 什么是死鎖?發(fā)生的條件,避免死鎖
- 介紹一下ForkJoinPool的使用
什么是線程?什么是進(jìn)程?為什么要有線程?有什么關(guān)系與區(qū)別?
線程:
線程是進(jìn)程中的最小執(zhí)行單元,是CPU調(diào)度的基本單位。一個進(jìn)程可以包含多個線程,這些線程共享進(jìn)程的資源,包括內(nèi)存空間、文件描述符等。線程之間可以并發(fā)執(zhí)行,使得程序可以同時處理多個任務(wù),提高了系統(tǒng)的響應(yīng)能力和并發(fā)性。
進(jìn)程:
進(jìn)程是操作系統(tǒng)中的一個執(zhí)行實(shí)例,是程序在計算機(jī)上的一次執(zhí)行活動。每個進(jìn)程都有自己獨(dú)立的內(nèi)存空間和系統(tǒng)資源,進(jìn)程之間是相互獨(dú)立的。進(jìn)程的切換代價相對較高,因?yàn)樾枰4婧突謴?fù)進(jìn)程的所有狀態(tài)信息。
為什么要有線程?
在單核CPU時代,進(jìn)程是獨(dú)立運(yùn)行的,每個進(jìn)程有自己的地址空間,進(jìn)程之間切換開銷大。為了充分利用CPU的計算資源,提高系統(tǒng)并發(fā)能力,引入了線程的概念。線程在同一個進(jìn)程內(nèi)共享進(jìn)程的資源,線程間切換開銷較小,可以實(shí)現(xiàn)更高效的多任務(wù)處理。
關(guān)系與區(qū)別:
- 關(guān)系:線程是進(jìn)程的一部分,一個進(jìn)程可以包含多個線程。線程共享進(jìn)程的資源,包括內(nèi)存空間、文件描述符等。
- 區(qū)別:進(jìn)程是一個獨(dú)立的執(zhí)行實(shí)例,有自己獨(dú)立的內(nèi)存空間和系統(tǒng)資源。線程是進(jìn)程的最小執(zhí)行單元,多個線程共享進(jìn)程的資源。進(jìn)程之間相互獨(dú)立,線程之間可以并發(fā)執(zhí)行,共享進(jìn)程的資源。
總結(jié):線程是進(jìn)程內(nèi)部的執(zhí)行流,是處理器執(zhí)行任務(wù)的最小單位。一個進(jìn)程可以包含多個線程,線程之間可以并發(fā)執(zhí)行,共享進(jìn)程的資源,從而提高系統(tǒng)的并發(fā)性和性能。多線程編程可以實(shí)現(xiàn)更高效的多任務(wù)處理,但也需要注意線程同步和共享資源的安全性問題。
什么是守護(hù)線程?
守護(hù)線程(Daemon Thread)是一種特殊類型的線程,其特點(diǎn)是在程序運(yùn)行時在后臺提供一種通用服務(wù)的功能。與普通線程(用戶線程)相對應(yīng),守護(hù)線程的生命周期依賴于程序中是否還存在正在運(yùn)行的用戶線程。當(dāng)所有的用戶線程結(jié)束運(yùn)行時,守護(hù)線程會被自動終止,而不會等待其運(yùn)行完成。
在Java中,通過調(diào)用Thread類的setDaemon(true)
方法將線程設(shè)置為守護(hù)線程。守護(hù)線程通常用于提供程序的支持和后臺服務(wù),如垃圾回收器(GC線程)就是一個典型的守護(hù)線程。垃圾回收器在程序運(yùn)行過程中自動回收不再使用的對象,但當(dāng)所有用戶線程執(zhí)行完畢時,程序就終止了,此時也不需要再繼續(xù)執(zhí)行垃圾回收的工作,因此垃圾回收線程會自動終止。
守護(hù)線程的一個重要應(yīng)用場景是Web服務(wù)器,它可以創(chuàng)建一個守護(hù)線程來處理網(wǎng)絡(luò)連接,如果所有的用戶請求線程都結(jié)束了,服務(wù)器就沒有處理請求的必要了,守護(hù)線程會自動退出。
需要注意的是,守護(hù)線程并不適合處理需要完整執(zhí)行的任務(wù),因?yàn)樗纳芷谑遣豢煽氐?#xff0c;當(dāng)所有用戶線程結(jié)束時,它會被強(qiáng)制終止,可能導(dǎo)致任務(wù)未完成。因此,守護(hù)線程適合處理后臺服務(wù)和支持性工作,而不適合處理關(guān)鍵業(yè)務(wù)邏輯。
如何創(chuàng)建、啟動 Java 線程?
在Java中,創(chuàng)建和啟動線程通常有兩種方式:繼承Thread類和實(shí)現(xiàn)Runnable接口。
1. 繼承Thread類:
創(chuàng)建一個繼承自Thread類的子類,并重寫run()方法來定義線程的執(zhí)行邏輯。然后通過調(diào)用start()方法來啟動線程。示例代碼如下:
public class MyThread extends Thread {@Overridepublic void run() {// 線程執(zhí)行邏輯System.out.println("Thread is running!");}public static void main(String[] args) {MyThread thread = new MyThread();thread.start(); // 啟動線程}
}
2. 實(shí)現(xiàn)Runnable接口:
創(chuàng)建一個實(shí)現(xiàn)了Runnable接口的類,并實(shí)現(xiàn)run()方法來定義線程的執(zhí)行邏輯。然后將實(shí)現(xiàn)了Runnable接口的類作為參數(shù)傳遞給Thread類的構(gòu)造方法,并調(diào)用start()方法啟動線程。示例代碼如下:
public class MyRunnable implements Runnable {@Overridepublic void run() {// 線程執(zhí)行邏輯System.out.println("Thread is running!");}public static void main(String[] args) {MyRunnable myRunnable = new MyRunnable();Thread thread = new Thread(myRunnable);thread.start(); // 啟動線程}
}
無論是繼承Thread類還是實(shí)現(xiàn)Runnable接口,調(diào)用start()方法后,線程會進(jìn)入就緒狀態(tài),并由Java虛擬機(jī)自動調(diào)度執(zhí)行。需要注意的是,線程的執(zhí)行順序是由系統(tǒng)的線程調(diào)度器決定的,因此線程的執(zhí)行順序可能是隨機(jī)的。
除了繼承Thread類和實(shí)現(xiàn)Runnable接口外,還可以使用Callable接口和Future類創(chuàng)建線程。Callable接口允許線程返回一個結(jié)果,而Future類可以用于獲取Callable線程的返回值。這是一種更加靈活的方式來創(chuàng)建和管理線程。
線程池參數(shù)
在Java中,線程池的參數(shù)主要由ThreadPoolExecutor
類的構(gòu)造方法來定義,它的構(gòu)造方法有以下幾個參數(shù):
-
corePoolSize:
表示線程池的核心線程數(shù),也就是線程池中始終保持活動狀態(tài)的線程數(shù)量。即使這些線程處于空閑狀態(tài),也不會被回收。當(dāng)有新的任務(wù)提交時,如果核心線程數(shù)還沒有達(dá)到上限,會優(yōu)先創(chuàng)建新的核心線程來執(zhí)行任務(wù)。 -
maximumPoolSize:
表示線程池的最大線程數(shù),包括核心線程和非核心線程。當(dāng)有新的任務(wù)提交時,如果核心線程數(shù)已滿,并且線程池中的線程數(shù)量還沒有達(dá)到最大線程數(shù),會創(chuàng)建新的非核心線程來執(zhí)行任務(wù)。當(dāng)線程池中的線程數(shù)達(dá)到最大線程數(shù)后,如果任務(wù)繼續(xù)提交,任務(wù)會被放入任務(wù)隊列中等待執(zhí)行。 -
keepAliveTime:
表示非核心線程的空閑時間。當(dāng)非核心線程空閑時間超過該值時,會被回收釋放,以減少線程池的線程數(shù)量。這個參數(shù)只有在allowCoreThreadTimeOut
設(shè)置為true
時才生效。 -
unit:
表示keepAliveTime的時間單位,可以是TimeUnit.SECONDS
、TimeUnit.MINUTES
等等。 -
workQueue:
表示任務(wù)隊列,用于存放等待執(zhí)行的任務(wù)。當(dāng)線程池的線程數(shù)達(dá)到核心線程數(shù)后,后續(xù)提交的任務(wù)會被放入任務(wù)隊列中等待執(zhí)行。線程池提供了多種任務(wù)隊列的實(shí)現(xiàn),常用的有ArrayBlockingQueue
、LinkedBlockingQueue
、SynchronousQueue
等。 -
threadFactory:
表示線程工廠,用于創(chuàng)建新的線程。可以通過自定義線程工廠來給線程設(shè)置特定的名稱、優(yōu)先級等。 -
handler:
表示線程池的拒絕策略,當(dāng)線程池的任務(wù)隊列和線程數(shù)都已滿,無法繼續(xù)接收新的任務(wù)時,會根據(jù)指定的拒絕策略來處理這些任務(wù)。常用的拒絕策略有ThreadPoolExecutor.AbortPolicy
(拋出異常)、ThreadPoolExecutor.CallerRunsPolicy
(由提交任務(wù)的線程來執(zhí)行任務(wù))、ThreadPoolExecutor.DiscardPolicy
(直接丟棄任務(wù))等。
綜上所述,線程池的參數(shù)主要包括核心線程數(shù)、最大線程數(shù)、非核心線程的空閑時間、任務(wù)隊列、線程工廠和拒絕策略等。合理地設(shè)置這些參數(shù)可以根據(jù)系統(tǒng)的需求來控制線程池的并發(fā)度、資源消耗和任務(wù)調(diào)度,以達(dá)到最優(yōu)的性能和資源利用率。
詳細(xì)解釋Callable接口和Future類
Callable
接口和Future
類是Java多線程中用于處理有返回值的任務(wù)的一組接口和類。它們通常與線程池結(jié)合使用,使得多線程編程更加靈活和高效。
Callable接口:
Callable
接口是一個泛型接口,定義了一個帶有返回值的任務(wù),它只有一個方法call()
,沒有像Runnable
接口那樣的run()
方法。call()
方法可以返回一個結(jié)果,并且可以拋出異常。通常情況下,我們可以通過實(shí)現(xiàn)Callable
接口來創(chuàng)建具有返回值的任務(wù),并將任務(wù)提交給線程池執(zhí)行。Callable
接口的定義如下:
public interface Callable<V> {V call() throws Exception;
}
Future類:
Future
類是一個接口,它代表了異步計算的結(jié)果。當(dāng)一個線程提交了一個Callable
任務(wù)到線程池后,線程池會返回一個Future
對象,通過該對象可以獲得任務(wù)的執(zhí)行結(jié)果。Future
接口定義了一些方法,用于查詢?nèi)蝿?wù)是否完成、獲取任務(wù)的執(zhí)行結(jié)果或者取消任務(wù)的執(zhí)行。常用的Future
實(shí)現(xiàn)類是FutureTask
。Future
接口的定義如下:
public interface Future<V> {boolean cancel(boolean mayInterruptIfRunning);boolean isCancelled();boolean isDone();V get() throws InterruptedException, ExecutionException;V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
}
通過Future
對象,我們可以在主線程中繼續(xù)執(zhí)行其他任務(wù),然后在需要獲取任務(wù)結(jié)果的地方調(diào)用get()
方法來等待任務(wù)執(zhí)行完成并獲取結(jié)果。如果任務(wù)還沒有執(zhí)行完成,get()
方法將會阻塞主線程,直到任務(wù)執(zhí)行完成并返回結(jié)果。如果不想等待任務(wù)執(zhí)行完成,可以使用isDone()
方法來判斷任務(wù)是否完成。
總結(jié):Callable
接口用于定義帶有返回值的任務(wù),Future
類用于獲取任務(wù)的執(zhí)行結(jié)果或者取消任務(wù)的執(zhí)行。通過這兩個接口,我們可以更好地管理多線程任務(wù),實(shí)現(xiàn)高效的并發(fā)編程。
偏向鎖 / 輕量級鎖 / 重量級鎖
偏向鎖、輕量級鎖、重量級鎖都是Java中用于優(yōu)化鎖的實(shí)現(xiàn),針對不同的鎖競爭場景,采用不同的策略來提高鎖的性能。
1. 偏向鎖(Biased Locking):
偏向鎖是Java中針對單線程訪問同步塊的優(yōu)化措施。它的設(shè)計思想是假設(shè)在大多數(shù)情況下,鎖只會被一個線程訪問,因此當(dāng)一個線程訪問同步塊時,會將對象頭中的標(biāo)記設(shè)置為偏向鎖,并記錄下持有偏向鎖的線程ID。這樣,下次這個線程再次訪問同步塊時,就無需再進(jìn)行加鎖操作,而是直接進(jìn)入臨界區(qū)。如果有其他線程嘗試競爭偏向鎖,偏向鎖就會升級為輕量級鎖。
2. 輕量級鎖(Lightweight Locking):
輕量級鎖是針對多個線程交替執(zhí)行同步塊的優(yōu)化措施。當(dāng)一個線程嘗試獲取鎖時,會先在棧幀中分配一塊用于存儲鎖記錄的空間,并將對象頭中的標(biāo)記設(shè)置為輕量級鎖。然后線程嘗試使用CAS(比較并交換)操作來嘗試獲取鎖,如果獲取成功,則進(jìn)入臨界區(qū)執(zhí)行。如果CAS操作失敗,說明有其他線程也在競爭鎖,這時輕量級鎖會膨脹為重量級鎖。
3. 重量級鎖(Heavyweight Locking):
重量級鎖是Java中默認(rèn)的鎖實(shí)現(xiàn),用于處理復(fù)雜的鎖競爭場景。當(dāng)一個線程嘗試獲取鎖時,會進(jìn)入重量級鎖的阻塞狀態(tài),操作系統(tǒng)會將該線程掛起,直到鎖被釋放,然后喚醒該線程。重量級鎖采用操作系統(tǒng)的原生鎖機(jī)制,會涉及用戶態(tài)和內(nèi)核態(tài)的切換,開銷較大,適用于競爭激烈的情況。
在鎖的優(yōu)化中,JVM會根據(jù)線程競爭的情況自動選擇偏向鎖、輕量級鎖或重量級鎖來提高鎖的性能。偏向鎖適用于只有一個線程訪問同步塊的情況,輕量級鎖適用于多個線程交替執(zhí)行同步塊的情況,而重量級鎖適用于競爭激烈的情況。這些優(yōu)化措施都是為了減少鎖的開銷,提高程序的并發(fā)性能。
synchronized 和 java.util.concurrent.lock.Lock 之間的區(qū)別
synchronized
和java.util.concurrent.lock.Lock
都是Java中用于實(shí)現(xiàn)線程同步的機(jī)制,但它們之間有一些重要的區(qū)別:
-
鎖的類型:
synchronized
是Java中的關(guān)鍵字,屬于內(nèi)置鎖(Intrinsic Lock),可以直接在方法或代碼塊中使用。每個Java對象都有一個內(nèi)置鎖,通過synchronized
關(guān)鍵字來獲取。java.util.concurrent.lock.Lock
是Java并發(fā)包中提供的接口,屬于顯式鎖(Explicit Lock)。它提供了更靈活的鎖定和釋放機(jī)制,并且可以有多個條件變量。
-
獲取鎖的方式:
synchronized
的獲取鎖是隱式的,在進(jìn)入synchronized
代碼塊或方法時自動獲取鎖,在退出代碼塊或方法時自動釋放鎖。java.util.concurrent.lock.Lock
的獲取鎖是顯式的,需要手動調(diào)用lock()
方法來獲取鎖,調(diào)用unlock()
方法來釋放鎖。這樣可以更精確地控制鎖的范圍和持有時間。
-
鎖的可中斷性:
synchronized
獲取鎖的過程是不可中斷的,即使其他線程嘗試中斷持有鎖的線程,也無法中斷。java.util.concurrent.lock.Lock
中的鎖可以通過lockInterruptibly()
方法實(shí)現(xiàn)可中斷的獲取鎖操作,當(dāng)其他線程中斷當(dāng)前線程時,可以中斷獲取鎖的過程。
-
鎖的公平性:
synchronized
鎖是非公平的,當(dāng)一個線程釋放鎖后,任何一個等待該鎖的線程都有機(jī)會獲取鎖。java.util.concurrent.lock.Lock
中的鎖可以是公平的,通過ReentrantLock
類的構(gòu)造函數(shù)可以指定是否使用公平鎖,默認(rèn)是非公平鎖。
-
靈活性和功能:
java.util.concurrent.lock.Lock
提供了更多的功能,例如可以嘗試獲取鎖、設(shè)定獲取鎖的超時時間、創(chuàng)建多個條件變量等,可以滿足更復(fù)雜的同步需求。synchronized
相對簡單,適用于一些簡單的同步需求,而且在JVM中使用synchronized
的優(yōu)化措施,如偏向鎖、輕量級鎖等,可以帶來一定的性能優(yōu)勢。
總體而言,對于簡單的同步需求,synchronized
是較為方便的選擇。而對于復(fù)雜的同步需求,或者需要更精細(xì)地控制鎖的行為,java.util.concurrent.lock.Lock
提供了更多的靈活性和功能,可以更好地滿足需求。
synchronized 和 java.util.concurrent.lock.Lock 之間的區(qū)別
synchronized
和java.util.concurrent.lock.Lock
都是Java中用于實(shí)現(xiàn)線程同步的機(jī)制,但它們之間有一些重要的區(qū)別:
-
鎖的類型:
synchronized
是Java中的關(guān)鍵字,屬于內(nèi)置鎖(Intrinsic Lock),可以直接在方法或代碼塊中使用。每個Java對象都有一個內(nèi)置鎖,通過synchronized
關(guān)鍵字來獲取。java.util.concurrent.lock.Lock
是Java并發(fā)包中提供的接口,屬于顯式鎖(Explicit Lock)。它提供了更靈活的鎖定和釋放機(jī)制,并且可以有多個條件變量。
-
獲取鎖的方式:
synchronized
的獲取鎖是隱式的,在進(jìn)入synchronized
代碼塊或方法時自動獲取鎖,在退出代碼塊或方法時自動釋放鎖。java.util.concurrent.lock.Lock
的獲取鎖是顯式的,需要手動調(diào)用lock()
方法來獲取鎖,調(diào)用unlock()
方法來釋放鎖。這樣可以更精確地控制鎖的范圍和持有時間。
-
鎖的可中斷性:
synchronized
獲取鎖的過程是不可中斷的,即使其他線程嘗試中斷持有鎖的線程,也無法中斷。java.util.concurrent.lock.Lock
中的鎖可以通過lockInterruptibly()
方法實(shí)現(xiàn)可中斷的獲取鎖操作,當(dāng)其他線程中斷當(dāng)前線程時,可以中斷獲取鎖的過程。
-
鎖的公平性:
synchronized
鎖是非公平的,當(dāng)一個線程釋放鎖后,任何一個等待該鎖的線程都有機(jī)會獲取鎖。java.util.concurrent.lock.Lock
中的鎖可以是公平的,通過ReentrantLock
類的構(gòu)造函數(shù)可以指定是否使用公平鎖,默認(rèn)是非公平鎖。
-
靈活性和功能:
java.util.concurrent.lock.Lock
提供了更多的功能,例如可以嘗試獲取鎖、設(shè)定獲取鎖的超時時間、創(chuàng)建多個條件變量等,可以滿足更復(fù)雜的同步需求。synchronized
相對簡單,適用于一些簡單的同步需求,而且在JVM中使用synchronized
的優(yōu)化措施,如偏向鎖、輕量級鎖等,可以帶來一定的性能優(yōu)勢。
總體而言,對于簡單的同步需求,synchronized
是較為方便的選擇。而對于復(fù)雜的同步需求,或者需要更精細(xì)地控制鎖的行為,java.util.concurrent.lock.Lock
提供了更多的靈活性和功能,可以更好地滿足需求。
java.util.concurrent.lock.Lock 與 java.util.concurrent.lock.ReadWriteLock 之間的區(qū)別
java.util.concurrent.lock.Lock
和java.util.concurrent.lock.ReadWriteLock
都是Java并發(fā)包中用于實(shí)現(xiàn)線程同步的接口,它們之間的區(qū)別主要在于鎖的類型和用途:
1. 鎖的類型:
-
java.util.concurrent.lock.Lock
是一種通用的鎖接口,用于實(shí)現(xiàn)基本的互斥鎖。它提供了兩個基本方法:lock()
用于獲取鎖,unlock()
用于釋放鎖。Lock
接口的實(shí)現(xiàn)類ReentrantLock
是一種可重入鎖,它允許同一個線程多次獲取同一個鎖,避免了死鎖的問題。 -
java.util.concurrent.lock.ReadWriteLock
是一種讀寫鎖接口,它可以讓多個線程同時讀取共享資源,但在寫操作時需要互斥。ReadWriteLock
接口提供了兩個鎖:讀鎖(讀共享鎖)和寫鎖(寫?yīng)氄兼i)。讀鎖可以被多個線程同時獲取,用于讀取共享資源;而寫鎖只能被一個線程獲取,用于修改共享資源。
2. 用途:
-
java.util.concurrent.lock.Lock
適用于一般性的互斥場景,它提供了靈活的鎖定和解鎖機(jī)制,可以手動控制鎖的獲取和釋放。它的實(shí)現(xiàn)類ReentrantLock
可以實(shí)現(xiàn)公平鎖或非公平鎖,也支持可中斷的獲取鎖操作。 -
java.util.concurrent.lock.ReadWriteLock
適用于讀多寫少的場景,它可以提高讀操作的并發(fā)性能。在讀多寫少的情況下,多個線程可以同時獲取讀鎖來讀取共享資源,而只有在寫操作時需要排他性,此時才需要獲取寫鎖。
綜上所述,Lock
接口適用于通用的互斥場景,提供了靈活的鎖定和解鎖機(jī)制;而ReadWriteLock
接口適用于讀多寫少的場景,可以提高讀操作的并發(fā)性能。在不同的場景下,可以根據(jù)需求選擇合適的鎖實(shí)現(xiàn)。
什么是死鎖?發(fā)生的條件,避免死鎖
死鎖發(fā)生的條件
互斥,共享資源只能被一個線程占用
占有且等待,線程 t1 已經(jīng)取得共享資源 s1,嘗試獲取共享資源 s2 的時候,不釋放共享資源 s1
不可搶占,其他線程不能強(qiáng)行搶占線程 t1 占有的資源 s1
循環(huán)等待,線程 t1 等待線程 t2 占有的資源,線程 t2 等待線程 t1 占有的資源
避免死鎖的方法
對于以上 4 個條件,只要破壞其中一個條件,就可以避免死鎖的發(fā)生。
對于第一個條件 “互斥” 是不能破壞的,因?yàn)榧渔i就是為了保證互斥。
其他三個條件,我們可以嘗試
一次性申請所有的資源,破壞 “占有且等待” 條件
占有部分資源的線程進(jìn)一步申請其他資源時,如果申請不到,主動釋放它占有的資源,破壞 “不可搶占” 條件
按序申請資源,破壞 “循環(huán)等待” 條件