目錄
- 1.并行和并發(fā)有什么區(qū)別吨艇?
- 2.線程和進程的區(qū)別与斤?
- 3.創(chuàng)建線程有哪幾種方式网持?
- 4.為什么需要定一個類去實現(xiàn)Runnable接口呢画切?繼承Thread類和實現(xiàn)Runnable接口有啥區(qū)別呢竣稽?
- 5.runnable和callable的區(qū)別
- 6.守護線程是什么?
- 7.線程有哪些狀態(tài)霍弹?
- 8.sleep() 和 wait() 有什么區(qū)別毫别?
- 9.notify()和 notifyAll()有什么區(qū)別?
- 10.創(chuàng)建線程池有哪幾種方式庞萍?
- 11.線程池都有哪些狀態(tài)拧烦?
- 12.線程池中 submit() 和 execute() 方法有什么區(qū)別?
- 13.在 Java 程序中怎么保證多線程的運行安全钝计?
- 14.線程同步的方式
- 15.Lock
- 16.死鎖
- 17.解決死鎖的方法
- 18.synchronized 和 Lock 有什么區(qū)別?
- 19.synchronized 和 ReentrantLock 區(qū)別是什么齐佳?
1.并行和并發(fā)有什么區(qū)別私恬?
- 并行:多個處理器或多核處理器同時處理多個任務。
- 并發(fā):多個任務在同一個 CPU 核上炼吴,按細分的時間片輪流(交替)執(zhí)行本鸣,從邏輯上來看那些任務是同時執(zhí)行。
2.線程和進程的區(qū)別硅蹦?
進程:進程指正在運行的程序荣德。
線程:線程是進程中的一個執(zhí)行單元,負責當前進程中程序的執(zhí)行童芹,一個進程中至少有一個線程涮瞻。同一個進程中的多個線程之間可以并發(fā)的執(zhí)行。
3.創(chuàng)建線程有哪幾種方式假褪?
創(chuàng)建線程有三種方式:
- 繼承 Thread 重寫 run 方法署咽;
- 實現(xiàn) Runnable 接口;
- 實現(xiàn) Callable 接口生音。
4.為什么需要定一個類去實現(xiàn)Runnable接口呢宁否?繼承Thread類和實現(xiàn)Runnable接口有啥區(qū)別呢?
實現(xiàn)Runnable接口缀遍,避免了繼承Thread類的單繼承局限性慕匠。覆蓋Runnable接口中的run方法,將線程任務代碼定義到run方法中域醇。
創(chuàng)建Thread類的對象台谊,只有創(chuàng)建Thread類的對象才可以創(chuàng)建線程蓉媳。線程任務已被封裝到Runnable接口的run方法中,而這個run方法所屬于Runnable接口的子類對象青伤,所以將這個子類對象作為參數(shù)傳遞給Thread的構(gòu)造函數(shù)督怜,這樣,線程對象創(chuàng)建時就可以明確要運行的線程的任務狠角。
第二種方式實現(xiàn)Runnable接口避免了單繼承的局限性号杠,所以較為常用。實現(xiàn)Runnable接口的方式丰歌,更加的符合面向?qū)ο笠腆€程分為兩部分,一部分線程對象立帖,一部分線程任務眼溶。繼承Thread類,線程對象和線程任務耦合在一起晓勇。一旦創(chuàng)建Thread類的子類對象堂飞,既是線程對象,有又有線程任務绑咱。實現(xiàn)runnable接口绰筛,將線程任務單獨分離出來封裝成對象,類型就是Runnable接口類型描融。Runnable接口對線程對象和線程任務進行解耦铝噩。
采用繼承Thread類創(chuàng)建線程的優(yōu)點是編寫簡單,如果需要訪問當前線程窿克,無需使用Thread.currentThread()方法骏庸,直接使用this即可獲得當前線程。缺點在于由于該線程類已經(jīng)繼承了Thead類年叮,所以不能再繼承其他的類具被。
而采用實現(xiàn)runnable接口,其優(yōu)點是線程類只是實現(xiàn)了runnable接口谋右,還可以繼承其他的類硬猫。同時,可以多個線程共享同一個目標對象或者資源改执,所以非常適合多個線程處理同一個資源的情況啸蜜。但是這樣的方法也有點缺點,如果需要訪問當前線程辈挂,必須使用Thread.currentThread()方法衬横。
5.runnable和callable的區(qū)別
5.1相同點
都是接口
都可以編寫多線程程序
都采用Thread.start()啟動線程
5.2不同點
Runnable沒有返回值;Callable可以返回執(zhí)行結(jié)果终蒂,是個泛型蜂林,和Future遥诉、FutureTask配合可以用來獲取異步執(zhí)行的結(jié)果
Callable接口的call()方法允許拋出異常;Runnable的run()方法異常只能在內(nèi)部消化噪叙,不能往上繼續(xù)拋
注:Callalbe接口支持返回執(zhí)行結(jié)果矮锈,需要調(diào)用FutureTask.get()得到,此方法會阻塞主進程的繼續(xù)往下執(zhí)行睁蕾,如果不調(diào)用不會阻塞苞笨。
6.守護線程是什么?
守護線程是運行在后臺的一種特殊進程子眶。它獨立于控制終端并且周期性地執(zhí)行某種任務或等待處理某些發(fā)生的事件瀑凝。在 Java 中垃圾回收線程就是特殊的守護線程。
7.線程有哪些狀態(tài)臭杰?
線程的狀態(tài):
- NEW 尚未啟動
- RUNNABLE 正在執(zhí)行中
- BLOCKED 阻塞的(被同步鎖或者IO鎖阻塞)
- WAITING 永久等待狀態(tài)
- TIMED_WAITING 等待指定的時間重新被喚醒的狀態(tài)
- TERMINATED 執(zhí)行完成
8.sleep() 和 wait() 有什么區(qū)別粤咪?
- 類的不同:sleep() 來自 Thread,wait() 來自 Object渴杆。
- 釋放鎖:sleep() 不釋放鎖寥枝;wait() 釋放鎖。
- 用法不同:sleep() 時間到會自動恢復磁奖;wait() 可以使用 notify()/notifyAll()直接喚醒脉顿。
9.notify()和 notifyAll()有什么區(qū)別?
notifyAll()會喚醒所有的線程点寥,notify()之后喚醒一個線程。notifyAll() 調(diào)用后来吩,會將全部線程由等待池移到鎖池敢辩,然后參與鎖的競爭,競爭成功則繼續(xù)執(zhí)行弟疆,如果不成功則留在鎖池等待鎖被釋放后再次參與競爭戚长。而 notify()只會喚醒一個線程,具體喚醒哪一個線程由虛擬機控制怠苔。
10.創(chuàng)建線程池有哪幾種方式同廉?
線程池創(chuàng)建有七種方式,最核心的是最后一種:
- newSingleThreadExecutor():它的特點在于工作線程數(shù)目被限制為 1柑司,操作一個無界的工作隊列迫肖,所以它保證了所有任務的都是被順序執(zhí)行,最多會有一個任務處于活動狀態(tài)攒驰,并且不允許使用者改動線程池實例蟆湖,因此可以避免其改變線程數(shù)目;
- newCachedThreadPool():它是一種用來處理大量短時間工作任務的線程池玻粪,具有幾個鮮明特點:它會試圖緩存線程并重用隅津,當無緩存線程可用時诬垂,就會創(chuàng)建新的工作線程;如果線程閑置的時間超過 60 秒伦仍,則被終止并移出緩存结窘;長時間閑置時,這種線程池充蓝,不會消耗什么資源隧枫。其內(nèi)部使用 SynchronousQueue 作為工作隊列;
- newFixedThreadPool(int nThreads):重用指定數(shù)目(nThreads)的線程棺克,其背后使用的是無界的工作隊列悠垛,任何時候最多有 nThreads 個工作線程是活動的。這意味著娜谊,如果任務數(shù)量超過了活動隊列數(shù)目确买,將在工作隊列中等待空閑線程出現(xiàn);如果有工作線程退出纱皆,將會有新的工作線程被創(chuàng)建湾趾,以補足指定的數(shù)目 nThreads;
- newSingleThreadScheduledExecutor():創(chuàng)建單線程池派草,返回 ScheduledExecutorService搀缠,可以進行定時或周期性的工作調(diào)度;
- newScheduledThreadPool(int corePoolSize):和newSingleThreadScheduledExecutor()類似近迁,創(chuàng)建的是個 ScheduledExecutorService艺普,可以進行定時或周期性的工作調(diào)度,區(qū)別在于單一工作線程還是多個工作線程鉴竭;
- newWorkStealingPool(int parallelism):這是一個經(jīng)常被人忽略的線程池歧譬,Java 8 才加入這個創(chuàng)建方法,其內(nèi)部會構(gòu)建ForkJoinPool搏存,利用Work-Stealing算法瑰步,并行地處理任務,不保證處理順序璧眠;
- ThreadPoolExecutor():是最原始的線程池創(chuàng)建缩焦,上面1-3創(chuàng)建方式都是對ThreadPoolExecutor的封裝。
11.線程池都有哪些狀態(tài)责静?
- RUNNING:這是最正常的狀態(tài)袁滥,接受新的任務,處理等待隊列中的任務泰演。
- SHUTDOWN:不接受新的任務提交呻拌,但是會繼續(xù)處理等待隊列中的任務。
- STOP:不接受新的任務提交睦焕,不再處理等待隊列中的任務藐握,中斷正在執(zhí)行任務的線程靴拱。
- TIDYING:所有的任務都銷毀了,workCount 為 0猾普,線程池的狀態(tài)在轉(zhuǎn)換為 TIDYING 狀態(tài)時袜炕,會執(zhí)行鉤子方法 terminated()。
- TERMINATED:terminated()方法結(jié)束后初家,線程池的狀態(tài)就會變成這個偎窘。
12.線程池中 submit() 和 execute() 方法有什么區(qū)別?
- execute():只能執(zhí)行 Runnable 類型的任務溜在。
- submit():可以執(zhí)行 Runnable 和 Callable 類型的任務陌知。
13.在 Java 程序中怎么保證多線程的運行安全?
- 方法一:使用安全類掖肋,比如 Java. util. concurrent 下的類仆葡。
- 方法二:使用自動鎖 synchronized。
- 方法三:使用手動鎖 Lock志笼。
Lock lock = new ReentrantLock();
lock. lock();
try {
System. out. println("獲得鎖");
} catch (Exception e) {
// TODO: handle exception
} finally {
System. out. println("釋放鎖");
lock. unlock();
}
14.線程同步的方式
線程同步的方式有兩種:
- 方式1:同步代碼塊
- 方式2:同步方法
同步代碼塊: 在代碼塊聲明上 加上synchronized
synchronized (鎖對象) {
可能會產(chǎn)生線程安全問題的代碼
}
同步代碼塊中的鎖對象可以是任意的對象沿盅;但多個線程時,要使用同一個鎖對象才能夠保證線程安全纫溃。
同步方法:在方法聲明上加上synchronized
public synchronized void method(){
可能會產(chǎn)生線程安全問題的代碼
}
同步方法中的鎖對象是 this
15.Lock
查閱Lock接口描述腰涧,Lock
實現(xiàn)提供了比使用 synchronized
方法和語句可獲得的更廣泛的鎖定操作。
Lock提供了一個更加面對對象的鎖紊浩,在該鎖中提供了更多的操作鎖的功能窖铡。
我們使用Lock接口,以及其中的lock()方法和unlock()方法替代同步坊谁,對電影院賣票案例中Ticket類進行如下代碼修改:
package cn.jxufe.java.chapter10.demo06;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Test01ThreadSafty {
public static void main(String[] args) {
// TODO Auto-generated method stub
Ticket mt = new Ticket();
Thread th1 = new Thread(mt);
Thread th2 = new Thread(mt);
Thread th3 = new Thread(mt);
th1.start();
th2.start();
th3.start();
}
}
/*
* 使用JDK1.5 的接口Lock,替換同步代碼塊,實現(xiàn)線程的安全性
* Lock接口方法:
* lock() 獲取鎖
* unlock()釋放鎖
* 實現(xiàn)類ReentrantLock
*/
class Ticket implements Runnable {
private static int ticket = 10;
//在類的成員位置,創(chuàng)建Lock接口的實現(xiàn)類對象
private Lock l = new ReentrantLock();
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i < 20; i++) {
//調(diào)用Lock接口方法lock獲取鎖
l.lock();
if (ticket > 0) {
try {
Thread.sleep(20);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "賣票:還剩下" + ticket-- + "張票");
}
//釋放鎖,調(diào)用Lock接口方法unlock
l.unlock();
}
}
}
16.死鎖
是指兩個或兩個以上的進程在執(zhí)行過程中,因爭奪資源而造成的一種互相等待的現(xiàn)象,若無外力作用,它們都將無法推進下去
(1) 因為系統(tǒng)資源不足万伤。
(2) 進程運行推進順序不合適。
(3) 資源分配不當?shù)取?/p>
當線程 A 持有獨占鎖a呜袁,并嘗試去獲取獨占鎖 b 的同時,線程 B 持有獨占鎖 b简珠,并嘗試獲取獨占鎖 a 的情況下阶界,就會發(fā)生 AB 兩個線程由于互相持有對方需要的鎖,而發(fā)生的阻塞現(xiàn)象聋庵,我們稱為死鎖膘融。
死鎖的四個必要條件:
(1)** 互斥條件:一個資源每次只能被一個進程使用。
(2) 請求與保持條件:一個進程因請求資源而阻塞時祭玉,對已獲得的資源保持不放氧映。
(3) 不剝奪條件:**進程已獲得的資源,在末使用完之前脱货,不能強行剝奪岛都。
(4) 循環(huán)等待條件:若干進程之間形成一種頭尾相接的循環(huán)等待資源關系律姨。
17.解決死鎖的方法
- 盡量使用 tryLock(long timeout, TimeUnit unit)的方法(ReentrantLock、ReentrantReadWriteLock)臼疫,設置超時時間择份,超時可以退出防止死鎖。
- 盡量使用 Java. util. concurrent 并發(fā)類代替自己手寫鎖烫堤。
- 盡量降低鎖的使用粒度荣赶,盡量不要幾個功能用同一把鎖。
- 盡量減少同步的代碼塊鸽斟。
18.synchronized 和 Lock 有什么區(qū)別拔创?
- synchronized 可以給類、方法富蓄、代碼塊加鎖剩燥;而 lock 只能給代碼塊加鎖。
- synchronized 不需要手動獲取鎖和釋放鎖格粪,使用簡單躏吊,發(fā)生異常會自動釋放鎖,不會造成死鎖帐萎;而 lock 需要自己加鎖和釋放鎖比伏,如果使用不當沒有 unLock()去釋放鎖就會造成死鎖。
- 通過 Lock 可以知道有沒有成功獲取鎖疆导,而 synchronized 卻無法辦到赁项。
19.synchronized 和 ReentrantLock 區(qū)別是什么?
synchronized 早期的實現(xiàn)比較低效澈段,對比 ReentrantLock悠菜,大多數(shù)場景性能都相差較大,但是在 Java 6 中對 synchronized 進行了非常多的改進败富。
主要區(qū)別如下:
- ReentrantLock 使用起來比較靈活悔醋,但是必須有釋放鎖的配合動作;
- ReentrantLock 必須手動獲取與釋放鎖兽叮,而 synchronized 不需要手動釋放和開啟鎖芬骄;
- ReentrantLock 只適用于代碼塊鎖,而 synchronized 可用于修飾方法鹦聪、代碼塊等账阻。
- volatile 標記的變量不會被編譯器優(yōu)化;synchronized 標記的變量可以被編譯器優(yōu)化泽本。
Note:
本文內(nèi)容都是來源網(wǎng)上淘太,參考原文為java面試