摘要:并發和并行并發和并行是兩個非常容易被混淆的概念。并發說的是在一個時間段內,多件事情在這個時間段內交替執行。并行說的是多件事情在同一個時刻同事發生。由于線程池是一個線程,得不到執行,而被餓死,最終導致了程序死鎖的現象。
同步(Synchronous)和異步(Asynchronous)
同步和異步通常來形容一次方法調用,同步方法調用一旦開始,調用者必須等到方法調用返回后,才能繼續后續的行為。異步方法調用更像一個消息傳遞,一旦開始,方法調用就會立即返回,調用者就可以繼續后續的操作。而異步方法通常會在另外一個線程中“真實”地執行。整個過程,不會阻礙調用者的工作。
如圖:
上圖中顯示了同步方法調用和異步方法調用的區別。對于調用者來說,異步調用似乎是一瞬間就完成的。如果異步調用需要返回結果,那么當這個異步調用真實完成時,則會通知調用者。
打個比方,比如購物,如果你去商場買空調,當你到了商場看重了一款空調,你就向售貨員下單。售貨員去倉庫幫你調配物品。這天你熱的是在不行了,就催著商家趕緊給你送貨,于是你就在商店里面候著他們,直到商家把你和空調一起送回家,一次愉快的購物就結束了。這就是同步調用。
不過,如果我們趕時髦,就坐在家里打開電腦,在電腦上訂購了一臺空調。當你完成網上支付的時候,對你來說購物過程已經結束了。雖然空調還沒有送到家,但是你的任務已經完成了。商家接到你的訂單后,就會加緊安平送貨,當然這一切已經跟你無關了。你已經支付完成,想干什么就能去干什么,出去溜幾圈都不成問題,等送貨上門的時候,接到商家的電話,回家一趟簽收就完事了。這就是異步調用。
并發(Concurrency)和并行(Parallelism)并發和并行是兩個非常容易被混淆的概念。他們都可以表示兩個或者多個任務一起執行,但是側重點有所不同。并發偏重于多個任務交替執行,而多個任務之間有可能還是串行的,而并行是真正意義上的“同時執行”,下圖很好地詮釋了這點。
大家排隊在一個咖啡機上接咖啡,交替執行,是并發;兩臺咖啡機上面接咖啡,是并行。
從嚴格意義上來說,并行的多任務是真的同時執行,而對于并發來說,這個過程只是交替的,一會執行任務A,一會執行任務B,系統會不停地在兩者之間切換。但對于外部觀察者來說,即使多個任務之間是串行并發的,也會造成多任務間并行執行的錯覺。
并發說的是在一個時間段內,多件事情在這個時間段內交替執行。
并行說的是多件事情在同一個時刻同事發生。
實際上,如果系統內只有一個CPU,而使用多進程或者多線程任務,那么真實環境中這些任務不可能是真實并行的,畢竟一個CPU一次只能執行一條指令,在這種情況下多進程或者多線程就是并發的,而不是并行的(操作系統會不停地切換多任務)。真實的并行也只可能出現在擁有多個CPU的系統中(比如多核CPU)。
臨界區
臨界區用來表示一種公共資源或者說共享數據,可以被多個線程使用,但是每一次只能有一個線程使用它,一旦臨界區資源被占用,其他線程要想使用這個資源就必須等待。
比如,一個辦公室里有一臺打印機,打印機一次只能執行一個任務。如果小王和小明同時需要打印文件,很明顯,如果小王先發了打印任務,打印機就開始打印小王的文件,小明的任務就只能等待小王打印結束后才能打印,這里的打印機就是一個臨界區的例子。
在并行程序中,臨界區資源是保護的對象,如果意外出現打印機同時執行兩個任務的情況,那么最有可能的結果就是打印出來的文件是損壞的文件,它既不是小王想要的,也不是小明想要的。
阻塞(Blocking)和非阻塞(Non-Blocking)
阻塞和非阻塞通常用來形容很多線程間的相互影響。比如一個線程占用了臨界區資源,那么其他所有需要這個資源的線程就必須在這個臨界區中等待。等待會導致線程掛起,這種情況就是阻塞。此時,如果占用資源的線程一直不愿意釋放資源,那么其他線程阻塞在這個臨界區上的線程都不能工作。
非阻塞的意思與之相反,它強調沒有一個線程可以妨礙其他線程執行,所有的線程都會嘗試不斷向前執行。
死鎖(Deadlock)、饑餓(Starvation)和活鎖(Livelock)
死鎖、饑餓和活鎖都屬于多線程的活躍性問題。如果發現上述幾種情況,那么相關線程就不再活躍,也就是說它可能很難再繼續往下執行了。
死鎖應該是最糟糕的一種情況了(當然,其他幾種情況也好不到哪里去),如下圖顯示了一個死鎖的發生:
A、B、C、D四輛小車都在這種情況下都無法繼續行駛了。他們彼此之間相互占用了其他車輛的車道,如果大家都不愿意釋放自己的車道,那么這個狀況將永遠持續下去,誰都不可能通過,死鎖是一個很嚴重的并且應該避免和實時小心的問題,后面的文章中會做更詳細的討論。
饑餓是指某一個或者多個線程因為種種原因無法獲得所要的資源,導致一直無法執行。比如它的優先級可能太低,而高優先級的線程不斷搶占它需要的資源,導致低優先級線程無法工作。在自然界中,母雞給雛鳥喂食很容易出現這種情況:由于雛鳥很多,食物有限,雛鳥之間的事務競爭可能非常厲害,經常搶不到事務的雛鳥有可能被餓死。線程的饑餓非常類似這種情況。此外,某一個線程一直占著關鍵資源不放,導致其他需要這個資源的線程無法正常執行,這種情況也是饑餓的一種。于死鎖想必,饑餓還是有可能在未來一段時間內解決的(比如,高優先級的線程已經完成任務,不再瘋狂執行)。
活鎖是一種非常有趣的情況。不知道大家是否遇到過這么一種場景,當你要做電梯下樓時,電梯到了,門開了,這是你正準備出去。但很不巧的是,門外一個人當著你的去路,他想進來。于是,你很禮貌地靠左走,禮讓對方。同時,對方也非常禮貌的靠右走,希望禮讓你。結果,你們倆就又撞上了。于是乎,你們都意識到了問題,希望盡快避讓對方,你立即向右邊走,同時,他立即向左邊走。結果,又撞上了!不過介于人類的智慧,我相信這個動作重復兩三次后,你應該可以順利解決這個問題。因為這個時候,大家都會本能地對視,進行交流,保證這種情況不再發生。但如果這種情況發生在兩個線程之間可能就不那么幸運了。如果線程智力不夠。且都秉承著“謙讓”的原則,主動將資源釋放給他人使用,那么久會導致資源不斷地在兩個線程間跳動,而沒有一個線程可以同時拿到所有資源正常執行。這種情況就是活鎖。
死鎖的例子package com.jvm.visualvm; public class Demo4 { public static void main(String[] args) { Obj1 obj1 = new Obj1(); Obj2 obj2 = new Obj2(); Thread thread1 = new Thread(new SynAddRunalbe(obj1, obj2, 1, 2, true)); thread1.setName("thread1"); thread1.start(); Thread thread2 = new Thread(new SynAddRunalbe(obj1, obj2, 2, 1, false)); thread2.setName("thread2"); thread2.start(); } /** * 線程死鎖等待演示 */ public static class SynAddRunalbe implements Runnable { Obj1 obj1; Obj2 obj2; int a, b; boolean flag; public SynAddRunalbe(Obj1 obj1, Obj2 obj2, int a, int b, boolean flag) { this.obj1 = obj1; this.obj2 = obj2; this.a = a; this.b = b; this.flag = flag; } @Override public void run() { try { if (flag) { synchronized (obj1) { Thread.sleep(100); synchronized (obj2) { System.out.println(a + b); } } } else { synchronized (obj2) { Thread.sleep(100); synchronized (obj1) { System.out.println(a + b); } } } } catch (InterruptedException e) { e.printStackTrace(); } } } public static class Obj1 { } public static class Obj2 { } }
運行上面代碼,可以通過jstack查看到死鎖信息:
"thread2" #13 prio=5 os_prio=0 tid=0x0000000029225000 nid=0x3c94 waiting for monitor entry [0x0000000029c9f000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.jvm.visualvm.Demo4$SynAddRunalbe.run(Demo4.java:50) - waiting to lock <0x00000007173d40f0> (a com.jvm.visualvm.Demo4$Obj1) - locked <0x00000007173d6310> (a com.jvm.visualvm.Demo4$Obj2) at java.lang.Thread.run(Thread.java:745)
Locked ownable synchronizers:
- None
"thread1" #12 prio=5 os_prio=0 tid=0x0000000029224800 nid=0x6874 waiting for monitor entry [0x0000000029b9f000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.jvm.visualvm.Demo4$SynAddRunalbe.run(Demo4.java:43) - waiting to lock <0x00000007173d6310> (a com.jvm.visualvm.Demo4$Obj2) - locked <0x00000007173d40f0> (a com.jvm.visualvm.Demo4$Obj1) at java.lang.Thread.run(Thread.java:745)
Locked ownable synchronizers:
- None
thread1持有com.jvm.visualvm.Demo4$Obj1的鎖,等待獲取com.jvm.visualvm.Demo4$Obj2的鎖
thread2持有com.jvm.visualvm.Demo4$Obj2的鎖,等待獲取com.jvm.visualvm.Demo4$Obj1的鎖,兩個線程相互等待獲取對方持有的鎖,出現死鎖。
package com.jvm.jconsole; import java.util.concurrent.*; public class ExecutorLock { private static ExecutorService single = Executors.newSingleThreadExecutor(); public static class AnotherCallable implements Callable{ @Override public String call() throws Exception { System.out.println("in AnotherCallable"); return "annother success"; } } public static class MyCallable implements Callable { @Override public String call() throws Exception { System.out.println("in MyCallable"); Future submit = single.submit(new AnotherCallable()); return "success:" + submit.get(); } } public static void main(String[] args) throws ExecutionException, InterruptedException { MyCallable task = new MyCallable(); Future submit = single.submit(task); System.out.println(submit.get()); System.out.println("over"); single.shutdown(); } }
執行代碼,輸出:
in MyCallable
使用jstack命令查看線程堆棧信息:
"pool-1-thread-1" #12 prio=5 os_prio=0 tid=0x0000000028e3d000 nid=0x58a4 waiting on condition [0x00000000297ff000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method) - parking to wait for <0x0000000717921bf0> (a java.util.concurrent.FutureTask) at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175) at java.util.concurrent.FutureTask.awaitDone(FutureTask.java:429) at java.util.concurrent.FutureTask.get(FutureTask.java:191) at com.jvm.jconsole.ExecutorLock$MyCallable.call(ExecutorLock.java:25) at com.jvm.jconsole.ExecutorLock$MyCallable.call(ExecutorLock.java:20) at java.util.concurrent.FutureTask.run(FutureTask.java:266) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at java.lang.Thread.run(Thread.java:745)
Locked ownable synchronizers:
- <0x00000007173f2690> (a java.util.concurrent.ThreadPoolExecutor$Worker)
"main" #1 prio=5 os_prio=0 tid=0x00000000033e4000 nid=0x5f94 waiting on condition [0x00000000031fe000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method) - parking to wait for <0x00000007173f1d48> (a java.util.concurrent.FutureTask) at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175) at java.util.concurrent.FutureTask.awaitDone(FutureTask.java:429) at java.util.concurrent.FutureTask.get(FutureTask.java:191) at com.jvm.jconsole.ExecutorLock.main(ExecutorLock.java:32)
Locked ownable synchronizers:
- None
堆棧信息結合圖中的代碼,可以看出主線程在32行處于等待中,線程池中的工作線程在25行處于等待中,等待獲取結果。由于線程池是一個線程,AnotherCallable得不到執行,而被餓死,最終導致了程序死鎖的現象。
喜歡就關注我吧文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/75793.html
摘要:有三種狀態運行關閉終止。類類,提供了一系列工廠方法用于創建線程池,返回的線程池都實現了接口。線程池的大小一旦達到最大值就會保持不變,在提交新任務,任務將會進入等待隊列中等待。此線程池支持定時以及周期性執行任務的需求。 這是java高并發系列第19篇文章。 本文主要內容 介紹Executor框架相關內容 介紹Executor 介紹ExecutorService 介紹線程池ThreadP...
摘要:方法由兩個參數,表示期望的值,表示要給設置的新值。操作包含三個操作數內存位置預期原值和新值。如果處的值尚未同時更改,則操作成功。中就使用了這樣的操作。上面操作還有一點是將事務范圍縮小了,也提升了系統并發處理的性能。 這是java高并發系列第21篇文章。 本文主要內容 從網站計數器實現中一步步引出CAS操作 介紹java中的CAS及CAS可能存在的問題 悲觀鎖和樂觀鎖的一些介紹及數據庫...
摘要:一旦等到期望的事件,線程就會再次進入運行狀態。表示結束狀態,線程執行完畢之后進入結束狀態。一個進程可以包括多個線程。 進程 進程(Process)是計算機中的程序關于某數據集合上的一次運行活動,是系統進行資源分配和調度的基本單位,是操作系統結構的基礎。程序是指令、數據及其組織形式的描述,進程是程序的實體。 進程具有的特征: 動態性:進程是程序的一次執行過程,是臨時的,有生命期的,是動態...
摘要:我們需要先了解這些概念。在中,其表現在對于共享變量的某些操作,是不可分的,必須連續的完成。有序性有序性指的是程序按照代碼的先后順序執行。 JMM(java內存模型),由于并發程序要比串行程序復雜很多,其中一個重要原因是并發程序中數據訪問一致性和安全性將會受到嚴重挑戰。如何保證一個線程可以看到正確的數據呢?這個問題看起來很白癡。對于串行程序來說,根本就是小菜一碟,如果你讀取一個變量,這個...
摘要:我的是忙碌的一年,從年初備戰實習春招,年三十都在死磕源碼,三月份經歷了阿里五次面試,四月順利收到實習。因為我心理很清楚,我的目標是阿里。所以在收到阿里之后的那晚,我重新規劃了接下來的學習計劃,將我的短期目標更新成拿下阿里轉正。 我的2017是忙碌的一年,從年初備戰實習春招,年三十都在死磕JDK源碼,三月份經歷了阿里五次面試,四月順利收到實習offer。然后五月懷著忐忑的心情開始了螞蟻金...
閱讀 2791·2021-11-02 14:42
閱讀 3174·2021-10-08 10:04
閱讀 1197·2019-08-30 15:55
閱讀 1037·2019-08-30 15:54
閱讀 2328·2019-08-30 15:43
閱讀 1689·2019-08-29 15:18
閱讀 872·2019-08-29 11:11
閱讀 2374·2019-08-26 13:52