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( )中启动的,即对象构造完毕以后。如果你启动了一个没有构造完毕的对象,在不同的平台上管道可能会产生不一致的行为。 本章只介绍了最基本的协作方式(比如生产者
发表评论
-
VBS脚本常用经典代码收集
2012-01-20 02:13 714VBS脚本常用经典代码收集 2010年06月21日 1. ... -
多线程加速图像模板匹配
2012-01-20 02:13 655多线程加速图像模板匹 ... -
ORACLE PL/SQL编程详解之七 程序包的创建与应用
2012-01-20 02:13 670ORACLE PL/SQL编程详解之七 ... -
fread函数和fwrite函数
2012-01-20 02:13 852fread函数和fwrite函数 2010年06月28日 ... -
[耀湾/微亚细亚] 夜降り萃梦乡 FIN.
2012-01-19 10:03 660[耀湾/微亚细亚] 夜降り萃梦乡 FIN. 2009年02月 ... -
她为我写的 ..........你就是我的天使…………
2012-01-19 10:03 552她为我写的 ..........你就是我的天使………… 2 ... -
D---的记忆3
2012-01-19 10:03 640D---的记忆3 2008年10月12日 一、洞察 ... -
《因为是你》--2
2012-01-19 10:03 640《因为是你》--2 2012年01月13日 兼职保姆 ... -
如何有效维护和发布“Windows + Android SDK + Eclipse”开发环境下的apk和代码
2012-01-17 02:44 1026如何有效维护和发布“Windows + Android SDK ... -
Kinect for Windows SDK beta 放出
2012-01-17 02:43 576Kinect for Windows SDK beta 放出 ... -
Windows SDK编程(Delphi版) 之 消息处理
2012-01-17 02:43 817Windows SDK编程(Delphi版) ... -
Windows Mobile 6 SDK及中文版模拟器下载地址
2012-01-17 02:43 1517Windows Mobile 6 SDK及中文版模拟器下载地址 ... -
SDK 问题
2012-01-17 02:43 685SDK 问题 5小时前 问题:点击AVD Manager ... -
回顾日志_20110609
2012-01-15 22:20 485回顾日志_20110609 2011年06月09日 先来 ... -
笔记66-67
2012-01-15 22:19 608笔记66-67 2010年06月02日 ... -
FLEX网站收集
2012-01-15 22:19 925FLEX网站收集 2009年12月19日 1.FLEX网 ... -
ActionScript3.0 垃圾回收机制 2010-4-19 雨
2012-01-15 22:19 636ActionScript3.0 垃圾回收机制 2010-4-1 ... -
SNS网站设计的几大技术支持
2012-01-15 22:19 742SNS网站设计的几大技术支持 2010年04月04日 第 ...
相关推荐
《Java编程思想》是Java程序员领域的一本经典之作,由Bruce Eckel撰写,以其深入浅出的讲解方式和丰富的实例闻名。这本书对于想要深入理解Java语言的人来说,是一份宝贵的资源。"Thinking in Java",直译为“思考...
《Java编程思想》是 Bruce Eckel 的经典著作,第四版更是深入浅出地介绍了Java语言的核心概念和技术。这个压缩包包含的源代码是书中的示例程序,它们旨在帮助读者理解书中阐述的各种编程原理和实践。通过分析这些源...
《侯捷-Java编程思想》是一本深受Java开发者喜爱的经典著作,尽管是繁体版本,但其中也包含英文内容,方便不同语言背景的读者理解。这本书深入浅出地讲解了Java编程的核心概念和技术,旨在帮助读者掌握Java编程的...
《Java编程思想》是Java开发领域的一本经典著作,它深入浅出地介绍了Java语言的核心概念和编程技术。这本书以其详尽的解释、丰富的实例和严谨的逻辑深受程序员喜爱。以下将围绕标题和描述中的知识点进行详细阐述: ...
《Java编程思想》是一本由 Bruce Eckel 编著的经典Java教程,对于初学者和有经验的程序员来说,都是深入理解Java语言的重要参考书。这本书深入浅出地讲解了Java的核心概念,包括面向对象编程、泛型、并发、集合框架...
《Java编程思想》是Java初学者的一本经典教材,它以其深入浅出的讲解和丰富的实例,深受读者喜爱。本书全面覆盖了Java的基础知识,包括语法特性、面向对象编程概念、异常处理、集合框架、多线程、网络编程等多个方面...
3. **Java编程思想**: 本书的核心在于其"思考"的理念,鼓励程序员不仅学习语言语法,更要理解面向对象设计原则。Java是一种强类型、面向对象的语言,其特性如垃圾回收、自动内存管理、丰富的类库等,使得它在各种...
《JAVA编程思想习题及答案》是一份针对Java编程学习者的宝贵资源,它涵盖了Java语言的核心概念、设计模式以及实际编程技巧。这份资料通过习题的形式帮助学习者深入理解和掌握Java编程思想,同时提供了解答,使学习...
《Java编程思想》是 Bruce Eckel 的经典著作,这本书深入浅出地介绍了Java语言的核心概念和技术,被广大Java程序员视为学习Java的必备参考书。这个压缩包包含了书中的实例代码和习题答案,对于读者理解和掌握Java...
此外,第三版的《Java编程思想》还可能包含了Java语言的新特性和更新,例如Generics(泛型)、枚举类型、异常处理的改进、集合框架的增强以及并发编程的相关内容。这些新特性让Java更加强大,适应现代软件开发的需求...
《Java编程思想企业版》是一本深度探讨Java编程技术的权威著作,专为企业级应用设计。这本书涵盖了Java语言的核心概念、高级特性以及企业级开发中的关键应用。通过深入理解本书,开发者可以提升自己的编程技能,更好...
Java编程思想是编程领域的一本经典著作,它深入浅出地介绍了Java编程的各个方面,包括面向对象编程、泛型、集合框架、并发编程等核心概念。在这个压缩包中,包含了两个重要的jar文件:net.mindview.util.jar和pets....
Java编程思想是学习Java语言的一本经典教材,它深入浅出地介绍了Java编程的各种概念和技术。第四版在原有的基础上增加了许多新的内容,包括对Java 8及后续版本特性的讲解。"java编程思想4课后练习答案"是针对这本书...
《Java编程思想习题答案代码》是一份针对学习Java编程思想的宝贵资源,它提供了书中的课后习题解答,旨在帮助学习者在探索Java语言的过程中进行实践和检验自己的理解。这份资料通常与《Thinking in Java》这本书配套...
Java编程思想是编程领域的一本经典著作,由Bruce Eckel撰写,它深入浅出地介绍了Java语言的核心概念和编程技巧。这本书旨在帮助开发者理解面向对象编程,并提供了丰富的示例来阐述如何有效地使用Java进行程序设计。...
《Java编程思想第四版》是Java编程领域里一本经典的教程,由 Bruce Eckel 所著。这本书深入浅出地介绍了Java语言的核心概念和技术,包括面向对象编程、异常处理、集合框架、多线程、网络编程等多个方面。对于学习...
这15个知识点只是《Java编程思想》一书中部分内容的概述,实际上,书中还涵盖了更多高级主题,如设计模式、并发库的高级特性、垃圾回收机制等。通过深入学习和实践这些知识点,开发者可以掌握Java编程的精髓,提升...