`
xew473kf
  • 浏览: 13871 次
最近访客 更多访客>>
社区版块
存档分类
最新评论

Java编程思想――并发(3)

 
阅读更多

Java编程思想――并发(3)
2010年07月28日
  一个线程可以处于以下四种状态之一:
  1.新建(new):线程对象已经建立,但还没有启动,所以它还不能运行。
  2.就绪(Runnable):在这种状态下,只要调度程序把时间片分配给线程,线程就可以运行。也就是说,在任意时刻,线程可以运行也可以不运行。只要调度程序能分配时间片给线程,它就可以运行;这不同于死亡和阻塞状态。
  3.死亡(Dead):线程死亡的通常方式是从run( )方法返回。在Java 2废弃stop( )以前,你也可以调用它,但这很容易让你的程序进入不稳定状态。还有一个destroy( )方法(这个方法从来没被实现过,也许以后也不会被实现,它也属于被废止的)。在本章的后面你将学习一种与调用stop( )功能等价的方式。
  4.阻塞(Blocked):线程能够运行,但有某个条件阻止它的运行。当线程处于阻塞状态时,调度机制将忽略线程,不会分配给线程任何处理器时间。直到线程重新进入了就绪状态,它才有可能执行操作。
  进入阻塞状态
  当一个线程被阻塞时,必然存在某种原因使其不能继续运行。一个线程进入阻塞状态,可能有如下原因:
  1.你通过调用sleep(milliseconds)使线程进入休眠状态,在这种情况下,线程在指定的时间内不会运行。
  2.你通过调用wait( )使线程挂起。直到线程得到了notify( )或notifyAll( )消息,线程才会进入就绪状态。我们将在下一节验证这一点。
  3.线程在等待某个输入/输出完成。
  4.线程试图在某个对象上调用其同步控制方法,但是对象锁不可用。
  在较早的代码中,你也可能看到suspend( )和resume( )用来阻塞和唤醒线程,但是在Java 2中这些方法被废止了(因为可能导致死锁),所以本书不讨论这些内容。 在理解了线程之间可能存在相互冲突,以及怎样避免冲突之后,下一步就是学习怎样使线程之间相互协作。这种协作关键是通过线程之间的握手来进行的,这种握手可以通过Object的方法wait( )和notify( )来安全的实现。  调用sleep( )的时候锁并没有被释放,理解这一点很重要。另一方面,wait( )方法的确释放了锁,这就意味着在调用wait( )期间,可以调用线程中对象的其他同步控制方法。当一个线程在方法里遇到了对wait( )的调用的时候,线程的执行被挂起,对象上的锁被释放。
  有两种形式的wait( )。第一种接受毫秒作为参数,意思与sleep( )方法里参数的意思相同,都是指"在此期间暂停" 。不同之处在于,对于wait( ):
  1.在wait( )期间锁是释放的。
  2.你可以通过notify( )、notifyAll( ),或者时间到期,从wait( )中恢复执行。
  第二种形式的wait( )不要参数;这种用法更常见。wait( )将无限等待直到线程接收到notify( )或者notifyAll( )消息。
  wait( ), notify( ),以及notifyAll( )的一个比较特殊的方面是这些方法是基类Object的一部分,而不是像Sleep( )那样属于Thread的一部分。尽管开始看起来有点奇怪,仅仅针对线程的功能却作为通用基类的一部分而实现,不过这是有道理的,因为这些功能要用到的锁也是所有对象的一部分。所以,你可以把wait( )放进任何同步控制方法里,而不用考虑这个类是继承自Thread还是实现了Runnable接口。实际上,你只能在同步控制方法或同步控制块里调用wait( ), notify( )和notifyAll( )(因为不用操作锁,所以sleep( )可以在非同步控制方法里调用)。如果你在非同步控制方法里调用这些方法,程序能通过编译,但运行的时候,你将得到IllegalMonitorStateException异常,伴随着一些含糊的消息,比如"当前线程不是拥有者"。消息的意思是,调用 wait( ), notify( )和notifyAll( )的线程在调用这些方法前必须"拥有"(获取)对象的锁。
  你能够让另一个对象执行这种操作以维护其自己的锁。要这么做的话,你必须首先得到对象的锁。比如,如果你要在对象x上调用notify( ),那么你就必须在能够取得x的锁的同步控制块中这么做:
  synchronized(x) {
  x.notify();
  }
  特别地,当你在等待某个条件,这个条件必须由当前方法以外的因素才能改变的时候(典型地,这个条件被另一个线程所改变),就应该使用wait( )。你也不希望在线程里测试条件的时候空等;这也称为"忙等",它会极大占用CPU时间。所以wait( )允许你在等待外部条件的时候,让线程休眠,只有在收到notify( )或notifyAll( )的时候线程才唤醒并对变化进行检查。所以,wait( )为在线程之间进行同步控制提供了一种方法。
  例如,考虑一个餐馆,有一个厨师和一个服务员。服务员必须等待厨师准备好食物。当厨师准备好食物的时候,他通知服务员,后者将得到食物然后继续等待。这是一个线程协作的极好的例子:厨师代表了生产者,服务员代表了消费者。下面是模拟这个场景的代码:
  //: c13:Restaurant.java
  // The producer-consumer approach to thread cooperation.
  import com.bruceeckel.simpletest.*;
  class Order {
  private static int i = 0;
  private int count = i++;
  public Order() {
  if(count == 10) {
  System.out.println("Out of food, closing");
  System.exit(0);
  }
  }
  public String toString() { return "Order " + count; }
  }
  class WaitPerson extends Thread {
  private Restaurant restaurant;
  public WaitPerson(Restaurant r) {
  restaurant = r;
  start();
  }
  public void run() {
  while(true) {
  while(restaurant.order == null)
  synchronized(this) {
  try {
  wait();
  } catch(InterruptedException e) {
  throw new RuntimeException(e);
  }
  }
  System.out.println(
  "Waitperson got " + restaurant.order);
  restaurant.order = null;
  }
  }
  }
  class Chef extends Thread {
  private Restaurant restaurant;
  private WaitPerson waitPerson;
  public Chef(Restaurant r, WaitPerson w) {
  restaurant = r;
  waitPerson = w;
  start();
  }
  public void run() {
  while(true) {
  if(restaurant.order == null) {
  restaurant.order = new Order();
  System.out.print("Order up! ");
  synchronized(waitPerson) {
  waitPerson.notify();
  }
  }
  try {
  sleep(100);
  } catch(InterruptedException e) {
  throw new RuntimeException(e);
  }
  }
  }
  }
  public class Restaurant {
  private static Test monitor = new Test();
  Order order; // Package access
  public static void main(String[] args) {
  Restaurant restaurant = new Restaurant();
  WaitPerson waitPerson = new WaitPerson(restaurant);
  Chef chef = new Chef(restaurant, waitPerson);
  monitor.expect(new String[] {
  "Order up! Waitperson got Order 0",
  "Order up! Waitperson got Order 1",
  "Order up! Waitperson got Order 2",
  "Order up! Waitperson got Order 3",
  "Order up! Waitperson got Order 4",
  "Order up! Waitperson got Order 5",
  "Order up! Waitperson got Order 6",
  "Order up! Waitperson got Order 7",
  "Order up! Waitperson got Order 8",
  "Order up! Waitperson got Order 9",
  "Out of food, closing"
  }, Test.WAIT);
  }
  } ///:~
  Order是一个简单的能自己计数的类,但要注意它也包含了终止程序的方法;当订单数累计到10时,将调用System.exit( )。
  WaitPerson(服务员)必须知道自己所工作的Restaurant(餐馆),因为他们必须从餐馆的"订单窗口"取出订单restaurant.order。在run( )中,WaitPerson调用wait( )进入等待模式,停止线程的执行直到被Chef(厨师)的notify( )方法所唤醒。因为是很简单的程序,我们知道只有一个线程在等待WaitPerson对象的锁:即WaitPerson线程自己。正因为这个原因,使得调用notify( )是安全的。在更复杂的情况下,多个线程可能在等待同一个特定的锁,所以你不知道哪个线程被唤醒。解决方法是调用notifyAll( ),它将唤醒所有等待这个锁的线程。每个线程必须自己决定是否对这个通知作出反应。
  注意对wait( )的调用被包装在一个while( )语句里,它在测试的正是等待的条件。开始看起来可能很奇怪--如果你在等一个订单,那么一旦你被唤醒,订单必须是可用的,对不对?问题是在多线程程序里,一些别的线程可能在WaitPerson苏醒的同时冲进来抢走订单。唯一安全的方法就是对于wait( )总是使用如下方式:
  while(conditionIsNotMet) wait( );
  这可以保证在你跳出等待循环之前条件将被满足,如果你被不相干的条件所通知(比如notifyAll( )),或者在你完全退出循环之前条件已经被改变,你被确保可以回来继续等待。
  一个Chef对象必须知道他/她工作的餐馆(这样可以通过restaurant.order下订单)和取走食物的WaitPerson,这样才能在订单准备好的时候通知WaitPerson。在这个简化过的例子中,由Chef产生订单对象,然后通知WaitPerson订单已经准备好了。
  请注意对notify( )的调用必须首先获取WaitPerson对象的锁。WaitPerson.run( )里对wait( )的调用将自动释放这个锁,所以这是可能的。因为要调用notify( )必须获取锁,这就能保证如果两个线程试图在同一个对象上调用notify( )时不会互相冲突。
  上面的例子中,一个线程只有一个单一的地点来存储某个对象,这样另一个线程就可以在以后使用这个对象。然而,在一个典型的生产者-消费者实现中,你要使用先进先出的队列来存放被生产和消费的对象。要对这个问题做更多的了解,请参阅本章后面的练习。  通过输入/输出在线程间进行通信通常很有用。线程库以"管道"(pipes)的形式对线程间的输入/输出提供了支持。它们在Java输入/输出库中的对应物就是PipedWriter类(允许线程向管道写)和PipedReader类(允许不同线程从同一个管道中读取)。这个模型可以看成是生产者-消费者问题的变体,这里的管道就是一个封装好的解决方案。
  下面是一个简单例子,两个线程使用一个管道进行通信:
  //: c13:PipedIO.java
  // Using pipes for inter-thread I/O
  import java.io.*;
  import java.util.*;
  class Sender extends Thread {
  private Random rand = new Random();
  private PipedWriter out = new PipedWriter();
  public PipedWriter getPipedWriter() { return out; }
  public void run() {
  while(true) {
  for(char c = 'A'; c Read: " + (char)in.read());
  }
  } catch(IOException e) {
  throw new RuntimeException(e);
  }
  }
  }
  public class PipedIO {
  public static void main(String[] args) throws Exception {
  Sender sender = new Sender();
  Receiver receiver = new Receiver(sender);
  sender.start();
  receiver.start();
  new Timeout(4000, "Terminated");
  }
  } ///:~
  Sender和Receiver代表了两个线程,它们执行某些任务并且需要互相通信。Sender创建了一个PipedWriter,它是一个单独的对象,但是对于Receiver,PipedReader的建立必须在构造器中与一个PipedWriter相关联。Sender把数据放进Writer然后休眠一段随机的时间。然而,Receiver没有调用sleep( )和wait( )。但当它调用read( )时,如果没有数据,它将自动阻塞。这样你不用使用wait( )循环,就得到了生产者-消费者的效果。
  注意到sender和receiver是在main( )中启动的,即对象构造完毕以后。如果你启动了一个没有构造完毕的对象,在不同的平台上管道可能会产生不一致的行为。  本章只介绍了最基本的协作方式(比如生产者
分享到:
评论

相关推荐

    Java编程思想(第4版)读书笔记by CZF

    Java编程思想(第4版)读书笔记by CZFJava编程思想(第4版)读书笔记by CZFJava编程思想(第4版)读书笔记by CZF

    JAVA并发编程实践.pdf

    Joshua Bloch是Google的首席Java架构师,《Effective Java》的作者、《3ava PLizzlers》的合著者,他不像他的兄弟(his brother,Bloctl与Neal主持的Java编程专栏里虚构的人物)那样编程,从来都不。Doug Lea是...

    Java并发编程实践 PDF 高清版

    本书的读者是那些具有一定Java编程经验的程序员、希望了解Java SE 5,6在线程技术上的改进和新特性的程序员,以及Java和并发编程的爱好者。 目录 代码清单 序 第1章 介绍 1.1 并发的(非常)简短历史 1.2 线程的...

    java并发编程实践

    能够成为读者的理论支持,又可以作为构建可靠的、可伸缩的、可维护...《JAVA并发编程实践》适合于具有一定Java编程经验的程序员、希望了解Java SE 5以及6在线程技术上的改进和新特性的程序员,以及Java和并发编程的爱好

    java并发编程实践(中文版pdf全部40M分2部分上传)2

    《JAVA并发编程实践》既能够成为读者的理论支持,又可以作为...《JAVA并发编程实践》适合于具有一定Java编程经验的程序员、希望了解Java SE 5以及6在线程技术上的改进和新特性的程序员,以及Java和并发编程的爱好者。

    Java Concurrency in Practice JAVA并发编程实践中文版(全)

    随着多核处理器的普及,使用并发成为构建高性能应用程序的关键。...本书的读者是那些具有一定Java编程经验的程序员、希望了解Java SE 5,6在线程技术上的改进和新特性的程序员,以及Java和并发编程的爱好者。

    java葵花宝典,编程思想,并发,虚拟机合集

    java葵花宝典,编程思想,并发,虚拟机合集-你值得拥有

    JAVA并发编程实践 中文版

    随着多核处理器的普及,使用并发成为构建高性能应用程序的关键。... 本书的读者是那些具有一定java编程经验的程序员、希望了解java se 5,6在线程技术上的改进和新特性的程序员,以及java和并发编程的爱好者。

    java并发编程实践(中文版pdf全部40M分2部分上传)1

    《JAVA并发编程实践》既能够成为读者的理论支持,又可以作为...《JAVA并发编程实践》适合于具有一定Java编程经验的程序员、希望了解Java SE 5以及6在线程技术上的改进和新特性的程序员,以及Java和并发编程的爱好者。

    StudyJava:Java 基础学习(java编程思想和java技术核心卷)

    StudyJavaJava Basics1.java 基础学习2.java变成思想和java核心技术整理3.java8等java新特性学习整理###Java编程思想第1章 对象导论1.1 抽象过程1.2 每个对象都有一个接口1.3 每个对象都提供服务1.4 被隐藏的具体...

    Java编程思想中关于并发的总结

    在本文中小编给大家整理的是关于Java编程思想中关于并发的总结以及相关实例内容,需要的朋友们参考下。

    C++编程思想(Thinking in C++)完美版pdf

    Bruce Eckel 《Thinking in Java》(Java编程思想)作者。Eckel有20年专业编程经验,并自1986年起教育人们如何撰写面向对象程序,足迹遍及全球,成为一位知名的 C++教师和顾问,如今兼涉Java。他是C++标准委员会拥有...

    Java并发编程实践

    这是一本目前在 Java 并发性领域研究的编程图书中最值得一读的力作。随着计算机技术的不断迅速发展,各种各样的编程模型也越来越多,越来越复杂化与多样化。虽然当前CPU主频在不断升高,但是X86架构的硬件已经成为...

    java编程think第4版完整源码加python测试框架

    java编程think第4版完整源码加python测试框架,不难看出这是一本经典之作。本书共22章,包括操作符、控制执行流程、访问权限控制、复用类、多态、接口、通过异常处理错误、字符串、泛型、数组、容器深入研究、JavaI/O...

    java并发编程实战 pdf

    学习Java其实应该上升到如何学习程序设计这种境界,其实学习程序设计又是接受一种编程思想。每一种语言的程序设计思想大同小异,只是一些由语言特性的而带来的细微差别,比如Java中的Interface,你几乎在以前的学习...

    java 编程思想 (多态,接口和内部类)

    java对象的引用,控制程序流,初始化和清除,封装,复用类,多态,接口和内部类,异常处理try——catch,类的检查,对象的集合,i/o系统,并发,分析与设计,对象的传递和返回

    Java电子书打包-5

    Java编程思想(第4版).pdf java编程思想.pdf Java并发编程实践-电子书-03章.pdf Java并发程序设计教程.pdf Java核心技术 第八版 卷Ⅰ(基础篇).书签版.pdf Java核心技术(卷2)第8版.pdf Java解惑(中文).pdf }

    JAVA并发编程-2-线程并发工具类

    JAVA并发编程-2-线程并发工具类一、Fork/Join1、分而治之与工作密取2、使用标准范式3、Fork/Join的同步用法4、Fork/Join的异步用法二、CountDownLatch三、CyclicBarrier四、Semaphore信号量五、Exchanger ...

    JAVA并发编程实践中文版.pdf

    本书既能成为读者的理论支持,又可以作为构建可靠的、可伸缩的、可维护的并发程序的技术支持。本书不仅仅提供并发API的清单及其机制,还提供了设计原则、模式和思想模型。

Global site tag (gtag.js) - Google Analytics