1. 并行和并發(fā)有什么區(qū)別?
- 并行是指兩個或多個事件在同一時刻發(fā)生;而并發(fā)是指兩個或多個事件在同一時間間隔內(nèi)發(fā)生。
- 并行:同時做不同事情的能力(在多臺處理器上同時處理多個任務)
- 并發(fā):交替做不同事情的能力(在一臺處理器上“同時”處理多個任務,這個同時實際上是交替進行)
2.進程和線程的區(qū)別
- 進程是程序運行和資源分配的基本單位;線程是cpu調(diào)度和分派的基本單位。
- 內(nèi)存分配:系統(tǒng)在運行的時候會為每個進程分配不同的內(nèi)存空間;而對線程而言,除了CPU之外,系統(tǒng)不會為線程分配內(nèi)存,線程所使用的資源來自其所屬進程的資源,線程組之間只能共享資源。
- 開銷方面:每個進程都有獨立的代碼和數(shù)據(jù)空間(程序上下文),程序之間的切換會有較大的開銷;而多個線程共享資源(代碼和數(shù)據(jù)空間),減少切換次數(shù),效率高,開銷小。
- 包含關(guān)系:進程可以包含多個線程,同一個進程的多個線程并發(fā)執(zhí)行;沒有線程的進程可以看做單線程。
3.守護線程
- 守護線程(Daemon Thread):是個服務線程,準確來說是服務其他線程。
- 如果一個JVM里面的所有非daemon線程都退出了,JVM就會退出;而不管此時是否還有daemon線程在運行。只要非daemon在運行就不會退出JVM。
- 例子
(1)非守護線程
public class DaemonDemo {
public static void main(String[] args) {
WorkerThread thread = new WorkerThread();
System.out.println("work thread daemon:"+thread.isDaemon());
System.out.println("main thread daemon:"+Thread.currentThread().isDaemon());
thread.start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("main thread exit");
}
}
class WorkerThread extends Thread{
public WorkerThread(){
//false:非守護線程
//true:設(shè)為守護線程
setDaemon(false);
}
public void run(){
for (int i = 0; i < 10; i++) {
System.out.println(i);
try {
sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
運行結(jié)果:
work thread daemon:false
main thread daemon:false
0
1
2
main thread exit
3
4
5
6
7
8
9
等到非守護線程運行完畢,JVM才退出。
(2)守護線程
把上面例子進行修改,該為守護線程setDaemon(true);
運行結(jié)果:
work thread daemon:true
main thread daemon:false
0
1
2
main thread exit
不管守護線程是否運行完畢,JVM就退出。
4.創(chuàng)建線程的方式
- 繼承Thread類
- 定義Thread類的子類MyThread,并重寫run()方法,該run()方法的方法體就代表了線程要完成的任務。因此把run()方法稱為執(zhí)行體。
- 創(chuàng)建MyThread的實例,即創(chuàng)建了線程對象。
- 調(diào)用線程對象的start()方法來啟動該線程。
public class ThreadDemo {
public static void main(String[] args) {
new MyThread().start();
}
}
class MyThread extends Thread{
@Override
public void run() {
System.out.println("hello thread");
}
}
- 實現(xiàn)Runnable接口
- 定義Runnable接口的實現(xiàn)類MyThread02,并重寫此接口的run()方法,該方法是線程的執(zhí)行體。
- 創(chuàng)建MyThread02的實例,并依此實例作為Thread的target來創(chuàng)建Thread對象,該Thread對象才是真正的線程對象。
- 調(diào)用線程對象的start()方法來啟動該線程。
public class RunnableDemo {
public static void main(String[] args) {
MyThread02 myThread02 = new MyThread02();
new Thread(myThread02).start();
}
}
class MyThread02 implements Runnable{
@Override
public void run() {
System.out.println("hello runnable");
}
}
- 實現(xiàn)Callable接口
- 創(chuàng)建Callable接口的實現(xiàn)類MyThread03,并實現(xiàn)call()方法,該方法作為線程的執(zhí)行體,并且還有返回值。
- 創(chuàng)建MyThread03的實例,使用FutureTask類來包裝Callable對象,該FutureTask對象封裝了該Callable對象的call()方法的返回值。
- 調(diào)用FutureTask對象的get()方法來或得子線程執(zhí)行結(jié)束后的返回值。
- 使用FutureTask對象作為Thread對象的target,創(chuàng)建線程。
- 調(diào)用srart()方法,啟動線程。
public class CallableDemo {
public static void main(String[] args) {
MyThread03 thread03 = new MyThread03();
FutureTask<String> futureTask = new FutureTask<String>(thread03);
new Thread(futureTask).start();
try {
String s = futureTask.get();
System.out.println(s);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
class MyThread03 implements Callable{
@Override
public Object call() throws Exception {
return "hello callable";
}
}
5.Runnable和Callable的區(qū)別
- Runnable接口中的run()方法沒有返回值,它做的事情是純粹去執(zhí)行run()方法中的代碼。
- Callable接口中的call()方法是有返回值的,是一個泛型,和Future、FutureTask配合可以用來獲取異步執(zhí)行的結(jié)果。
6.線程有哪些狀態(tài)?
線程通常有五種狀態(tài):創(chuàng)建、就緒、運行、阻塞和死亡。
- 創(chuàng)建狀態(tài):new一個線程對象,并沒有調(diào)用該對象的start()方法,這就是線程處于創(chuàng)建狀態(tài)。
- 就緒狀態(tài):當調(diào)用了該線程對象的start()方法之后,該線程就進入就緒狀態(tài),開始去搶占CPU時間片。
- 運行狀態(tài):線程開始運行run()方法中的代碼,表明已經(jīng)搶占到了CPU時間片。
- 阻塞狀態(tài):一個線程在運行的過程中,受到某些操作的影響,放棄了已經(jīng)獲取到的CPU時間片,并且不再參與CPU時間片的爭搶。
- 死亡狀態(tài):對于已經(jīng)死亡的線程,無法再使用start()方法令其進入就緒狀態(tài)。
7.sleep()和wait()有什么區(qū)別?
- sleep():方法是線程類(Thread)的靜態(tài)方法。讓調(diào)用線程進入睡眠狀態(tài),讓出CPU給其他線程。等到休眠時間結(jié)束后,線程就進程就緒狀態(tài)跟其他線程一起搶占CPU時間片。
- wait():方法是Object類的方法,當一個線程執(zhí)行到wait()方法時,它就進入到一個和該對象相關(guān)的等待池,同時讓出CPU執(zhí)行權(quán)和占有的鎖,使得其他線程能夠訪問,可以通過notify(),notifyAll()來喚醒等待的線程。
8.notify()和notifyAll()有什么區(qū)別?
- 如果線程調(diào)用了對象的wait()方法,那么線程便會處于在該對象的等待池中,等待池中的線程不會去競爭該對象的鎖。
- notify():隨機喚醒一個wait線程
notifyAll():喚醒所有wait線程
被喚醒的線程就會進入該對象的鎖池中,鎖池中的線程就會去競爭該對象鎖。 - 優(yōu)先級高的線程競爭對象鎖的概率大,假設(shè)某線程沒有競爭到該對象鎖,它還會留在鎖池中。只有線程再次調(diào)用wait(),它才會重新回到等待池中。而競爭到對象鎖的線程繼續(xù)往下執(zhí)行,直到執(zhí)行完了synchronized代碼塊,它才會釋放該對象。這時鎖池中的線程會繼續(xù)競爭該對象鎖。
9.線程的run()和start()有什么區(qū)別?
- 每個線程都是通過某個特定Thread對象所對應的方法run()來完成其操作的。方法run()稱為線程體。通過調(diào)用Thread類的start()來啟動一個線程。
- start()啟動一個線程,真正實現(xiàn)了多線程運行,這時無需等待run()方法代碼執(zhí)行完畢,可以直接繼續(xù)執(zhí)行下面的代碼。這時此線程處于就緒狀態(tài),并沒有運行。然后通過Thread類調(diào)用run()來完成其運行狀態(tài),這里run()稱為線程體,run()方法結(jié)束,此線程終止,然后CPU調(diào)度到其他線程。
10.創(chuàng)建線程池的方式
- newFixedThreadPool(int nThreads)
創(chuàng)建一個固定長度的線程池,每當提交一個任務就創(chuàng)建一個線程,直到達到線程池的最大數(shù)量,這時線程規(guī)模將不再變化,當線程發(fā)生未預期的錯誤而結(jié)束時,線程池會補充一個新的線程。 - newCachedThreadPool()
創(chuàng)建一個可緩存的線程池,如果線程池的規(guī)模超過了處理需求,可靈活自動回收空閑線程,而當需求增加時,則可以自動添加新線程,線程池的規(guī)模不存在任何限制。 - newSingleThreadExecutor()
這是一個單線程的Executor,它創(chuàng)建單個工作線程來執(zhí)行任務,如果這個線程異常結(jié)束,會創(chuàng)建一個新的來替代它;它的特點是能確保依照任務在隊列中的順序來串行執(zhí)行。 - newScheduledThreadPool(int corePoolSize)
創(chuàng)建了一個固定長度的線程池,而且以延遲或定時的方式來執(zhí)行任務,類似于Timer。
11.線程池的狀態(tài):
線程池有5種狀態(tài):Running、ShutDown、Stop、Tidying、Terminated。
線程池各個狀態(tài)切換框架圖:
線程池狀態(tài)圖
12.線程池中submit()和execute()方法的區(qū)別
- 接收的參數(shù)不一樣
- submit有返回值,而execute()沒有
- submit方便Exception管理
13.在java程序中怎么保證多線程的運行安全?
- 原子性:提供互斥訪問,同一時刻只能有一個線程對數(shù)據(jù)進行操作(atomic,synchronized)
- 可見性:一個線程對主內(nèi)存的修改可以及時被其他線程看到(synchronized,volatile)
- 有序性:一個線程觀察其他線程中的指令執(zhí)行順序,由于指令重排序,該觀察結(jié)果一般雜亂無序。
14.多線程鎖的升級原理是什么?
在java中,鎖有4中狀態(tài),級別從低到高依次為:無狀態(tài)鎖,偏向鎖,輕量級鎖和重量級鎖狀態(tài),這幾個狀態(tài)會隨著競爭情況逐漸升級。鎖可以升級但不能降級。
鎖升級的圖示:
鎖升級圖
15.什么是死鎖?
死鎖是指兩個或兩個以上的進程在執(zhí)行過程中,互相持有對方所需要的資源,導致這些線程處于等待狀態(tài),若無外力作用,它們將無法推進下去。
16.怎么防止死鎖?
死鎖產(chǎn)生的四個必要條件:
- 互斥條件:某種資源一次只允許一個進程訪問,即該資源一旦分配給某個進程,其他進程就不能再訪問,直到該進程訪問結(jié)束。
- 請求和保持條件:一個進程本身占有資源(一種或多種),同時還有資源未得到滿足,正在等待其他進行釋放該資源。
- 不可剝奪條件:進程已或得的資源,在未使用之前,不可被剝奪,只能使用完后自己釋放
- 循環(huán)等待條件:指進程發(fā)生死鎖之后,若干個進程之間形成一種頭尾相接的循環(huán)等待資源關(guān)系。
這四個條件是死鎖的必要條件,只要系統(tǒng)發(fā)生死鎖,這些條件必然成立,而只要上述條件之一不滿足,就不會發(fā)生死鎖。
17.ThreadLocal是什么?有哪些使用場景?
線程局部變量是局限于線程內(nèi)部的變量,屬于線程自身所有,不在多個線程間共享。java提供ThreadLocal類來支持線程局部變量,是一種實現(xiàn)線程安全的方式。但是在管理環(huán)境(web服務器)使用線程局部變量的時候要特別小心,在這種情況下,工作線程的生命周期比任何應用變量的生命周期都要長。任何線程局部變量一旦在工作完后沒有釋放,java應用就存在內(nèi)存泄露的風險。
18.synchronized底層實現(xiàn)原理?
synchronized可以保證方法或者代碼塊在運行時,同一時刻只有一個方法進入到臨界區(qū),同時它還可以保證共享變量的內(nèi)存可見性。
java中的每一個對象都可以作為鎖,這是synchronized實現(xiàn)同步的基礎(chǔ):
- 普通同步方法,鎖的是當前實例對象
- 靜態(tài)同步方法,鎖的是當前類的class對象
- 同步方法塊,鎖的是括號里面的對象
19.synchronized和volatile的區(qū)別是什么?
- volatile本質(zhì)是在告訴jvm當前變量在寄存器(工作內(nèi)存)中的值是不確定的,需要從主存中讀??;synchronized則是鎖定當前變量,只有當前線程可以訪問該變量,其他線程被阻塞住。
- volatile只能使用在變量上;synchronized則可以使用在變量、方法和類上。
- volatile只能實現(xiàn)變量的修改可見性;而synchronized則可以保證變量修改的原子性和可見性。
- volatile不會造成線程阻塞;而sychronized可能會造成線程阻塞。
4.volatile標記的變量不會被編譯器優(yōu)化;synchronized標記的變量可以被編譯器優(yōu)化。
20. synchronized和Lock有什么區(qū)別?
- synchronized是java內(nèi)置關(guān)鍵字;在jvm層面,Lock是個java類。
- synchronized無法判斷是否獲取鎖的狀態(tài)。Lock可以判斷是否獲取到鎖。
- synchronized會自動釋放鎖(a線程執(zhí)行完同步代碼會釋放鎖;b線程執(zhí)行過程中出現(xiàn)異常會釋放鎖);Lock需在finally中手動釋放鎖(unlock()方法釋放鎖),否則容易造成線程死鎖。
- 用synchronized關(guān)鍵字的兩個線程1和線程2,如果當前線程1獲得鎖,線程2線程等待。如果線程1阻塞,線程2則會一直等待下去,而Lock鎖就不一定會等待下去,如果嘗試獲取不到鎖,線程可以不用一直等待就結(jié)束了。
- synchronized的鎖可重入、不可中斷、非公平,而Lock鎖可重入、可判斷、可公平(兩者皆可);
- Lock鎖適合大量同步的代碼的同步問題,synchronized鎖適合代碼少量的同步問題。
21. synchronized 和 ReentrantLock 區(qū)別是什么?
- synchronized是和if、else、for、while一樣的關(guān)鍵字,ReentrantLock是類,這是二者的本質(zhì)區(qū)別。
- 既然ReentrantLock是類,那么它就提供了比synchronized更多更靈活的特性,可以被繼承、可以有方法、可以有各種各樣的類變量,ReentrantLock比synchronized的擴展性體現(xiàn)在幾點上:
(1)ReentrantLock可以對獲取鎖的等待時間進行設(shè)置,這樣就避免了死鎖
(2)ReentrantLock可以獲取各種鎖的信息
(3)ReentrantLock可以靈活地實現(xiàn)多路通知 - 二者的鎖機制其實也是不一樣的:
ReentrantLock底層調(diào)用的是Unsafe的park方法加鎖;synchronized操作的應該是對象頭中mark word。
22.atomic的原理
- Atomic包中的類基本的特性就是在多線程環(huán)境下,當有多個線程同時對單個(包括基本類型及引用類型)變量進行操作時,具有排他性,即當多個線程同時對該變量的值進行更新時,僅有一個線程能成功,而未成功的線程可以向自旋鎖一樣,繼續(xù)嘗試,一直等到執(zhí)行成功。
- Atomic系列的類中的核心方法都會調(diào)用unsafe類中的幾個本地方法。我們需要先知道一個東西就是Unsafe類,全名為:sun.misc.Unsafe,這個類包含了大量的對C代碼的操作,包括很多直接內(nèi)存分配以及原子操作的調(diào)用,而它之所以標記為非安全的,是告訴你這個里面大量的方法調(diào)用都會存在安全隱患,需要小心使用,否則會導致嚴重的后果,例如在通過unsafe分配內(nèi)存的時候,如果自己指定某些區(qū)域可能會導致一些類似C++一樣的指針越界到其他進程的問題。