JAVA线程与线程、进程与进程间通信
I.线程与线程间通信
1、进程是什么?
是具有一定独立功能的程序、它是系统进行资源分配和调度的一个独立单位,重点在系统调度和单独的单位,也就是说进程是可以独 立运行的一段程序。
2、线程又是什么?
线程进程的一个实体,是CPU调度和分派的基本单位,他是比进程更小的能独立运行的基本单位,线程自己基本上不拥有系统资源。
在运行时,只是暂用一些计数器、寄存器和栈 。
他们之间的关系
1、一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程(通常说的主线程)。
2、资源分配给进程,同一进程的所有线程共享该进程的所有资源。
3、线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。
4、处理机分给线程,即真正在处理机上运行的是线程。
5、线程是指进程内的一个执行单元,也是进程内的可调度实体。
从三个角度来剖析二者之间的区别
1、调度:线程作为调度和分配的基本单位,进程作为拥有资源的基本单位。
2、并发性:不仅进程之间可以并发执行,同一个进程的多个线程之间也可以并发执行。
3、拥有资源:进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于进程的资源。
二、多线程间通信方式:
1、共享变量
2、wait/notify机制
3、Lock/Condition机制
4、管道
三、共享变量
线程间发送信号的一个简单方式是在共享对象的变量里设置信号值。线程A在一个同步块里设置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共享实例的引用,以便进行通信。如果它们持有的引用指向不同的MySingal实例,那么彼此将不能检测到对方的信号。需要处理的数据可以存放在一个共享缓存区里,它和MySignal实例是分开存放的。
四、wait()/notify机制
为了实现线程通信,我们可以使用Object类提供的wait()、notify()、notifyAll()三个方法。调用wait()方法会释放对该同步监视器的锁定。这三个方法必须由同步监视器对象来调用,这可分成两种情况:
- 对于使用synchronized修饰的同步方法,因为该类的默认实例是(this)就是同步监视器,所以可以直接调用这三使用个方法。
- 对于synchronized修饰的同步代码块,同步监视器是synchronized括号里的对象,所以必须使用该对象调用这三个方法。
假设系统中有两条线程,这两条线程分别代表取钱者和存钱者。现在系统有一种特殊的要求,系统要求存款者和取钱者不断的实现存款和取钱动作,而且要求每当存款者将钱存入指定账户后,取钱者立即将钱取走.不允许存款者两次存钱,也不允许取钱者两次取钱。
我们通过设置一个旗标来标识账户中是否已有存款,有就为true,没有就标为false。具体代码如下:
首先我们定义一个Account类,这个类中有取钱和存钱的两个方法,由于这两个方法可能需要并发的执行取钱、存钱操作,所有将这两个方法都修改为同步方法.(使用synchronized关键字)。
- 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();
- }
- }
- }
接下来创建两个线程类,分别为取钱和存钱线程!
取钱线程类:
- 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) {
- //创建一个账户
- 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();
- }
- }
大致的输出结果:
- 存款者甲存钱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机制
如何程序不使用synchronized关键字来保持同步,而是直接适用Lock对像来保持同步,则系统中不存在隐式的同步监视器对象,也就不能使用wait()、notify()、notifyAll()来协调线程的运行.
当使用LOCK对象保持同步时,JAVA为我们提供了Condition类来协调线程的运行。关于Condition类,JDK文档里进行了详细的解释.,再次就不啰嗦了。
我们就拿Account类进行稍微的修改 一下吧!
- 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对象来暂停指定线程,唤醒指定线程!
六、管道
管道流是JAVA中线程通讯的常用方式之一,基本流程如下:
1)创建管道输出流PipedOutputStream pos和管道输入流PipedInputStream pis
2)将pos和pis匹配,pos.connect(pis);
3)将pos赋给信息输入线程,pis赋给信息获取线程,就可以实现线程间的通讯了
- 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
- ......
管道流虽然使用起来方便,但是也有一些缺点
1)管道流只能在两个线程之间传递数据
线程consumer1和consumer2同时从pis中read数据,当线程producer往管道流中写入一段数据后,每一个时刻只有一个线程能获取到数据,并不是两个线程都能获取到producer发送来的数据,因此一个管道流只能用于两个线程间的通讯。不仅仅是管道流,其他IO方式都是一对一传输。
2)管道流只能实现单向发送,如果要两个线程之间互通讯,则需要两个管道流
可以看到上面的例子中,线程producer通过管道流向线程consumer发送数据,如果线程consumer想给线程producer发送数据,则需要新建另一个管道流pos1和pis1,将pos1赋给consumer1,将pis1赋给producer,具体例子本文不再多说。
II.进程与进程间通信
一、进程间通信方式
(1)管道(Pipe):管道可用于具有亲缘关系进程间的通信,允许一个进程和另一个与它有共同祖先的进程之间进行通信。
(2)命名管道(named pipe):命名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关 系 进程间的通信。命名管道在文件系统中有对应的文件名。命名管道通过命令mkfifo或系统调用mkfifo来创建。
(3)信号(Signal):信号是比较复杂的通信方式,用于通知接受进程有某种事件发生,除了用于进程间通信外,进程还可以发送 信号给进程本身;linux除了支持Unix早期信号语义函数sigal外,还支持语义符合Posix.1标准的信号函数sigaction(实际上,该函数是基于BSD的,BSD为了实现可靠信号机制,又能够统一对外接口,用sigaction函数重新实现了signal函数)。
(4)消息(Message)队列:消息队列是消息的链接表,包括Posix消息队列system V消息队列。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺
(5)共享内存:使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。
(6)内存映射(mapped memory):内存映射允许任何多个进程间通信,每一个使用该机制的进程通过把一个共享的文件映射到自己的进程地址空间来实现它。
(7)信号量(semaphore):主要作为进程间以及同一进程不同线程之间的同步手段。
(8)套接口(Socket):更为一般的进程间通信机制,可用于不同机器之间的进程间通信。起初是由Unix系统的BSD分支开发出来的,但现在一般可以移植到其它类Unix系统上:Linux和System V的变种都支持套接字。
JAVA线程与线程、进程与进程间通信的更多相关文章
- Java多线程:线程与进程
实际上,线程和进程的区别,在学OS时必然是学习过的,所缺的不过是一些总结. 1. 进程 2. 线程 3. 进程与线程 4. 多进程与多线程对比 5. Java多进程与多线程 5.1. Java多进程 ...
- Java线程与线程、进程与进程之间通信方式
1.1 基本概念以及线程与进程之间的区别联系 关于进程和线程,首先从定义上理解就有所不同: 进程是具有一定独立功能的程序.它是系统进行资源分配和调度的一个独立单位,重点在系统调度和单独的单位,也就是说 ...
- JAVA面试常见问题之进程和线程篇
1.线程和进程的概念.并行和并发的概念 进程:计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础.在早期面向进程设计的计算机结构中,进程是程序的基本 ...
- java 多线程 1 线程 进程
Java多线程(一).多线程的基本概念和使用 2012-09-10 16:06 5108人阅读 评论(0) 收藏 举报 分类: javaSE综合知识点(14) 版权声明:本文为博主原创文章,未经博 ...
- Java并发编程:进程和线程之由来
Java多线程基础:进程和线程之由来 在前面,已经介绍了Java的基础知识,现在我们来讨论一点稍微难一点的问题:Java并发编程.当然,Java并发编程涉及到很多方面的内容,不是一朝一夕就能够融会贯通 ...
- Java多线程中的进程,线程,并行,并发
2:什么是进程? 通过任务管理器我们就看到了进程的存在. 而通过观察,我们发现只有运行的程序才会出现进程. 进程:就是正在运行的程序. 进程是系统进行资源分配和调用的独立单位.每一个进程都有它自己的内 ...
- Java多线程基础:进程和线程之由来
转载: Java多线程基础:进程和线程之由来 在前面,已经介绍了Java的基础知识,现在我们来讨论一点稍微难一点的问题:Java并发编程.当然,Java并发编程涉及到很多方面的内容,不是一朝一夕就能够 ...
- java并发编程:进程和线程
java并发编程涉及到很多内容,当然也包括多线程,再次补充点相关概念 原文地址:http://www.cnblogs.com/dolphin0520/p/3910667.html 一.操作系统中为什么 ...
- Java并发编程:进程和线程之由来__进程让操作系统的并发性成为可能,而线程让进程的内部并发成为可能
转载自海子:http://www.cnblogs.com/dolphin0520/p/3910667.html Java多线程基础:进程和线程之由来 在前面,已经介绍了Java的基础知识,现在我们来讨 ...
随机推荐
- TortoiseSVN文件夹图标不显示
伴随着十二月的脚步,小编带领的市委组织部项目有条不紊的进行着,在最近的项目中遇到一个问题TortoiseSVN文件夹的图标不显示,为什么小编已经安装好TortoiseSVN了,发现文件夹的图标还是系统 ...
- HTML5中 HTML格式化/HTML样式/链表/表格-样式 韩俊强的博客
HTML5学习从简单到复杂,循环渐进! 每日更新关注:http://weibo.com/hanjunqiang 新浪微博! 1.HTML格式化 <!DOCTYPE html> <h ...
- I/O操作之文件压缩与解压
与文件压缩与解压相关的类在java.util.zip包下 实例 //文件压缩 import java.io.File; import java.io.FileInputStream; import j ...
- Android实现登录小demo
安卓,在小编实习之前的那段岁月里面,小编都没有玩儿过,如果说玩儿过,那就是安卓手机了,咳咳,敲登录的时候有种特别久违的熟悉,这种熟悉的感觉就和当时敲机房收费系统一样,那叫一个艰难啊,不过小编相信,在小 ...
- (NO.00005)iOS实现炸弹人游戏(二):素材选择的取舍
大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请告诉我,如果觉得不错请多多支持点赞.谢谢! hopy ;) 前面一篇里我们基本明确了游戏的大致玩法和特点.下面就游戏中会用到 ...
- 讲究门面的Request
为什么说Request讲究门面?注意这里所说的门面并非我们常理解的外表的意思,其实是说它使用了门面设计模式,门面的使用主要用于数据安全的考虑.一个大的系统体系的多个子系统之间涉及交互通信.一个系统中的 ...
- JUI/DWZ介绍、简单使用
简介 由于开发的项目使用JUI,所以学习了. DWZ富客户端框架(jQuery RIAframework), 是中国人自己开发的基于jQuery实现的Ajax RIA开源框架.(现在更名为JUI) 可 ...
- 如何在Cocos2D游戏中实现A*寻路算法(七)
大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请告诉我,如果觉得不错请多多支持点赞.谢谢! hopy ;) 免责申明:本博客提供的所有翻译文章原稿均来自互联网,仅供学习交流 ...
- STL:vector容器用法详解
vector类称作向量类,它实现了动态数组,用于元素数量变化的对象数组.像数组一样,vector类也用从0开始的下标表示元素的位置:但和数组不同的是,当vector对象创建后,数组的元素个数会随着ve ...
- 【Android 应用开发】 ActionBar 基础
作者 : 万境绝尘 (octopus_truth@163.com) 转载请注明出处 : http://blog.csdn.net/shulianghan/article/details/3920439 ...