詳談java線程與線程、進(jìn)程與進(jìn)程間通信
線程與線程間通信
一、基本概念以及線程與進(jìn)程之間的區(qū)別聯(lián)系:
關(guān)于進(jìn)程和線程,首先從定義上理解就有所不同
1、進(jìn)程是什么?
是具有一定獨(dú)立功能的程序、它是系統(tǒng)進(jìn)行資源分配和調(diào)度的一個獨(dú)立單位,重點(diǎn)在系統(tǒng)調(diào)度和單獨(dú)的單位,也就是說進(jìn)程是可以獨(dú) 立運(yùn)行的一段程序。
2、線程又是什么?
線程進(jìn)程的一個實(shí)體,是CPU調(diào)度和分派的基本單位,他是比進(jìn)程更小的能獨(dú)立運(yùn)行的基本單位,線程自己基本上不擁有系統(tǒng)資源。
在運(yùn)行時,只是暫用一些計(jì)數(shù)器、寄存器和棧 。
他們之間的關(guān)系
1、一個線程只能屬于一個進(jìn)程,而一個進(jìn)程可以有多個線程,但至少有一個線程(通常說的主線程)。
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)的一個執(zhí)行單元,也是進(jìn)程內(nèi)的可調(diào)度實(shí)體。
從三個角度來剖析二者之間的區(qū)別
1、調(diào)度:線程作為調(diào)度和分配的基本單位,進(jìn)程作為擁有資源的基本單位。
2、并發(fā)性:不僅進(jìn)程之間可以并發(fā)執(zhí)行,同一個進(jìn)程的多個線程之間也可以并發(fā)執(zhí)行。
3、擁有資源:進(jìn)程是擁有資源的一個獨(dú)立單位,線程不擁有系統(tǒng)資源,但可以訪問隸屬于進(jìn)程的資源。.
二、多線程間通信方式:
1、共享變量
2、wait/notify機(jī)制
3、Lock/Condition機(jī)制
4、管道
三、共享變量
線程間發(fā)送信號的一個簡單方式是在共享對象的變量里設(shè)置信號值。線程A在一個同步塊里設(shè)置boolean型成員變量hasDataToProcess為true,線程B也在同步塊里讀取hasDataToProcess這個成員變量。這個簡單的例子使用了一個持有信號的對象,并提供了set和check方法:
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必須獲得指向一個MySignal共享實(shí)例的引用,以便進(jìn)行通信。如果它們持有的引用指向不同的MySingal實(shí)例,那么彼此將不能檢測到對方的信號。需要處理的數(shù)據(jù)可以存放在一個共享緩存區(qū)里,它和MySignal實(shí)例是分開存放的。
四、wait()/notify機(jī)制
為了實(shí)現(xiàn)線程通信,我們可以使用Object類提供的wait()、notify()、notifyAll()三個方法。調(diào)用wait()方法會釋放對該同步監(jiān)視器的鎖定。這三個方法必須由同步監(jiān)視器對象來調(diào)用,這可分成兩種情況:
•對于使用synchronized修飾的同步方法,因?yàn)樵擃惖哪J(rèn)實(shí)例是(this)就是同步監(jiān)視器,所以可以直接調(diào)用這三使用個方法。
•對于synchronized修飾的同步代碼塊,同步監(jiān)視器是synchronized括號里的對象,所以必須使用該對象調(diào)用這三個方法。
假設(shè)系統(tǒng)中有兩條線程,這兩條線程分別代表取錢者和存錢者?,F(xiàn)在系統(tǒng)有一種特殊的要求,系統(tǒng)要求存款者和取錢者不斷的實(shí)現(xiàn)存款和取錢動作,而且要求每當(dāng)存款者將錢存入指定賬戶后,取錢者立即將錢取走.不允許存款者兩次存錢,也不允許取錢者兩次取錢。
我們通過設(shè)置一個旗標(biāo)來標(biāo)識賬戶中是否已有存款,有就為true,沒有就標(biāo)為false。具體代碼如下:
首先我們定義一個Account類,這個類中有取錢和存錢的兩個方法,由于這兩個方法可能需要并發(fā)的執(zhí)行取錢、存錢操作,所有將這兩個方法都修改為同步方法.(使用synchronized關(guān)鍵字)。
public class Account {
private String accountNo;
private double balance;
//標(biāo)識賬戶中是否有存款的旗標(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)識賬戶是否已有存款的標(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)建兩個線程類,分別為取錢和存錢線程!
取錢線程類:
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);
}
}
}
存錢線程類:
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);
}
}
}
最后我們測試一下這個取錢和存錢的操作
public class TestDraw {
public static void main(String[] args) {
//創(chuàng)建一個賬戶
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é)果:
存款者甲存錢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對像來保持同步,則系統(tǒng)中不存在隱式的同步監(jiān)視器對象,也就不能使用wait()、notify()、notifyAll()來協(xié)調(diào)線程的運(yùn)行.
當(dāng)使用LOCK對象保持同步時,JAVA為我們提供了Condition類來協(xié)調(diào)線程的運(yùn)行。關(guān)于Condition類,JDK文檔里進(jìn)行了詳細(xì)的解釋.,再次就不啰嗦了。
我們就拿Account類進(jìn)行稍微的修改 一下吧!
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Account {
//顯示定義Lock對象
private final Lock lock=new ReentrantLock();
//獲得指定Lock對象對應(yīng)的條件變量
private final Condition con=lock.newCondition();
private String accountNo;
private double balance;
//標(biāo)識賬戶中是否有存款的旗標(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)識賬戶是否已有存款的標(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對像來充當(dāng)同步監(jiān)視器,使用Condition對象來暫停指定線程,喚醒指定線程!
六、管道
管道流是JAVA中線程通訊的常用方式之一,基本流程如下:
1)創(chuàng)建管道輸出流PipedOutputStream pos和管道輸入流PipedInputStream pis
2)將pos和pis匹配,pos.connect(pis);
3)將pos賦給信息輸入線程,pis賦給信息獲取線程,就可以實(shí)現(xiàn)線程間的通訊了
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);
/**
* 啟動線程
*/
p.start();
c1.start();
}
}
/**
* 生產(chǎn)者線程(與一個管道輸入流相關(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)者線程(與一個管道輸入流相關(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();
}
}
}
程序啟動后,就可以看到producer線程往consumer1線程發(fā)送數(shù)據(jù)
consumer1:0 consumer1:1 consumer1:2 consumer1:3 ......
管道流雖然使用起來方便,但是也有一些缺點(diǎn)
1)管道流只能在兩個線程之間傳遞數(shù)據(jù)
線程consumer1和consumer2同時從pis中read數(shù)據(jù),當(dāng)線程producer往管道流中寫入一段數(shù)據(jù)后,每一個時刻只有一個線程能獲取到數(shù)據(jù),并不是兩個線程都能獲取到producer發(fā)送來的數(shù)據(jù),因此一個管道流只能用于兩個線程間的通訊。不僅僅是管道流,其他IO方式都是一對一傳輸。
2)管道流只能實(shí)現(xiàn)單向發(fā)送,如果要兩個線程之間互通訊,則需要兩個管道流
可以看到上面的例子中,線程producer通過管道流向線程consumer發(fā)送數(shù)據(jù),如果線程consumer想給線程producer發(fā)送數(shù)據(jù),則需要新建另一個管道流pos1和pis1,將pos1賦給consumer1,將pis1賦給producer,具體例子本文不再多說。
II.進(jìn)程與進(jìn)程間通信
一、進(jìn)程間通信方式
(1)管道(Pipe):管道可用于具有親緣關(guān)系進(jìn)程間的通信,允許一個進(jìn)程和另一個與它有共同祖先的進(jìn)程之間進(jìn)行通信。
(2)命名管道(named pipe):命名管道克服了管道沒有名字的限制,因此,除具有管道所具有的功能外,它還允許無親緣關(guān) 系 進(jìn)程間的通信。命名管道在文件系統(tǒng)中有對應(yīng)的文件名。命名管道通過命令mkfifo或系統(tǒng)調(diào)用mkfifo來創(chuàng)建。
(3)信號(Signal):信號是比較復(fù)雜的通信方式,用于通知接受進(jìn)程有某種事件發(fā)生,除了用于進(jìn)程間通信外,進(jìn)程還可以發(fā)送 信號給進(jìn)程本身;linux除了支持Unix早期信號語義函數(shù)sigal外,還支持語義符合Posix.1標(biāo)準(zhǔn)的信號函數(shù)sigaction(實(shí)際上,該函數(shù)是基于BSD的,BSD為了實(shí)現(xiàn)可靠信號機(jī)制,又能夠統(tǒng)一對外接口,用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ì)列克服了信號承載信息量少,管道只能承載無格式字節(jié)流以及緩沖區(qū)大小受限等缺
(5)共享內(nèi)存:使得多個進(jìn)程可以訪問同一塊內(nèi)存空間,是最快的可用IPC形式。是針對其他通信機(jī)制運(yùn)行效率較低而設(shè)計(jì)的。往往與其它通信機(jī)制,如信號量結(jié)合使用,來達(dá)到進(jìn)程間的同步及互斥。
(6)內(nèi)存映射(mapped memory):內(nèi)存映射允許任何多個進(jìn)程間通信,每一個使用該機(jī)制的進(jìn)程通過把一個共享的文件映射到自己的進(jìn)程地址空間來實(shí)現(xiàn)它。
(7)信號量(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)容了,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
基于springboot的flowable工作流實(shí)戰(zhàn)流程分析
這篇文章主要介紹了基于springboot的flowable工作流實(shí)戰(zhàn)流程分析,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-10-10
SpringBoot+Vue靜態(tài)資源刷新后無法訪問的問題解決方案
這篇文章主要介紹了SpringBoot+Vue靜態(tài)資源刷新后無法訪問的問題解決方案,文中通過代碼示例和圖文講解的非常詳細(xì),對大家解決問題有一定的幫助,需要的朋友可以參考下2024-05-05
SpringCloud?Eureka服務(wù)注冊中心應(yīng)用入門詳解
這篇文章主要介紹了Spring?Cloud?Eureka服務(wù)注冊中心入門流程分析,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-07-07
Spring Boot容器加載時執(zhí)行特定操作(推薦)
這篇文章主要介紹了Spring Boot容器加載時執(zhí)行特定操作及spring內(nèi)置的事件,需要的朋友可以參考下2018-01-01
深入理解springboot中配置文件application.properties
本文主要介紹了springboot中配置文件application.properties,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-10-10
springMVC返回復(fù)雜的json格式數(shù)據(jù)方法
下面小編就為大家分享一篇springMVC返回復(fù)雜的json格式數(shù)據(jù)方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-03-03

