一、共享变量java
二、wait/notify机制linux
三、Lock/Condition机制缓存
四、管道多线程
线程间发送信号的一个简单方式是在共享对象的变量里设置信号值。线程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实例是分开存放的。app
为了实现线程通讯,咱们可使用Object类提供的wait()、notify()、notifyAll()三个方法。调用wait()方法会释放对该同步监视器的锁定。这三个方法必须由同步监视器对象来调用,这可分红两种状况:ide
假设系统中有两条线程,这两条线程分别表明取钱者和存钱者。如今系统有一种特殊的要求,系统要求存款者和取钱者不断的实现存款和取钱动做,并且要求每当存款者将钱存入指定帐户后,取钱者当即将钱取走.不容许存款者两次存钱,也不容许取钱者两次取钱。函数
咱们经过设置一个旗标来标识帐户中是否已有存款,有就为true,没有就标为false。具体代码以下:测试
首先咱们定义一个Account类,这个类中有取钱和存钱的两个方法,因为这两个方法可能须要并发的执行取钱、存钱操做,全部将这两个方法都修改成同步方法.(使用synchronized关键字)。this
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(); } } }
接下来建立两个线程类,分别为取钱和存钱线程!
取钱线程类:
存钱线程类:
最后咱们测试一下这个取钱和存钱的操做!
大体的输出结果:
如何程序不使用synchronized关键字来保持同步,而是直接适用Lock对像来保持同步,则系统中不存在隐式的同步监视器对象,也就不能使用wait()、notify()、notifyAll()来协调线程的运行.
当使用LOCK对象保持同步时,JAVA为咱们提供了Condition类来协调线程的运行。关于Condition类,JDK文档里进行了详细的解释.,再次就不啰嗦了。
咱们就拿Account类进行稍微的修改 一下吧!
输出结果和上面是同样的! 只不过这里 显示的使用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,具体例子本文再也不多说。
1、进程间通讯方式
(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的变种都支持套接字。