摘要:大多數待遇豐厚的開發職位都要求開發者精通多線程技術并且有豐富的程序開發調試優化經驗,所以線程相關的問題在面試中經常會被提到。掌握了這些技巧,你就可以輕松應對多線程和并發面試了。進入等待通行準許時,所提供的對象。
最近看到網上流傳著,各種面試經驗及面試題,往往都是一大堆技術題目貼上去,而沒有答案。
不管你是新程序員還是老手,你一定在面試中遇到過有關線程的問題。Java語言一個重要的特點就是內置了對并發的支持,讓Java大受企業和程序員的歡迎。大多數待遇豐厚的Java開發職位都要求開發者精通多線程技術并且有豐富的Java程序開發、調試、優化經驗,所以線程相關的問題在面試中經常會被提到。
在典型的Java面試中, 面試官會從線程的基本概念問起
如:為什么你需要使用線程, 如何創建線程,用什么方式創建線程比較好(比如:繼承thread類還是調用Runnable接口),然后逐漸問到并發問題像在Java并發編程的過程中遇到了什么挑戰,Java內存模型,JDK1.5引入了哪些更高階的并發工具,并發編程常用的設計模式,經典多線程問題如生產者消費者,哲學家就餐,讀寫器或者簡單的有界緩沖區問題。僅僅知道線程的基本概念是遠遠不夠的, 你必須知道如何處理死鎖,競態條件,內存沖突和線程安全等并發問題。掌握了這些技巧,你就可以輕松應對多線程和并發面試了。
許多Java程序員在面試前才會去看面試題,這很正常。
因為收集面試題和練習很花時間,所以我從許多面試者那里收集了Java多線程和并發相關的50個熱門問題。
關注微信公眾號 "搜云庫" 獲取最新文章 【福利】公眾號后臺回復 “進群” 拉你進微信【技術分享群】 【福利】在里面你可以認識到很多搞技術大佬,免費提問,互相學習下面是Java線程相關的熱門面試題,你可以用它來好好準備面試。
【前25題】想進大廠?50個多線程面試題,你會多少?(一)前25題想進大廠?50個多線程面試題,你會多少?(一)
什么是線程?
什么是線程安全和線程不安全?
什么是自旋鎖?
什么是Java內存模型?
什么是CAS?
什么是樂觀鎖和悲觀鎖?
什么是AQS?
什么是原子操作?在Java Concurrency API中有哪些原子類(atomic classes)?
什么是Executors框架?
什么是阻塞隊列?如何使用阻塞隊列來實現生產者-消費者模型?
什么是Callable和Future?
什么是FutureTask?
什么是同步容器和并發容器的實現?
什么是多線程?優缺點?
什么是多線程的上下文切換?
ThreadLocal的設計理念與作用?
ThreadPool(線程池)用法與優勢?
Concurrent包里的其他東西:ArrayBlockingQueue、CountDownLatch等等。
synchronized和ReentrantLock的區別?
Semaphore有什么作用?
Java Concurrency API中的Lock接口(Lock interface)是什么?對比同步它有什么優勢?
Hashtable的size()方法中明明只有一條語句”return count”,為什么還要做同步?
ConcurrentHashMap的并發度是什么?
ReentrantReadWriteLock讀寫鎖的使用?
CyclicBarrier和CountDownLatch的用法及區別?
LockSupport工具?
Condition接口及其實現原理?
Fork/Join框架的理解?
wait()和sleep()的區別?
線程的五個狀態(五種狀態,創建、就緒、運行、阻塞和死亡)?
start()方法和run()方法的區別?
Runnable接口和Callable接口的區別?
volatile關鍵字的作用?
Java中如何獲取到線程dump文件?
線程和進程有什么區別?
線程實現的方式有幾種(四種)?
高并發、任務執行時間短的業務怎樣使用線程池?并發不高、任務執行時間長的業務怎樣使用線程池?并發高、業務執行時間長的業務怎樣使用線程池?
如果你提交任務時,線程池隊列已滿,這時會發生什么?
鎖的等級:方法鎖、對象鎖、類鎖?
如果同步塊內的線程拋出異常會發生什么?
并發編程(concurrency)并行編程(parallellism)有什么區別?
如何保證多線程下 i++ 結果正確?
一個線程如果出現了運行時異常會怎么樣?
如何在兩個線程之間共享數據?
生產者消費者模型的作用是什么?
怎么喚醒一個阻塞的線程?
Java中用到的線程調度算法是什么
單例模式的線程安全性?
線程類的構造方法、靜態塊是被哪個線程調用的?
同步方法和同步塊,哪個是更好的選擇?
如何檢測死鎖?怎么預防死鎖?
【前25題】想進大廠?50個多線程面試題,你會多少?(一)前25題 想進大廠?50個多線程面試題,你會多少?(一)
CyclicBarrier和CountDownLatch的用法及區別?CyclicBarrier和CountDownLatch 都位于java.util.concurrent 這個包下
CountDownLatch | CyclicBarrier |
---|---|
減計數方式 | 加計數方式 |
計算為0時釋放所有等待的線程 | 計數達到指定值時釋放所有等待線程 |
計數為0時,無法重置 | 計數達到指定值時,計數置為0重新開始 |
調用countDown()方法計數減一,調用await()方法只進行阻塞,對計數沒任何影響 | 調用await()方法計數加1,若加1后的值不等于構造方法的值,則線程阻塞 |
不可重復利用 | 可重復利用 |
CountDownLatch類只提供了一個構造器:
public CountDownLatch(int count) { }; //參數count為計數值
然后下面這3個方法是CountDownLatch類中最重要的方法:
public void await() throws InterruptedException { }; //調用await()方法的線程會被掛起,它會等待直到count值為0才繼續執行 public boolean await(long timeout, TimeUnit unit) throws InterruptedException { }; //和await()類似,只不過等待一定的時間后count值還沒變為0的話就會繼續執行 public void countDown() { }; //將count值減1
CountDownLatch, 一個同步輔助類,在完成一組正在其他線程中執行的操作之前,它允許一個或多個線程一直等待。
下面舉個例子說明:
package main.java.CountDownLatch; import java.util.concurrent.CountDownLatch; /** * PROJECT_NAME:downLoad * Author:lucaifang * Date:2016/3/18 */ public class countDownlatchTest { public static void main(String[] args) throws InterruptedException { CountDownLatch countDownLatch = new CountDownLatch(5); for(int i=0;i<5;i++){ new Thread(new readNum(i,countDownLatch)).start(); } countDownLatch.await(); System.out.println("線程執行結束。。。。"); } static class readNum implements Runnable{ private int id; private CountDownLatch latch; public readNum(int id,CountDownLatch latch){ this.id = id; this.latch = latch; } @Override public void run() { synchronized (this){ System.out.println("id:"+id); latch.countDown(); System.out.println("線程組任務"+id+"結束,其他任務繼續"); } } } }
輸出結果:
id:1 線程組任務1結束,其他任務繼續 id:0 線程組任務0結束,其他任務繼續 id:2 線程組任務2結束,其他任務繼續 id:3 線程組任務3結束,其他任務繼續 id:4 線程組任務4結束,其他任務繼續 線程執行結束。。。。
線程在countDown()之后,會繼續執行自己的任務
二、CyclicBarrier用法CyclicBarrier會在所有線程任務結束之后,才會進行后續任務,具體可以看下面例子。
CyclicBarrier提供2個構造器:
public CyclicBarrier(int parties, Runnable barrierAction) { } public CyclicBarrier(int parties) { }
參數parties指讓多少個線程或者任務等待至barrier狀態;參數barrierAction為當這些線程都達到barrier狀態時會執行的內容。
CyclicBarrier中最重要的方法就是await方法
//掛起當前線程,直至所有線程都到達barrier狀態再同時執行后續任務; public int await() throws InterruptedException, BrokenBarrierException { }; //讓這些線程等待至一定的時間,如果還有線程沒有到達barrier狀態就直接讓到達barrier的線程執行后續任務 public int await(long timeout, TimeUnit unit)throws InterruptedException,BrokenBarrierException,TimeoutException { };
舉例說明
package main.java.countOff; import java.util.concurrent.CyclicBarrier; /** * PROJECT_NAME:downLoad * Author:lucaifang * Date:2016/3/18 */ public class cyclicBarrierTest { public static void main(String[] args) throws InterruptedException { CyclicBarrier cyclicBarrier = new CyclicBarrier(5, new Runnable() { @Override public void run() { System.out.println("線程組執行結束"); } }); for (int i = 0; i < 5; i++) { new Thread(new readNum(i,cyclicBarrier)).start(); } //CyclicBarrier 可以重復利用, // 這個是CountDownLatch做不到的 // for (int i = 11; i < 16; i++) { // new Thread(new readNum(i,cyclicBarrier)).start(); // } } static class readNum implements Runnable{ private int id; private CyclicBarrier cyc; public readNum(int id,CyclicBarrier cyc){ this.id = id; this.cyc = cyc; } @Override public void run() { synchronized (this){ System.out.println("id:"+id); try { cyc.await(); System.out.println("線程組任務" + id + "結束,其他任務繼續"); } catch (Exception e) { e.printStackTrace(); } } } } }
輸出結果:
id:1 id:2 id:4 id:0 id:3 線程組執行結束 線程組任務3結束,其他任務繼續 線程組任務1結束,其他任務繼續 線程組任務4結束,其他任務繼續 線程組任務0結束,其他任務繼續 線程組任務2結束,其他任務繼續
http://blog.csdn.net/tolcf/article/details/50925145
LockSupport工具?1、LockSupport基本介紹與基本使用
LockSupport是JDK中比較底層的類,用來創建鎖和其他同步工具類的基本線程阻塞。java鎖和同步器框架的核心 AQS: AbstractQueuedSynchronizer,就是通過調用 LockSupport .park()和 LockSupport .unpark()實現線程的阻塞和喚醒 的。
LockSupport 很類似于二元信號量(只有1個許可證可供使用),如果這個許可還沒有被占用,當前線程獲取許可并繼 續 執行;如果許可已經被占用,當前線 程阻塞,等待獲取許可。
全部操作:park()/park(Object)
等待通行準許。
parkNanos(long)/parkNanos(Object, long)
在指定運行時間(即相對時間)內,等待通行準許。
parkUntil(long)/parkUntil(Object, long)
在指定到期時間(即絕對時間)內,等待通行準許。
unpark(Thread)
發放通行準許或提前發放。(注:不管提前發放多少次,只用于一次性使用。)
getBlocker(Thread)
進入等待通行準許時,所提供的對象。
主要用途:當前線程需要喚醒另一個線程,但是只確定它會進入阻塞,但不確定它是否已經進入阻塞,因此不管是否已經進入阻塞,還是準備進入阻塞,都將發放一個通行準許。
正確用法:把LockSupport視為一個sleep()來用,只是sleep()是定時喚醒,LockSupport既可以定時喚醒,也可以由其它線程喚醒。
public static void main(String[] args) { LockSupport.park(); System.out.println("block."); }
運行該代碼,可以發現主線程一直處于阻塞狀態。因為 許可默認是被占用的 ,調用park()時獲取不到許可,所以進入阻塞狀態。
如下代碼:先釋放許可,再獲取許可,主線程能夠正常終止。LockSupport許可的獲取和釋放,一般來說是對應的,如果多次unpark,只有一次park也不會出現什么問題,結果是許可處于可用狀態。
public static void main(String[] args) { Thread thread = Thread.currentThread(); LockSupport.unpark(thread);//釋放許可 LockSupport.park();// 獲取許可 System.out.println("b"); }
LockSupport是不可重入 的,如果一個線程連續2次調用 LockSupport .park(),那么該線程一定會一直阻塞下去。
public static void main(String[] args) throws Exception { Thread thread = Thread.currentThread(); LockSupport.unpark(thread); System.out.println("a"); LockSupport.park(); System.out.println("b"); LockSupport.park(); System.out.println("c"); }
這段代碼打印出a和b,不會打印c,因為第二次調用park的時候,線程無法獲取許可出現死鎖。
LockSupport基本介紹與基本使用
https://www.cnblogs.com/hvicen/p/6217303.html
LockSupport基本介紹與基本使用
http://www.tianshouzhi.com/api/tutorials/mutithread/303
Condition接口及其實現原理?在java.util.concurrent包中,有兩個很特殊的工具類,Condition和ReentrantLock,使用過的人都知道,ReentrantLock(重入鎖)是jdk的concurrent包提供的一種獨占鎖的實現
我們知道在線程的同步時可以使一個線程阻塞而等待一個信號,同時放棄鎖使其他線程可以能競爭到鎖
在synchronized中我們可以使用Object的wait()和notify方法實現這種等待和喚醒
但是在Lock中怎么實現這種wait和notify呢?
答案是Condition,學習Condition主要是為了方便以后學習blockqueue和concurrenthashmap的源碼,同時也進一步理解ReentrantLock。
ReentrantLock和Condition的使用方式通常是這樣的:
public static void main(String[] args) { final ReentrantLock reentrantLock = new ReentrantLock(); final Condition condition = reentrantLock.newCondition(); Thread thread = new Thread((Runnable) () -> { try { reentrantLock.lock(); System.out.println("我要等一個新信號" + this); condition.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("拿到一個信號!!" + this); reentrantLock.unlock(); }, "waitThread1"); thread.start(); Thread thread1 = new Thread((Runnable) () -> { reentrantLock.lock(); System.out.println("我拿到鎖了"); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } condition.signalAll(); System.out.println("我發了一個信號!!"); reentrantLock.unlock(); }, "signalThread"); thread1.start(); }
運行后,結果如下:
我要等一個新信號lock.ReentrantLockTest$1@a62fc3 我拿到鎖了 我發了一個信號!! 拿到一個信號!!
可以看到
Condition的執行方式,是當在線程1中調用await方法后,線程1將釋放鎖,并且將自己沉睡,等待喚醒,
線程2獲取到鎖后,開始做事,完畢后,調用Condition的signal方法,喚醒線程1,線程1恢復執行。
以上說明Condition是一個多線程間協調通信的工具類,使得某個,或者某些線程一起等待某個條件(Condition),只有當該條件具備( signal 或者 signalAll方法被帶調用)時 ,這些等待線程才會被喚醒,從而重新爭奪鎖。
Condition自己也維護了一個隊列,該隊列的作用是維護一個等待signal信號的隊列,兩個隊列的作用是不同,事實上,每個線程也僅僅會同時存在以上兩個隊列中的一個,流程是這樣的
線程1調用reentrantLock.lock時,線程被加入到AQS的等待隊列中。
線程1調用await方法被調用時,該線程從AQS中移除,對應操作是鎖的釋放。
接著馬上被加入到Condition的等待隊列中,以為著該線程需要signal信號。
線程2,因為線程1釋放鎖的關系,被喚醒,并判斷可以獲取鎖,于是線程2獲取鎖,并被加入到AQS的等待隊列中。
線程2調用signal方法,這個時候Condition的等待隊列中只有線程1一個節點,于是它被取出來,并被加入到AQS的等待隊列中。 注意,這個時候,線程1 并沒有被喚醒。
signal方法執行完畢,線程2調用reentrantLock.unLock()方法,釋放鎖。這個時候因為AQS中只有線程1,于是,AQS釋放鎖后按從頭到尾的順序喚醒線程時,線程1被喚醒,于是線程1回復執行。
直到釋放所整個過程執行完畢。
可以看到,整個協作過程是靠結點在AQS的等待隊列和Condition的等待隊列中來回移動實現的,Condition作為一個條件類,很好的自己維護了一個等待信號的隊列,并在適時的時候將結點加入到AQS的等待隊列中來實現的喚醒操作。
怎么理解Condition
http://www.importnew.com/9281.html
深入理解Condition
https://www.jianshu.com/p/6b5aa7b7684c
Fork/Join框架的理解? Fork/Join是什么Oracle的官方給出的定義是:Fork/Join框架是一個實現了ExecutorService接口的多線程處理器。它可以把一個大的任務劃分為若干個小的任務并發執行,充分利用可用的資源,進而提高應用的執行效率。
我們再通過Fork和Join這兩個單詞來理解下Fork/Join框架,Fork就是把一個大任務切分為若干子任務并行的執行,Join就是合并這些子任務的執行結果,最后得到這個大任務的結果。
比如計算1+2+。。+10000,可以分割成10個子任務,每個子任務分別對1000個數進行求和,最終匯總這10個子任務的結果。
工作竊取算法工作竊取算法是指線程從其他任務隊列中竊取任務執行(可能你會很詫異,這個算法有什么用。待會你就知道了)。考慮下面這種場景:有一個很大的計算任務,為了減少線程的競爭,會將這些大任務切分為小任務并分在不同的隊列等待執行,然后為每個任務隊列創建一個線程執行隊列的任務。那么問題來了,有的線程可能很快就執行完了,而其他線程還有任務沒執行完,執行完的線程與其空閑下來不如幫助其他線程執行任務,這樣也能加快執行進程。所以,執行完的空閑線程從其他隊列的尾部竊取任務執行,而被竊取任務的線程則從隊列的頭部取任務執行(這里使用了雙端隊列,既不影響被竊取任務的執行過程又能加快執行進度)。
從以上的介紹中,能夠發現工作竊取算法的優點是充分利用線程提高并行執行的進度。當然缺點是在某些情況下仍然存在競爭,比如雙端隊列只有任務需要執行的時候
使用Fork/Join框架分為兩步:分割任務:首先需要創建一個ForkJoin任務,執行該類的fork方法可以對任務不斷切割,直到分割的子任務足夠小
合并任務執行結果:子任務執行的結果同一放在一個隊列中,通過啟動一個線程從隊列中取執行結果。
Fork/Join實現了ExecutorService,所以它的任務也需要放在線程池中執行。它的不同在于它使用了工作竊取算法,空閑的線程可以從滿負荷的線程中竊取任務來幫忙執行。
shitong下面是計算1+2+3+4為例演示如何使用使用Fork/Join框架:
package com.rhwayfun.concurrency.r0406; import java.util.concurrent.ExecutionException; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.ForkJoinTask; import java.util.concurrent.RecursiveTask; /** * Created by rhwayfun on 16-4-6. */ public class CountTask extends RecursiveTask{ //閾值 private static final int THRESHOLD = 2; //起始值 private int start; //結束值 private int end; public CountTask(int start, int end) { this.start = start; this.end = end; } @Override protected Integer compute() { boolean compute = (end - start) <= THRESHOLD; int res = 0; if (compute){ for (int i = start; i <= end; i++){ res += i; } }else { //如果長度大于閾值,則分割為小任務 int mid = (start + end) / 2; CountTask task1 = new CountTask(start,mid); CountTask task2 = new CountTask(mid + 1, end); //計算小任務的值 task1.fork(); task2.fork(); //得到兩個小任務的值 int task1Res = task1.join(); int task2Res = task2.join(); res = task1Res + task2Res; } return res; } public static void main(String[] args) throws ExecutionException, InterruptedException { ForkJoinPool pool = new ForkJoinPool(); CountTask task = new CountTask(1,5); ForkJoinTask submit = pool.submit(task); System.out.println("Final result:" + submit.get()); } }
代碼執行結果為:
15
代碼中使用了FokJoinTask,其與一般任務的區別在于它需要實現compute方法,在方法需要判斷任務是否在閾值區間內,如果不是則需要把任務切分到足夠小,直到能夠進行計算。
每個被切分的子任務又會重新進入compute方法,再繼續判斷是否需要繼續切分,如果不需要則直接得到子任務執行的結果,如果需要的話則繼續切分,如此循環,直到調用join方法得到最終的結果。
**
可以發現Fork/Join框架的需要把提交給ForkJoinPool,ForkJoinPool由ForkJoinTask數組和ForkJoinWorkerThread數組組成,前者負責將存放程序提交給ForkJoinPool的任務,后者則負責執行這些任務。關鍵在于在于fork方法與join方法**
Java并發編程系列之二十:Fork/Join框架
http://blog.csdn.net/u011116672/article/details/51073683
wait()和sleep()的區別?sleep()
方法是線程類(Thread)的靜態方法,讓調用線程進入睡眠狀態,讓出執行機會給其他線程,等到休眠時間結束后,線程進入就緒狀態和其他線程一起競爭cpu的執行時間。
因為sleep() 是static靜態的方法,他不能改變對象的機鎖,當一個synchronized塊中調用了sleep() 方法,線程雖然進入休眠,但是對象的機鎖沒有被釋放,其他線程依然無法訪問這個對象。
wait()
wait()是Object類的方法,當一個線程執行到wait方法時,它就進入到一個和該對象相關的等待池,同時釋放對象的機鎖,使得其他線程能夠訪問,可以通過notify,notifyAll方法來喚醒等待的線程
線程的五個狀態(五種狀態,創建、就緒、運行、阻塞和死亡)?線程通常都有五種狀態,創建、就緒、運行、阻塞和死亡。
第一是創建狀態。在生成線程對象,并沒有調用該對象的start方法,這是線程處于創建狀態。
第二是就緒狀態。當調用了線程對象的start方法之后,該線程就進入了就緒狀態,但是此時線程調度程序還沒有把該線程設置為當前線程,此時處于就緒狀態。在線程運行之后,從等待或者睡眠中回來之后,也會處于就緒狀態。
第三是運行狀態。線程調度程序將處于就緒狀態的線程設置為當前線程,此時線程就進入了運行狀態,開始運行run函數當中的代碼。
第四是阻塞狀態。線程正在運行的時候,被暫停,通常是為了等待某個時間的發生(比如說某項資源就緒)之后再繼續運行。sleep,suspend,wait等方法都可以導致線程阻塞。
第五是死亡狀態。如果一個線程的run方法執行結束或者調用stop方法后,該線程就會死亡。對于已經死亡的線程,無法再使用start方法令其進入就緒
start()方法和run()方法的區別?
每個線程都是通過某個特定Thread對象所對應的方法run()來完成其操作的,方法run()稱為線程體。通過調用Thread類的start()方法來啟動一個線程。
start()方法來啟動一個線程,真正實現了多線程運行。這時無需等待run方法體代碼執行完畢,可以直接繼續執行下面的代碼;
這時此線程是處于就緒狀態, 并沒有運行。 然后通過此Thread類調用方法run()來完成其運行狀態, 這里方法run()稱為線程體,它包含了要執行的這個線程的內容, Run方法運行結束, 此線程終止。然后CPU再調度其它線程。
run()方法是在本線程里的,只是線程里的一個函數,而不是多線程的。
如果直接調用run(),其實就相當于是調用了一個普通函數而已,直接待用run()方法必須等待run()方法執行完畢才能執行下面的代碼,所以執行路徑還是只有一條,根本就沒有線程的特征,所以在多線程執行時要使用start()方法而不是run()方法。
有點深的問題了,也看出一個Java程序員學習知識的廣度。
Runnable接口中的run()方法的返回值是void,它做的事情只是純粹地去執行run()方法中的代碼而已;
Callable接口中的call()方法是有返回值的,是一個泛型,和Future、FutureTask配合可以用來獲取異步執行的結果。
這其實是很有用的一個特性,因為多線程相比單線程更難、更復雜的一個重要原因就是因為多線程充滿著未知性,某條線程是否執行了?某條線程執行了多久?某條線程執行的時候我們期望的數據是否已經賦值完畢?無法得知,我們能做的只是等待這條多線程的任務執行完畢而已。而Callable+Future/FutureTask卻可以獲取多線程運行的結果,可以在等待時間太長沒獲取到需要的數據的情況下取消該線程的任務,真的是非常有用。
volatile關鍵字的作用?volatile關鍵字的作用主要有兩個:
(1)多線程主要圍繞可見性和原子性兩個特性而展開,使用volatile關鍵字修飾的變量,保證了其在多線程之間的可見性,即每次讀取到volatile變量,一定是最新的數據
(2)代碼底層執行不像我們看到的高級語言—-Java程序這么簡單,它的執行是Java代碼–>字節碼–>根據字節碼執行對應的C/C++代碼–>C/C++代碼被編譯成匯編語言–>和硬件電路交互,現實中,為了獲取更好的性能JVM可能會對指令進行重排序,多線程下可能會出現一些意想不到的問題。使用volatile則會對禁止語義重排序,當然這也一定程度上降低了代碼執行效率
從實踐角度而言,volatile的一個重要作用就是和CAS結合,保證了原子性,詳細的可以參見java.util.concurrent.atomic包下的類,比如AtomicInteger。
Java中如何獲取到線程dump文件?死循環、死鎖、阻塞、頁面打開慢等問題,打線程dump是最好的解決問題的途徑。所謂線程dump也就是線程堆棧,獲取到線程堆棧有兩步:
(1)獲取到線程的pid,可以通過使用jps命令,在Linux環境下還可以使用ps -ef | grep java
(2)打印線程堆棧,可以通過使用jstack pid命令,在Linux環境下還可以使用kill -3 pid
另外提一點,Thread類提供了一個getStackTrace()方法也可以用于獲取線程堆棧。這是一個實例方法,因此此方法是和具體線程實例綁定的,每次獲取獲取到的是具體某個線程當前運行的堆棧,
虛擬機性能監控與故障處理工具 詳解
http://www.ymq.io/2017/08/01/jvm-4/
線程和進程有什么區別?進程是系統進行資源分配的基本單位,有獨立的內存地址空間
線程是CPU獨立運行和獨立調度的基本單位,沒有多帶帶地址空間,有獨立的棧,局部變量,寄存器, 程序計數器等。
創建進程的開銷大,包括創建虛擬地址空間等需要大量系統資源
創建線程開銷小,基本上只有一個內核對象和一個堆棧。
一個進程無法直接訪問另一個進程的資源;同一進程內的多個線程共享進程的資源。
進程切換開銷大,線程切換開銷小;進程間通信開銷大,線程間通信開銷小。
線程屬于進程,不能獨立執行。每個進程至少要有一個線程,成為主線程
線程實現的方式有幾種(四種)?繼承Thread類,重寫run方法
實現Runnable接口,重寫run方法,實現Runnable接口的實現類的實例對象作為Thread構造函數的target
實現Callable接口通過FutureTask包裝器來創建Thread線程
通過線程池創建線程
前面兩種可以歸結為一類:無返回值,原因很簡單,通過重寫run方法,run方式的返回值是void,所以沒有辦法返回結果
后面兩種可以歸結成一類:有返回值,通過Callable接口,就要實現call方法,這個方法的返回值是Object,所以返回的結果可以放在Object對象中
線程實現方式3:通過Callable和FutureTask創建線程創建Callable接口的實現類 ,并實現Call方法
創建Callable實現類的實現,使用FutureTask類包裝Callable對象,該FutureTask對象封裝了Callable對象的Call方法的返回值
使用FutureTask對象作為Thread對象的target創建并啟動線程
調用FutureTask對象的get()來獲取子線程執行結束的返回值
public class ThreadDemo03 { public static void main(String[] args) { // TODO Auto-generated method stub Callable
程序運行結果:
main
Thread-0–>我是通過實現Callable接口通過FutureTask包裝器來實現的線程
public class ThreadDemo05{ private static int POOL_NUM = 10; //線程池數量 public static void main(String[] args) throws InterruptedException { // TODO Auto-generated method stub ExecutorService executorService = Executors.newFixedThreadPool(5); for(int i = 0; i程序運行結果:
通過線程池方式創建的線程:pool-1-thread-3 通過線程池方式創建的線程:pool-1-thread-4 通過線程池方式創建的線程:pool-1-thread-1 通過線程池方式創建的線程:pool-1-thread-5 通過線程池方式創建的線程:pool-1-thread-2 通過線程池方式創建的線程:pool-1-thread-5 通過線程池方式創建的線程:pool-1-thread-1 通過線程池方式創建的線程:pool-1-thread-4 通過線程池方式創建的線程:pool-1-thread-3 通過線程池方式創建的線程:pool-1-thread-2ExecutorService、Callable都是屬于Executor框架。返回結果的線程是在JDK1.5中引入的新特征,還有Future接口也是屬于這個框架,有了這種特征得到返回值就很方便了。
通過分析可以知道,他同樣也是實現了Callable接口,實現了Call方法,所以有返回值。這也就是正好符合了前面所說的兩種分類執行Callable任務后,可以獲取一個Future的對象,在該對象上調用get就可以獲取到Callable任務返回的Object了。get方法是阻塞的,即:線程無返回結果,get方法會一直等待。
再介紹Executors類:newCachedThreadPool創建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閑線程,若無可回收,則新建線程。
newFixedThreadPool 創建一個定長線程池,可控制線程最大并發數,超出的線程會在隊列中等待。
newScheduledThreadPool 創建一個定長線程池,支持定時及周期性任務執行。
newSingleThreadExecutor 創建一個單線程化的線程池,它只會用唯一的工作線程來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先級)執行。
Java多線程實現的四種方式
http://blog.csdn.net/u011480603/article/details/75332435
高并發、任務執行時間短的業務怎樣使用線程池?并發不高、任務執行時間長的業務怎樣使用線程池?并發高、業務執行時間長的業務怎樣使用線程池?這是我在并發編程網上看到的一個問題,把這個問題放在最后一個,希望每個人都能看到并且思考一下,因為這個問題非常好、非常實際、非常專業。關于這個問題,個人看法是:
(1)高并發、任務執行時間短的業務,線程池線程數可以設置為CPU核數+1,減少線程上下文的切換
(2)并發不高、任務執行時間長的業務要區分開看:
a)假如是業務時間長集中在IO操作上,也就是IO密集型的任務,因為IO操作并不占用CPU,所以不要讓所有的CPU閑下來,可以加大線程池中的線程數目,讓CPU處理更多的業務
b)假如是業務時間長集中在計算操作上,也就是計算密集型任務,這個就沒辦法了,和(1)一樣吧,線程池中的線程數設置得少一些,減少線程上下文的切換
(3)并發高、業務執行時間長,解決這種類型任務的關鍵不在于線程池而在于整體架構的設計,看看這些業務里面某些數據是否能做緩存是第一步,增加服務器是第二步,至于線程池的設置,設置參考(2)。最后,業務執行時間長的問題,也可能需要分析一下,看看能不能使用中間件對任務進行拆分和解耦。
如果你提交任務時,線程池隊列已滿,這時會發生什么?如果你使用的LinkedBlockingQueue,也就是無界隊列的話,沒關系,繼續添加任務到阻塞隊列中等待執行,因為LinkedBlockingQueue可以近乎認為是一個無窮大的隊列,可以無限存放任務;如果你使用的是有界隊列比方說ArrayBlockingQueue的話,任務首先會被添加到ArrayBlockingQueue中,ArrayBlockingQueue滿了,則會使用拒絕策略RejectedExecutionHandler處理滿了的任務,默認是AbortPolicy。
鎖的等級:方法鎖、對象鎖、類鎖?方法鎖(synchronized修飾方法時)
通過在方法聲明中加入 synchronized關鍵字來聲明 synchronized 方法。
synchronized 方法控制對類成員變量的訪問:
每個類實例對應一把鎖,每個 synchronized 方法都必須獲得調用該方法的類實例的鎖方能執行,否則所屬線程阻塞,方法一旦執行,就獨占該鎖,直到從該方法返回時才將鎖釋放,此后被阻塞的線程方能獲得該鎖,重新進入可執行狀態。這種機制確保了同一時刻對于每一個類實例,其所有聲明為 synchronized 的成員函數中至多只有一個處于可執行狀態,從而有效避免了類成員變量的訪問沖突。對象鎖(synchronized修飾方法或代碼塊)
當一個對象中有synchronized method或synchronized block的時候調用此對象的同步方法或進入其同步區域時,就必須先獲得對象鎖。如果此對象的對象鎖已被其他調用者占用,則需要等待此鎖被釋放。(方法鎖也是對象鎖)
java的所有對象都含有1個互斥鎖,這個鎖由JVM自動獲取和釋放。線程進入synchronized方法的時候獲取該對象的鎖,當然如果已經有線程獲取了這個對象的鎖,那么當前線程會等待;synchronized方法正常返回或者拋異常而終止,JVM會自動釋放對象鎖。這里也體現了用synchronized來加鎖的1個好處,方法拋異常的時候,鎖仍然可以由JVM來自動釋放。
類鎖(synchronized 修飾靜態的方法或代碼塊)
由于一個class不論被實例化多少次,其中的靜態方法和靜態變量在內存中都只有一份。所以,一旦一個靜態的方法被申明為synchronized。此類所有的實例化對象在調用此方法,共用同一把鎖,我們稱之為類鎖。
對象鎖是用來控制實例方法之間的同步,類鎖是用來控制靜態方法(或靜態變量互斥體)之間的同步
如果同步塊內的線程拋出異常會發生什么?這個問題坑了很多Java程序員,若你能想到鎖是否釋放這條線索來回答還有點希望答對。無論你的同步塊是正常還是異常退出的,里面的線程都會釋放鎖,所以對比鎖接口我更喜歡同步塊,因為它不用我花費精力去釋放鎖,該功能可以在finally block里釋放鎖實現。
并發編程(concurrency)并行編程(parallellism)有什么區別?并發(concurrency)和并行(parallellism)是:
解釋一:并行是指兩個或者多個事件在同一時刻發生;而并發是指兩個或多個事件在同一時間間隔發生。
解釋二:并行是在不同實體上的多個事件,并發是在同一實體上的多個事件。
解釋三:在一臺處理器上“同時”處理多個任務,在多臺處理器上同時處理多個任務。如hadoop分布式集群
所以并發編程的目標是充分的利用處理器的每一個核,以達到最高的處理性能。
如何保證多線程下 i++ 結果正確?根據volatile特性來用1000個線程不斷的累加數字,每次累加1個,到最后值確不是1000.
volatile只能保證你數據的可見性(獲取到的是最新的數據,不能保證原子性,說白了,volatile跟原子性沒關系
import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; public class Counter { public static AtomicInteger count = new AtomicInteger();//原子操作 public static CountDownLatch latch= new CountDownLatch(1000);//線程協作處理 public static volatile int countNum = 0;//volatile 只能保證可見性,不能保證原子性 public static int synNum = 0;//同步處理計算 public static void inc() { try { Thread.sleep(1); } catch (InterruptedException e) { } countNum++; int c = count.addAndGet(1); add(); System.out.println(Thread.currentThread().getName() + "------>" + c); } public static synchronized void add(){ synNum++; } public static void main(String[] args) { //同時啟動1000個線程,去進行i++計算,看看實際結果 for (int i = 0; i < 1000; i++) { new Thread(new Runnable() { @Override public void run() { Counter.inc(); latch.countDown(); } },"thread" + i).start(); } try { latch.await(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()); System.out.println("運行結果:Counter.count=" + count.get() + ",,," + countNum + ",,," + synNum); }count.get()是AtomicInteger的值;
count是用volatile修飾的變量的值;
synNum是用synchronized修飾的值;
用synchronized和AtomicInteger能保證是你想要的數據,volatile并不能保證。
第一次運行結果:
main
運行結果:Counter.count=1000,,,991,,,1000第二次運行結果:
main
運行結果:Counter.count=1000,,,998,,,1000第三次運行結果:
main
運行結果:Counter.count=1000,,,993,,,1000可見,就算用了volatile,也不能保證數據是你想要的數據,volatile只能保證你數據的可見性(獲取到的是最新的數據,不能保證原子性,說白了,volatile跟原子性沒關系)
要保證原子性,對數據的累加,可以用AtomicInteger類;
也可以用synchronized來保證數據的一致性
一個線程如果出現了運行時異常會怎么樣?如果這個異常沒有被捕獲的話,這個線程就停止執行了。另外重要的一點是:如果這個線程持有某個某個對象的監視器,那么這個對象監視器會被立即釋放
如何在兩個線程之間共享數據?通過在線程之間共享對象就可以了,然后通過wait/notify/notifyAll、await/signal/signalAll進行喚起和等待,比方說阻塞隊列BlockingQueue就是為線程之間共享數據而設計的
生產者消費者模型的作用是什么?這個問題很理論,但是很重要:
(1)通過平衡生產者的生產能力和消費者的消費能力來提升整個系統的運行效率,這是生產者消費者模型最重要的作用
(2)解耦,這是生產者消費者模型附帶的作用,解耦意味著生產者和消費者之間的聯系少,聯系越少越可以獨自發展而不需要收到相互的制約
怎么喚醒一個阻塞的線程?如果線程是因為調用了wait()、sleep()或者join()方法而導致的阻塞,可以中斷線程,并且通過拋出InterruptedException來喚醒它;如果線程遇到了IO阻塞,無能為力,因為IO是操作系統實現的,Java代碼并沒有辦法直接接觸到操作系統。
Java中用到的線程調度算法是什么?搶占式。一個線程用完CPU之后,操作系統會根據線程優先級、線程饑餓情況等數據算出一個總的優先級并分配下一個時間片給某個線程執行。
單例模式的線程安全性?老生常談的問題了,首先要說的是單例模式的線程安全意味著:某個類的實例在多線程環境下只會被創建一次出來。單例模式有很多種的寫法,我總結一下:
(1)餓漢式單例模式的寫法:線程安全
(2)懶漢式單例模式的寫法:非線程安全
(3)雙檢鎖單例模式的寫法:線程安全
線程類的構造方法、靜態塊是被哪個線程調用的?這是一個非常刁鉆和狡猾的問題。請記住:線程類的構造方法、靜態塊是被new這個線程類所在的線程所調用的,而run方法里面的代碼才是被線程自身所調用的。
如果說上面的說法讓你感到困惑,那么我舉個例子,假設Thread2中new了Thread1,main函數中new了Thread2,那么:
(1)Thread2的構造方法、靜態塊是main線程調用的,Thread2的run()方法是Thread2自己調用的
(2)Thread1的構造方法、靜態塊是Thread2調用的,Thread1的run()方法是Thread1自己調用的
同步方法和同步塊,哪個是更好的選擇?同步塊是更好的選擇,因為它不會鎖住整個對象(當然也可以讓它鎖住整個對象)。同步方法會鎖住整個對象,哪怕這個類中有多個不相關聯的同步塊,這通常會導致他們停止執行并需要等待獲得這個對象上的鎖。
public class SynObj{
public synchronized void showA(){ System.out.println("showA.."); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } } public void showB(){ synchronized (this) { System.out.println("showB.."); } }}
如何檢測死鎖?怎么預防死鎖?所謂死鎖:是指兩個或兩個以上的進程在執行過程中,因爭奪資源而造成的一種互相等待的現象,若無外力作用,它們都將無法推進下去。此時稱系統處于死鎖
通俗地講就是兩個或多個進程被無限期地阻塞、相互等待的一種狀態
死鎖產生的原因?
1.因競爭資源發生死鎖 現象:系統中供多個進程共享的資源的數目不足以滿足全部進程的需要時,就會引起對諸資源的競爭而發生死鎖現象
2.進程推進順序不當發生死鎖
死鎖的四個必要條件:
互斥條件:進程對所分配到的資源不允許其他進程進行訪問,若其他進程訪問該資源,只能等待,直至占有該資源的進程使用完成后釋放該資源
請求和保持條件:進程獲得一定的資源之后,又對其他資源發出請求,但是該資源可能被其他進程占有,此事請求阻塞,但又對自己獲得的資源保持不放
不可剝奪條件:是指進程已獲得的資源,在未完成使用之前,不可被剝奪,只能在使用完后自己釋放
環路等待條件:是指進程發生死鎖后,若干進程之間形成一種頭尾相接的循環等待資源關系
這四個條件是死鎖的必要條件,只要系統發生死鎖,這些條件必然成立,而只要上述條件之
一不滿足,就不會發生死鎖。檢測死鎖
有兩個容器,一個用于保存線程正在請求的鎖,一個用于保存線程已經持有的鎖。每次加鎖之前都會做如下檢測:
檢測當前正在請求的鎖是否已經被其它線程持有,如果有,則把那些線程找出來
遍歷第一步中返回的線程,檢查自己持有的鎖是否正被其中任何一個線程請求,如果第二步返回真,表示出現了死鎖
死鎖的解除與預防:
理解了死鎖的原因,尤其是產生死鎖的四個必要條件,就可以最大可能地避免、預防和
解除死鎖。所以,在系統設計、進程調度等方面注意如何不讓這四個必要條件成立,如何確
定資源的合理分配算法,避免進程永久占據系統資源。此外,也要防止進程在處于等待狀態的情況下占用資源。因此,對資源的分配要給予合理的規劃。
http://blog.csdn.net/yyf_it/a...
推薦閱讀
http://blog.csdn.net/yyf_it/article/details/52412071想進大廠?50個多線程面試題,你會多少?(一)
想進大廠?50個多線程面試題,你會多少?(二)
BTA 常問的 Java基礎40道常見面試題及詳細答案
Spring 常見的一些面試題整理
常用的分布式事務解決方案介紹有多少種?
什么是微服務架構?
Dapper,大規模分布式系統的跟蹤系統
Contact作者:鵬磊
出處:http://www.ymq.io
版權歸作者所有,轉載請注明出處
Wechat:關注公眾號,搜云庫,專注于開發技術的研究與知識分享
關注微信公眾號 "搜云庫" 獲取最新文章 【福利】公眾號后臺回復 “進群” 拉你進微信【技術分享群】 【福利】在里面你可以認識到很多搞技術大佬,免費提問,互相學習
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/68879.html
摘要:為程序員金三銀四精心挑選的余道面試題與答案,歡迎大家向我推薦你在面試過程中遇到的問題我會把大家推薦的問題添加到下面的常用面試題清單中供大家參考。 為Java程序員金三銀四精心挑選的300余道Java面試題與答案,歡迎大家向我推薦你在面試過程中遇到的問題,我會把大家推薦的問題添加到下面的常用面試題清單中供大家參考。 前兩天寫的以下博客,大家比較認可,熱度不錯,希望可以幫到準備或者正在參加...
摘要:前言從號開始在寫下第一篇文章說是筆記還差不多,驚奇地收到有人收藏我的文章的消息,覺得有點開心。突然腦子抽到想爬下里標簽下的文章有多少,哪篇被收藏最多,哪篇被點贊最多。。。現在和大家分享下,收藏量前的文章,被那么多人收藏應該是篇值得看的文章。 前言 從18號開始在sf寫下第一篇文章(說是筆記還差不多),驚奇地收到有人收藏我的文章的消息,覺得有點開心。突然腦子抽到想爬下sf里JAVA標簽下...
摘要:下面是線程相關的熱門面試題,你可以用它來好好準備面試。線程安全問題都是由全局變量及靜態變量引起的。持有自旋鎖的線程在之前應該釋放自旋鎖以便其它線程可以獲得自旋鎖。 最近看到網上流傳著,各種面試經驗及面試題,往往都是一大堆技術題目貼上去,而沒有答案。 不管你是新程序員還是老手,你一定在面試中遇到過有關線程的問題。Java語言一個重要的特點就是內置了對并發的支持,讓Java大受企業和程序員...
閱讀 2993·2021-10-13 09:39
閱讀 2694·2021-09-27 13:34
閱讀 2031·2019-08-30 15:55
閱讀 3260·2019-08-30 15:43
閱讀 3631·2019-08-30 11:16
閱讀 1748·2019-08-26 18:28
閱讀 1283·2019-08-26 13:56
閱讀 914·2019-08-26 13:35