最近看到網上流傳着,各類面試經驗及面試題,每每都是一大堆技術題目貼上去,而沒有答案。html
無論你是新程序員仍是老手,你必定在面試中遇到過有關線程的問題。Java語言一個重要的特色就是內置了對併發的支持,讓Java大受企業和程序員的歡迎。大多數待遇豐厚的Java開發職位都要求開發者精通多線程技術而且有豐富的Java程序開發、調試、優化經驗,因此線程相關的問題在面試中常常會被提到。
在典型的Java面試中, 面試官會從線程的基本概念問起java
如:爲何你須要使用線程, 如何建立線程,用什麼方式建立線程比較好(好比:繼承thread類仍是調用Runnable接口),而後逐漸問到併發問題像在Java併發編程的過程當中遇到了什麼挑戰,Java內存模型,JDK1.5引入了哪些更高階的併發工具,併發編程經常使用的設計模式,經典多線程問題如生產者消費者,哲學家就餐,讀寫器或者簡單的有界緩衝區問題。僅僅知道線程的基本概念是遠遠不夠的, 你必須知道如何處理死鎖,競態條件,內存衝突和線程安全等併發問題。掌握了這些技巧,你就能夠輕鬆應對多線程和併發面試了。
許多Java程序員在面試前纔會去看面試題,這很正常。程序員
由於收集面試題和練習很花時間,因此我從許多面試者那裏收集了Java多線程和併發相關的50個熱門問題。面試
下面是Java線程相關的熱門面試題,你能夠用它來好好準備面試。算法
前25題想進大廠?50個多線程面試題,你會多少?(一)編程
前25題 想進大廠?50個多線程面試題,你會多少?(一)設計模式
CyclicBarrier和CountDownLatch 都位於java.util.concurrent 這個包下api
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提供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基本介紹與基本使用
LockSupport是JDK中比較底層的類,用來建立鎖和其餘同步工具類的基本線程阻塞。java鎖和同步器框架的核心 AQS: AbstractQueuedSynchronizer,就是經過調用 LockSupport .park()和 LockSupport .unpark()實現線程的阻塞和喚醒 的。
LockSupport 很相似於二元信號量(只有1個許可證可供使用),若是這個許可尚未被佔用,當前線程獲取許可並繼 續 執行;若是許可已經被佔用,當前線 程阻塞,等待獲取許可。
當前線程須要喚醒另外一個線程,可是隻肯定它會進入阻塞,但不肯定它是否已經進入阻塞,所以無論是否已經進入阻塞,仍是準備進入阻塞,都將發放一個通行准許。
把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基本介紹與基本使用
http://www.javashuo.com/article/p-kxfabitl-dp.html
LockSupport基本介紹與基本使用
http://www.tianshouzhi.com/api/tutorials/mutithread/303
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信號的隊列,兩個隊列的做用是不一樣,事實上,每一個線程也僅僅會同時存在以上兩個隊列中的一個,流程是這樣的
怎麼理解Condition
http://www.importnew.com/9281.html
深刻理解Condition
https://www.jianshu.com/p/6b5aa7b7684c
Oracle的官方給出的定義是:Fork/Join框架是一個實現了ExecutorService接口的多線程處理器。它能夠把一個大的任務劃分爲若干個小的任務併發執行,充分利用可用的資源,進而提升應用的執行效率。
咱們再經過Fork和Join這兩個單詞來理解下Fork/Join框架,Fork就是把一個大任務切分爲若干子任務並行的執行,Join就是合併這些子任務的執行結果,最後獲得這個大任務的結果。
好比計算1+2+。。+10000,能夠分割成10個子任務,每一個子任務分別對1000個數進行求和,最終彙總這10個子任務的結果。
工做竊取算法是指線程從其餘任務隊列中竊取任務執行(可能你會很詫異,這個算法有什麼用。待會你就知道了)。考慮下面這種場景:有一個很大的計算任務,爲了減小線程的競爭,會將這些大任務切分爲小任務並分在不一樣的隊列等待執行,而後爲每一個任務隊列建立一個線程執行隊列的任務。那麼問題來了,有的線程可能很快就執行完了,而其餘線程還有任務沒執行完,執行完的線程與其空閒下來不如幫助其餘線程執行任務,這樣也能加快執行進程。因此,執行完的空閒線程從其餘隊列的尾部竊取任務執行,而被竊取任務的線程則從隊列的頭部取任務執行(這裏使用了雙端隊列,既不影響被竊取任務的執行過程又能加快執行進度)。
從以上的介紹中,可以發現工做竊取算法的優勢是充分利用線程提升並行執行的進度。固然缺點是在某些狀況下仍然存在競爭,好比雙端隊列只有任務須要執行的時候
分割任務:首先須要建立一個ForkJoin任務,執行該類的fork方法能夠對任務不斷切割,直到分割的子任務足夠小
合併任務執行結果:子任務執行的結果同一放在一個隊列中,經過啓動一個線程從隊列中取執行結果。
Fork/Join實現了ExecutorService,因此它的任務也須要放在線程池中執行。它的不一樣在於它使用了工做竊取算法,空閒的線程能夠從滿負荷的線程中竊取任務來幫忙執行。
下面是計算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<Integer>{ //閾值 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<Integer> 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
sleep()
方法是線程類(Thread)的靜態方法,讓調用線程進入睡眠狀態,讓出執行機會給其餘線程,等到休眠時間結束後,線程進入就緒狀態和其餘線程一塊兒競爭cpu的執行時間。
由於sleep() 是static靜態的方法,他不能改變對象的機鎖,當一個synchronized塊中調用了sleep() 方法,線程雖然進入休眠,可是對象的機鎖沒有被釋放,其餘線程依然沒法訪問這個對象。
wait()
wait()是Object類的方法,當一個線程執行到wait方法時,它就進入到一個和該對象相關的等待池,同時釋放對象的機鎖,使得其餘線程可以訪問,能夠經過notify,notifyAll方法來喚醒等待的線程
線程一般都有五種狀態,建立、就緒、運行、阻塞和死亡。
每一個線程都是經過某個特定Thread對象所對應的方法run()來完成其操做的,方法run()稱爲線程體。經過調用Thread類的start()方法來啓動一個線程。
start()方法來啓動一個線程,真正實現了多線程運行。這時無需等待run方法體代碼執行完畢,能夠直接繼續執行下面的代碼;
這時此線程是處於就緒狀態, 並無運行。 而後經過此Thread類調用方法run()來完成其運行狀態, 這裏方法run()稱爲線程體,它包含了要執行的這個線程的內容, Run方法運行結束, 此線程終止。而後CPU再調度其它線程。
run()方法是在本線程裏的,只是線程裏的一個函數,而不是多線程的。
若是直接調用run(),其實就至關因而調用了一個普通函數而已,直接待用run()方法必須等待run()方法執行完畢才能執行下面的代碼,因此執行路徑仍是隻有一條,根本就沒有線程的特徵,因此在多線程執行時要使用start()方法而不是run()方法。
有點深的問題了,也看出一個Java程序員學習知識的廣度。
這實際上是頗有用的一個特性,由於多線程相比單線程更難、更復雜的一個重要緣由就是由於多線程充滿着未知性,某條線程是否執行了?某條線程執行了多久?某條線程執行的時候咱們指望的數據是否已經賦值完畢?沒法得知,咱們能作的只是等待這條多線程的任務執行完畢而已。而Callable+Future/FutureTask卻能夠獲取多線程運行的結果,能夠在等待時間太長沒獲取到須要的數據的狀況下取消該線程的任務,真的是很是有用。
volatile關鍵字的做用主要有兩個:
(1)多線程主要圍繞可見性和原子性兩個特性而展開,使用volatile關鍵字修飾的變量,保證了其在多線程之間的可見性,即每次讀取到volatile變量,必定是最新的數據
(2)代碼底層執行不像咱們看到的高級語言—-Java程序這麼簡單,它的執行是Java代碼–>字節碼–>根據字節碼執行對應的C/C++代碼–>C/C++代碼被編譯成彙編語言–>和硬件電路交互,現實中,爲了獲取更好的性能JVM可能會對指令進行重排序,多線程下可能會出現一些意想不到的問題。使用volatile則會對禁止語義重排序,固然這也必定程度上下降了代碼執行效率
從實踐角度而言,volatile的一個重要做用就是和CAS結合,保證了原子性,詳細的能夠參見java.util.concurrent.atomic包下的類,好比AtomicInteger。
死循環、死鎖、阻塞、頁面打開慢等問題,打線程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/
前面兩種能夠歸結爲一類:無返回值,緣由很簡單,經過重寫run方法,run方式的返回值是void,因此沒有辦法返回結果
後面兩種能夠歸結成一類:有返回值,經過Callable接口,就要實現call方法,這個方法的返回值是Object,因此返回的結果能夠放在Object對象中
public class ThreadDemo03 { public static void main(String[] args) { // TODO Auto-generated method stub Callable<Object> oneCallable = new Tickets<Object>(); FutureTask<Object> oneTask = new FutureTask<Object>(oneCallable); Thread t = new Thread(oneTask); System.out.println(Thread.currentThread().getName()); t.start(); } } class Tickets<Object> implements Callable<Object>{ //重寫call方法 @Override public Object call() throws Exception { // TODO Auto-generated method stub System.out.println(Thread.currentThread().getName()+"-->我是經過實現Callable接口經過FutureTask包裝器來實現的線程"); return null; } }
程序運行結果:
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_NUM; i++) { RunnableThread thread = new RunnableThread(); //Thread.sleep(1000); executorService.execute(thread); } //關閉線程池 executorService.shutdown(); } } class RunnableThread implements Runnable { @Override public void run() { System.out.println("經過線程池方式建立的線程:" + Thread.currentThread().getName() + " "); } }
程序運行結果:
經過線程池方式建立的線程: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-2
ExecutorService、Callable都是屬於Executor框架。返回結果的線程是在JDK1.5中引入的新特徵,還有Future接口也是屬於這個框架,有了這種特徵獲得返回值就很方便了。
經過分析能夠知道,他一樣也是實現了Callable接口,實現了Call方法,因此有返回值。這也就是正好符合了前面所說的兩種分類
執行Callable任務後,能夠獲取一個Future的對象,在該對象上調用get就能夠獲取到Callable任務返回的Object了。get方法是阻塞的,即:線程無返回結果,get方法會一直等待。
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)是:
因此併發編程的目標是充分的利用處理器的每個核,以達到最高的處理性能。
根據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代碼並無辦法直接接觸到操做系統。
搶佔式。一個線程用完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