線程間通信:由于多線程共享地址空間和數據空間,所以多個線程間的通信是一個線程的數據可以直接提供給其他線程使用,而不必通過操作系統(也就是內核的調度)。
進程間的通信則不同,它的數據空間的獨立性決定了它的通信相對比較復雜,需要通過操作系統。以前進程間的通信只能是單機版的,現在操作系統都繼承了基于套接字(socket)的進程間的通信機制。這樣進程間的通信就不局限于單臺計算機了,實現了網絡通信。線程通信主要分為以下幾個部分,下面通過生活中圖書館借書的例子簡單講解以下:
通過共享對象通信
加入圖書館只有一本《java并發編程實戰》,小A早上的時候把這本書給借走了,然后下午小B去圖書館去找這本書,這時候小A和小B是兩個線程,《java并發編程實戰》就是共享對象(類似于多線程中的全局變量的資源),小B發現這本書已經被借走了,所以就回去等了幾天,幾天后,小B又去圖書館發現這本書被還回來了,就把書借走了,這就是通過共享對象進行通信。
忙等待
由于快要BAT實習生招聘了,所以小B非常想看這本書,所以小B就每隔一個小時(while循環)就去看看這本書有沒有被還回來了,這樣雖然比較耗費處理器資源,但是只要書一旦被還回來,小B就可以馬上知道。
wait() notify() notifyAll()
由于圖書館隔著宿舍比較近,所以小B發現每隔一個小時就去圖書館身體有點吃不消,不過很快,學校的圖書館系統增加了短信提醒功能(notify()),所以小B可以一邊睡覺一邊等短信。
丟失的信號
圖書館系統是這么設計的,當有一本書被還回來的時候,就會給等待者發短信,但是短信只能發送一次,如果沒有等待者,短信也會發出(只不過這個時候沒有沒有接受者),問題出現了,因為短信只會發一次,當書被還回來的時候,沒有人等待借書,他會發一條空短信,但是之后有等待借此本書的同學永遠也不會再收到短信,導致這些同學會無休止的等待。為了解決這個問題,我們要進入等待狀態的時候先打電話問問圖書館阿姨是否需要繼續等待。
假喚醒
圖書館系統有一個bug,會是不是給用戶發送錯誤短信,我們很聽話,收到短信就會去圖書館借書,但是到達圖書館后發現書根本就沒有被還回來,然后接著做其他的事情。
線程間的通信方式
#鎖機制:包括互斥鎖、條件變量、讀寫鎖
*互斥鎖提供了以排他方式防止數據結構被并發修改的方法。
*讀寫鎖允許多個線程同時讀共享數據,而對寫操作是互斥的。
*條件變量可以以原子的方式阻塞進程,直到某個特定條件為真為止。對條件的測試是在互斥鎖的保護下進行的。條件變量始終與互斥鎖一起使用。
#信號量機制(Semaphore):包括無名線程信號量和命名線程信號量
#信號機制(Signal):類似進程間的信號處理
線程間的通信目的主要是用于線程同步,所以線程沒有像進程通信中的用于數據交換的通信機制。
我覺得我對線程通信的理解還是不夠徹底,下面分享幾段代碼,幫助理解:
①同步
這里講的同步是指多個線程通過synchronized關鍵字這種方式來實現線程間的通信。
參考示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
public class MyObject { synchronized public void methodA() { //do something.... } synchronized public void methodB() { //do some other thing } } public class ThreadA extends Thread { private MyObject object; //省略構造方法 @Override public void run() { super .run(); object.methodA(); } } public class ThreadB extends Thread { private MyObject object; //省略構造方法 @Override public void run() { super .run(); object.methodB(); } } public class Run { public static void main(String[] args) { MyObject object = new MyObject(); //線程A與線程B 持有的是同一個對象:object ThreadA a = new ThreadA(object); ThreadB b = new ThreadB(object); a.start(); b.start(); } } |
由于線程A和線程B持有同一個MyObject類的對象object,盡管這兩個線程需要調用不同的方法,但是它們是同步執行的,比如:線程B需要等待線程A執行完了methodA()方法之后,它才能執行methodB()方法。這樣,線程A和線程B就實現了通信。
這種方式,本質上就是“共享內存”式的通信。多個線程需要訪問同一個共享變量,誰拿到了鎖(獲得了訪問權限),誰就可以執行。
②while輪詢的方式
代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
|
import java.util.ArrayList; import java.util.List; public class MyList { private List<String> list = new ArrayList<String>(); public void add() { list.add( "elements" ); } public int size() { return list.size(); } } import mylist.MyList; public class ThreadA extends Thread { private MyList list; public ThreadA(MyList list) { super (); this .list = list; } @Override public void run() { try { for ( int i = 0 ; i < 10 ; i++) { list.add(); System.out.println( "添加了" + (i + 1 ) + "個元素" ); Thread.sleep( 1000 ); } } catch (InterruptedException e) { e.printStackTrace(); } } } import mylist.MyList; public class ThreadB extends Thread { private MyList list; public ThreadB(MyList list) { super (); this .list = list; } @Override public void run() { try { while ( true ) { if (list.size() == 5 ) { System.out.println( "==5, 線程b準備退出了" ); throw new InterruptedException(); } } } catch (InterruptedException e) { e.printStackTrace(); } } } import mylist.MyList; import extthread.ThreadA; import extthread.ThreadB; public class Test { public static void main(String[] args) { MyList service = new MyList(); ThreadA a = new ThreadA(service); a.setName( "A" ); a.start(); ThreadB b = new ThreadB(service); b.setName( "B" ); b.start(); } } |
在這種方式下,線程A不斷地改變條件,線程ThreadB不停地通過while語句檢測這個條件(list.size()==5)是否成立 ,從而實現了線程間的通信。但是這種方式會浪費CPU資源。之所以說它浪費資源,是因為JVM調度器將CPU交給線程B執行時,它沒做啥“有用”的工作,只是在不斷地測試 某個條件是否成立。就類似于現實生活中,某個人一直看著手機屏幕是否有電話來了,而不是: 在干別的事情,當有電話來時,響鈴通知TA電話來了。關于線程的輪詢的影響
③wait/notify機制
代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
|
import java.util.ArrayList; import java.util.List; public class MyList { private static List<String> list = new ArrayList<String>(); public static void add() { list.add( "anyString" ); } public static int size() { return list.size(); } } public class ThreadA extends Thread { private Object lock; public ThreadA(Object lock) { super (); this .lock = lock; } @Override public void run() { try { synchronized (lock) { if (MyList.size() != 5 ) { System.out.println( "wait begin " + System.currentTimeMillis()); lock.wait(); System.out.println( "wait end " + System.currentTimeMillis()); } } } catch (InterruptedException e) { e.printStackTrace(); } } } public class ThreadB extends Thread { private Object lock; public ThreadB(Object lock) { super (); this .lock = lock; } @Override public void run() { try { synchronized (lock) { for ( int i = 0 ; i < 10 ; i++) { MyList.add(); if (MyList.size() == 5 ) { lock.notify(); System.out.println( "已經發出了通知" ); } System.out.println( "添加了" + (i + 1 ) + "個元素!" ); Thread.sleep( 1000 ); } } } catch (InterruptedException e) { e.printStackTrace(); } } } public class Run { public static void main(String[] args) { try { Object lock = new Object(); ThreadA a = new ThreadA(lock); a.start(); Thread.sleep( 50 ); ThreadB b = new ThreadB(lock); b.start(); } catch (InterruptedException e) { e.printStackTrace(); } } } |
線程A要等待某個條件滿足時(list.size()==5),才執行操作。線程B則向list中添加元素,改變list的size。
A,B之間如何通信的呢?也就是說,線程A如何知道list.size()已經為5了呢?
這里用到了Object類的wait()和notify()方法。
當條件未滿足時(list.size()!=5),線程A調用wait()放棄CPU,并進入阻塞狀態。---不像②while輪詢那樣占用CPU
當條件滿足時,線程B調用notify()通知線程A,所謂通知線程A,就是喚醒線程A,并讓它進入可運行狀態。
這種方式的一個好處就是CPU的利用率提高了。
但是也有一些缺點:比如,線程B先執行,一下子添加了5個元素并調用了notify()發送了通知,而此時線程A還執行;當線程A執行并調用wait()時,那它永遠就不可能被喚醒了。因為,線程B已經發了通知了,以后不再發通知了。這說明:通知過早,會打亂程序的執行邏輯。
④管道通信就是使用java.io.PipedInputStream和java.io.PipedOutputStream進行通信
具體就不介紹了。分布式系統中說的兩種通信機制:共享內存機制和消息通信機制。感覺前面的①中的synchronized關鍵字和②中的while輪詢“屬于”共享內存機制,由于是輪詢的條件使用了volatile關鍵字修飾時,這就表示它們通過判斷這個“共享的條件變量“是否改變了,來實現進程間的交流。
而管道通信,更像消息傳遞機制,也就是說:通過管道,將一個線程中的消息發送給另一個。
總結
以上就是本文關于java線程間通信的通俗解釋的全部內容,希望對大家有所幫助。如有不足之處,歡迎留言指出。
原文鏈接:http://blog.csdn.net/guang09080908/article/details/46956191