JAVA线程与线程、进程与进程间通信

I.线程与线程间通信

一、多线程间通信方式:

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();
        }
   }  

}  

接下来创建两个线程类,分别为取钱和存钱线程!

取钱线程类:

[java] view
plain
 copy

  1. public class DrawThread implements Runnable {
  2. private Account account;
  3. private double drawAmount;
  4. public DrawThread(Account account, double drawAmount) {
  5. super();
  6. this.account = account;
  7. this.drawAmount = drawAmount;
  8. }
  9. public void run() {
  10. for(int i=0;i<100;i++){
  11. account.draw(drawAmount);
  12. }
  13. }
  14. }

存钱线程类:

[java] view
plain
 copy

  1. public class depositThread implements Runnable{
  2. private Account account;
  3. private double depositAmount;
  4. public depositThread(Account account, double depositAmount) {
  5. super();
  6. this.account = account;
  7. this.depositAmount = depositAmount;
  8. }
  9. public void run() {
  10. for(int i=0;i<100;i++){
  11. account.deposit(depositAmount);
  12. }
  13. }
  14. }

最后我们测试一下这个取钱和存钱的操作!

[java] view
plain
 copy

  1. public  class TestDraw {
  2. public static void main(String[] args) {
  3. //创建一个账户
  4. Account account=new Account();
  5. new Thread(new DrawThread(account, 800),"取钱者").start();
  6. new Thread(new depositThread(account, 800),"存款者甲").start();
  7. new Thread(new depositThread(account, 800),"存款者乙").start();
  8. new Thread(new depositThread(account, 800),"存款者丙").start();
  9. }
  10. }

大致的输出结果:

[java] view
plain
 copy

  1. 存款者甲存钱800.0
  2. 账户余额为:800.0
  3. 取钱者 取钱:800.0
  4. 余额 : 0.0
  5. 存款者丙存钱800.0
  6. 账户余额为:800.0
  7. 取钱者 取钱:800.0
  8. 余额 : 0.0
  9. 存款者甲存钱800.0
  10. 账户余额为:800.0
  11. 取钱者 取钱:800.0
  12. 余额 : 0.0
  13. 存款者丙存钱800.0
  14. 账户余额为:800.0
  15. 取钱者 取钱:800.0
  16. 余额 : 0.0
  17. 存款者甲存钱800.0
  18. 账户余额为:800.0
  19. 取钱者 取钱:800.0
  20. 余额 : 0.0
  21. 存款者丙存钱800.0
  22. 账户余额为:800.0
  23. 取钱者 取钱:800.0
  24. 余额 : 0.0
  25. 存款者甲存钱800.0
  26. 账户余额为:800.0
  27. 取钱者 取钱:800.0
  28. 余额 : 0.0
  29. 存款者丙存钱800.0
  30. 账户余额为:800.0
  31. 取钱者 取钱:800.0
  32. 余额 : 0.0
  33. 存款者甲存钱800.0
  34. 账户余额为:800.0
  35. 取钱者 取钱:800.0
  36. 余额 : 0.0

四、Lock/Condition机制

如何程序不使用synchronized关键字来保持同步,而是直接适用Lock对像来保持同步,则系统中不存在隐式的同步监视器对象,也就不能使用wait()、notify()、notifyAll()来协调线程的运行.

当使用LOCK对象保持同步时,JAVA为我们提供了Condition类来协调线程的运行。关于Condition类,JDK文档里进行了详细的解释.,再次就不啰嗦了。

我们就拿Account类进行稍微的修改 一下吧!

[java] view
plain
 copy

  1. import java.util.concurrent.locks.Condition;
  2. import java.util.concurrent.locks.Lock;
  3. import java.util.concurrent.locks.ReentrantLock;
  4. public class Account {
  5. //显示定义Lock对象
  6. private final Lock lock=new ReentrantLock();
  7. //获得指定Lock对象对应的条件变量
  8. private final  Condition con=lock.newCondition();
  9. private String accountNo;
  10. private double balance;
  11. //标识账户中是否有存款的旗标
  12. private boolean flag=false;
  13. public Account() {
  14. super();
  15. }
  16. public Account(String accountNo, double balance) {
  17. super();
  18. this.accountNo = accountNo;
  19. this.balance = balance;
  20. }
  21. public void draw (double drawAmount){
  22. //加锁
  23. lock.lock();
  24. try {
  25. if(!flag){
  26. //            this.wait();
  27. con.await();
  28. }else {
  29. //取钱
  30. System.out.println(Thread.currentThread().getName()+" 取钱:"+drawAmount);
  31. balance=balance-drawAmount;
  32. System.out.println("余额 : "+balance);
  33. //将标识账户是否已有存款的标志设为false
  34. flag=false;
  35. //唤醒其它线程
  36. //               this.notifyAll();
  37. con.signalAll();
  38. }
  39. } catch (Exception e) {
  40. e.printStackTrace();
  41. }
  42. finally{
  43. lock.unlock();
  44. }
  45. }
  46. public void deposit(double depositAmount){
  47. //加锁
  48. lock.lock();
  49. try {
  50. if(flag){
  51. //              this.wait();
  52. con.await();
  53. }
  54. else{
  55. System.out.println(Thread.currentThread().getName()+"存钱"+depositAmount);
  56. balance=balance+depositAmount;
  57. System.out.println("账户余额为:"+balance);
  58. flag=true;
  59. //唤醒其它线程
  60. //                this.notifyAll();
  61. con.signalAll();
  62. }
  63. } catch (Exception e) {
  64. // TODO: handle exception
  65. e.printStackTrace();
  66. }finally{
  67. lock.unlock();
  68. }
  69. }
  70. }

输出结果和上面是一样的! 只不过这里 显示的使用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的变种都支持套接字。

时间: 2025-01-31 13:33:07

JAVA线程与线程、进程与进程间通信的相关文章

Java并发编程:进程和线程

.title { text-align: center } .todo { font-family: monospace; color: red } .done { color: green } .tag { background-color: #eee; font-family: monospace; padding: 2px; font-size: 80%; font-weight: normal } .timestamp { color: #bebebe } .timestamp-kwd

java中的进程,线程,线程池总结

1.线程与进程 进程 狭义上讲:正在执行的程序,由线程组成,可包含多个线程在运行. 广义上讲:进程是一个具有一定独立功能的程序有关于某个数据集合的一次运行的活动.它可以申请或者拥有系统资源,是一个动态的概念. 进程的概念主要两点:1.进程是一个实体,每一个进程都有自己的地址空间,一般情况下包括文本区域,数据区和堆栈. 线程 为进程中执行的程序片段. 一个线程由线程ID,当前指令针,寄存器和堆栈组成,另外线程是进程的实体,是被系统独立调试的分派的基本单元. 线程是程序中一个单一的顺序控制流程.进程

java中的并发:进程和线程

1.简介 1)进程:同一个系统中执行的一个子程序,包含三部分:虚拟CPU,代码,数据. 2)线程:同一个进程中执行的子程序流. 3)进程让操作系统的并发性成为可能,而线程让进程的内部并发成为可能.一个进程虽然包括多个线程,但是这些线程是共同享有进程占有的资源和地址空间的.进程是操作系统进行资源分配的基本单位,而线程是操作系统进行调度的基本单位. 2.创建一个进程 2.1首先了解三个类    1)Process(抽象类)       进程类,提供了执行从进程输入.执行输出到进程.等待进程完成.检查

Java线程和守护进程

ava的线程机制,有两类线程:User Thread(用户线程).Daemon Thread(守护线程) . 操作系统里面是没有守护线程的概念,只有守护进程,但是Java语言机制是构建在JVM的基础之上的,意思是Java平台把操作系统的底层给屏蔽起来,而守护线程机制又是对JVM这样的平台凑合,于是守护线程应运而生. Daemon的作用是为其他线程的运行提供服务,比如说GC线程.其实User Thread线程和Daemon Thread守护线唯一的区别就在虚拟机的离开:如果User Thread全

JAVA 并发编程-线程与进程的由来(一)

在学习Java编程之初,我们就接触过Java线程,当时敲过代码也总结过,但是现在看来还是有点缺陷,并没有联系线程的由来来说明问题,只是简单的介绍了什么是进程,什么是线程,以及它们之间的关系-<Java-线程>.今天我们从进程和线程的由来来补充一下之前的总结. 参考:http://www.cnblogs.com/dolphin0520/p/3910667.html 一.操作系统中为什么会出现进程? 说起进程的由来,我们需要从操作系统的发展历史谈起. 最初计算机: 也许在今天,我们无法想象在很多年

Java Processes and Threads 进程与线程

Processes and Threads In concurrent programming, there are two basic units of execution: processes and threads. In the Java programming language, concurrent programming is mostly concerned with threads. However, processes are also important. A comput

Java多线程基础:进程和线程之由来

在前面,已经介绍了Java的基础知识,现在我们来讨论一点稍微难一点的问题:Java并发编程.当然,Java并发编程涉及到很多方面的内容,不是一朝一夕就能够融会贯通使用的,需要在实践中不断积累.由于并发肯定涉及到多线程,因此在进入并发编程主题之前,我们先来了解一下进程和线程的由来,这对后面对并发编程的理解将会有很大的帮助. 下面是本文的目录大纲: 一.操作系统中为什么会出现进程? 二.为什么会出现线程? 三.多线程并发 若有不正之处,请多多谅解并欢迎指正. 请尊重作者劳动成果,转载请标明原文地址:

java中程序,进程和线程的区别

2.程序,进程和线程的区别 (视频下载) (全部书籍) 马克-to-win:程序,进程和线程的区别是什么?这个问题比较抽象难理解,但又非常重要.我并不想给出一大堆抽象的学术解释,那样只能误国误民.所以我先给大家举一个例子.马克-to-win: 2000年的时候,我比较爱用ie的老版本.当时我喜欢边上网边下载很多东西,比如很多MP3同时下.我发觉如果我打开一个ie,之后用file/new,new出很多窗口来,边看网页,边下载很多mp3的话,如果有一个下载崩溃的话,所有的ie窗口就同时都崩溃了,包括

进程、线程、轻量级进程、协程和go中的Goroutine

一.进程 操作系统中最核心的概念是进程,分布式系统中最重要的问题是进程间通信. 进程是“程序执行的一个实例” ,担当分配系统资源的实体.进程创建必须分配一个完整的独立地址空间. 进程切换只发生在内核态,两步:1 切换页全局目录以安装一个新的地址空间 2 切换内核态堆栈和硬件上下文.  另一种说法类似:1 保存CPU环境(寄存器值.程序计数器.堆栈指针)2修改内存管理单元MMU的寄存器 3 转换后备缓冲器TLB中的地址转换缓存内容标记为无效. 二.线程 书中的定义:线程是进程的一个执行流,独立执行