java多线程写在哪一层_面试知识点三:Java多线程
35.并行和并發(fā)有什么區(qū)別?
36.線程和進(jìn)程的區(qū)別?
37.守護(hù)線程是什么?
38.創(chuàng)建線程有哪幾種方式?
39.說一下 runnable 和 callable 有什么區(qū)別?
40.線程有哪些狀態(tài)?
41.sleep() 和 wait() 有什么區(qū)別?
42.notify()和 notifyAll()有什么區(qū)別?
43.線程的 run()和 start()有什么區(qū)別?
44.創(chuàng)建線程池有哪幾種方式?
45.線程池都有哪些狀態(tài)?
46.線程池中 submit()和 execute()方法有什么區(qū)別?
47.在 java 程序中怎么保證多線程的運行安全?
48.多線程鎖的升級原理是什么?
49.什么是死鎖?
50.怎么防止死鎖?
51.ThreadLocal 是什么?有哪些使用場景?
52.說一下 synchronized 底層實現(xiàn)原理?
53.synchronized 和 volatile 的區(qū)別是什么?
54.synchronized 和 Lock 有什么區(qū)別?
55.synchronized 和 ReentrantLock 區(qū)別是什么?
56.說一下 atomic 的原理?
35.并發(fā)和并行有什么區(qū)別?
1、并發(fā)(concurrency):指在同一時刻只能有一條指令執(zhí)行,但多個進(jìn)程指令被快速的輪換執(zhí)行,使得在宏觀上具有多個進(jìn)程同時執(zhí)行的效果,但在微觀上并不是同時執(zhí)行的,只是把時間分成若干片,使多個進(jìn)程快速交替的執(zhí)行。
如上圖所示,并發(fā)就是只有一個CPU資源,程序(或線程)之間要競爭得到執(zhí)行機(jī)會。圖中的第一個階段,在A執(zhí)行的過程中,B、C不會執(zhí)行,因為這段時間內(nèi)這個CPU資源被A競爭到了,同理,第二階段只有B在執(zhí)行,第三階段只有C在執(zhí)行。其實,并發(fā)過程中,A、B、C并不是同時進(jìn)行的(微觀角度),但又是同時進(jìn)行的(宏觀角度)。
2、并行(parallellism):指在同一時刻,有多條指令在多個處理器上同時執(zhí)行
如圖所示,在同一時刻,ABC都是同時執(zhí)行(微觀、宏觀)
通過多線程實現(xiàn)并發(fā),并行:
? java中的Thread類定義了多線程,通過多線程可以實現(xiàn)并發(fā)或并行。
? 在CPU比較繁忙,資源不足的時候(開啟了很多進(jìn)程),操作系統(tǒng)只為一個含有多線程的進(jìn)程分配僅有的CPU資源,這些線程就會為自己盡量多搶時間片,這就是通過多線程實現(xiàn)并發(fā),線程之間會競爭CPU資源爭取執(zhí)行機(jī)會。
? 在CPU資源比較充足的時候,一個進(jìn)程內(nèi)的多線程,可以被分配到不同的CPU資源,這就是通過多線程實現(xiàn)并行。
? 至于多線程實現(xiàn)的是并發(fā)還是并行?上面所說,所寫多線程可能被分配到一個CPU內(nèi)核中執(zhí)行,也可能被分配到不同CPU執(zhí)行,分配過程是操作系統(tǒng)所為,不可人為控制。所以,如果有人問我我所寫的多線程是并發(fā)還是并行的?我會說,都有可能。
? 不管并發(fā)還是并行,都提高了程序?qū)PU資源的利用率,最大限度地利用CPU資源
36.線程和進(jìn)程的區(qū)別?
線程具有許多傳統(tǒng)進(jìn)程所具有的特征,故又稱為輕型進(jìn)程(Light—Weight Process)或進(jìn)程元;而把傳統(tǒng)的進(jìn)程稱為重型進(jìn)程(Heavy—Weight Process),它相當(dāng)于只有一個線程的任務(wù)。在引入了線程的操作系統(tǒng)中,通常一個進(jìn)程都有若干個線程,至少需要一個線程。下面,我們從調(diào)度、并發(fā)性、 系統(tǒng)開銷、擁有資源等方面,來比較線程與進(jìn)程。
1.調(diào)度
在傳統(tǒng)的操作系統(tǒng)中,擁有資源的基本單位和獨立調(diào)度、分派的基本單位都是進(jìn)程。而在引入線程的操作系統(tǒng)中,則把線程作為調(diào)度和分派的基本單位。而把進(jìn)程作為資源擁有的基本單位,使傳統(tǒng)進(jìn)程的兩個屬性分開,線程便能輕裝運行,從而可顯著地提高系統(tǒng)的并發(fā)程度。在同一進(jìn)程中,線程的切換不會引起進(jìn)程的切換,在由一個進(jìn)程中的線程切換到另一個進(jìn)程中的線程時,將會引起進(jìn)程的切換。
2.并發(fā)性
在引入線程的操作系統(tǒng)中,不僅進(jìn)程之間可以并發(fā)執(zhí)行,而且在一個進(jìn)程中的多個線程之間,亦可并發(fā)執(zhí)行,因而使操作系統(tǒng)具有更好的并發(fā)性,從而能更有效地使用系統(tǒng)資源和提高系統(tǒng)吞吐量。例如,在一個未引入線程的單CPU操作系統(tǒng)中,若僅設(shè)置一個文件服務(wù)進(jìn)程,當(dāng)它由于某種原因而被阻塞時,便沒有其它的文件服務(wù)進(jìn)程來提供服務(wù)。在引入了線程的操作系統(tǒng)中,可以在一個文件服務(wù)進(jìn)程中,設(shè)置多個服務(wù)線程,當(dāng)?shù)谝粋€線程等待時,文件服務(wù)進(jìn)程中的第二個線程可以繼續(xù)運行;當(dāng)?shù)诙€線程阻塞時,第三個線程可以繼續(xù)執(zhí)行,從而顯著地提高了文件服務(wù)的質(zhì)量以及系統(tǒng)吞吐量。
3.擁有資源
不論是傳統(tǒng)的操作系統(tǒng),還是設(shè)有線程的操作系統(tǒng),進(jìn)程都是擁有資源的一個獨立單位,它可以擁有自己的資源。一般地說,線程自己不擁有系統(tǒng)資源(也有一點必不可少的資源),但它可以訪問其隸屬進(jìn)程的資源。亦即,一個進(jìn)程的代碼段、數(shù)據(jù)段以及系統(tǒng)資源,如已打開的文件、I/O設(shè)備等,可供同一進(jìn)程的其它所有線程共享。
4.系統(tǒng)開銷
由于在創(chuàng)建或撤消進(jìn)程時,系統(tǒng)都要為之分配或回收資源,如內(nèi)存空間、I/O設(shè)備等。因此,操作系統(tǒng)所付出的開銷將顯著地大于在創(chuàng)建或撤消線程時的開銷。類似地,在進(jìn)行進(jìn)程切換時,涉及到整個當(dāng)前進(jìn)程CPU環(huán)境的保存以及新被調(diào)度運行的進(jìn)程的CPU環(huán)境的設(shè)置。而線程切換只須保存和設(shè)置少量寄存器的內(nèi)容,并不涉及存儲器管理方面的操作。可見,進(jìn)程切換的開銷也遠(yuǎn)大于線程切換的開銷。此外,由于同一進(jìn)程中的多個線程具有相同的地址空間,致使它們之間的同步和通信的實現(xiàn),也變得比較容易。在有的系統(tǒng)中,線程的切換、同步和通信都無須操作系統(tǒng)內(nèi)核的干預(yù) 。
37.守護(hù)線程是什么?
守護(hù)線程(即daemon thread),是個服務(wù)線程,準(zhǔn)確地來說就是服務(wù)其他的線程,這是它的作用。而其他的線程只有一種,那就是用戶線程。所以java里線程分2種,
1、守護(hù)線程,比如垃圾回收線程,就是最典型的守護(hù)線程。
2、用戶線程,就是應(yīng)用程序里的自定義線程。
守護(hù)線程,專門用于服務(wù)其他的線程,如果其他的線程(即用戶自定義線程)都執(zhí)行完畢,連main線程也執(zhí)行完畢,那么jvm就會退出(即停止運行)——此時,連jvm都停止運行了,守護(hù)線程當(dāng)然也就停止執(zhí)行了。再換一種說法,如果有用戶自定義線程存在的話,jvm就不會退出——此時,守護(hù)線程也不能退出,也就是它還要運行,干嘛呢,就是為了執(zhí)行垃圾回收的任務(wù)啊。
用戶也可以在應(yīng)用程序代碼自定義守護(hù)線程,只需要調(diào)用Thread類的設(shè)置方法setDaemon(boolean)設(shè)置一下即可,舉例:
public class Thread01 extendsThread {
@Overridepublic voidrun() {for (int i = 0; i < 100; i++) {
System.out.println(i);
}
}
}
測試一下,將thread設(shè)置成守護(hù)線程:
public classtest {public static voidmain(String[] args) {
Thread thread= newThread01();
thread.setDaemon(true);
thread.start();
}
}
結(jié)果可能是如下一種,也可能什么都不打印
0
1
2
3
thread被設(shè)置成守護(hù)線程,那么用戶線程是main線程,當(dāng)main線程執(zhí)行完了之后,JVM退出,守護(hù)線程thread也就不再執(zhí)行。
38.創(chuàng)建線程有哪幾種方式?
三種
1、繼承Thread類,重寫父類的run()方法
2、實現(xiàn)Runnable接口,重寫run()方法,通過其實現(xiàn)類使用Thread
3、實現(xiàn)Callable接口,重寫call()方法,通過Runnable實現(xiàn)類使用Thread
39.說一下 runnable 和 callable 有什么區(qū)別?
1、Runnable沒有返回值;Callable可以返回執(zhí)行結(jié)果,是個泛型,和Future、FutureTask配合可以用來獲取異步執(zhí)行的結(jié)果
2、Callable接口的call()方法允許拋出異常;Runnable的run()方法異常只能在內(nèi)部消化,不能往上繼續(xù)拋
注:Callalbe接口支持返回執(zhí)行結(jié)果,需要調(diào)用FutureTask.get()得到,此方法會阻塞主進(jìn)程的繼續(xù)往下執(zhí)行,如果不調(diào)用不會阻塞。
Runnable接口:
public interfaceRunnable {public abstract voidrun();
}
Callable接口:
public interface Callable{
V call()throwsException;
}
40.線程有哪些狀態(tài)?
虛擬機(jī)中的線程狀態(tài)有六種,定義在Thread.State中:
public enumState {/*** Thread state for a thread which has not yet started.*/NEW,/*** Thread state for a runnable thread. A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.*/RUNNABLE,/*** Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@linkObject#wait() Object.wait}.*/BLOCKED,/*** Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
*
*
{@linkObject#wait() Object.wait} with no timeout*
{@link#join() Thread.join} with no timeout*
{@linkLockSupport#park() LockSupport.park}*
*
*
A thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* For example, a thread that has called Object.wait()
* on an object is waiting for another thread to call
* Object.notify() or Object.notifyAll() on
* that object. A thread that has called Thread.join()
* is waiting for a specified thread to terminate.*/WAITING,/*** Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
*
*
{@link#sleep Thread.sleep}*
{@linkObject#wait(long) Object.wait} with timeout*
{@link#join(long) Thread.join} with timeout*
{@linkLockSupport#parkNanos LockSupport.parkNanos}*
{@linkLockSupport#parkUntil LockSupport.parkUntil}*
*/TIMED_WAITING,/*** Thread state for a terminated thread.* The thread has completed execution.*/TERMINATED;
}
1、New:新建狀態(tài)
當(dāng)線程實例被new出來之后,調(diào)用start()方法之前,線程實例處于新建狀態(tài)。比如"Thread thread = new Thread()",thread就是一個處于NEW狀態(tài)的線程。
2、Runnable:可運行狀態(tài)
new出來線程,調(diào)用start()方法即處于Runnable狀態(tài)了。處于Runnable狀態(tài)的線程可能正在Java虛擬機(jī)中運行,也可能正在等待處理器的資源,因為一個線程必須獲得CPU的資源后,才可以運行其run()方法中的內(nèi)容,否則排隊等待。
3、Blocked:阻塞狀態(tài)
如果某一線程正在等待監(jiān)視器鎖,以便進(jìn)入一個同步的塊/方法,那么這個線程的狀態(tài)就是阻塞Bloked。
4、Waiting:等待狀態(tài)
某一線程因為調(diào)用不帶超時的Object的wait()方法、不帶超時的Thread的join()方法、LockSupport的park()方法,就會處于等待Waiting狀態(tài),等待被其它線程喚醒。
5、Timed_Waiting:超時等待狀態(tài)
某一線程因為調(diào)用帶有指定正等待時間(即傳入時間參數(shù))的Object的wait()方法、Thread的join()方法、Thread的sleep()方法、LockSupport的parkNanos()方法、LockSupport的parkUntil()方法,就會處于超時等待Timed_Waiting狀態(tài)。
6、Terminated:中止?fàn)顟B(tài)
線程調(diào)用終止或者run()方法執(zhí)行結(jié)束后,線程即處于終止?fàn)顟B(tài)。處于終止?fàn)顟B(tài)的線程不具備繼續(xù)運行的能力。
線程的轉(zhuǎn)換狀態(tài)
上面也提到了,某一時間點線程的狀態(tài)只能是上述6個狀態(tài)中的其中一個;但是,線程在程序運行過程中的狀態(tài)是會發(fā)生變化的,由一個狀態(tài)轉(zhuǎn)變?yōu)榱硪粋€狀態(tài),那么下面給出線程狀態(tài)轉(zhuǎn)換圖幫助我們清晰地理解線程的狀態(tài)轉(zhuǎn)變過程:
41.sleep() 和 wait() 有什么區(qū)別?
1、這兩個方法來自不同的類,sleep來自Thread類,而wait來自O(shè)bject類。
2、sleep方法沒有釋放鎖,而wait方法釋放了鎖,使得其他線程可以使用同步控制塊或者方法。
3、使用范圍:wait,notify和notifyAll只能在同步控制方法或者同步控制塊里面使用,而sleep可以在任何地方使用
42.notify()和 notifyAll()有什么區(qū)別?
假設(shè)一個線程A調(diào)用了某個對象的wait()方法,線程A就會釋放該對象的鎖(因為wait()方法必須出現(xiàn)在synchronized中,這樣自然在執(zhí)行wait()方法之前線程A就已經(jīng)擁有了該對象的鎖),同時線程A就進(jìn)入到了該對象的等待池中。
如果另外的一個線程調(diào)用了相同對象的notifyAll()方法,那么處于該對象的等待池中的線程就會全部進(jìn)入該對象的鎖池中,準(zhǔn)備爭奪鎖的擁有權(quán)。
如果另外的一個線程調(diào)用了相同對象的notify()方法,那么僅僅有一個處于該對象的等待池中的線程(隨機(jī))會進(jìn)入該對象的鎖池.
43.線程的 run()和 start()有什么區(qū)別?
線程通過調(diào)用start方法啟動,然后執(zhí)行run()方法中的內(nèi)容,使用start方法才真正實現(xiàn)了多線程運行,因為這個時候不用等待我們的run方法執(zhí)行完成就可以繼續(xù)執(zhí)行下面的代碼,這才叫多線程嘛!
直接使用thread執(zhí)行run方法呢?因為run方法是thread里面的一個普通的方法,所以我們直接調(diào)用run方法,這個時候它是會運行在我們的主線程中的,因為這個時候我們的程序中只有主線程一個線程,那么他們的執(zhí)行順序一定是順序執(zhí)行,所以這樣并沒有做到多線程的這種目的。
44.創(chuàng)建線程池有哪幾種方式?
1、newSingleThreadExecutor()
創(chuàng)建一個單線程化的Executor,即只創(chuàng)建唯一的工作線程來執(zhí)行任務(wù),它只會用唯一的工作線程來執(zhí)行任務(wù),保證所有任務(wù)按照指定順序(FIFO, LIFO,優(yōu)先級)執(zhí)行。如果這個線程異常結(jié)束,會有另一個取代它,保證順序執(zhí)行。單工作線程最大的特點是可保證順序地執(zhí)行各個任務(wù),并且在任意給定的時間不會有多個線程是活動的。
2、newFixedThreadPool(int nThreads)
創(chuàng)建一個指定工作線程數(shù)量的線程池。每當(dāng)提交一個任務(wù)就創(chuàng)建一個工作線程,如果工作線程數(shù)量達(dá)到線程池初始的最大數(shù),則將提交的任務(wù)存入到池隊列中。FixedThreadPool是一個典型且優(yōu)秀的線程池,它具有線程池提高程序效率和節(jié)省創(chuàng)建線程時所耗的開銷的優(yōu)點。但是,在線程池空閑時,即線程池中沒有可運行任務(wù)時,它不會釋放工作線程,還會占用一定的系統(tǒng)資源。
3、newCachedThreadPool()
創(chuàng)建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閑線程,若無可回收,則新建線程。
這種類型的線程池特點是:
工作線程的創(chuàng)建數(shù)量幾乎沒有限制(其實也有限制的,數(shù)目為Interger. MAX_VALUE),?這樣可靈活的往線程池中添加線程。
如果長時間沒有往線程池中提交任務(wù),即如果工作線程空閑了指定的時間(默認(rèn)為1分鐘),則該工作線程將自動終止。終止后,如果你又提交了新的任務(wù),則線程池重新創(chuàng)建一個工作線程。
在使用CachedThreadPool時,一定要注意控制任務(wù)的數(shù)量,否則,由于大量線程同時運行,很有會造成系統(tǒng)癱瘓。
4、newScheduledThreadPool(int corePoolSize)
創(chuàng)建一個定長的線程池,而且支持定時的以及周期性的任務(wù)執(zhí)行
45.線程池都有哪些狀態(tài)?
線程池的五種狀態(tài):
1、RUNNING
(1) 狀態(tài)說明:線程池處在RUNNING狀態(tài)時,能夠接收新任務(wù),以及對已添加的任務(wù)進(jìn)行處理。
(02) 狀態(tài)切換:線程池的初始化狀態(tài)是RUNNING。換句話說,線程池被一旦被創(chuàng)建,就處于RUNNING狀態(tài),并且線程池中的任務(wù)數(shù)為0!
2、 SHUTDOWN
(1) 狀態(tài)說明:線程池處在SHUTDOWN狀態(tài)時,不接收新任務(wù),但能處理已添加的任務(wù)。
(2) 狀態(tài)切換:調(diào)用線程池的shutdown()接口時,線程池由RUNNING -> SHUTDOWN。
3、STOP
(1) 狀態(tài)說明:線程池處在STOP狀態(tài)時,不接收新任務(wù),不處理已添加的任務(wù),并且會中斷正在處理的任務(wù)。
(2) 狀態(tài)切換:調(diào)用線程池的shutdownNow()接口時,線程池由(RUNNING or SHUTDOWN ) -> STOP。
4、TIDYING
(1) 狀態(tài)說明:當(dāng)所有的任務(wù)已終止,ctl記錄的”workerCount”為0,線程池會變?yōu)門IDYING狀態(tài)。當(dāng)線程池變?yōu)門IDYING狀態(tài)時,會執(zhí)行鉤子函數(shù)terminated()。terminated()在ThreadPoolExecutor類中是空的,若用戶想在線程池變?yōu)門IDYING時,進(jìn)行相應(yīng)的處理;可以通過重載terminated()函數(shù)來實現(xiàn)。
(2) 狀態(tài)切換:當(dāng)線程池在SHUTDOWN狀態(tài)下,阻塞隊列為空并且線程池中執(zhí)行的任務(wù)也為空時,就會由 SHUTDOWN -> TIDYING。
當(dāng)線程池在STOP狀態(tài)下,線程池中執(zhí)行的任務(wù)為空時,就會由STOP -> TIDYING。
5、 TERMINATED
(1) 狀態(tài)說明:線程池徹底終止,就變成TERMINATED狀態(tài)。
(2) 狀態(tài)切換:線程池處在TIDYING狀態(tài)時,執(zhí)行完terminated()之后,就會由 TIDYING -> TERMINATED。
46.線程池中 submit()和 execute()方法有什么區(qū)別?
先看一下ExecutorService接口定義的三種submit方法
1、Future submit(Callable task)
public classtest {public static voidmain(String[] args) {
ExecutorService executorService= Executors.newFixedThreadPool(5);
List> list = new ArrayList<>();for (int i = 0; i < 5; i++) {
Future submit = executorService.submit(new Callable() {
@Overridepublic String call() throwsException {return "call方法返回字符串";
}
});
list.add(submit);
}try{
Thread.sleep(200);
}catch(InterruptedException e) {
e.printStackTrace();
}
System.out.println("返回結(jié)果list的size====" +list.size());for (FuturestringFuture : list) {try{
String s=stringFuture.get();
System.out.println("返回結(jié)果====" +s);
}catch(InterruptedException e) {
e.printStackTrace();
}catch(ExecutionException e) {
e.printStackTrace();
}
}
executorService.shutdown();
}
}
結(jié)果:
返回結(jié)果list的size====5返回結(jié)果====call方法返回字符串
返回結(jié)果====call方法返回字符串
返回結(jié)果====call方法返回字符串
返回結(jié)果====call方法返回字符串
返回結(jié)果====call方法返回字符串
可以看到,Callable接口定義的call()方法是有返回值的,所以可以通過submit(Callable task)的結(jié)果submit.get()獲取返回值。
2、Future> submit(Runnable task);
public classtest {public static voidmain(String[] args) {
ExecutorService executorService= Executors.newFixedThreadPool(5);
List> list = new ArrayList<>();for (int i = 0; i < 5; i++) {final int num =i;
Future> submit = executorService.submit(newRunnable() {
@Overridepublic voidrun() {
System.out.println(num);
}
});
list.add(submit);
}try{
Thread.sleep(200);
}catch(InterruptedException e) {
e.printStackTrace();
}
System.out.println("返回結(jié)果list的size====" +list.size());for (Future>future : list) {boolean done =future.isDone();
System.out.println("執(zhí)行是否成功===" +done);
}
executorService.shutdown();
}
}
結(jié)果:
1
2
0
4
3返回結(jié)果list的size====5執(zhí)行是否成功===true執(zhí)行是否成功===true執(zhí)行是否成功===true執(zhí)行是否成功===true執(zhí)行是否成功===true
可以看到,Runnable接口定義的run()方法是沒有返回值的,但是可以通過submit(Callable task)的結(jié)果submit.isDone()判斷是否執(zhí)行成功。
3、Future submit(Runnable task, T result)
public classtest {public static voidmain(String[] args) {
ExecutorService executorService= Executors.newFixedThreadPool(5);
List> list = new ArrayList<>();for (int i = 0; i < 5; i++) {final int num =i;
String s= "runnable的返回值";
Future submit = executorService.submit(newRunnable() {
@Overridepublic voidrun() {
System.out.println(num);
}
}, s);
list.add(submit);
}try{
Thread.sleep(200);
}catch(InterruptedException e) {
e.printStackTrace();
}
System.out.println("返回結(jié)果list的size====" +list.size());for (Futurefuture : list) {
String s= null;try{
s=future.get();
System.out.println(s);
}catch(InterruptedException e) {
e.printStackTrace();
}catch(ExecutionException e) {
e.printStackTrace();
}
}
executorService.shutdown();
}
}
結(jié)果:
0
2
4
3
1返回結(jié)果list的size====5runnable的返回值
runnable的返回值
runnable的返回值
runnable的返回值
runnable的返回值
可以看到,Runnable接口定義的run()方法是沒有返回值的,但是submit()方法的第二個參數(shù)可以定義返回值。
所以,submit()和 execute()方法有什么區(qū)別?
1、submit()方法是否返回值的,executor()方法是沒有返回值的。
2、submit在執(zhí)行過程中與execute不一樣,不會拋出異常而是把異常保存在成員變量中,在FutureTask.get獲取的時候再把異常拋出來。
3、execute直接拋出異常之后線程就死掉了,submit保存異常線程沒有死掉,因此execute的線程池可能會出現(xiàn)沒有意義的情況,因為線程沒有得到重用。而submit不會出現(xiàn)這種情況。
47.在 java 程序中怎么保證多線程的運行安全?
線程安全在三個方面體現(xiàn)
1.原子性:提供互斥訪問,同一時刻只能有一個線程對數(shù)據(jù)進(jìn)行操作,(synchronized);
synchronized是一種同步鎖,通過鎖實現(xiàn)原子操作。
JDK提供鎖分兩種:一種是synchronized,依賴JVM實現(xiàn)鎖,因此在這個關(guān)鍵字作用對象的作用范圍內(nèi)是同一時刻只能有一個線程進(jìn)行操作;另一種是LOCK,是JDK提供的代碼層面的鎖,依賴CPU指令,代表性的是ReentrantLock。
2.可見性:一個線程對主內(nèi)存的修改可以及時地被其他線程看到,(volatile);
volatile的可見性是通過內(nèi)存屏障和禁止重排序?qū)崿F(xiàn)的
volatile會在寫操作時,會在寫操作后加一條store屏障指令,將本地內(nèi)存中的共享變量值刷新到主內(nèi)存。
volatile在進(jìn)行讀操作時,會在讀操作前加一條load指令,從內(nèi)存中讀取共享變量。
3.有序性:一個線程觀察其他線程中的指令執(zhí)行順序,由于指令重排序,該觀察結(jié)果一般雜亂無序,(happens-before原則)。
48.多線程鎖的升級原理是什么?
在所有的鎖都啟用的情況下線程進(jìn)入臨界區(qū)時會先去獲取偏向鎖,如果已經(jīng)存在偏向鎖了,則會嘗試獲取輕量級鎖,啟用自旋鎖,如果自旋也沒有獲取到鎖,則使用重量級鎖,沒有獲取到鎖的線程阻塞掛起,直到持有鎖的線程執(zhí)行完同步塊;
偏向鎖是在無鎖爭用的情況下使用的,也就是在當(dāng)前線程沒有執(zhí)行完之前,沒有其它線程會執(zhí)行該同步塊,一旦有了第二個線程的爭用,偏向鎖就會升級為輕量級鎖,如果輕量級鎖自旋到達(dá)閾值后,沒有獲取到鎖,就會升級為重量級鎖;
如果線程爭用激烈,那么應(yīng)該禁用偏向鎖。
49.什么是死鎖?
簡單的說,死鎖就是線程1已經(jīng)持有鎖A,要去獲取鎖B,線程2已經(jīng)持有鎖B,要去獲取鎖A,即兩個線程都在等待獲取對方持有的鎖。
圖示:
50.怎么防止死鎖?
1、讓程序每次至多只能獲得一個鎖。當(dāng)然,在多線程環(huán)境下,這種情況通常并不現(xiàn)實
2、設(shè)計時考慮清楚鎖的順序,盡量減少嵌在的加鎖交互數(shù)量
3、既然死鎖的產(chǎn)生是兩個線程無限等待對方持有的鎖,那么只要等待時間有個上限不就好了。當(dāng)然synchronized不具備這個功能,但是我們可以使用Lock類中的tryLock方法去嘗試獲取鎖,這個方法可以指定一個超時時限,在等待超過該時限之后便會返回一個失敗信息。
51.ThreadLocal 是什么?有哪些使用場景?
ThreadLocal,很多地方叫做線程本地變量,也有些地方叫做線程本地存儲,其實意思差不多。ThreadLocal為變量在每個線程中都創(chuàng)建了一個副本,那么每個線程可以訪問自己內(nèi)部的副本變量。
ThreadLocal是如何為每個線程創(chuàng)建變量的副本的:
首先,在每個線程Thread內(nèi)部有一個ThreadLocal.ThreadLocalMap類型的成員變量threadLocals,這個threadLocals就是用來存儲實際的變量副本的,鍵值為當(dāng)前ThreadLocal變量,value為變量副本(即T類型的變量)。
初始時,在Thread里面,threadLocals為空,當(dāng)通過ThreadLocal變量調(diào)用get()方法或者set()方法,就會對Thread類中的threadLocals進(jìn)行初始化,并且以當(dāng)前ThreadLocal變量為鍵值,以ThreadLocal要保存的副本變量為value,存到threadLocals。
然后在當(dāng)前線程里面,如果要使用副本變量,就可以通過get方法在threadLocals里面查找。
最常見的ThreadLocal使用場景為 用來解決 數(shù)據(jù)庫連接、Session管理等。
private static ThreadLocalconnectionHolder= new ThreadLocal() {publicConnection initialValue() {returnDriverManager.getConnection(DB_URL);
}
};public staticConnection getConnection() {returnconnectionHolder.get();
}
private static final ThreadLocal threadSession = newThreadLocal();public static Session getSession() throwsInfrastructureException {
Session s=(Session) threadSession.get();try{if (s == null) {
s=getSessionFactory().openSession();
threadSession.set(s);
}
}catch(HibernateException ex) {throw newInfrastructureException(ex);
}returns;
}
52.說一下 synchronized 底層實現(xiàn)原理?
從字節(jié)碼中可知同步語句塊的實現(xiàn)使用的是monitorenter 和 monitorexit 指令,其中monitorenter指令指向同步代碼塊的開始位置,monitorexit指令則指明同步代碼塊的結(jié)束位置,當(dāng)執(zhí)行monitorenter指令時,當(dāng)前線程將試圖獲取 objectref(即對象鎖) 所對應(yīng)的 monitor 的持有權(quán),當(dāng) objectref 的 monitor 的進(jìn)入計數(shù)器為 0,那線程可以成功取得 monitor,并將計數(shù)器值設(shè)置為 1,取鎖成功。如果當(dāng)前線程已經(jīng)擁有 objectref 的 monitor 的持有權(quán),那它可以重入這個 monitor (關(guān)于重入性稍后會分析),重入時計數(shù)器的值也會加 1。倘若其他線程已經(jīng)擁有 objectref 的 monitor 的所有權(quán),那當(dāng)前線程將被阻塞,直到正在執(zhí)行線程執(zhí)行完畢,即monitorexit指令被執(zhí)行,執(zhí)行線程將釋放 monitor(鎖)并設(shè)置計數(shù)器值為0 ,其他線程將有機(jī)會持有 monitor 。
53.synchronized 和 volatile 的區(qū)別是什么?
volatile本質(zhì)是在告訴jvm當(dāng)前變量在寄存器(工作內(nèi)存)中的值是不確定的,需要從主存中讀取; synchronized則是鎖定當(dāng)前變量,只有當(dāng)前線程可以訪問該變量,其他線程被阻塞住。
volatile僅能使用在變量級別;synchronized則可以使用在變量、方法、和類級別的
volatile僅能實現(xiàn)變量的修改可見性,不能保證原子性;而synchronized則可以保證變量的修改可見性和原子性
volatile不會造成線程的阻塞;synchronized可能會造成線程的阻塞。
volatile標(biāo)記的變量不會被編譯器優(yōu)化;synchronized標(biāo)記的變量可以被編譯器優(yōu)化
54.synchronized 和 Lock 有什么區(qū)別?
1)Lock是一個接口,而synchronized是Java中的關(guān)鍵字,synchronized是內(nèi)置的語言實現(xiàn);
2)synchronized在發(fā)生異常時,會自動釋放線程占有的鎖,因此不會導(dǎo)致死鎖現(xiàn)象發(fā)生;而Lock在發(fā)生異常時,如果沒有主動通過unLock()去釋放鎖,則很可能造成死鎖現(xiàn)象,因此使用Lock時需要在finally塊中釋放鎖;
3)Lock可以讓等待鎖的線程響應(yīng)中斷,而synchronized卻不行,使用synchronized時,等待的線程會一直等待下去,不能夠響應(yīng)中斷;
4)通過Lock可以知道有沒有成功獲取鎖,而synchronized卻無法辦到。
5)Lock可以提高多個線程進(jìn)行讀操作的效率。
55.synchronized 和 ReentrantLock 區(qū)別是什么?
兩者的共同點:
1)協(xié)調(diào)多線程對共享對象、變量的訪問
2)可重入,同一線程可以多次獲得同一個鎖
3)都保證了可見性和互斥性
兩者的不同點:
1)ReentrantLock顯示獲得、釋放鎖,synchronized隱式獲得釋放鎖
2)ReentrantLock可響應(yīng)中斷、可輪回,synchronized是不可以響應(yīng)中斷的,為處理鎖的不可用性提供了更高的靈活性
3)ReentrantLock是API級別的,synchronized是JVM級別的
4)ReentrantLock可以實現(xiàn)公平鎖
5)ReentrantLock通過Condition可以綁定多個條件
6)底層實現(xiàn)不一樣, synchronized是同步阻塞,使用的是悲觀并發(fā)策略,lock是同步非阻塞,采用的是樂觀并發(fā)策略
56.說一下 atomic 的原理?
以AtomicInteger為例
public class AtomicInteger extends Number implementsjava.io.Serializable {private static final long serialVersionUID = 6214790243416807050L;//setup to use Unsafe.compareAndSwapInt for updates
private static final Unsafe unsafe =Unsafe.getUnsafe();private static final longvalueOffset;static{try{
valueOffset=unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
}catch (Exception ex) { throw newError(ex); }
}private volatile intvalue;
}
從 AtomicInteger 的內(nèi)部屬性可以看出,它依賴于Unsafe 提供的一些底層能力,進(jìn)行底層操作;如根據(jù)valueOffset代表的該變量值在內(nèi)存中的偏移地址,從而獲取數(shù)據(jù)的。
變量value用volatile修飾,保證了多線程之間的內(nèi)存可見性。
下面以getAndIncrement為例,說明其原子操作過程
/*** Atomically increments by one the current value.
*
*@returnthe previous value*/
public final intgetAndIncrement() {return unsafe.getAndAddInt(this, valueOffset, 1);
}
public final int getAndAddInt(Object var1, long var2, intvar4) {intvar5;do{
var5= this.getIntVolatile(var1, var2);
}while(!this.compareAndSwapInt(var1, var2, var5, var5 +var4));returnvar5;
}
假設(shè)線程1和線程2通過getIntVolatile拿到value的值都為1,線程1被掛起,線程2繼續(xù)執(zhí)行
線程2在compareAndSwapInt操作中由于預(yù)期值和內(nèi)存值都為1,因此成功將內(nèi)存值更新為2
線程1繼續(xù)執(zhí)行,在compareAndSwapInt操作中,預(yù)期值是1,而當(dāng)前的內(nèi)存值為2,CAS操作失敗,什么都不做,返回false
線程1重新通過getIntVolatile拿到最新的value為2,再進(jìn)行一次compareAndSwapInt操作,這次操作成功,內(nèi)存值更新為3
如何保證原子性:自旋 +?CAS(樂觀鎖)。在這個過程中,通過compareAndSwapInt比較更新value值,如果更新失敗,重新獲取,然后更新。
CAS是什么?
CAS是英文單詞CompareAndSwap的縮寫,中文意思是:比較并替換。CAS需要有3個操作數(shù):內(nèi)存地址V,舊的預(yù)期值A(chǔ),即將要更新的目標(biāo)值B。
CAS指令執(zhí)行時,當(dāng)且僅當(dāng)內(nèi)存地址V的值與預(yù)期值A(chǔ)相等時,將內(nèi)存地址V的值修改為B,否則就什么都不做。整個比較并替換的操作是一個原子操作。
總結(jié)
以上是生活随笔為你收集整理的java多线程写在哪一层_面试知识点三:Java多线程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java通用日志记录_JAVA实现通用日
- 下一篇: mysql主从异步复制_centos7m