Java线程与线程、进程与进程之间通信方式
1.1 基本概念以及线程与进程之间的区别联系
- 关于进程和线程,首先从定义上理解就有所不同:
- 进程是具有一定独立功能的程序、它是系统进行资源分配和调度的一个独立单位,重点在系统调度和单独的单位,也就是说进程是可以独 立运行的一段程序。
- 线程是进程的一个实体,是CPU调度和分派的基本单位,他是比进程更小的能独立运行的基本单位,线程自己基本上不拥有系统资源。在运行时,只是暂用一些计数器、寄存器和栈 。
他们之间的关系
- 一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程(通常说的主线程)。
- 资源分配给进程,同一进程的所有线程共享该进程的所有资源。
- 线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。
- 处理机分给线程,即真正在处理机上运行的是线程。
- 线程是指进程内的一个执行单元,也是进程内的可调度实体。
从三个角度来剖析二者之间的区别
- 调度:线程作为调度和分配的基本单位,进程作为拥有资源的基本单位。
- 并发性:不仅进程之间可以并发执行,同一个进程的多个线程之间也可以并发执行。
- 拥有资源:进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于进程的资源。
1.2 多线程间通信方式
- 共享变量
- wait/notify机制
- Lock/Condition机制
- 管道
1.2.1 共享变量
- 线程间发送信号的一个简单方式是在共享对象的变量里设置信号值。线程A在一个同步块里设置boolean型成员变量hasDataToProcess为true,线程B也在同步块里读取hasDataToProcess这个成员变量。这个简单的例子使用了一个持有信号的对象,并提供了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必须获得指向一个MySignal共享实例的引用,以便进行通信。如果它们持有的引用指向不同的MySingal实例,那么彼此将不能检测到对方的信号。需要处理的数据可以存放在一个共享缓存区里,它和MySignal实例是分开存放的。
1.2.2 wait()/notify机制
- 为了实现线程通信,我们可以使用Object类提供的wait()、notify()、notifyAll()三个方法。调用wait()方法会释放对该同步监视器的锁定。这三个方法必须由同步监视器对象来调用,这可分成两种情况:
- 对于使用synchronized修饰的同步方法,因为该类的默认实例是(this)就是同步监视器,所以可以直接调用这三使用个方法。
- 对于synchronized修饰的同步代码块,同步监视器是synchronized括号里的对象,所以必须使用该对象调用这三个方法。
假设系统中有两条线程,这两条线程分别代表取钱者和存钱者。现在系统有一种特殊的要求,系统要求存款者和取钱者不断的实现存款和取钱动作,而且要求每当存款者将钱存入指定账户后,取钱者立即将钱取走.不允许存款者两次存钱,也不允许取钱者两次取钱。
我们通过设置一个旗标来标识账户中是否已有存款,有就为true,没有就标为false。具体代码如下:首先我们定义一个Account类,这个类中有取钱和存钱的两个方法,由于这两个方法可能需要并发的执行取钱、存钱操作,所有将这两个方法都修改为同步方法.(使用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
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;
//标识账户中是否有存款的旗标
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);
//将标识账户是否已有存款的标志设为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();
}
}
}
|
- 接下来创建两个线程类,分别为取钱和存钱线程!
- 取钱线程类:
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);
}
}
}
|
- 最后我们测试一下这个取钱和存钱的操作
1
2
3
4
5
6
7
8
9
10
|
public class TestDraw {
public static void main(String[] args) {
//创建一个账户
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();
}
}
|
- 大致的输出结果如下
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
|
1.2.3 Lock/Condition机制
- 如何程序不使用synchronized关键字来保持同步,而是直接适用Lock对像来保持同步,则系统中不存在隐式的同步监视器对象,也就不能使用wait()、notify()、notifyAll()来协调线程的运行.
- 当使用LOCK对象保持同步时,Java为我们提供了Condition类来协调线程的运行。关于Condition类,JDK文档里进行了详细的解释.,再次就不啰嗦了。
- 我们就拿Account类进行稍微的修改 一下吧!
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对象
private final Lock lock=new ReentrantLock();
//获得指定Lock对象对应的条件变量
private final Condition con=lock.newCondition();
private String accountNo;
private double balance;
//标识账户中是否有存款的旗标
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);
//将标识账户是否已有存款的标志设为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();
}
}
}
|
- 输出结果和上面是一样的! 只不过这里 显示的使用Lock对像来充当同步监视器,使用Condition对象来暂停指定线程,唤醒指定线程!
1.2.4 管道
管道流是JAVA中线程通讯的常用方式之一,基本流程如下:
创建管道输出流PipedOutputStream pos和管道输入流PipedInputStream pis
将pos和pis匹配,pos.connect(pis);
将pos赋给信息输入线程,pis赋给信息获取线程,就可以实现线程间的通讯了
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) {
/**
* 创建管道输出流
*/
PipedOutputStream pos = new PipedOutputStream();
/**
* 创建管道输入流
*/
PipedInputStream pis = new PipedInputStream();
try {
/**
* 将管道输入流与输出流连接 此过程也可通过重载的构造函数来实现
*/
pos.connect(pis);
} catch (IOException e) {
e.printStackTrace();
}
/**
* 创建生产者线程
*/
Producer p = new Producer(pos);
/**
* 创建消费者线程
*/
Consumer1 c1 = new Consumer1(pis);
/**
* 启动线程
*/
p.start();
c1.start();
}
}
/**
* 生产者线程(与一个管道输入流相关联)
*
*/
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();
}
}
}
/**
* 消费者线程(与一个管道输入流相关联)
*
*/
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线程发送数据
consumer1:0
consumer1:1
consumer1:2
consumer1:3
......
管道流虽然使用起来方便,但是也有一些缺点
管道流只能在两个线程之间传递数据 。线程consumer1和consumer2同时从pis中read数据,当线程producer往管道流中写入一段数据后,每一个时刻只有一个线程能获取到数据,并不是两个线程都能获取到producer发送来的数据,因此一个管道流只能用于两个线程间的通讯。不仅仅是管道流,其他IO方式都是一对一传输。
管道流只能实现单向发送,如果要两个线程之间互通讯,则需要两个管道流 。可以看到上面的例子中,线程producer通过管道流向线程consumer发送数据,如果线程consumer想给线程producer发送数据,则需要新建另一个管道流pos1和pis1,将pos1赋给consumer1,将pis1赋给producer,具体例子本文不再多说。
2 进程与进程间通信
2.1 进程间通信方式
- 管道(Pipe) :管道可用于具有亲缘关系进程间的通信,允许一个进程和另一个与它有共同祖先的进程之间进行通信。
- 命名管道(named pipe) :命名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关 系 进程间的通信。命名管道在文件系统中有对应的文件名。命名管道通过命令mkfifo或系统调用mkfifo来创建。
- 信号(Signal) :信号是比较复杂的通信方式,用于通知接受进程有某种事件发生,除了用于进程间通信外,进程还可以发送 信号给进程本身;Linux除了支持Unix早期信号语义函数sigal外,还支持语义符合Posix.1标准的信号函数sigaction(实际上,该函数是基于BSD的,BSD为了实现可靠信号机制,又能够统一对外接口,用sigaction函数重新实现了signal函数)。
- 消息(Message)队列 :消息队列是消息的链接表,包括Posix消息队列system V消息队列。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺
- 共享内存 :使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。
- 内存映射(mapped memory) :内存映射允许任何多个进程间通信,每一个使用该机制的进程通过把一个共享的文件映射到自己的进程地址空间来实现它。
- 信号量(semaphore) :主要作为进程间以及同一进程不同线程之间的同步手段。
- 套接口(Socket) :更为一般的进程间通信机制,可用于不同机器之间的进程间通信。起初是由Unix系统的BSD分支开发出来的,但现在一般可以移植到其它类Unix系统上:linux和System V的变种都支持套接字。
转自:https://www.tuhd.top/2017/08/04/2017-08-04-threadandprocess/
Java线程与线程、进程与进程之间通信方式的更多相关文章
- java核心知识点学习----并发和并行的区别,进程和线程的区别,如何创建线程和线程的四种状态,什么是线程计时器
多线程并发就像是内功,框架都像是外功,内功不足,外功也难得精要. 1.进程和线程的区别 一个程序至少有一个进程,一个进程至少有一个线程. 用工厂来比喻就是,一个工厂可以生产不同种类的产品,操作系统就是 ...
- JAVA线程与线程、进程与进程间通信
I.线程与线程间通信 一.基本概念以及线程与进程之间的区别联系: 关于进程和线程,首先从定义上理解就有所不同1.进程是什么?是具有一定独立功能的程序.它是系统进行资源分配和调度的一个独立单位,重点在系 ...
- Java多线程:线程与进程
实际上,线程和进程的区别,在学OS时必然是学习过的,所缺的不过是一些总结. 1. 进程 2. 线程 3. 进程与线程 4. 多进程与多线程对比 5. Java多进程与多线程 5.1. Java多进程 ...
- Java并发编程:线程和进程的创建(转)
Java并发编程:如何创建线程? 在前面一篇文章中已经讲述了在进程和线程的由来,今天就来讲一下在Java中如何创建线程,让线程去执行一个子任务.下面先讲述一下Java中的应用程序和进程相关的概念知识, ...
- -1-5 java 多线程 概念 进程 线程区别联系 java创建线程方式 线程组 线程池概念 线程安全 同步 同步代码块 Lock锁 sleep()和wait()方法的区别 为什么wait(),notify(),notifyAll()等方法都定义在Object类中
本文关键词: java 多线程 概念 进程 线程区别联系 java创建线程方式 线程组 线程池概念 线程安全 同步 同步代码块 Lock锁 sleep()和wait()方法的区别 为什么wait( ...
- 线程概念( 线程的特点,进程与线程的关系, 线程和python理论知识,线程的创建)
参考博客: https://www.cnblogs.com/xiao987334176/p/9041318.html 线程概念的引入背景 进程 之前我们已经了解了操作系统中进程的概念,程序并不能单独运 ...
- python 全栈开发,Day41(线程概念,线程的特点,进程和线程的关系,线程和python 理论知识,线程的创建)
昨日内容回顾 队列 队列 : 先进先出.数据进程安全 队列实现方式: 管道 + 锁 生产者消费者模型 : 解决数据供需不平衡 管道 双向通信 数据进程不安全 EOFError: 管道是由操作系统进行引 ...
- python 进程和线程-线程和线程变量ThreadLocal
线程 线程是由若干个进程组成的,所以一个进程至少包含一个线程:并且线程是操作系统直接支持的执行单元.多任务可以由多进程完成,也可由一个进程的多个线程来完成 Python的线程是真正的Posix Thr ...
- 11.python之线程,协程,进程,
一,进程与线程 1.什么是线程 线程是操作系统能够进行运算调度的最小单位.它被包含在进程之中,是进程中的实际运作单位.一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行 ...
随机推荐
- MongoDB数据迁移
将集合user从192.168.1.12:27017导入到192.168.1.120:27017 数据的导出:mongoexport 数据的导入:mongoimport 导出集合user的过程: [r ...
- 牛客提高D2t2 幸运数字考试
分析 预处理出所有合法数字 然后直接lower_bound查询即可 代码 #include<iostream> #include<cstdio> #include<cst ...
- Kubernetes tutorial - K8S 官方入门教程
tutorials 教程 kubectl 的命令手册 1 Creating a Cluster 1.1 Using Minikube to Create a Cluster Kubernetes Cl ...
- Jenkins持续集成_01_Mac安装配置
前言 Jenkins是一款开源 CI&CD 软件,用于自动化各种任务,包括构建.测试和部署软件.在自动化测试中,用来持续集成,跑定时任务进行定时自动化监测.更详细介绍可查看jenkins官网: ...
- SQL根据日期计算当月有多少天(转)
原文链接:https://blog.csdn.net/dobear_0922/article/details/2393235 --1.删除测试用表IF OBJECT_ID(N'Test', N'U') ...
- [Linux] 015 用户管理命令
1. 用户管理命令:useradd 命令名称:useradd 命令所在路径:/bin/sbin/useradd 执行权限:root 语法:useradd 用户名 功能描述:添加新用户 范例: $use ...
- luogu P3919 [模板]可持久化数组(可持久化线段树/平衡树)(主席树)
luogu P3919 [模板]可持久化数组(可持久化线段树/平衡树) 题目 #include<iostream> #include<cstdlib> #include< ...
- Day9---Python的集合类
集合类 1.生成办法: 可使用{.....} 或者set()生成集合,例如 a = {23,214,34,324,234,34} #这里的集合就是数学上的集合a = set('dsfasfsdf') ...
- python 实现加法
https://ac.nowcoder.com/acm/contest/338/G 链接:https://ac.nowcoder.com/acm/contest/338/G来源:牛客网 题目描述 Th ...
- 使用join和CountDownLatch来等待线程结束
1.join方法的实现 join只能在start()之后调用, join 某个线程A,会使当前线程B进入等待,直到线程A结束生命周期(isAlive()==false) ,或者达到给定的时间. 在此期 ...