)
1 线程与线程间通信1.1 基本概念以及线程与进程之间的区别联系关于进程和线程首先从定义上理解就有所不同进程是具有一定独立功能的程序、它是系统进行资源分配和调度的一个独立单位重点在系统调度和单独的单位也就是说进程是可以独 立运行的一段程序。线程是进程的一个实体是CPU调度和分派的基本单位他是比进程更小的能独立运行的基本单位线程自己基本上不拥有系统资源。在运行时只是暂用一些计数器、寄存器和栈 。他们之间的关系一个线程只能属于一个进程而一个进程可以有多个线程但至少有一个线程通常说的主线程。资源分配给进程同一进程的所有线程共享该进程的所有资源。线程在执行过程中需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。处理机分给线程即真正在处理机上运行的是线程。线程是指进程内的一个执行单元也是进程内的可调度实体。从三个角度来剖析二者之间的区别调度线程作为调度和分配的基本单位进程作为拥有资源的基本单位。并发性不仅进程之间可以并发执行同一个进程的多个线程之间也可以并发执行。拥有资源进程是拥有资源的一个独立单位线程不拥有系统资源但可以访问隶属于进程的资源。1.2 多线程间通信方式共享变量wait/notify机制Lock/Condition机制管道1.2.1 共享变量线程间发送信号的一个简单方式是在共享对象的变量里设置信号值。线程A在一个同步块里设置boolean型成员变量hasDataToProcess为true线程B也在同步块里读取hasDataToProcess这个成员变量。这个简单的例子使用了一个持有信号的对象并提供了set和check方法:12345678910111213public 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关键字)。123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657public class Account {private String accountNo;private double balance;//标识账户中是否有存款的旗标private boolean flagfalse;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);balancebalance-drawAmount;System.out.println(余额 : balance);//将标识账户是否已有存款的标志设为falseflagfalse;//唤醒其它线程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);balancebalancedepositAmount;System.out.println(账户余额为balance);flagtrue;//唤醒其它线程this.notifyAll();}} catch (Exception e) {// TODO: handle exceptione.printStackTrace();}}}接下来创建两个线程类分别为取钱和存钱线程取钱线程类123456789101112131415161718public 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 i0;i100;i){account.draw(drawAmount);}}}存钱线程类123456789101112131415161718public 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 i0;i100;i){account.deposit(depositAmount);}}}最后我们测试一下这个取钱和存钱的操作12345678910public class TestDraw {public static void main(String[] args) {//创建一个账户Account accountnew 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();}}大致的输出结果如下123456789101112131415161718192021222324252627282930313233343536存款者甲存钱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.01.2.3 Lock/Condition机制如何程序不使用synchronized关键字来保持同步而是直接适用Lock对像来保持同步则系统中不存在隐式的同步监视器对象也就不能使用wait()、notify()、notifyAll()来协调线程的运行.当使用LOCK对象保持同步时Java为我们提供了Condition类来协调线程的运行。关于Condition类JDK文档里进行了详细的解释.再次就不啰嗦了。我们就拿Account类进行稍微的修改 一下吧1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980import java.util.concurrent.locks.Condition;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;public class Account {//显示定义Lock对象private final Lock locknew ReentrantLock();//获得指定Lock对象对应的条件变量private final Condition conlock.newCondition();private String accountNo;private double balance;//标识账户中是否有存款的旗标private boolean flagfalse;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);balancebalance-drawAmount;System.out.println(余额 : balance);//将标识账户是否已有存款的标志设为falseflagfalse;//唤醒其它线程// 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);balancebalancedepositAmount;System.out.println(账户余额为balance);flagtrue;//唤醒其它线程// this.notifyAll();con.signalAll();}} catch (Exception e) {// TODO: handle exceptione.printStackTrace();}finally{lock.unlock();}}}输出结果和上面是一样的 只不过这里 显示的使用Lock对像来充当同步监视器,使用Condition对象来暂停指定线程唤醒指定线程1.2.4 管道管道流是JAVA中线程通讯的常用方式之一基本流程如下创建管道输出流PipedOutputStream pos和管道输入流PipedInputStream pis将pos和pis匹配pos.connect(pis);将pos赋给信息输入线程pis赋给信息获取线程就可以实现线程间的通讯了123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687import 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的变种都支持套接字。