七. 多线程编程9.线程间通信

上述例题无条件的阻塞了其他线程异步访问某个方法。Java对象中隐式管程的应用是很强大的,但是你可以通过进程间通信达到更微妙的境界。这在Java中是尤为简单的。

像前面所讨论过的,多线程通过把任务分成离散的和合乎逻辑的单元代替了事件循环程序。线程还有第二优点:它远离了轮询。轮询通常由重复监测条件的循环实现。一旦条件成立,就要采取适当的行动。这浪费了CPU时间。举例来说,考虑经典的序列问题,当一个线程正在产生数据而另一个程序正在消费它。为使问题变得更有趣,假设数据产生器必须等待消费者完成工作才能产生新的数据。在轮询系统,消费者在等待生产者产生数据时会浪费很多CPU周期。一旦生产者完成工作,它将启动轮询,浪费更多的CPU时间等待消费者的工作结束,如此下去。很明显,这种情形不受欢迎。

为避免轮询,Java包含了通过wait( ),notify( )和notifyAll( )方法实现的一个进程间通信机制。这些方法在对象中是用final方法实现的,所以所有的类都含有它们。这三个方法仅在synchronized方法中才能被调用。尽管这些方法从计算机科学远景方向上来说具有概念的高度先进性,实际中用起来是很简单的:

  • wait( ) 告知被调用的线程放弃管程进入睡眠直到其他线程进入相同管程并且调用notify( )。
  • notify( ) 恢复相同对象中第一个调用 wait( ) 的线程。
  • notifyAll( ) 恢复相同对象中所有调用 wait( ) 的线程。具有最高优先级的线程最先运行。

这些方法在Object中被声明,如下所示:
    final void wait( ) throws InterruptedException
    final void notify( )
    final void notifyAll( )
wait( )存在的另外的形式允许你定义等待时间。

下面的例子程序错误的实行了一个简单生产者/消费者的问题。它由四个类组成:Q,设法获得同步的序列;Producer,产生排队的线程对象;Consumer,消费序列的线程对象;以及PC,创建单个Q,Producer,和Consumer的小类。
// An incorrect implementation of a producer and consumer.
class Q {
    int n;
    synchronized int get() {
        System.out.println("Got: " + n);
        return n;
    }
    synchronized void put(int n) {
        this.n = n;
        System.out.println("Put: " + n);
    }
}
class Producer implements Runnable {
    Q q;
    Producer(Q q) {
        this.q = q;
        new Thread(this, "Producer").start();
    }
    public void run() {
        int i = 0;
        while(true) {
            q.put(i++);
        }
    }
}
class Consumer implements Runnable {
    Q q;
    Consumer(Q q) {
        this.q = q;
        new Thread(this, "Consumer").start();
    }
    public void run() {
        while(true) {
           q.get();
        }
    }
}
class PC {
    public static void main(String args[]) {
        Q q = new Q();
        new Producer(q);
        new Consumer(q);
        System.out.println("Press Control-C to stop.");
    }
}
尽管Q类中的put( )和get( )方法是同步的,没有东西阻止生产者超越消费者,也没有东西阻止消费者消费同样的序列两次。这样,你就得到下面的错误输出(输出将随处理器速度和装载的任务而改变):
Put: 1
Got: 1
Got: 1
Got: 1
Got: 1
Got: 1
Put: 2
Put: 3
Put: 4
Put: 5
Put: 6
Put: 7
Got: 7
生产者生成1后,消费者依次获得同样的1五次。生产者在继续生成2到7,消费者没有机会获得它们。

用Java正确的编写该程序是用wait( )和notify( )来对两个方向进行标志,如下所示:
// A correct implementation of a producer and consumer.
class Q {
    int n;
    boolean valueSet = false www.yigouyule2.cc ;
    synchronized int get() {
        if(!valueSet)
            try {
                wait();
            } catch(InterruptedException e) {
                System.out.println("InterruptedException caught");
            }
            System.out.println("Got: " + n);
            valueSet = false;
            notify();
            return n;
        }
        synchronized void put(int n) {
            if(valueSet)
            try {
                wait();
            } catch(InterruptedException e) {
                System.out.println("InterruptedException caught");
            }
            this.n = n;
            valueSet = true;
            System.out.println("Put: " + n);
            notify();
        }
    }
    class Producer implements Runnable {
        Q q;
        Producer(Q q) {
        this.q = q;
        new Thread(this, "Producer").start();
    }
    public void run() {
        int i = 0;
        while(true) {
            q.put(i++);
        }
    }
}
class Consumer implements Runnable {
    Q q;
    Consumer(Q q) {
        this.q = q;
        new Thread(this, "Consumer").start();
    }
    public void run() {
        while(true) {
            q.get();
        }
    }
}
class PCFixed {
    public static void main(String args[]) {
        Q q = new Q();
        new Producer(q);
        new Consumer(q);
        System.out.println("Press Control-C to stop.");
    }
}
内部get( ), wait( )被调用。这使执行挂起直到Producer 告知数据已经预备好。这时,内部get( ) 被恢复执行。获取数据后,get( )调用notify( )。这告诉Producer可以向序列中输入更多数据。在put( )内,wait( )挂起执行直到Consumer取走了序列中的项目。当执行再继续,下一个数据项目被放入序列,notify( )被调用,这通知Consumer它应该移走该数据。

下面是该程序的输出,它清楚的显示了同步行为:
Put: 1
Got: 1
Put: 2
Got: 2
Put: 3
Got: 3
Put: 4
Got: 4
Put: 5
Got: 5

时间: 2024-07-30 23:52:39

七. 多线程编程9.线程间通信的相关文章

2016年4月24日_JAVA学习笔记_多线程三_线程间通信

1.毕老师第十四天内容,线程间的通信.大概是使用wait(),notify()等一系列函数来控制各个线程的CPU执行资格和执行权,通过合适的时机在各个线程当中切换来达到线程间通信的目的. 涉及到的方法: wait():让线程处于等待状态,被wait()的线程会被存储到线程池当中,直到被唤醒.只能在同步方法中被调用. notify():随机选择一个在该对象上调用wait方法的线程,解除其阻塞状态.只能在同步方法和同步代码块中被调用. notifyAll():接触所有在该对象上调用wait()方法的

java多线程同步以及线程间通信详解&消费者生产者模式&死锁&Thread.join()(多线程编程之二)

本篇我们将讨论以下知识点: 1.线程同步问题的产生 什么是线程同步问题,我们先来看一段卖票系统的代码,然后再分析这个问题: [java] view plain copy print? package com.zejian.test; /** * @author zejian * @time 2016年3月12日 下午2:55:42 * @decrition 模拟卖票线程 */ public class Ticket implements Runnable { //当前拥有的票数 private 

【Android 并发编程】线程间通信的三种基本方式

1. 使用管道流Pipes "管道"是java.io包的一部分.它是Java的特性,而不是Android特有的.一条"管道"为两个线程建立一个单向的通道.生产者负责写数据,消费者负责读取数据. 下面是一个使用管道流进行通信的例子. public class PipeExampleActivity extends Activity { private static final String TAG = "PipeExampleActivity";

七. 多线程编程7.线程优先级

线程优先级被线程调度用来判定何时每个线程允许运行.理论上,优先级高的线程比优先级低的线程获得更多的CPU时间.实际上,线程获得的CPU时间通常由包括优先级在内的多个因素决定(例如,一个实行多任务处理的操作系统如何更有效的利用CPU时间). 一个优先级高的线程自然比优先级低的线程优先.举例来说,当低优先级线程正在运行,而一个高优先级的线程被恢复(例如从沉睡中或等待I/O中),它将抢占低优先级线程所使用的CPU. 理论上,等优先级线程有同等的权利使用CPU.但你必须小心了.记住,Java是被设计成能

七. 多线程编程8.线程同步

当两个或两个以上的线程需要共享资源,它们需要某种方法来确定资源在某一刻仅被一个线程占用.达到此目的的过程叫做同步(synchronization).像你所看到的,Java为此提供了独特的,语言水平上的支持. 同步的关键是管程(也叫信号量semaphore)的概念.管程是一个互斥独占锁定的对象,或称互斥体(mutex).在给定的时间,仅有一个线程可以获得管程.当一个线程需要锁定,它必须进入管程.所有其他的试图进入已经锁定的管程的线程必须挂起直到第一个线程退出管程.这些其他的线程被称为等待管程.一个

[javaSE] 并发编程(线程间通信)

新建一个资源类Resource 定义成员变量String name 定义成员变量int age 新建一个输入类Input,实现Runnable接口 定义一个构造方法Input(),传入参数:Resource对象 实现run()方法 定义while(true)往Resuorce对象的属性赋值 新建一个输出类Output,实现Runnable接口 定义一个构造方法Output(),传入参数:Resource对象 实现run()方法 定义while(true)打印Resuorce对象的属性 main方

【并发编程】线程间通信的方式

管道流Pipes "管道"是java.io包的一部分,它是Java的特性.而不是Android特有的.一条"管道"为两个线程建立一个单向的通道.生产者负责写数据.消费者负责读取数据. public class PipeExampleActivity extends Activity { private static final String TAG = "PipeExampleActivity"; private EditText editTex

七. 多线程编程11.线程的挂起、恢复和终止

有时,线程的挂起是很有用的.例如,一个独立的线程可以用来显示当日的时间.如果用户不希望用时钟,线程被挂起.在任何情形下,挂起线程是很简单的,一旦挂起,重新启动线程也是一件简单的事. 挂起,终止和恢复线程机制在Java 2和早期版本中有所不同.尽管你运用Java 2的途径编写代码,你仍需了解这些操作在早期Java环境下是如何完成的.例如,你也许需要更新或维护老的代码.你也需要了解为什么Java 2会有这样的变化.因为这些原因,下面内容描述了执行线程控制的原始方法,接着是Java 2的方法. Jav

【黑马】程序员————多线程(二)单例设计模式、线程间通信,JDK1.5互斥锁

------Java培训.Android培训.iOS培训..Net培训.期待与您交流!----- 一.单例设计模式 单例设计模式的意义: A.保证类在内存中只有一个对象,不提供外部访问方式,构造函数用private修饰. B.提供公共方法(static修饰,类的静态方法),获取类的实例.单例设计模式分为饿汉和懒汉两种模式. 饿汉式&懒汉式 class Test33 { public static void main(String[] args) { Fanjianan.getInstance()