一、基本概念以及線程與進(jìn)程之間的區(qū)別聯(lián)系:
關(guān)于進(jìn)程和線程,首先從定義上理解就有所不同
1、進(jìn)程是什么?
是具有一定獨(dú)立功能的程序、它是系統(tǒng)進(jìn)行資源分配和調(diào)度的一個(gè)獨(dú)立單位,重點(diǎn)在系統(tǒng)調(diào)度和單獨(dú)的單位,也就是說進(jìn)程是可以獨(dú) 立運(yùn)行的一段程序。
2、線程又是什么?
線程進(jìn)程的一個(gè)實(shí)體,是CPU調(diào)度和分派的基本單位,他是比進(jìn)程更小的能獨(dú)立運(yùn)行的基本單位,線程自己基本上不擁有系統(tǒng)資源。
在運(yùn)行時(shí),只是暫用一些計(jì)數(shù)器、寄存器和棧 。
他們之間的關(guān)系
1、一個(gè)線程只能屬于一個(gè)進(jìn)程,而一個(gè)進(jìn)程可以有多個(gè)線程,但至少有一個(gè)線程(通常說的主線程)。
2、資源分配給進(jìn)程,同一進(jìn)程的所有線程共享該進(jìn)程的所有資源。
3、線程在執(zhí)行過程中,需要協(xié)作同步。不同進(jìn)程的線程間要利用消息通信的辦法實(shí)現(xiàn)同步。
4、處理機(jī)分給線程,即真正在處理機(jī)上運(yùn)行的是線程。
5、線程是指進(jìn)程內(nèi)的一個(gè)執(zhí)行單元,也是進(jìn)程內(nèi)的可調(diào)度實(shí)體。
從三個(gè)角度來剖析二者之間的區(qū)別
1、調(diào)度:線程作為調(diào)度和分配的基本單位,進(jìn)程作為擁有資源的基本單位。
2、并發(fā)性:不僅進(jìn)程之間可以并發(fā)執(zhí)行,同一個(gè)進(jìn)程的多個(gè)線程之間也可以并發(fā)執(zhí)行。
3、擁有資源:進(jìn)程是擁有資源的一個(gè)獨(dú)立單位,線程不擁有系統(tǒng)資源,但可以訪問隸屬于進(jìn)程的資源。.
二、多線程間通信方式:
1、共享變量
2、wait/notify機(jī)制
3、Lock/Condition機(jī)制
4、管道
三、共享變量
線程間發(fā)送信號(hào)的一個(gè)簡單方式是在共享對(duì)象的變量里設(shè)置信號(hào)值。線程A在一個(gè)同步塊里設(shè)置boolean型成員變量hasDataToProcess為true,線程B也在同步塊里讀取hasDataToProcess這個(gè)成員變量。這個(gè)簡單的例子使用了一個(gè)持有信號(hào)的對(duì)象,并提供了set和check方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public class MySignal{ protected boolean hasDataToProcess = false ; public synchronized boolean hasDataToProcess(){ return this .hasDataToProcess; } public synchronized void setHasDataToProcess( boolean hasData){ this .hasDataToProcess = hasData; } } |
線程A和B必須獲得指向一個(gè)MySignal共享實(shí)例的引用,以便進(jìn)行通信。如果它們持有的引用指向不同的MySingal實(shí)例,那么彼此將不能檢測(cè)到對(duì)方的信號(hào)。需要處理的數(shù)據(jù)可以存放在一個(gè)共享緩存區(qū)里,它和MySignal實(shí)例是分開存放的。
四、wait()/notify機(jī)制
為了實(shí)現(xiàn)線程通信,我們可以使用Object類提供的wait()、notify()、notifyAll()三個(gè)方法。調(diào)用wait()方法會(huì)釋放對(duì)該同步監(jiān)視器的鎖定。這三個(gè)方法必須由同步監(jiān)視器對(duì)象來調(diào)用,這可分成兩種情況:
•對(duì)于使用synchronized修飾的同步方法,因?yàn)樵擃惖哪J(rèn)實(shí)例是(this)就是同步監(jiān)視器,所以可以直接調(diào)用這三使用個(gè)方法。
•對(duì)于synchronized修飾的同步代碼塊,同步監(jiān)視器是synchronized括號(hào)里的對(duì)象,所以必須使用該對(duì)象調(diào)用這三個(gè)方法。
假設(shè)系統(tǒng)中有兩條線程,這兩條線程分別代表取錢者和存錢者。現(xiàn)在系統(tǒng)有一種特殊的要求,系統(tǒng)要求存款者和取錢者不斷的實(shí)現(xiàn)存款和取錢動(dòng)作,而且要求每當(dāng)存款者將錢存入指定賬戶后,取錢者立即將錢取走.不允許存款者兩次存錢,也不允許取錢者兩次取錢。
我們通過設(shè)置一個(gè)旗標(biāo)來標(biāo)識(shí)賬戶中是否已有存款,有就為true,沒有就標(biāo)為false。具體代碼如下:
首先我們定義一個(gè)Account類,這個(gè)類中有取錢和存錢的兩個(gè)方法,由于這兩個(gè)方法可能需要并發(fā)的執(zhí)行取錢、存錢操作,所有將這兩個(gè)方法都修改為同步方法.(使用synchronized關(guān)鍵字)。
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
|
public class Account { private String accountNo; private double balance; //標(biāo)識(shí)賬戶中是否有存款的旗標(biāo) private boolean flag= false ; public Account() { super (); } public Account(String accountNo, double balance) { super (); this .accountNo = accountNo; this .balance = balance; } public synchronized void draw ( double drawAmount){ try { if (!flag){ this .wait(); } else { //取錢 System.out.println(Thread.currentThread().getName()+ " 取錢:" +drawAmount); balance=balance-drawAmount; System.out.println( "余額 : " +balance); //將標(biāo)識(shí)賬戶是否已有存款的標(biāo)志設(shè)為false flag= false ; //喚醒其它線程 this .notifyAll(); } } catch (Exception e) { e.printStackTrace(); } } public synchronized void deposit( double depositAmount){ try { if (flag){ this .wait(); } else { System.out.println(Thread.currentThread().getName()+ "存錢" +depositAmount); balance=balance+depositAmount; System.out.println( "賬戶余額為:" +balance); flag= true ; //喚醒其它線程 this .notifyAll(); } } catch (Exception e) { // TODO: handle exception e.printStackTrace(); } } } |
接下來創(chuàng)建兩個(gè)線程類,分別為取錢和存錢線程!
取錢線程類:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
public class DrawThread implements Runnable { private Account account; private double drawAmount; public DrawThread(Account account, double drawAmount) { super (); this .account = account; this .drawAmount = drawAmount; } public void run() { for ( int i= 0 ;i< 100 ;i++){ account.draw(drawAmount); } } } |
存錢線程類:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
public class depositThread implements Runnable{ private Account account; private double depositAmount; public depositThread(Account account, double depositAmount) { super (); this .account = account; this .depositAmount = depositAmount; } public void run() { for ( int i= 0 ;i< 100 ;i++){ account.deposit(depositAmount); } } } |
最后我們測(cè)試一下這個(gè)取錢和存錢的操作
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public class TestDraw { public static void main(String[] args) { //創(chuàng)建一個(gè)賬戶 Account account= new Account(); new Thread( new DrawThread(account, 800 ), "取錢者" ).start(); new Thread( new depositThread(account, 800 ), "存款者甲" ).start(); new Thread( new depositThread(account, 800 ), "存款者乙" ).start(); new Thread( new depositThread(account, 800 ), "存款者丙" ).start(); } } |
大致的輸出結(jié)果:
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
|
存款者甲存錢 800.0 賬戶余額為: 800.0 取錢者 取錢: 800.0 余額 : 0.0 存款者丙存錢 800.0 賬戶余額為: 800.0 取錢者 取錢: 800.0 余額 : 0.0 存款者甲存錢 800.0 賬戶余額為: 800.0 取錢者 取錢: 800.0 余額 : 0.0 存款者丙存錢 800.0 賬戶余額為: 800.0 取錢者 取錢: 800.0 余額 : 0.0 存款者甲存錢 800.0 賬戶余額為: 800.0 取錢者 取錢: 800.0 余額 : 0.0 存款者丙存錢 800.0 賬戶余額為: 800.0 取錢者 取錢: 800.0 余額 : 0.0 存款者甲存錢 800.0 賬戶余額為: 800.0 取錢者 取錢: 800.0 余額 : 0.0 存款者丙存錢 800.0 賬戶余額為: 800.0 取錢者 取錢: 800.0 余額 : 0.0 存款者甲存錢 800.0 賬戶余額為: 800.0 取錢者 取錢: 800.0 余額 : 0.0 |
五、Lock/Condition機(jī)制
如何程序不使用synchronized關(guān)鍵字來保持同步,而是直接適用Lock對(duì)像來保持同步,則系統(tǒng)中不存在隱式的同步監(jiān)視器對(duì)象,也就不能使用wait()、notify()、notifyAll()來協(xié)調(diào)線程的運(yùn)行.
當(dāng)使用LOCK對(duì)象保持同步時(shí),JAVA為我們提供了Condition類來協(xié)調(diào)線程的運(yùn)行。關(guān)于Condition類,JDK文檔里進(jìn)行了詳細(xì)的解釋.,再次就不啰嗦了。
我們就拿Account類進(jìn)行稍微的修改 一下吧!
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
77
78
79
80
|
import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class Account { //顯示定義Lock對(duì)象 private final Lock lock= new ReentrantLock(); //獲得指定Lock對(duì)象對(duì)應(yīng)的條件變量 private final Condition con=lock.newCondition(); private String accountNo; private double balance; //標(biāo)識(shí)賬戶中是否有存款的旗標(biāo) private boolean flag= false ; public Account() { super (); } public Account(String accountNo, double balance) { super (); this .accountNo = accountNo; this .balance = balance; } public void draw ( double drawAmount){ //加鎖 lock.lock(); try { if (!flag){ // this.wait(); con.await(); } else { //取錢 System.out.println(Thread.currentThread().getName()+ " 取錢:" +drawAmount); balance=balance-drawAmount; System.out.println( "余額 : " +balance); //將標(biāo)識(shí)賬戶是否已有存款的標(biāo)志設(shè)為false flag= false ; //喚醒其它線程 // this.notifyAll(); con.signalAll(); } } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } public void deposit( double depositAmount){ //加鎖 lock.lock(); try { if (flag){ // this.wait(); con.await(); } else { System.out.println(Thread.currentThread().getName()+ "存錢" +depositAmount); balance=balance+depositAmount; System.out.println( "賬戶余額為:" +balance); flag= true ; //喚醒其它線程 // this.notifyAll(); con.signalAll(); } } catch (Exception e) { // TODO: handle exception e.printStackTrace(); } finally { lock.unlock(); } } } |
輸出結(jié)果和上面是一樣的! 只不過這里 顯示的使用Lock對(duì)像來充當(dāng)同步監(jiān)視器,使用Condition對(duì)象來暫停指定線程,喚醒指定線程!
六、管道
管道流是JAVA中線程通訊的常用方式之一,基本流程如下:
1)創(chuàng)建管道輸出流PipedOutputStream pos和管道輸入流PipedInputStream pis
2)將pos和pis匹配,pos.connect(pis);
3)將pos賦給信息輸入線程,pis賦給信息獲取線程,就可以實(shí)現(xiàn)線程間的通訊了
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
77
78
79
80
81
82
83
84
85
86
87
|
import java.io.IOException; import java.io.PipedInputStream; import java.io.PipedOutputStream; public class testPipeConnection { public static void main(String[] args) { /** * 創(chuàng)建管道輸出流 */ PipedOutputStream pos = new PipedOutputStream(); /** * 創(chuàng)建管道輸入流 */ PipedInputStream pis = new PipedInputStream(); try { /** * 將管道輸入流與輸出流連接 此過程也可通過重載的構(gòu)造函數(shù)來實(shí)現(xiàn) */ pos.connect(pis); } catch (IOException e) { e.printStackTrace(); } /** * 創(chuàng)建生產(chǎn)者線程 */ Producer p = new Producer(pos); /** * 創(chuàng)建消費(fèi)者線程 */ Consumer1 c1 = new Consumer1(pis); /** * 啟動(dòng)線程 */ p.start(); c1.start(); } } /** * 生產(chǎn)者線程(與一個(gè)管道輸入流相關(guān)聯(lián)) * */ class Producer extends Thread { private PipedOutputStream pos; public Producer(PipedOutputStream pos) { this .pos = pos; } public void run() { int i = 0 ; try { while ( true ) { this .sleep( 3000 ); pos.write(i); i++; } } catch (Exception e) { e.printStackTrace(); } } } /** * 消費(fèi)者線程(與一個(gè)管道輸入流相關(guān)聯(lián)) * */ class Consumer1 extends Thread { private PipedInputStream pis; public Consumer1(PipedInputStream pis) { this .pis = pis; } public void run() { try { while ( true ) { System.out.println( "consumer1:" +pis.read()); } } catch (IOException e) { e.printStackTrace(); } } } |
程序啟動(dòng)后,就可以看到producer線程往consumer1線程發(fā)送數(shù)據(jù)
1
2
3
4
5
|
consumer1: 0 consumer1: 1 consumer1: 2 consumer1: 3 ...... |
管道流雖然使用起來方便,但是也有一些缺點(diǎn)
1)管道流只能在兩個(gè)線程之間傳遞數(shù)據(jù)
線程consumer1和consumer2同時(shí)從pis中read數(shù)據(jù),當(dāng)線程producer往管道流中寫入一段數(shù)據(jù)后,每一個(gè)時(shí)刻只有一個(gè)線程能獲取到數(shù)據(jù),并不是兩個(gè)線程都能獲取到producer發(fā)送來的數(shù)據(jù),因此一個(gè)管道流只能用于兩個(gè)線程間的通訊。不僅僅是管道流,其他IO方式都是一對(duì)一傳輸。
2)管道流只能實(shí)現(xiàn)單向發(fā)送,如果要兩個(gè)線程之間互通訊,則需要兩個(gè)管道流
可以看到上面的例子中,線程producer通過管道流向線程consumer發(fā)送數(shù)據(jù),如果線程consumer想給線程producer發(fā)送數(shù)據(jù),則需要新建另一個(gè)管道流pos1和pis1,將pos1賦給consumer1,將pis1賦給producer,具體例子本文不再多說。
II.進(jìn)程與進(jìn)程間通信
一、進(jìn)程間通信方式
(1)管道(Pipe):管道可用于具有親緣關(guān)系進(jìn)程間的通信,允許一個(gè)進(jìn)程和另一個(gè)與它有共同祖先的進(jìn)程之間進(jìn)行通信。
(2)命名管道(named pipe):命名管道克服了管道沒有名字的限制,因此,除具有管道所具有的功能外,它還允許無親緣關(guān) 系 進(jìn)程間的通信。命名管道在文件系統(tǒng)中有對(duì)應(yīng)的文件名。命名管道通過命令mkfifo或系統(tǒng)調(diào)用mkfifo來創(chuàng)建。
(3)信號(hào)(Signal):信號(hào)是比較復(fù)雜的通信方式,用于通知接受進(jìn)程有某種事件發(fā)生,除了用于進(jìn)程間通信外,進(jìn)程還可以發(fā)送 信號(hào)給進(jìn)程本身;linux除了支持Unix早期信號(hào)語義函數(shù)sigal外,還支持語義符合Posix.1標(biāo)準(zhǔn)的信號(hào)函數(shù)sigaction(實(shí)際上,該函數(shù)是基于BSD的,BSD為了實(shí)現(xiàn)可靠信號(hào)機(jī)制,又能夠統(tǒng)一對(duì)外接口,用sigaction函數(shù)重新實(shí)現(xiàn)了signal函數(shù))。
(4)消息(Message)隊(duì)列:消息隊(duì)列是消息的鏈接表,包括Posix消息隊(duì)列system V消息隊(duì)列。有足夠權(quán)限的進(jìn)程可以向隊(duì)列中添加消息,被賦予讀權(quán)限的進(jìn)程則可以讀走隊(duì)列中的消息。消息隊(duì)列克服了信號(hào)承載信息量少,管道只能承載無格式字節(jié)流以及緩沖區(qū)大小受限等缺
(5)共享內(nèi)存:使得多個(gè)進(jìn)程可以訪問同一塊內(nèi)存空間,是最快的可用IPC形式。是針對(duì)其他通信機(jī)制運(yùn)行效率較低而設(shè)計(jì)的。往往與其它通信機(jī)制,如信號(hào)量結(jié)合使用,來達(dá)到進(jìn)程間的同步及互斥。
(6)內(nèi)存映射(mapped memory):內(nèi)存映射允許任何多個(gè)進(jìn)程間通信,每一個(gè)使用該機(jī)制的進(jìn)程通過把一個(gè)共享的文件映射到自己的進(jìn)程地址空間來實(shí)現(xiàn)它。
(7)信號(hào)量(semaphore):主要作為進(jìn)程間以及同一進(jìn)程不同線程之間的同步手段。
(8)套接口(Socket):更為一般的進(jìn)程間通信機(jī)制,可用于不同機(jī)器之間的進(jìn)程間通信。起初是由Unix系統(tǒng)的BSD分支開發(fā)出來的,但現(xiàn)在一般可以移植到其它類Unix系統(tǒng)上:Linux和System V的變種都支持套接字。
以上這篇詳談java線程與線程、進(jìn)程與進(jìn)程間通信就是小編分享給大家的全部內(nèi)容了,希望能給大家一個(gè)參考,也希望大家多多支持服務(wù)器之家。