1. 什么是進程?線程?區別?
1)進程是一個獨立的運行環境,它可以被看作是一個程序或者一個應用。而線程是在進程中執行的一個任務。eg:打開360安全衛士,它本身是一個程序,也是一個進程,它里面有殺毒,清理垃圾,電腦加速等功能,當你點擊殺毒的時候,殺毒任務就相當於一個線程。
2)進程是操作系統進行資源分配的基本單位,而線程是操作系統進行調度的基本單位。
3)進程讓操作系統的並發性成為可能,而線程讓進程的內部並發成為可能。
2. 什么叫線程安全?
一個類或者程序所提供的接口對於線程來說是原子操作或者多個線程之間的切換不會導致該接口的執行結果存在二義性,也就是說我們不用考慮同步的問題。
出現線程不安全的原因是什么?
如果我們創建的多個線程,存在着共享數據,那么就有可能出現線程的安全問題:當其中一個線程操作共享數據時,還未操作完成,另外的線程就參與進來,導致對共享數據的操作出現問題。
線程不安全解決辦法?
要求一個線程操作共享數據時,只有當其完成操作共享數據,其它線程才有機會執行共享數據。java提供了兩種方式來實現同步互斥訪問:synchronized和Lock。
3. 創建線程的方式?區別?
1)繼承Thread類;
2)實現Runnable接口;
3)實現Callable接口;
4) 通過線程池創建線程;
區別
Java中,類僅支持單繼承,如果一個類繼承了Thread類,就無法再繼承其它類,因此,如果一個類既要繼承其它的類,又必須創建為一個線程,就可以使用實現Runable接口的方式。
使用實現Runable接口的方式創建的線程可以處理同一資源,實現資源的共享。
使用實現Callable接口的方式創建的線程,可以獲取到線程執行的返回值、是否執行完成等信息。
4. Java中Runnable和Callable有什么不同?
Runnable和Callable都是創建線程的方式。Runnable從JDK1.0開始就有了,Callable是在JDK1.5增加的。
(1)實現Callable接口的任務線程能返回執行結果;而實現Runnable接口的任務線程不能返回結果;
(2)Callable接口的call()方法允許拋出異常;而Runnable接口的run()方法的異常只能在內部消化,不能繼續上拋;
5. Thread 類中的start() 和run() 方法有什么區別?
介紹說明
(1) start() :它的作用是啟動一個新線程。通過start()方法來啟動的新線程,處於就緒(可運行)狀態,並沒有運行,一旦得到cpu時間片,就開始執行相應線程的run()方法,這里方法run()稱為線程體,它包含了要執行的這個線程的內容,run方法運行結束,此線程隨即終止。
(2) run():就和普通的成員方法一樣,可以被重復調用。如果直接調用run方法,並不會啟動新線程!程序中依然只有主線程這一個線程,其程序執行路徑還是只有一條,還是要順序執行。
start和run區別
(1) start() 可以啟動一個新線程,run()不能。
(2) start()不能被重復調用,run()可以。
(3)start()中的run代碼可以不執行完就繼續執行下面的代碼,即進行了線程切換。直接調用run方法必須等待其代碼全部執行完才能繼續執行下面的代碼。
(4)start() 實現了多線程,run()沒有實現多線程。
6. Java多線程中調用wait() 和 sleep()方法有什么不同?
sleep()和wait()都是使線程暫停執行一段時間的方法。二者區別為:
1)原理不同(面試時可不答,偏重2,3)
sleep()方法是Thread類的靜態方法,是線程用來控制自身流程的。
而wait()方法是Object類的方法,用於線程間的通信。
2)對鎖的處理機制不同
調用wait()的時候方法會釋放當前持有的鎖,而sleep方法不會釋放鎖。
3)使用地方不同
sleep方法則可以放在任何地方使用,而wait()方法必須放在同步方法或者同步代碼塊中使用。
sleep()方法必須捕獲異常,而wait()、notify()、notifyAll()不需要捕獲異常。
推薦:由於sleep不會釋放鎖標志,容易導致死鎖問題的發生,一般情況下,不推薦使用sleep()方法,而推薦使用wait()方法。
7.介紹下CAS
CAS(Compare and Swap),比較與交換,實現並發算法時常用到的一種技術,是Java保證原子性的一種重要方法,也是一種樂觀鎖的實現方式。CAS有3個參數,內存值V,舊的預期值A,要修改的新值B。當且僅當預期值A和內存值V相同時,將內存值V修改為B,否則什么都不做。
缺點:ABA問題
說說對於 synchronized 關鍵字的了解?
synchronized關鍵字解決的是多個線程之間訪問資源的同步性;
synchronized 關鍵字可以保證被它修飾的方法或者代碼塊在任意時刻只能有一個線程執行。
synchronized保證了對變量操作的可見性,原子性和有序性。
synchronized 的使用方式有三種:
(1)修飾同步代碼塊
(2)修飾非靜態(實例)的方法
(3)修飾靜態的方法
eg;雙重校驗鎖實現單例模式使用到了synchronized
Lock和synchronized的區別
(1)synchronized是Java中的關鍵字,在JVM層面,而Lock是一個接口;
(2)synchronized會自動釋放線程占有的鎖,而Lock需要主動通過unLock()去釋放鎖,否則可能造成死鎖現象。
(3)使用synchronized時,等待的線程會一直等待下去,不能夠響應中斷,而Lock可以讓等待鎖的線程響應中斷;
(4)通過Lock可以判斷鎖狀態,即是否成功獲取鎖,而synchronized無法判斷。
(5)Lock可以提高多個線程進行讀操作的效率。
說明:在性能上來說,如果競爭資源不激烈,兩者的性能是差不多的,而當競爭資源非常激烈時(即有大量線程同時競爭),此時Lock的性能要遠遠優於synchronized。所以說,在具體使用時要根據適當情況。
synchronized和Lock底層實現?
synchronized用的鎖是存在java對象里的,通過對代碼反編譯,可以看出被synchronized修飾的代碼塊,在執行之前先使用monitorenter指令加鎖,然后在執行結束之后再使用monitorexit指令釋放鎖資源,在整個執行期間此代碼都是鎖定的狀態,這就是典型悲觀鎖的實現流程。
lock鎖使用的是CAS和volatile來實現同步的,CAS使用硬件命令實現緩存一致性保證了原子性,volatile保證了可見性,多線程環境下所有的線程通過CAS進行競爭資源,只能有一個成功,其它的都會自旋。
在多線程中,什么是上下文切換?
上下文切換是存儲和恢復CPU狀態的過程,它使得線程執行能夠從中斷點恢復執行。是多任務操作系統和多線程環境的基本特征。
並發編程三要素
(1) 原子性:程序中的所有操作是不可中斷的,要么全部執行成功要么全部執行失敗。
(2) 有序性:程序執行的順序按照代碼的先后順序執行。(處理器可能會對指令進行重排序)
(3) 可見性:當多個線程訪問同一個變量時,如果其中一個線程對其作了修改,其他線程能立即獲取到最新的值。
Java中堆和棧有什么不同?(相對於線程來說)
棧是一塊和線程緊密相關的內存區域。每個線程都有自己的棧內存,用於存儲本地變量,方法參數和棧調用,一個線程中存儲的變量對其它線程是不可見的。
堆是所有線程共享的一片公用內存區域。對象都在堆里創建,為了提升效率線程會從堆中弄一個緩存到自己的棧,如果多個線程使用該變量就可能引發問題,這時volatile 變量就可以發揮作用了,它要求線程從主存中讀取變量的值。
什么是線程池? 為什么要使用它?
線程池(thread pool):一種線程使用模式。
創建線程要花費資源和時間,如果任務來了才創建線程那么響應時間會變長,而且一個進程能創建的線程數有限。為了避免這些問題,在程序啟動的時候就創建若干線程來響應處理,它們被稱為線程池,里面的線程叫工作線程。
線程池的好處:
(1)通過重用線程池中的線程,來減少每個線程創建和銷毀的性能開銷。
(2)對線程進行一些維護和管理,比如定時開始,周期執行,並發數控制等等。
什么是ThreadLocal?
ThreadLocal用於創建線程的本地變量,我們知道一個對象的所有線程共享它的全局變量,所以這些變量是非線程安全的,我們可以使用同步技術。但是當我們不想使用同步的時候,我們可以選擇ThreadLocal變量。每個線程都會擁有他們自己的Thread變量,它們可以使用get()/set()方法去獲取他們的默認值或者在線程內部改變他們的值。
死鎖是什么?如何避免死鎖?
死鎖是指兩個或兩個以上的進程在執行過程中,因爭奪資源而造成的一種互相等待的現象,若無外力作用,它們都將無法推進下去。這是一個嚴重的問題,因為死鎖會讓你的程序掛起無法完成任務,死鎖的發生必須滿足以下四個條件:
1)互斥條件:一個資源每次只能被一個進程使用。
2)請求與保持條件:一個進程因請求資源而阻塞時,對已獲得的資源保持不放。
3)不剝奪條件:進程已獲得的資源,在末使用完之前,不能強行剝奪。
4)循環等待條件:若干進程之間形成一種頭尾相接的循環等待資源關系。
避免死鎖最簡單的方法就是阻止循環等待條件,將系統中所有的資源設置標志位、排序,規定所有的進程申請資源必須以一定的順序(升序或降序)做操作來避免死鎖。
Thread類中的yield方法有什么作用?
Thread.yield() 方法會使當前線程從運行狀態變為就緒狀態,把運行機會讓給其它相同優先級的線程。它是一個靜態的原生(native)方法而且只保證當前線程放棄CPU占用而不能保證使其它線程一定能占用CPU,執行yield()的線程有可能會被再次繼續執行的。
Java中notify 和 notifyAll有什么區別?
調用notify時,只有一個等待線程會被喚醒而且它不能保證哪個線程會被喚醒,這取決於線程調度器。雖然如果你調用notifyAll方法,那么等待該鎖的所有線程都會被喚醒。
Java中interrupted 和 isInterruptedd方法的區別?
interrupted() 和 isInterrupted()的主要區別是前者會將中斷狀態清除而后者不會。
Java多線程的中斷機制是用內部標識來實現的,調用Thread.interrupt()來中斷一個線程就會設置中斷標識為true。當中斷線程調用靜態方法Thread.interrupted()來檢查中斷狀態時,中斷狀態會被清零。而非靜態方法isInterrupted()用來查詢其它線程的中斷狀態且不會改變中斷狀態標識。簡單的說就是任何拋出InterruptedException異常的方法都會將中斷狀態清零。無論如何,一個線程的中斷狀態有有可能被其它線程調用中斷來改變。
有三個線程T1,T2,T3,怎么確保它們按順序執行?
在多線程中有多種方法讓線程按特定順序執行,你可以用線程類的join()方法在一個線程中啟動另一個線程,另外一個線程完成該線程繼續執行。為了確保三個線程的順序你應該先啟動最后一個(T3調用T2,T2調用T1),這樣T1就會先完成而T3最后完成。
如何創建守護線程?
使用Thread類的setDaemon(true)方法可以將線程設置為守護線程,需要注意的是,需要在調用start()方法前調用這個方法,否則會拋出IllegalThreadStateException異常。
/** * @author liao.wenhui * @date 2019/7/15 15:13 */ public class DaemonThread { public static void main(String[] args) { Thread daemonThread = new Thread(new Runnable() { @Override public void run() { } }); //設置守護線程 daemonThread.setDaemon(true); daemonThread.start(); } }
前提知識:
守護進程(Daemon)是運行在后台的一種特殊進程。它獨立於控制終端並且周期性地執行某種任務或等待處理某些發生的事件(百度百科)。
Java線程分為兩類分別為daemon線程(守護線程)和User線程(用戶線程),在JVM啟動時候會調用main函數,main函數所在的線程是一個用戶線程,這個是我們可以看到的線程,其實JVM內部同時還啟動了好多守護線程,比如垃圾回收線程。那么守護線程和用戶線程有什么區別那?區別之一是當最后一個非守護線程結束時候,JVM會正常退出,而不管當前是否有守護線程,也就是說守護線程是否結束並不影響JVM的退出。言外之意是只要有一個用戶線程還沒結束正常情況下JVM就不會退出。
什么是線程調度器(Thread Scheduler)和時間分片(Time Slicing)?
線程調度器是一個操作系統服務,它負責為Runnable狀態的線程分配CPU時間。一旦我們創建一個線程並啟動它,它的執行便依賴於線程調度器的實現。
時間分片是指將可用的CPU時間分配給可用的Runnable線程的過程。分配CPU時間可以基於線程優先級或者線程等待的時間。線程調度並不受到Java虛擬機控制,所以由應用程序來控制它是更好的選擇(即最好不要讓你的程序依賴於線程的優先級)。
Java線程池中submit() 和 execute()方法有什么區別?
兩個方法都可以向線程池提交任務,execute()方法的返回類型是void,它定義在Executor接口中, 而submit()方法可以返回持有計算結果的Future對象,它定義在ExecutorService接口中,它擴展了Executor接口,其它線程池類像ThreadPoolExecutor和ScheduledThreadPoolExecutor都有這些方法。
什么是FutureTask?
在Java並發程序中FutureTask表示一個可以取消的異步運算。它有啟動和取消運算、查詢運算是否完成和取回運算結果等方法。只有當運算完成的時候結果才能取回,如果運算尚未完成get方法將會阻塞。一個FutureTask對象可以對調用了Callable和Runnable的對象進行包裝,由於FutureTask也是調用了Runnable接口所以它可以提交給Executor來執行。