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的基础知识,现在我们来讨 ...
随机推荐
- 学习TensorFlow,生成tensorflow输入输出的图像格式
TensorFLow能够识别的图像文件,可以通过numpy,使用tf.Variable或者tf.placeholder加载进tensorflow:也可以通过自带函数(tf.read)读取,当图像文件过 ...
- UNIX网络编程——非阻塞connect:时间获取客户程序
#include "unp.h" int connect_nonb(int sockfd, const SA *saptr, socklen_t salen, int nsec) ...
- 套接字工厂——ServerSocketFactory
接收器Acceptor在接收连接的过程中,根据不同的使用场合可能需要不同的安全级别,例如在支付相关的交易就必须对信息加密后再发送,这其中还涉及到密钥协商的过程,而在另外一些普通场合则无需对报文加密.反 ...
- Android:android sdk源码中怎么没有httpclient的源码了
欢迎关注公众号,每天推送Android技术文章,二维码如下:(可扫描) 今天想使用这个API,怎么也找不到.废了好多时间... 查阅资料才知道如下解释: 在android 6.0(API 23)中,G ...
- Java中封装性的使用
//Java面对对象基本特性之一:封装性 //作用:保护某些属性和方法不被外部所看见 //封装的实现:通过关键字private声明 //鼠标右键--->Source---->Generat ...
- Unity UGUI基础之Image
UGUI的Image等价于NGUI的Sprite组件,用于显示图片. 一.Image组件: Source Image(图像源):纹理格式为Sprite(2D and UI)的图片资源(导入图片后选择T ...
- 关于Tomcat的URIEncoding以及GET乱码
最近在维护着Linux上的服务器,当然,开发和前期测试是在windows上执行的. 在做意见反馈的时候,出现了windows上正常,Linux下却是乱码的问题. 先排查了web服务器与mysql的连接 ...
- iOS中 用FMDB封装一个SQLite数据库
建立一个单例: DataBaseHandle.h #import <Foundation/Foundation.h> @class PersonModel; @class FMDataba ...
- Touch Handling in Cocos2D 3.x(一)
原帖地址 Cocos2d 3.0 带来经过完全修正后的触摸处理系统.本篇博文将提供所有触摸相关的信息: 接收触摸 取得触摸位置 处理不同个触摸生命周期中的事件 实现一个拖放机制 让我们开始吧 ;) 通 ...
- MySQL数据库写入图片并读取图片显示到JLabel上的详解
相较于Oracle,MySQL作为一个轻量级的开源的数据库,可谓是大大简化了我们的操作.这次我就来写一个关于数据库存入图片,获取图片的例子吧,也为了今后的复习使用.(我们一般采取存入路径的方式,而不是 ...