1、在 java 中守護線程和本地線程區別?
java 中的線程分為兩種:守護線程(Daemon)和用戶線程(User)
任何線程都可以設置為守護線程和用戶線程,通過方法 Thread.setDaemon(boolon);true 則把該線程設置為守護線程,反之則為用戶線程。Thread.setDaemon()必須在 Thread.start()之前調用,否則運行時會拋出異常。
兩者的區別:
唯一的區別是判斷虛擬機(JVM)何時離開,Daemon 是為其他線程提供服務,如果全部的 User Thread 已經撤離,Daemon 沒有可服務的線程,JVM 撤離。也可以理解為守護線程是 JVM 自動創建的線程(但不一定),用戶線程是程序創建的線程;比如 JVM 的垃圾回收線程是一個守護線程,當所有線程已經撤離,不再產生垃圾,守護線程自然就沒事可干了,當垃圾回收線程是 Java 虛擬機上僅剩的線程時,Java 虛擬機會自動離開。
擴展:Thread Dump 打印出來的線程信息,含有 daemon 字樣的線程即為守護進程,可能會有:服務守護進程、編譯守護進程、windows 下的監聽 Ctrl+break的守護進程、Finalizer 守護進程、引用處理守護進程、GC 守護進程。
2、線程與進程的區別
進程是操作系統分配資源的最小單元,線程是操作系統調度的最小單元。一個程序至少有一個進程,一個進程至少有一個線程。
3、多線程中的上下文切換
多線程會共同使用一組計算機上的 CPU,而線程數大于給程序分配的 CPU 數量時,為了讓各個線程都有執行的機會,就需要輪轉使用 CPU。不同的線程切換使用 CPU發生的切換數據等就是上下文切換
4、死鎖與活鎖的區別,死鎖與饑餓的區別
死鎖:是指兩個或兩個以上的進程(或線程)在執行過程中,因爭奪資源而造成的一種互相等待的現象,若無外力作用,它們都將無法推進下去。
產生死鎖的必要條件:
1、互斥條件:所謂互斥就是進程在某一時間內獨占資源。
2、請求與保持條件:一個進程因請求資源而阻塞時,對已獲得的資源保持不放。
3、不剝奪條件:進程已獲得資源,在末使用完之前,不能強行剝奪。
4、循環等待條件:若干進程之間形成一種頭尾相接的循環等待資源關系。
活鎖:任務或者執行者沒有被阻塞,由于某些條件沒有滿足,導致一直重復嘗試,失敗,嘗試,失敗。
活鎖和死鎖的區別在于,處于活鎖的實體是在不斷的改變狀態,所謂的“活”, 而處于死鎖的實體表現為等待;活鎖有可能自行解開,死鎖則不能。
饑餓:一個或者多個線程因為種種原因無法獲得所需要的資源,導致一直無法執行的狀態
Java 中導致饑餓的原因:
1、高優先級線程吞噬所有的低優先級線程的 CPU 時間。
2、線程被永久堵塞在一個等待進入同步塊的狀態,因為其他線程總是能在它之前持續地對該同步塊進行訪問。
3、線程在等待一個本身也處于永久等待完成的對象(比如調用這個對象的 wait 方法),因為其他線程總是被持續地獲得喚醒。
5、Java 中用到的線程調度算法
采用時間片輪轉的方式。可以設置線程的優先級,會映射到下層的系統上面的優先級上,如非特別需要,盡量不要用,防止線程饑餓
6、線程組
ThreadGroup 類,可以把線程歸屬到某一個線程組中,線程組中可以有線程對象,也可以有線程組,組中還可以有線程,這樣的組織結構有點類似于樹的形式。
7、使用 Executor 框架
每次執行任務創建線程 new Thread()比較消耗性能,創建一個線程是比較耗時、耗資源的。
調用 new Thread()創建的線程缺乏管理,被稱為野線程,而且可以無限制的創建,線程之間的相互競爭會導致過多占用系統資源而導致系統癱瘓,還有線程之間的頻繁交替也會消耗很多系統資源。
接使用 new Thread() 啟動的線程不利于擴展,比如定時執行、定期執行、定時定期執行、線程中斷等都不便實現。
Executor 框架是一個根據一組執行策略調用,調度,執行和控制的異步任務的框架。
無限制的創建線程會引起應用程序內存溢出。所以創建一個線程池是個更好的的解決方案,因為可以限制線程的數量并且可以回收再利用這些線程。利用Executors 框架可以非常方便的創建一個線程池。
8、在 Java 中 Executor 和 Executors 的區別
Executors 工具類的不同方法按照我們的需求創建了不同的線程池,來滿足業務的需求。
Executor 接口對象能執行我們的線程任務。
ExecutorService 接口繼承了 Executor 接口并進行了擴展,提供了更多的方法我們能獲得任務執行的狀態并且可以獲取任務的返回值。
使用 ThreadPoolExecutor 可以創建自定義線程池
Future 表示異步計算的結果,他提供了檢查計算是否完成的方法,以等待計算的完成,并可以使用 get()方法獲取計算的結果。
9、原子操作
原子操作(atomic operation)意為”不可被中斷的一個或一系列操作” 。處理器使用基于對緩存加鎖或總線加鎖的方式來實現多處理器之間的原子操作。在 Java 中可以通過鎖和循環 CAS 的方式來實現原子操作。 CAS 操作——Compare & Set,或是 Compare & Swap,現在幾乎所有的 CPU 指令都支持 CAS的原子操作。
原子操作是指一個不受其他操作影響的操作任務單元。原子操作是在多線程環境
下避免數據不一致必須的手段。
int++并不是一個原子操作,所以當一個線程讀取它的值并加 1 時,另外一個線程
有可能會讀到之前的值,這就會引發錯誤。為了解決這個問題,必須保證增加操作是原子的,在 JDK1.5 之前我們可以使用同步技術來做到這一點。到 JDK1.5,java.util.concurrent.atomic 包提供了 int 和long 類型的原子包裝類,它們可以自動的保證對于他們的操作是原子的并且不需要使用同步。
java.util.concurrent 這個包里面提供了一組原子類。其基本的特性就是在多線程環境下,當有多個線程同時執行這些類的實例包含的方法時,具有排他性,即當某個線程進入方法,執行其中的指令時,不會被其他線程打斷,而別的線程就像自旋鎖一樣,一直等到該方法執行完成,才由 JVM 從等待隊列中選擇一個另一個線程進入,這只是一種邏輯上的理解。
原子類:AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference
原子數組:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray
原子屬性更新器:AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater
解決 ABA 問題的原子類:AtomicMarkableReference(通過引入一個 boolean來反映中間有沒有變過),AtomicStampedReference(通過引入一個 int 來累加來反映中間有沒有變過)
10、Java Concurrency API 中的 Lock 接口
Lock 接口比同步方法和同步塊提供了更具擴展性的鎖操作。
他們允許更靈活的結構,可以具有完全不同的性質,并且可以支持多個相關類的條件對象。
它的優勢有:
可以使鎖更公平
可以使線程在等待鎖的時候響應中斷
可以讓線程嘗試獲取鎖,并在無法獲取鎖的時候立即返回或者等待一段時間
可以在不同的范圍,以不同的順序獲取和釋放鎖。
整體上來說 Lock 是 synchronized 的擴展版,Lock 提供了無條件的、可輪詢的(tryLock 方法)、定時的(tryLock 帶參方法)、可中斷的(lockInterruptibly)、可多條件隊列的(newCondition 方法)鎖操作。另外 Lock 的實現類基本都支持非公平鎖(默認)和公平鎖,synchronized 只支持非公平鎖,當然,在大部分情況下,非公平鎖是高效的選擇。
11、阻塞隊列
阻塞隊列(BlockingQueue)是一個支持兩個附加操作的隊列。
這兩個附加的操作是:在隊列為空時,獲取元素的線程會等待隊列變為非空。當隊列滿時,存儲元素的線程會等待隊列可用。
阻塞隊列常用于生產者和消費者的場景,生產者是往隊列里添加元素的線程,消費者是從隊列里拿元素的線程。阻塞隊列就是生產者存放元素的容器,而消費者也只從容器里拿元素。
JDK7 提供了 7 個阻塞隊列。分別是:
ArrayBlockingQueue :一個由數組結構組成的有界阻塞隊列。
LinkedBlockingQueue :一個由鏈表結構組成的有界阻塞隊列。
PriorityBlockingQueue :一個支持優先級排序的無界阻塞隊列。
DelayQueue:一個使用優先級隊列實現的無界阻塞隊列。
SynchronousQueue:一個不存儲元素的阻塞隊列。
LinkedTransferQueue:一個由鏈表結構組成的無界阻塞隊列。
LinkedBlockingDeque:一個由鏈表結構組成的雙向阻塞隊列。
Java 5 之前實現同步存取時,可以使用普通的一個集合,然后在使用線程的協作和線程同步可以實現生產者,消費者模式,主要的技術就是用好,wait ,notify,notifyAll,sychronized 這些關鍵字。而在 java 5 之后,可以使用阻塞隊列來實現,此方式大大簡少了代碼量,使得多線程編程更加容易,安全方面也有保障。
BlockingQueue 接口是 Queue 的子接口,它的主要用途并不是作為容器,而是作為線程同步的的工具,因此他具有一個很明顯的特性,當生產者線程試圖向BlockingQueue 放入元素時,如果隊列已滿,則線程被阻塞,當消費者線程試圖從中取出一個元素時,如果隊列為空,則該線程會被阻塞,正是因為它所具有這個特性,所以在程序中多個線程交替向 BlockingQueue 中放入元素,取出元素,它可以很好的控制線程之間的通信。
阻塞隊列使用最經典的場景就是 socket 客戶端數據的讀取和解析,讀取數據的線程不斷將數據放入隊列,然后解析線程不斷從隊列取數據解析。
12、Callable 和 Future
Callable 接口類似于 Runnable,從名字就可以看出來了,但是 Runnable 不會返回結果,并且無法拋出返回結果的異常,而 Callable 功能更強大一些,被線程執行后,可以返回值,這個返回值可以被 Future 拿到,也就是說,Future 可以拿到異步執行任務的返回值。可以認為是帶有回調的 Runnable。
Future 接口表示異步任務,是還沒有完成的任務給出的未來結果。所以說 Callable用于產生結果,Future 用于獲取結果。
13、FutureTask,使用ExecutorService 啟動任務
在 Java 并發程序中 FutureTask 表示一個可以取消的異步運算。它有啟動和取消運算、查詢運算是否完成和取回運算結果等方法。只有當運算完成的時候結果才能取回,如果運算尚未完成 get 方法將會阻塞。一個 FutureTask 對象可以對調用了 Callable 和 Runnable 的對象進行包裝,由于 FutureTask 也是調用了 Runnable接口所以它可以提交給 Executor 來執行。
14、并發容器的實現
何為同步容器:可以簡單地理解為通過 synchronized 來實現同步的容器,如果有多個線程調用同步容器的方法,它們將會串行執行。比如 Vector,Hashtable,以及 Collections.synchronizedSet,synchronizedList 等方法返回的容器。可以通過查看 Vector,Hashtable 等這些同步容器的實現代碼,可以看到這些容器實現線程安全的方式就是將它們的狀態封裝起來,并在需要同步的方法上加上關鍵字 synchronized。
并發容器使用了與同步容器完全不同的加鎖策略來提供更高的并發性和伸縮性,例如在 ConcurrentHashMap 中采用了一種粒度更細的加鎖機制,可以稱為分段鎖,在這種鎖機制下,允許任意數量的讀線程并發地訪問 map,并且執行讀操作的線程和寫操作的線程也可以并發的訪問 map,同時允許一定數量的寫操作線程并發地修改 map,所以它可以在并發環境下實現更高的吞吐量。
15、多線程同步和互斥有幾種實現方法
線程同步是指線程之間所具有的一種制約關系,一個線程的執行依賴另一個線程的消息,當它沒有得到另一個線程的消息時應等待,直到消息到達時才被喚醒。線程互斥是指對于共享的進程系統資源,在各單個線程訪問時的排它性。當有若干個線程都要使用某一共享資源時,任何時刻最多只允許一個線程去使用,其它要使用該資源的線程必須等待,直到占用資源者釋放該資源。線程互斥可以看成是一種特殊的線程同步。
線程間的同步方法大體可分為兩類:用戶模式和內核模式。顧名思義,內核模式就是指利用系統內核對象的單一性來進行同步,使用時需要切換內核態與用戶態,而用戶模式就是不需要切換到內核態,只在用戶態完成操作。用戶模式下的方法有:原子操作(例如一個單一的全局變量),臨界區。內核模式下的方法有:事件,信號量,互斥量。