1 线程与线程间通信

1.1 基本概念以及线程与进程之间的区别联系

  • 关于进程和线程,首先从定义上理解就有所不同:

    • 进程是具有一定独立功能的程序、它是系统进行资源分配和调度的一个独立单位,重点在系统调度和单独的单位,也就是说进程是可以独 立运行的一段程序。
    • 线程是进程的一个实体,是CPU调度和分派的基本单位,他是比进程更小的能独立运行的基本单位,线程自己基本上不拥有系统资源。在运行时,只是暂用一些计数器、寄存器和栈 。
  • 他们之间的关系

    1. 一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程(通常说的主线程)。
    2. 资源分配给进程,同一进程的所有线程共享该进程的所有资源。
    3. 线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。
    4. 处理机分给线程,即真正在处理机上运行的是线程。
    5. 线程是指进程内的一个执行单元,也是进程内的可调度实体。
  • 从三个角度来剖析二者之间的区别

    1. 调度:线程作为调度和分配的基本单位,进程作为拥有资源的基本单位。
    2. 并发性:不仅进程之间可以并发执行,同一个进程的多个线程之间也可以并发执行。
    3. 拥有资源:进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于进程的资源。

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中线程通讯的常用方式之一,基本流程如下:

    1. 创建管道输出流PipedOutputStream pos和管道输入流PipedInputStream pis

    2. 将pos和pis匹配,pos.connect(pis);

    3. 将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线程间和进程间通信的更多相关文章

  1. Java 线程间通讯(共享变量方式)

    Java线程间通讯,最常用的方式便是共享变量方式,多个线程共享一个静态变量就可以实现在线程间通讯,但是这需要注意的就是线程同步问题. 一.没考虑线程同步: package com.wyf; publi ...

  2. Java线程间通信-回调的实现方式

    Java线程间通信-回调的实现方式   Java线程间通信是非常复杂的问题的.线程间通信问题本质上是如何将与线程相关的变量或者对象传递给别的线程,从而实现交互.   比如举一个简单例子,有一个多线程的 ...

  3. 说说Java线程间通信

    序言 正文 [一] Java线程间如何通信? 线程间通信的目标是使线程间能够互相发送信号,包括如下几种方式: 1.通过共享对象通信 线程间发送信号的一个简单方式是在共享对象的变量里设置信号值:线程A在 ...

  4. 说说 Java 线程间通信

    序言 正文 一.Java线程间如何通信? 线程间通信的目标是使线程间能够互相发送信号,包括如下几种方式: 1.通过共享对象通信 线程间发送信号的一个简单方式是在共享对象的变量里设置信号值:线程A在一个 ...

  5. Java线程间通信方式剖析——Java进阶(四)

    原创文章,同步发自作者个人博客,转载请在文章开头处以超链接注明出处 http://www.jasongj.com/java/thread_communication/ CountDownLatch C ...

  6. 【JAVA线程间通信技术】

    之前的例子都是多个线程执行同一种任务,下面开始讨论多个线程执行不同任务的情况. 举个例子:有个仓库专门存储货物,有的货车专门将货物送往仓库,有的货车则专门将货物拉出仓库,这两种货车的任务不同,而且为了 ...

  7. Java 线程间通讯(管道流方式)

    一.管道流是JAVA中线程通讯的常用方式之一,基本流程如下: 1)创建管道输出流PipedOutputStream pos和管道输入流PipedInputStream pis 2)将pos和pis匹配 ...

  8. Java线程间通信之wait/notify

    Java中的wait/notify/notifyAll可用来实现线程间通信,是Object类的方法,这三个方法都是native方法,是平台相关的,常用来实现生产者/消费者模式.我们来看下相关定义: w ...

  9. java线程间通信:一个小Demo完全搞懂

    版权声明:本文出自汪磊的博客,转载请务必注明出处. Java线程系列文章只是自己知识的总结梳理,都是最基础的玩意,已经掌握熟练的可以绕过. 一.从一个小Demo说起 上篇我们聊到了Java多线程的同步 ...

随机推荐

  1. PHP使用MySQL实现消息队列

    消息队列常用在流量削峰(秒杀场景),异步通信等地方. 大体的结构如下: 类似于消费者和生产者的关系,首先生产者在消息队列未满的时候,才将生产的产品放进消息队列中:消费者在消息队列不为空的时候,才从消息 ...

  2. Using Android Phone to recover SD card formatted with DD command under linux

    Using Android Phone to recover SD card formatted with DD command under linux 1. Formatted a sd card ...

  3. Python表达式与运算符

    表达式与运算符 Python语言支持以下类型的运算符: 算术运算符 比较(关系)运算符 赋值运算符 逻辑运算符 位运算符 成员运算符 身份运算符 运算符优先级 算术运算符 运算符 描述 + 加 - 两 ...

  4. centos7 tar.xz格式文件的解压方法

    现在很多找到的软件都是tar.xz的格式的,xz 是一个使用 LZMA压缩算法的无损数据压缩文件格式. 和gzip与bzip2一样,同样支持多文件压缩,但是约定不能将多于一个的目标文件压缩进同一个档案 ...

  5. React 组件库框架搭建

    前言 公司业务积累了一定程度,需要搭建自己的组件库,有了组件库,整个团队开发效率会提高恨多. 做组件库需要提供开发调试环境,和组件文档的展示,调研了几个比较主流的方案,如下: docz 配置简单,功能 ...

  6. bzoj 2141 : 排队 (cdq分治+bit)

    链接: https://www.lydsy.com/JudgeOnline/problem.php?id=2141 思路: 其实就是求动态逆序对...cdq降维,用树状数组前后求两遍逆序对就好了 切水 ...

  7. 自学Linux Shell12.4-for命令

    点击返回 自学Linux命令行与Shell脚本之路 12.4-for命令 1. for命令 格式一 for var in list do commands done 格式二 for var in li ...

  8. 洛谷 P2515 [HAOI2010]软件安装 解题报告

    P2515 [HAOI2010]软件安装 题目描述 现在我们的手头有\(N\)个软件,对于一个软件\(i\),它要占用\(W_i\)的磁盘空间,它的价值为\(V_i\).我们希望从中选择一些软件安装到 ...

  9. 洛谷 P2341 [HAOI2006]受欢迎的牛 解题报告

    P2341 [HAOI2006]受欢迎的牛 题目描述 每头奶牛都梦想成为牛棚里的明星.被所有奶牛喜欢的奶牛就是一头明星奶牛.所有奶 牛都是自恋狂,每头奶牛总是喜欢自己的.奶牛之间的"喜欢&q ...

  10. 离线安装.NET 3.5

    最近为系统新增一个功能,写完以后进行部署,发现在IIS7上部署没有问题,但是IIS6上部署会出现未知情况,具体表现为取不到数据,估计是IIS6和IIS7直接的差异导致程序异常退出. 为了重现异常,在本 ...