??xml version="1.0" encoding="utf-8" standalone="yes"?>MM131亚洲国产美女久久,中文字幕无码精品亚洲资源网久久,亚洲一区中文字幕在线电影网 http://m.tkk7.com/nighthawk/category/2524.html享受JAVAQn受h?/description>zh-cnWed, 28 Feb 2007 20:30:02 GMTWed, 28 Feb 2007 20:30:02 GMT60设计模式之Adapter(适配?http://m.tkk7.com/nighthawk/articles/10432.htmlnighthawknighthawkThu, 18 Aug 2005 06:41:00 GMThttp://m.tkk7.com/nighthawk/articles/10432.htmlhttp://m.tkk7.com/nighthawk/comments/10432.htmlhttp://m.tkk7.com/nighthawk/articles/10432.html#Feedback0http://m.tkk7.com/nighthawk/comments/commentRss/10432.htmlhttp://m.tkk7.com/nighthawk/services/trackbacks/10432.html适配器模式定?
两个不兼容的类U合在一起用,属于l构型模?需要有Adaptee(被适配?和Adaptor(适配?两个w䆾.

Z使用?
我们l常到要将两个没有关系的类l合在一起?W一解决Ҏ(gu)是:(x)修改各自cȝ接口Q但是如果我们没有源代码Q或者,我们不愿意ؓ(f)?jin)一个应用而修改各自的接口?怎么?

使用AdapterQ在q两U接口之间创Z个؜合接?淯?.

如何使用?
实现Adapter方式,其实"think in Java"?cd?一节中已经提到,有两U方式:(x)l合(composition)和(h)?inheritance).


假设我们要打桩,有两U类Q方形桩 圆Ş?
public class SquarePeg{
  public void insert(String str){
    System.out.println("SquarePeg insert():"+str);
  }

}

public class RoundPeg{
  public void insertIntohole(String msg){
    System.out.println("RoundPeg insertIntoHole():"+msg);
}
}

现在有一个应?需要既打方形桩,又打圆Ş?那么我们需要将q两个没有关pȝcȝ合应?假设RoundPeg我们没有源代?或源代码我们不想修改,那么我们使用Adapter来实现这个应?

public class PegAdapter extends SquarePeg{

  private RoundPeg roundPeg;

  public PegAdapter(RoundPeg peg)(this.roundPeg=peg;)

  public void insert(String str){ roundPeg.insertIntoHole(str);}

}

在上面代码中,RoundPeg属于Adaptee,是被适配?PegAdapter是Adapter,Adaptee(被适配者RoundPeg)和Target(目标SquarePeg)q行适配.实际上这是将l合Ҏ(gu)(composition)和(h)?inheritance)Ҏ(gu)l合q用.

PegAdapter首先l承SquarePegQ然后用new的组合生成对象方式,生成RoundPeg的对象roundPegQ再重蝲父类insert()Ҏ(gu)。从q里,你也?jin)解使用new生成对象和用extendsl承生成对象的不?前者无需对原来的cM?甚至无需要知道其内部l构和源代码.

如果你有些Java使用的经验,已经发现Q这U模式经怋用?/P>

q一步?/B>
上面的PegAdapter是(h)承了(jin)SquarePeg,如果我们需要两边(h)承,即(h)承SquarePeg 又(h)承RoundPeg,因ؓ(f)Java中不允许多(h)承,但是我们可以实现(implements)两个接口(interface)

public interface IRoundPeg{
  public void insertIntoHole(String msg);

}

public interface ISquarePeg{
  public void insert(String str);

}

下面是新的RoundPeg 和SquarePeg, 除了(jin)实现接口q一区别Q和上面的没什么区别?BR>public class SquarePeg implements ISquarePeg{
  public void insert(String str){
    System.out.println("SquarePeg insert():"+str);
  }

}

public class RoundPeg implements IRoundPeg{
  public void insertIntohole(String msg){
    System.out.println("RoundPeg insertIntoHole():"+msg);
  }
}

下面是新的PegAdapter,叫做two-way adapter:

public class PegAdapter implements IRoundPeg,ISquarePeg{

  private RoundPeg roundPeg;
  private SquarePeg squarePeg;

  // 构造方?BR>  public PegAdapter(RoundPeg peg){this.roundPeg=peg;}
  // 构造方?BR>  public PegAdapter(SquarePeg peg)(this.squarePeg=peg;)

  public void insert(String str){ roundPeg.insertIntoHole(str);}

}

q有一U叫Pluggable Adapters,可以动态的获取几个adapters中一个。用Reflection技术,可以动态的发现cM的PublicҎ(gu)?BR>                                                                                                               ?JDON



nighthawk 2005-08-18 14:41 发表评论
]]>
JSP/Servlet 中的汉字~码问题 http://m.tkk7.com/nighthawk/articles/9222.htmlnighthawknighthawkThu, 04 Aug 2005 07:02:00 GMThttp://m.tkk7.com/nighthawk/articles/9222.htmlhttp://m.tkk7.com/nighthawk/comments/9222.htmlhttp://m.tkk7.com/nighthawk/articles/9222.html#Feedback0http://m.tkk7.com/nighthawk/comments/commentRss/9222.htmlhttp://m.tkk7.com/nighthawk/services/trackbacks/9222.html
每个国家Q或区域Q都规定?jin)计机信息交换用的字符~码集,如美国的扩展 ASCII? 中国?GB2312-80Q日本的 JIS {,作ؓ(f)该国?区域内信息处理的基础Q有着l一~码的重要作用。字W编码集按长度分?SBCSQ单字节字符集)(j)QDBCSQ双字节字符集)(j)两大cR早期的软gQ尤其是操作pȝQ,Z(jin)解决本地字符信息的计机处理Q出C(jin)各种本地化版本(L10NQ,Z(jin)区分Q引q了(jin) LANG, Codepage {概c(din)但是由于各个本地字W集代码范围重叠Q相互间信息交换困难QY件各个本地化版本独立l护成本较高。因此有必要本地化工作中的共性抽取出来,作一致处理,特别的本地化处理内定w低到最。这也就是所谓的国际化(I18NQ。各U语a信息被进一步规范ؓ(f) Locale 信息。处理的底层字符集变成了(jin)几乎包含?jin)所有字形的 Unicode?

现在大部分具有国际化特征的Y件核?j)字W处理都是以 Unicode 为基的,在Y件运行时Ҏ(gu)当时?Locale/Lang/Codepage 讄定相应的本地字W编码设|,q依此处理本地字W。在处理q程中需要实?Unicode 和本地字W集的相互{换,甚或?Unicode Z间的两个不同本地字符集的怺转换。这U方式在|络环境下被q一步g伸,M|络两端的字W信息也需要根据字W集的设|{换成可接受的内容?

Java 语言内部是用 Unicode 表示字符的,遵守 Unicode V2.0。Java E序无论是从/往(xin)文gpȝ以字W流?写文Ӟq是往(xin) URL q接?HTML 信息Q或?URL q接d参数|都会(x)有字W编码的转换。这样做虽然增加?jin)编E的复杂度,Ҏ(gu)引vhQ但却是W合国际化的思想的?

从理Z来说Q这些根据字W集讄而进行的字符转换不应该生太多问题。而事实是׃应用E序的实际运行环境不同,Unicode 和各个本地字W集的补充、完善,以及(qing)pȝ或应用程序实现的不规范,转码时出现的问题时时困扰着E序员和用户?

2. GB2312-80QGBKQGB18030-2000 汉字字符集及(qing) Encoding

其实解决 JAVA E序中的汉字~码问题的方法往(xin)往(xin)很简单,但理解其背后的原因,定位问题Q还需要了(jin)解现有的汉字~码和编码{换?

GB2312-80 是在国内计算机汉字信息技术发展初始阶D制定的Q其中包含了(jin)大部分常用的一、二U汉字,?9 区的W号。该字符集是几乎所有的中文pȝ和国际化的Y仉支持的中文字W集Q这也是最基本的中文字W集。其~码范围是高?sh)?xa1Q?xfeQ低位也?0xa1-0xfeQ汉字从 0xb0a1 开始,l束?0xf7feQ?

GBK ?GB2312-80 的扩展,是向上兼容的。它包含?20902 个汉字,其编码范围是 0x8140-0xfefeQ剔除高?sh)?0x80 的字位。其所有字W都可以一对一映射?Unicode 2.0Q也是?JAVA 实际上提供了(jin) GBK 字符集的支持。这是现阶段 Windows 和其它一些中文操作系l的~省字符集,但ƈ不是所有的国际化Y仉支持该字W集Q感觉是他们q不完全知道 GBK 是怎么回事。值得注意的是它不是国家标准,而只是规范。随着 GB18030-2000国标的发布,它将在不久的来完成它的历史使命?

GB18030-2000(GBK2K) ?GBK 的基上进一步扩展了(jin)汉字Q增加了(jin)藏、蒙{少数民族的字Ş。GBK2K 从根本上解决?jin)字位不够,字Ş不的问题。它有几个特点,

它ƈ没有定所有的字ŞQ只是规定了(jin)~码范围Q留待以后扩充?
~码是变长的Q其二字节部分与 GBK 兼容Q四字节部分是扩充的字Ş、字位,其编码范围是首字?0x81-0xfe、二字节0x30-0x39、三字节 0x81-0xfe、四字节0x30-0x39?
它的推广是分阶段的,首先要求实现的是能够完全映射?Unicode 3.0 标准的所有字形?
它是国家标准Q是强制性的?BR>现在q没有Q何一个操作系l或软g实现?GBK2K 的支持,q是现阶D和来汉化的工作内宏V?
Unicode 的介l?.....免?jin)吧?

JAVA 支持的encoding中与中文~程相关的有Q?有几个在JDK文中未列出) ASCII 7-bit, ?ascii7
ISO8859-1 8-bit, ?8859_1,ISO-8859-1,ISO_8859-1,latin1...
GB2312-80 同gb2312,gb2312-1980,EUC_CN,euccn,1381,Cp1381, 1383, Cp1383, ISO2022CN,ISO2022CN_GB......
GBK (注意大小?,同MS936
UTF8 UTF-8
GB18030 (现在只有IBM JDK1.3.?有支?, 同Cp1392,1392


JAVA 语言采用Unicode处理字符. 但从另一个角度来_(d)在javaE序中也可以采用非Unicode的{码,重要的是保证E序入口和出口的汉字信息不失真。如完全采用ISO-8859-1来处理汉字也能达到正的l果。网l上行的许多解x(chng)法,都属于这U类型。ؓ(f)?jin)不致引h؜淆,本文不对q种Ҏ(gu)作讨论?

3. 中文转码??'、ؕ码的由来

两个方向转换都有可能得到错误的结果:(x)

Unicode-->Byte, 如果目标代码集不存在对应的代码,则得到的l果?x3f.
如:(x)
"\u00d6\u00ec\u00e9\u0046\u00bb\u00f9".getBytes("GBK") 的结果是 "?ìéF?ù", Hex 值是3fa8aca8a6463fa8b4.
仔细看一下上面的l果Q你?x)发现\u00ec被{换ؓ(f)0xa8ac, \u00e9被{换ؓ(f)\xa8a6... 它的实际有效位变长了(jin)Q?q是因ؓ(f)GB2312W号Z的一些符可映射C些公qW号~码Q由于这些符号出现在ISO-8859-1或其它一些SBCS字符集中Q故它们在Unicode中编码比较靠前,有一些其有效位只?位,和汉字的~码重叠(其实q种映射只是~码的映,在显C时仔细不是一L(fng)。Unicode 中的W号是单字节宽,汉字中的W号是双字节? . 在Unicode\u00a0--\u00ff 之间q样的符h20个。了(jin)解这个特征非帔R要!由此׃隄解ؓ(f)什么JAVA~程中,汉字~码的错误结果中常常?x)出C些ؕ?其实是符号字W?, 而不全是'?'字符, 比如上面的例子?BR>
Byte-->Unicode, 如果Byte标识的字W在源代码集不存在,则得到的l果?xfffd.
如:(x)
Byte ba[] = {(byte)0x81,(byte)0x40,(byte)0xb0,(byte)0xa1}; new String(ba,"gb2312");
l果???, hex 值是"\ufffd\u554a". 0x8140 是GBK字符Q按GB2312转换表没有对应的|取\ufffd. (h意:(x)在显CuniCodeӞ因ؓ(f)没有对应的本地字W,所以也适用上一U情况,昄Z??".)

实际~程中,JSP/Servlet E序得到错误的汉字信息,往(xin)往(xin)是这两个q程的叠加,有时甚至是两个过E叠加后反复作用的结?

4. JSP/Servlet 汉字~码问题?qing)?WAS 中的解决办法

4.1 常见?encoding 问题的现?BR>|上常出现的 JSP/Servlet encoding 问题?sh)般都表现?browser 或应用程序端Q如:
览器中看到?Jsp/Servlet 面中的汉字怎么都成????
览器中看到?Servlet 面中的汉字怎么都成?jin)ؕ码?
JAVA 应用E序界面中的汉字怎么都成?jin)方块?
Jsp/Servlet 面无法昄 GBK 汉字?
JSP 面中内嵌在<%...%>,<%=...%>{Tag包含?JAVA code 中的中文成了(jin)qQ但面的其它汉字是对的?
Jsp/Servlet 不能接收 form 提交的汉字?
JSP/Servlet 数据库读写无法获得正的内容?BR>隐藏在这些问题后面的是各U错误的字符转换和处理(除第3个外Q是因ؓ(f) Java font 讄错误引v的)(j)。解决类似的字符 encoding 问题Q需要了(jin)?Jsp/Servlet 的运行过E,(g)查可能出现问题的各个炏V?

4.2 JSP/Servlet web ~程时的 encoding 问题
q行于Java 应用服务器的 JSP/Servlet ?Browser 提供 HTML 内容
其中有字W编码{换的地方?

JSP ~译。Java 应用服务器将Ҏ(gu) JVM ?file.encoding D?JSP 源文Ӟ~译生成 JAVA 源文Ӟ再根?file.encoding 值写回文件系l。如果当前系l语a支持 GBKQ那么这时候不?x)出?encoding 问题。如果是英文的系l,?LANG ?en_US ?Linux, AIX ?SolarisQ则要将 JVM ?file.encoding 值置?GBK 。系l语a如果?GB2312Q则Ҏ(gu)需要,定要不要设|?file.encodingQ将 file.encoding 设ؓ(f) GBK 可以解决潜在?GBK 字符q问题


Java 需要被~译?.class 才能?JVM 中执行,q个q程存在与a.同样?file.encoding 问题。从q里开?servlet ?jsp 的运行就cM?jin),只不q?Servlet 的编译不是自动进行的。对于JSPE序, 对生的JAVA 中间文g的编译是自动q行?在程序中直接调用sun.tools.javac.Mainc?. 因此如果在这一步出现问题的? 也要(g)查encoding和OS的语a环境Q或者将内嵌在JSP JAVA Code 中的?rn)态汉字{?Unicode, 要么?rn)态文本输Z要放?JAVA code 中?对于Servlet, javac ~译时手工指?encoding 参数可以了(jin)?


Servlet 需要将 HTML 面内容转换?browser 可接受的 encoding 内容发送出厅R依赖于?JAVA App Server 的实现方式,有的查?Browser ?accept-charset ?accept-language 参数或以其它猜的方式定 encoding |有的则不。因此采用固定encoding 也许是最好的解决Ҏ(gu)。对于中文网,可在 JSP ?Servlet 中设|?contentType="text/html; charset=GB2312"Q如果页面中有GBK字符Q则讄为contentType="text/html; charset=GBK"Q由于IE ?Netscape对GBK的支持程度不一P作这U设|时需要测试一下?BR>因ؓ(f)16?JAVA char在网l传送时?位会(x)被丢弃,也ؓ(f)?jin)确保Servlet面中的汉字Q包括内嵌的和servletq行q程中得到的Q是期望的内码,可以?PrintWriter out=res.getWriter() 取代 ServletOutputStream out=res.getOutputStream(). PrinterWriter 根据contentType中指定的charset作{?(ContentType需在此之前指定Q?; 也可以用OutputStreamWriter装 ServletOutputStream cdƈ用write(String)输出汉字字符丌Ӏ?BR>对于 JSPQJAVA Application Server 应当能够保在这个阶D将嵌入的汉字正传送出厅R?


q是解释 URL 字符 encoding 问题。如果通过 get/post 方式?browser q回的参数g包含汉字信息Q?servlet 无法得到正的倹{SUN?J2SDK 中,HttpUtils.parseName 在解析参数时Ҏ(gu)没有考虑 browser 的语a讄Q而是得到的值按 byte 方式解析。这是网上讨论得最多的 encoding 问题。因是设计缺P只能?bin 方式重新解析得到的字W串Q或者以 hack HttpUtils cȝ方式解决。参考文?2 均有介绍Q不q最好将其中的中?encoding GB2312?CP1381 都改?GBKQ否则遇?GBK 汉字Ӟq是?x)有问题?
Servlet API 2.3 提供一个新的函?HttpServeletRequest.setCharacterEncoding 用于在调?request.getParameter(“param_name? 前指定应用程序希望的 encodingQ这有助于d解决q个问题?BR>4.3 IBM Websphere Application Server 中的解决Ҏ(gu)

WebSphere Application Server Ҏ(gu)准的 Servlet API 2.x 作了(jin)扩展Q提供较好的多语a支持。运行在中文的操作系l中Q可以不作Q何设|就可以很好地处理汉字。下面的说明只是对WAS是运行在英文的系l中Q或者需要有GBK支持时有效?

上述c,d情况QW(xu)AS 都要查询 Browser 的语a讄Q在~省状况下, zh, zh-cn {均被映ؓ(f) JAVA encoding CP1381Q注意:(x) CP1381 只是{同?GB2312 的一?codepageQ没?GBK 支持Q。这样做我想是因为无法确?Browser q行的操作系l是支持GB2312, q是 GBKQ所以取其小。但是实际的应用pȝq是要求面中出?GBK 汉字Q最著名的是朱ȝ名字中的“镕"(rong2 Q?xe946Q\u9555)Q所以有时还是需要将 Encoding/Charset 指定?GBK。当?WAS 中变更缺省的 encoding 没有上面说的那么ȝ(ch)Q针?a,bQ参考文?5Q在 Application Server 的命令行参数中指?-Dfile.encoding=GBK 卛_Q?针对 dQ在 Application Server 的命令行参数中指?Ddefault.client.encoding=GBK。如果指定了(jin)-Ddefault.client.encoding=GBKQ那么c情况下可以不再指定charset?

上面列出的问题(sh)q有一个关于Tag<%...%>,<%=...%>中的 JAVA 代码里包含的?rn)态文本未能正显C的问题Q在WAS中的解决Ҏ(gu)是除?jin)设|正的file.encoding, q需要以相同Ҏ(gu)讄-Duser.language=zh -Duser.region=CN。这与JAVA locale的设|有兟?

4.4 数据库读写时?encoding 问题

JSP/Servlet ~程中经常出?encoding 问题的另一个地Ҏ(gu)d数据库中的数据?

行的关pL据库pȝ都支持数据库 encodingQ也是说在创徏数据库时可以指定它自q字符集设|,数据库的数据以指定的~码形式存储。当应用E序讉K数据Ӟ在入口和出口处都?x)?encoding 转换?对于中文数据Q数据库字符~码的设|应当保证数据的完整? GB2312QGBKQUTF-8 {都是可选的数据?encodingQ也可以选择 ISO8859-1 (8-bit)Q那么应用程序在写数据之前须?16Bit 的一个汉字或 Unicode 拆分成两?8-bit 的字W,L据之后则需两个字节合qv来,同时q要判别其中?SBCS 字符。没有充分利用数据库 encoding 的作用,反而增加了(jin)~程的复杂度QISO8859-1不是推荐的数据库 encoding。JSP/Servlet~程Ӟ可以先用数据库管理系l提供的理功能(g)查其中的中文数据是否正确?

然后应当注意的是d来的数据?encodingQJAVA E序中一般得到的?Unicode。写数据时则相反?

4.5 定位问题时常用的技?

定位中文encoding问题通常采用最W的也是最有效的办法——在你认为有嫌疑的程序处理后打印字符串的内码。通过打印字符串的内码Q你可以发现什么时候中文字W被转换成UnicodeQ什么时候Unicode被{回中文内码,什么时候一个中文字成了(jin)两个 Unicode 字符Q什么时候中文字W串被{成了(jin)一串问P什么时候中文字W串的高?sh)被截掉了(jin)…?

取用合适的h字符串也有助于区分问题的cd。如Q”aa啊aa?jng)aa?{中q间、GB、GBK特征字符均有的字W串。一般来_(d)英文字符无论怎么转换或处理,都不?x)失真(如果遇到了(jin),可以试着增加q箋(hu)的英文字母长度)(j)?BR>
5. l束?

其实 JSP/Servlet 的中文encoding q没有想像的那么复杂Q虽然定位和解决问题没有定规Q各U运行环境也各不然Q但后面的原理是一L(fng)。了(jin)解字W集的知识是解决字符问题的基。不q,随着中文字符集的变化Q不仅仅?java ~程Q中文信息处理中的问题还是会(x)存在一D|间的?

6. 参考文?

Character Problem Review
Java ~程技术中汉字问题的分析及(qing)解决
GB18030
Setting language encoding in web applications: Websphere applications Server


nighthawk 2005-08-04 15:02 发表评论
]]>
介绍U程、线E类?qing)Runnable。(转)(j) http://m.tkk7.com/nighthawk/articles/9219.htmlnighthawknighthawkThu, 04 Aug 2005 06:59:00 GMThttp://m.tkk7.com/nighthawk/articles/9219.htmlhttp://m.tkk7.com/nighthawk/comments/9219.htmlhttp://m.tkk7.com/nighthawk/articles/9219.html#Feedback1http://m.tkk7.com/nighthawk/comments/commentRss/9219.htmlhttp://m.tkk7.com/nighthawk/services/trackbacks/9219.html用户期望E序能展C异的性能。ؓ(f)?jin)满个期望,你的E序常常使用到线E。在q篇文章中我们开始练?fn)用线E。你学?fn)到U程、线E类?qing)Runnable?

用户不喜Ƣ反应迟钝的软g。当用户单击一个鼠标时Q他们希望程序立卛_应他们的hQ即使程序正处于Ҏ(gu)的运行之中,比如Z很长的文重编늠或等待一个网l操作的完成。对用户响应很慢的程序其性能拙劣。ؓ(f)提高E序性能Q开发者一般用线E?
q篇文章是探索线E的W一部䆾。虽然你可能认ؓ(f)U程是一U难于掌握的事物Q但我打向你显C线E是易于理解的。在q篇文章中,我将向你介绍U程和线E类Q以?qing)讨论Runnable。此外,在后面的文章中,我将探烦(ch)同步Q通过锁)(j)Q同步的问题Q比如死锁)(j)Q等?通知机制Q时序安排(有优先权和没有优先权Q,U程中断Q计时器Q挥发性,U程l和U程本地变量?
阅读关于U程设计的整个系列:(x)
·W?部䆾Q介l线E和U程c,以及(qing)Runnable
·W?部䆾Q用同步ɾU程串行化访问关键代码部?
注意
q篇文章?qing)其应用E序的三个相关线E练?fn)与applets不同。然而,我在应用E序中介l的多数应用到applets。主要不同的是:(x)Z(jin)安全的原因,不是所有的U程操作都可以放C个applet中(我将在以后的文章中讨论appletsQ?
什么是U程Q?
U程的概念ƈ不难于掌握:(x)它是E序代码的一个独立的执行通道。当多个U程执行Ӟl由相同代码的一个线E的通道通常与其它的不同。例如,假设一个线E执行一D늛当于一个if-else语句的if部分的字节代码时Q而另一个线E正执行相当于else部分的字节代码。JVM怎样保持对于每一个线E执行的跟踪呢?JVMl每一个线E它自己的方法调用堆栈。另外跟t当前指令字节代码,Ҏ(gu)堆栈跟踪本地变量QJVM传递给一个方法的参数Q以?qing)方法的q回倹{?
当多个线E在同一个程序中执行字节代码序列Ӟq种行ؓ(f)叫作多线E。多U程在多斚w有利于程序:(x)
·当执行其它Q务时多线EGUIQ图形用L(fng)面)(j)E序仍能保持对用L(fng)响应Q比如重~页码或打印一个文档?
·带线E的E序一般比它们没有带线E的副本E序完成得快。这其表现在线E运行在一个多处理器机器上Q在q里每一个线E都有它自己的处理器?
Java通过java.lang.Threadcd成多U程。每一个线E对象描qC个单独的执行U程。那些运行发生在U程的run()Ҏ(gu)中。因为缺省的run()Ҏ(gu)什么都不做Q你必须创徏Thread子类q载run()以完成有用的工作。练?fn)列?中领略一个在Thread中的U程?qing)多U程Q?
列表1. ThreadDemo.java
// ThreadDemo.java
class ThreadDemo
{
public static void main (String [] args)
{
MyThread mt = new MyThread ();
mt.start ();
for (int i = 0; i < 50; i++)
System.out.println ("i = " + i + ", i * i = " + i * i);
}
}
class MyThread extends Thread
{
public void run ()
{
for (int count = 1, row = 1; row < 20; row++, count++)
{
for (int i = 0; i < count; i++)
System.out.print ('*');
System.out.print ('\n');
}
}
}
列表1昄?jin)一个由cThreadDemo和MyThreadl成的应用程序的源代码。类ThreadDemo通过创徏一个MyThread对象驱动应用E序Q开始一个与其对象相关的U程q执行一D|C个正方Ş表的代码。相反, MyThread重蝲Thread的run()Ҏ(gu)打印Q通过标准输入)(j)一个由星ŞW号l成的直角三角Ş?
当你键入java ThreadDemoq行应用E序Ӟ JVM创徏一个运行main()Ҏ(gu)的开始线E。通过执行mt.start ()Q开始线E告诉JVM创徏一个执行包含MyThread对象的run()Ҏ(gu)的字节代码指令的W二个线E。当start()Ҏ(gu)q回Ӟ开始线E@环执行打C个正方Ş表,此时另一个新U程执行run()Ҏ(gu)打印直角三角形?
输出?x)象什么样呢?q行ThreadDemo可以看到。你注意到每一个线E的输出与其它线E的输出怺交替。这L(fng)l果是因Z个线E将它们的输出都发送到?jin)同L(fng)标准输出?
注意
多数Q不是所有)(j)JVM讑֤使用下层q_的线E性能。因为那些性能是^台特有的Q你的多U程E序的输出顺序可能与一些h的其他输出的序不一栗这U不同是׃时序的安排,我将在这一pd的稍后探讨这一话题?
U程c?
要精通写多线E代码,你必首先理解创建Threadcȝ多种Ҏ(gu)。这部䆾探讨这些方法。明地_(d)你将学到开始线E的Ҏ(gu)Q命名线E,使线E休眠,军_一个线E是否激z,一个线E与另一个线E相联,和在当前U程的线E组?qing)子l中列D所有激zȝU程。我也会(x)讨论U程调试辅助E序?qing)用L(fng)E与监督U程的对比?
我将在以后的文章中介l线E方法的余下部䆾QSun不赞成的Ҏ(gu)除外?
警告
Sun有一些不赞成的线E方法种c,比如suspend()和resume()Q因为它们能锁住你的E序或破坏对象。所以,你不必在你的代码中调用它们。考虑到针对这些方法工作区的SDK文gQ在q篇文章中我没有包含q些Ҏ(gu)?
构造线E?
Thread有八个构造器。最单的是:(x)
·Thread()Q用~省名称创徏一个Thread对象
·Thread(String name)Q用指定的name参数的名U创Z个Thread对象
下一个最单的构造器是Thread(Runnable target)和Thread(Runnable target, String name)?除Runnable参数之外Q这些构造器与前q的构造器一栗不同的是:(x)Runnable参数识别提供run()Ҏ(gu)的线E之外的对象。(你将在这文章稍后学到Runnable。)(j)最后几个构造器是Thread(String name)QThread(Runnable target)Q和Thread(Runnable target, String name)。然而,最后的构造器包含?jin)一个ؓ(f)?jin)组l意囄ThreadGroup参数?
最后四个构造器之一QThread(ThreadGroup group, Runnable target, String name, long stackSize)Qo(h)人感兴趣的是它能够让你指定想要的U程Ҏ(gu)调用堆栈的大。能够指定大将证明在用递归Ҏ(gu)Q一Uؓ(f)何一个方法不断重复调用自w的技术)(j)优美地解决一些问题的E序中是十分有帮助的。通过明确地设|堆栈大,你有时能够预防StackOverflowErrors。然而,太大导致OutOfMemoryErrors。同PSun方法调用堆栈的大小看作q_依赖。依赖^収ͼҎ(gu)调用堆栈的大可能改变。因此,在写调用Thread(ThreadGroup group, Runnable target, String name, long stackSize)代码前仔l考虑你的E序分枝?
开始你的运载工?
U程cM于运载工P(x)它们程序从开始移动到l束。Thread 和Thread子类对象不是U程。它们描qC个线E的属性,比如名称和包含线E执行的代码Q经׃个runQ)(j)Ҏ(gu)Q。当一个新U程执行runQ)(j)Ӟ另一个线E正调用Thread或其子类对象的start()Ҏ(gu)。例如,要开始第二个U程Q应用程序的开始线E—它执行mainQ)(j)—调用startQ)(j)。作为响应,JVM和^C起工作的U程操作代码保U程正确地初始化q调用Thread或其子类对象的run()Ҏ(gu)?
一旦start()完成Q多重线E便q行。因为我们趋向于在一U线性的方式中思维Q我们常发现当两个或更多U程正运行时理解q发Q同Ӟ(j)行ؓ(f)是困隄。因此,你应该看看显CZ旉Ҏ(gu)一个线E正在哪里执行(它的位置Q的图表。下囑ְ是这样一个图表?


与时间对比一个开始线E和一个新建线E执行位|的行ؓ(f)

图表昄?jin)几个重要的旉D:(x)
·开始线E的初始?
·U程开始执行mainQ)(j)瞬间
·U程开始执行startQ)(j)的瞬?
·start()创徏一个新U程q返回mainQ)(j)的瞬?
·新线E的初始?
·新线E开始执行runQ)(j)的瞬?
·每个U程l束的不同瞬?
注意新线E的初始化,它对runQ)(j)的执行,和它的结束都与开始线E的执行同时发生?
警告
一个线E调用start()后,在runQ)(j)Ҏ(gu)退出前q发调用那方法将Dstart()掷出一个java.lang.IllegalThreadStateException对象?



怎样使用名称
在一个调试会(x)话期_(d)使用用户友好方式从另一个线E区别其中一个线E证明是有帮助的。要区分其中一个线E,Javal一个线E取一个名U。Thread~省的名U是一个短U连字符和一个零开始的数字W号。你可以接受Java的缺省线E名U或选择使用你自q。ؓ(f)?jin)能够自定义名称QThread提供带有name参数和一个setName(String name)Ҏ(gu)的构造器。Thread也提供一个getName()Ҏ(gu)q回当前名称。表2昄?jin)怎样通过Thread(String name)创徏一个自定义名称和通过在run()Ҏ(gu)中调用getName()(g)索当前名Uͼ(x)
?.NameThatThread.java
// NameThatThread.java
class NameThatThread
{
public static void main (String [] args)
{
MyThread mt;
if (args.length == 0)
mt = new MyThread ();
else
mt = new MyThread (args [0]);
mt.start ();
}
}
class MyThread extends Thread
{
MyThread ()
{
//~译器创建等价于super()的字节代?
}
MyThread (String name)
{
super (name); //名UC递给Thread类
}
public void run ()
{
System.out.println ("My name is: " + getName ());
}
}
你能够在命o(h)行向MyThread传递一个可选的name参数。例如,java NameThatThread X 建立X作ؓ(f)U程的名U。如果你指定一个名U失败,你将看到下面的输出:(x)
My name is: Thread-1
如果你喜Ƣ,你能够在MyThread(String name)构造器中将super(name)调用改变成setName(String name)调用——作为setName(name)后一U方法调用达到同样徏立线E名U的目的——作为super(name)我作为练?fn)保留给你们?
注意
Java主要名U指zq行main() Ҏ(gu)的线E,开始线E。你特别要看看当开始线E掷Z个例外对象时在线E“main”的例外昄的JVM的缺省例外处理打印消息?
休眠或停止休?
在这一栏后面,我将向你介绍动画——在一个表面上重复d形,q稍微不同于完成一个运动画面。要完成动画Q一个线E必d它显CZ个连l画面时中止。调用Thread的静(rn)态sleep(long millis)Ҏ(gu)一个线E中止millis毫秒。另一个线E可能中断正在休眠的U程。如果这U事发生Q正在休眠的U程醒来ƈ从sleep(long millis)Ҏ(gu)掷出一个InterruptedException对象。结果,调用sleep(long millis)的代码必d一个try代码块中出现——或代码Ҏ(gu)必须在自qthrows子句中包括InterruptedException?
Z(jin)Csleep(long millis)Q我写了(jin)一个CalcPI1应用E序。这个应用程序开始了(jin)一个新U程便于用一个数学运法则计数学常量pi的倹{当新线E计时Q开始线E通过调用sleep(long millis)中止10毫秒。在开始线E醒后,它将打印pi的|其中新线E存贮在变量pi中。表3l出?jin)CalcPI1的源代码Q?
?. CalcPI1.java
// CalcPI1.java
class CalcPI1
{
public static void main (String [] args)
{
MyThread mt = new MyThread ();
mt.start ();
try
{
Thread.sleep (10); //休眠10毫秒
}
catch (InterruptedException e)
{
}
System.out.println ("pi = " + mt.pi);
}
}
class MyThread extends Thread
{
boolean negative = true;
double pi; //~省初始化ؓ(f)0.0
public void run ()
{
for (int i = 3; i < 100000; i += 2)
{
if (negative)
pi -= (1.0 / i);
else
pi += (1.0 / i);
negative = !negative;
}
pi += 1.0;
pi *= 4.0;
System.out.println ("Finished calculating PI");
}
}
如果你运行这个程序,你将看到输出如下Q但也可能不一P(j)Q?
pi = -0.2146197014017295
完成计算PI
Z么输Z正确呢?毕竟Qpi的值应q似{于3.14159。回{是Q开始线E醒得太快了(jin)。在新线E刚开始计piӞ开始线E就醒过来读取pi的当前值ƈ打印其倹{我们可以通过?0毫秒延迟增加为更长的值来q行补偿。这一更长的|不幸的是它是依赖于^台的Q将l新U程一个机?x)在开始线E醒q来之前完成计算?后面Q你学CU不依赖q_的技术,它将防止开始线E醒来直到新U程完成?
注意
U程同时提供一个sleep(long millis, int nanos)Ҏ(gu)Q它?yu)线E休眠millis 毫秒和nanos U秒。因为多数基于JVM的^台都不支持纳U的分解度QJVM U程处理代码纳U数字四舍五入成毫秒数字的近似倹{如果一个^C支持毫秒U的分解度,JVM U程处理代码毫U数字四舍五入成q_支持的最分解度的q似倍数?
它是ȝq是zȝQ?
当一个程序调用Thread的start()Ҏ(gu)Ӟ在一个新U程调用run()之前有一个时间段Qؓ(f)?jin)初始化Q。run()q回后,在JVM清除U程之前有一D|间通过。JVM认ؓ(f)U程立即ȀzM先于U程调用run()Q在U程执行run()期间和run()q回后。在q时间间隔期_(d)Thread的isAlive()Ҏ(gu)q回一个布?yu)(dng)真倹{否则,Ҏ(gu)q回一个假倹{?
isAlive()在一个线E需要在W一个线E能够检查其它线E的l果之前{待另一个线E完成其run()Ҏ(gu)的情形下证明是有帮助的。实质上Q那些需要等待的U程输入一个while循环。当isAlive()为其它线E返回真值时Q等待线E调用sleep(long millis) (?sleep(long millis, int nanos))周期性地休眠 (避免费更多的CPU循环)。一旦isAlive()q回假|{待U程便检查其它线E的l果?
你将在哪里用这L(fng)技术呢Q对于v动器Q一个CalcPI1的修改版本怎么P在打印pi的值前开始线E在哪里{待新线E的完成Q表4的CalcPI2源代码示范了(jin)q一技术:(x)
?. CalcPI2.java
// CalcPI2.java
class CalcPI2
{
public static void main (String [] args)
{
MyThread mt = new MyThread ();
mt.start ();
while (mt.isAlive ())
try
{
Thread.sleep (10); //休眠10毫秒
}
catch (InterruptedException e)
{
}
System.out.println ("pi = " + mt.pi);
}
}
class MyThread extends Thread
{
boolean negative = true;
double pi; //~省初始化成0.0
public void run ()
{
for (int i = 3; i < 100000; i += 2)
{
if (negative)
pi -= (1.0 / i);
else
pi += (1.0 / i);
negative = !negative;
}
pi += 1.0;
pi *= 4.0;
System.out.println ("Finished calculating PI");
}
}
CalcPI2的开始线E在10毫秒旉间隔休眠Q直到mt.isAlive ()q回假倹{当那些发生Ӟ开始线E从它的while循环中退出ƈ打印pi的内宏V如果你q行q个E序Q你看到如下的输出Q但不一定一P(j)Q?
完成计算PI
pi = 3.1415726535897894
q不Q现在看上去更精了(jin)Q?
注意
一个线E可能对它自p用isAlive() Ҏ(gu)。然而,q毫无意义,因ؓ(f)isAlive()一直返回真倹{?
合力
因ؓ(f)while循环/isAlive()Ҏ(gu)/sleep()Ҏ(gu)技术证明是有用的,Sun其打包q三个方法组成的一个组合里Qjoin()Qjoin(long millis)和join(long millis, int nanos)。当当前U程想等待其它线E结束时Q经由另一个线E的U程对象引用调用join()。相反,当它惛_中Q意线E等待其它线E结束或{待直到millis毫秒和nanosU秒l合通过Ӟ当前U程调用join(long millis)或join(long millis, int nanos)?作ؓ(f)sleep()Ҏ(gu)QJVM U程处理代码对join(long millis)和join(long millis,int nanos)Ҏ(gu)的参数值四舍五入??的CalcPI3源代码示范了(jin)一个对join()的调用:(x)
?. CalcPI3.java
// CalcPI3.java
class CalcPI3
{
public static void main (String [] args)
{
MyThread mt = new MyThread ();
mt.start ();
try
{
mt.join ();
}
catch (InterruptedException e)
{
}
System.out.println ("pi = " + mt.pi);
}
}
class MyThread extends Thread
{
boolean negative = true;
double pi; //~省初始化成0.0
public void run ()
{
for (int i = 3; i < 100000; i += 2)
{
if (negative)
pi -= (1.0 / i);
else
pi += (1.0 / i);
negative = !negative;
}
pi += 1.0;
pi *= 4.0;
System.out.println ("Finished calculating PI");
}
}
CalcPI3的开始线E等待与MyThread对象有关被mt引用的线E结束。接着开始线E打印pi的|其gCalcPI2的输Z栗?
警告
不要试图当前线E与其自w连接,因ؓ(f)q样当前U程要永远{待?


怎样使用名称
在一个调试会(x)话期_(d)使用用户友好方式从另一个线E区别其中一个线E证明是有帮助的。要区分其中一个线E,Javal一个线E取一个名U。Thread~省的名U是一个短U连字符和一个零开始的数字W号。你可以接受Java的缺省线E名U或选择使用你自q。ؓ(f)?jin)能够自定义名称QThread提供带有name参数和一个setName(String name)Ҏ(gu)的构造器。Thread也提供一个getName()Ҏ(gu)q回当前名称。表2昄?jin)怎样通过Thread(String name)创徏一个自定义名称和通过在run()Ҏ(gu)中调用getName()(g)索当前名Uͼ(x)
?.NameThatThread.java
// NameThatThread.java
class NameThatThread
{
public static void main (String [] args)
{
MyThread mt;
if (args.length == 0)
mt = new MyThread ();
else
mt = new MyThread (args [0]);
mt.start ();
}
}
class MyThread extends Thread
{
MyThread ()
{
//~译器创建等价于super()的字节代?
}
MyThread (String name)
{
super (name); //名UC递给Thread类
}
public void run ()
{
System.out.println ("My name is: " + getName ());
}
}
你能够在命o(h)行向MyThread传递一个可选的name参数。例如,java NameThatThread X 建立X作ؓ(f)U程的名U。如果你指定一个名U失败,你将看到下面的输出:(x)
My name is: Thread-1
如果你喜Ƣ,你能够在MyThread(String name)构造器中将super(name)调用改变成setName(String name)调用——作为setName(name)后一U方法调用达到同样徏立线E名U的目的——作为super(name)我作为练?fn)保留给你们?
注意
Java主要名U指zq行main() Ҏ(gu)的线E,开始线E。你特别要看看当开始线E掷Z个例外对象时在线E“main”的例外昄的JVM的缺省例外处理打印消息?
休眠或停止休?
在这一栏后面,我将向你介绍动画——在一个表面上重复d形,q稍微不同于完成一个运动画面。要完成动画Q一个线E必d它显CZ个连l画面时中止。调用Thread的静(rn)态sleep(long millis)Ҏ(gu)一个线E中止millis毫秒。另一个线E可能中断正在休眠的U程。如果这U事发生Q正在休眠的U程醒来ƈ从sleep(long millis)Ҏ(gu)掷出一个InterruptedException对象。结果,调用sleep(long millis)的代码必d一个try代码块中出现——或代码Ҏ(gu)必须在自qthrows子句中包括InterruptedException?
Z(jin)Csleep(long millis)Q我写了(jin)一个CalcPI1应用E序。这个应用程序开始了(jin)一个新U程便于用一个数学运法则计数学常量pi的倹{当新线E计时Q开始线E通过调用sleep(long millis)中止10毫秒。在开始线E醒后,它将打印pi的|其中新线E存贮在变量pi中。表3l出?jin)CalcPI1的源代码Q?
?. CalcPI1.java
// CalcPI1.java
class CalcPI1
{
public static void main (String [] args)
{
MyThread mt = new MyThread ();
mt.start ();
try
{
Thread.sleep (10); //休眠10毫秒
}
catch (InterruptedException e)
{
}
System.out.println ("pi = " + mt.pi);
}
}
class MyThread extends Thread
{
boolean negative = true;
double pi; //~省初始化ؓ(f)0.0
public void run ()
{
for (int i = 3; i < 100000; i += 2)
{
if (negative)
pi -= (1.0 / i);
else
pi += (1.0 / i);
negative = !negative;
}
pi += 1.0;
pi *= 4.0;
System.out.println ("Finished calculating PI");
}
}
如果你运行这个程序,你将看到输出如下Q但也可能不一P(j)Q?
pi = -0.2146197014017295
完成计算PI
Z么输Z正确呢?毕竟Qpi的值应q似{于3.14159。回{是Q开始线E醒得太快了(jin)。在新线E刚开始计piӞ开始线E就醒过来读取pi的当前值ƈ打印其倹{我们可以通过?0毫秒延迟增加为更长的值来q行补偿。这一更长的|不幸的是它是依赖于^台的Q将l新U程一个机?x)在开始线E醒q来之前完成计算?后面Q你学CU不依赖q_的技术,它将防止开始线E醒来直到新U程完成?
注意
U程同时提供一个sleep(long millis, int nanos)Ҏ(gu)Q它?yu)线E休眠millis 毫秒和nanos U秒。因为多数基于JVM的^台都不支持纳U的分解度QJVM U程处理代码纳U数字四舍五入成毫秒数字的近似倹{如果一个^C支持毫秒U的分解度,JVM U程处理代码毫U数字四舍五入成q_支持的最分解度的q似倍数?
它是ȝq是zȝQ?
当一个程序调用Thread的start()Ҏ(gu)Ӟ在一个新U程调用run()之前有一个时间段Qؓ(f)?jin)初始化Q。run()q回后,在JVM清除U程之前有一D|间通过。JVM认ؓ(f)U程立即ȀzM先于U程调用run()Q在U程执行run()期间和run()q回后。在q时间间隔期_(d)Thread的isAlive()Ҏ(gu)q回一个布?yu)(dng)真倹{否则,Ҏ(gu)q回一个假倹{?
isAlive()在一个线E需要在W一个线E能够检查其它线E的l果之前{待另一个线E完成其run()Ҏ(gu)的情形下证明是有帮助的。实质上Q那些需要等待的U程输入一个while循环。当isAlive()为其它线E返回真值时Q等待线E调用sleep(long millis) (?sleep(long millis, int nanos))周期性地休眠 (避免费更多的CPU循环)。一旦isAlive()q回假|{待U程便检查其它线E的l果?
你将在哪里用这L(fng)技术呢Q对于v动器Q一个CalcPI1的修改版本怎么P在打印pi的值前开始线E在哪里{待新线E的完成Q表4的CalcPI2源代码示范了(jin)q一技术:(x)
?. CalcPI2.java
// CalcPI2.java
class CalcPI2
{
public static void main (String [] args)
{
MyThread mt = new MyThread ();
mt.start ();
while (mt.isAlive ())
try
{
Thread.sleep (10); //休眠10毫秒
}
catch (InterruptedException e)
{
}
System.out.println ("pi = " + mt.pi);
}
}
class MyThread extends Thread
{
boolean negative = true;
double pi; //~省初始化成0.0
public void run ()
{
for (int i = 3; i < 100000; i += 2)
{
if (negative)
pi -= (1.0 / i);
else
pi += (1.0 / i);
negative = !negative;
}
pi += 1.0;
pi *= 4.0;
System.out.println ("Finished calculating PI");
}
}
CalcPI2的开始线E在10毫秒旉间隔休眠Q直到mt.isAlive ()q回假倹{当那些发生Ӟ开始线E从它的while循环中退出ƈ打印pi的内宏V如果你q行q个E序Q你看到如下的输出Q但不一定一P(j)Q?
完成计算PI
pi = 3.1415726535897894
q不Q现在看上去更精了(jin)Q?
注意
一个线E可能对它自p用isAlive() Ҏ(gu)。然而,q毫无意义,因ؓ(f)isAlive()一直返回真倹{?
合力
因ؓ(f)while循环/isAlive()Ҏ(gu)/sleep()Ҏ(gu)技术证明是有用的,Sun其打包q三个方法组成的一个组合里Qjoin()Qjoin(long millis)和join(long millis, int nanos)。当当前U程想等待其它线E结束时Q经由另一个线E的U程对象引用调用join()。相反,当它惛_中Q意线E等待其它线E结束或{待直到millis毫秒和nanosU秒l合通过Ӟ当前U程调用join(long millis)或join(long millis, int nanos)?作ؓ(f)sleep()Ҏ(gu)QJVM U程处理代码对join(long millis)和join(long millis,int nanos)Ҏ(gu)的参数值四舍五入??的CalcPI3源代码示范了(jin)一个对join()的调用:(x)
?. CalcPI3.java
// CalcPI3.java
class CalcPI3
{
public static void main (String [] args)
{
MyThread mt = new MyThread ();
mt.start ();
try
{
mt.join ();
}
catch (InterruptedException e)
{
}
System.out.println ("pi = " + mt.pi);
}
}
class MyThread extends Thread
{
boolean negative = true;
double pi; //~省初始化成0.0
public void run ()
{
for (int i = 3; i < 100000; i += 2)
{
if (negative)
pi -= (1.0 / i);
else
pi += (1.0 / i);
negative = !negative;
}
pi += 1.0;
pi *= 4.0;
System.out.println ("Finished calculating PI");
}
}
CalcPI3的开始线E等待与MyThread对象有关被mt引用的线E结束。接着开始线E打印pi的|其gCalcPI2的输Z栗?
警告
不要试图当前线E与其自w连接,因ؓ(f)q样当前U程要永远{待?


查询z跃U程
在有些情形下Q你可能想了(jin)解在你的E序中哪些线E是Ȁzȝ。Thread支持一Ҏ(gu)法帮助你完成q个dQ?activeCount()?enumerate(Thread [] thdarray)。但那些Ҏ(gu)只工作在当前U程的线E组中。换句话_(d)那些Ҏ(gu)只识别属于当前线E的同一U程l的z跃U程?(我将在以后的pd文章中讨论线E组——一U组l机制?
?rn)态activeCount()Ҏ(gu)q回在当前线E的U程l中正在z跃q行的线E数量。一个程序利用这个方法的整数q回D定一个Thread引用数组的大。检索那些引用,E序必须调用?rn)态enumerate(Thread [] thdarray)Ҏ(gu)。这个方法的整数q回值确定Thread引用存贮在数l中的enumerate(Thread []thdarray)的L。要看这些方法如何一起工作,h看表6Q?
?. Census.java
// Census.java
class Census
{
public static void main (String [] args)
{
Thread [] threads = new Thread [Thread.activeCount ()];
int n = Thread.enumerate (threads);
for (int i = 0; i < n; i++)
System.out.println (threads [i].toString ());
}
}
在运行时Q这个程序会(x)产生如下的输出:(x)
Thread[main,5,main]
输出昄一个线E,开始线E正在运行。左边的main表示U程的名U?昄U程的优先权Q右边的main表示U程的线E组。你也许很失望不能在输出中看CQ何系l线E,比如垃圾攉器线E。U限制由Thread的enumerate(Thread [] thdarray) Ҏ(gu)产生Q它仅询问当前线E线E组的活跃线E。然而, ThreadGroupcd含多Uenumerate()Ҏ(gu)允许你捕获对所有活跃线E的引用而不线E组。在E后的系列中Q探讨ThreadGroup时我向你显C如何列举所有的引用?
警告
当重申一个数l时不要依靠activeCount()的返回倹{如果你q样做了(jin)Q你的程序将冒掷Z个NullPointerException对象的风险。ؓ(f)什么呢Q在调用activeCount()和enumerate(Thread [] thdarray)之间Q一个或更多U程可能l束。结果, enumerate(Thread [] thdarray)能够复制数U程引用q它的数l。因此,仅考虑activeCount()的返回g为数l可能大的最大倹{同P考虑enumerate(Thread [] thdarray)的返回g为在一个程序对那种Ҏ(gu)调用时活跃线E的数目?
反臭?
如果你的E序出现故障q且你怀疑问题出在线E,通过调用Thread的dumpStack()和toString()Ҏ(gu)你能够了(jin)解到U程的更多细节。静(rn)态dumpStack()Ҏ(gu)提供一个new Exception ("Stack trace").printStackTrace ()的封装,打印一个追t当前线E的堆栈。toString()依据下面格式q回一个描q线E的名称、优先权和线E组的字W串Q?Thread[thread-name,priority,thread-group]. (在稍后的pd中你学到更多关于优先权的知识?
技?
在一些地方,q篇文章提到?jin)当前线E的概念。如果你需要访问描q当前线E的Thread对象Q则调用Thread的静(rn)态currentThread()Ҏ(gu)。例QThread current = Thread.currentThread ()?
{pȝ
不是所有线E都被^{创建。它们被分成两类Q用户和监督。一个用L(fng)E执行着对于E序用户十分重要的工作,工作必须在程序结束前完成。相反,一个监督线E执行着后勤事务Q比如垃圾收集)(j)和其它可能不?x)对应用E序的主要工作作?gu)A(ch)献但对于应用E序l箋(hu)它的主要工作却非常必要的后台d。和用户U程不一P监督U程不需要在应用E序l束前完成。当一个应用程序的开始线E(它是一个用L(fng)E)(j)l束ӞJVM(g)查是否还有其它用L(fng)E正在运行。如果有QJVM׃(x)L应用E序l束。否则,JVM׃(x)l束应用E序而不监督线E是否正在运行?
当一个线E调用一个线E对象的start()Ҏ(gu)Ӟ新的已经开始的U程是一个用L(fng)E。那是缺省的。要建立一个线E作为监督线E,E序必须在调用start()前调用Thread的一个带布尔真值参数的setDaemon(boolean isDaemon)Ҏ(gu)。稍后,你可以通过调用Thread的isDaemon()Ҏ(gu)(g)查一个线E是否是监督U程。如果是监督U程那个Ҏ(gu)q回一个布?yu)(dng)真倹{?
Z(jin)让你试试用户和监督线E,我写?jin)一个UserDaemonThreadDemoQ?
?. UserDaemonThreadDemo.java
// UserDaemonThreadDemo.java
class UserDaemonThreadDemo
{
public static void main (String [] args)
{
if (args.length == 0)
new MyThread ().start ();
else
{
MyThread mt = new MyThread ();
mt.setDaemon (true);
mt.start ();
}
try
{
Thread.sleep (100);
}
catch (InterruptedException e)
{
}
}
}
class MyThread extends Thread
{
public void run ()
{
System.out.println ("Daemon is " + isDaemon ());
while (true);
}
}
~译?jin)代码后Q通过Java2 SDK的java命o(h)q行UserDaemonThreadDemo。如果你没有使用命o(h)行参数运行程序,例如java UserDaemonThreadDemoQ?new MyThread ().start ()执行。这D代码片断开始一个在q入一个无限@环前打印Daemon is false的用L(fng)E?你必LCtrl-C或一个等价于l束一个无限@环的l合按键?因ؓ(f)新线E是一个用L(fng)E,应用E序在开始线E结束后仍保持运行。然而,如果你指定了(jin)臛_一个命令行参数Q例如java UserDaemonThreadDemo xQmt.setDaemon (true)执行q且新线E将是一个监督线E。结果,一旦开始线E从100毫秒休眠中醒来ƈl束Q新的监督线E也结束?
警告
如果U程开始执行后调用setDaemon(boolean isDaemon)Ҏ(gu)QsetDaemon(boolean isDaemon)Ҏ(gu)掷Z个IllegalThreadStateException对象?
Runnable
学习(fn)前面部䆾的例子后Q你可能认ؓ(f)引入多线E进入一个类L要求你去扩展Threadq将你的子类重蝲Thread's run()Ҏ(gu)。然而那q不L一U选择。Java对(h)承的强制执行止一个类扩展两个或更多个类。结果,如果一个类扩展?jin)一个无U程c,那个cd不能扩展Thread. 假限制Q怎样才可能将多线E引入一个已l扩展了(jin)其它cȝc?q运的是Q?Java的设计者已l意识到不可能创建Thread子类的情形M(x)发生的。这D产生java.lang.Runnable接口和带Runnable参数的Thread构造器Q如Thread(Runnable target)?
Runnable接口声明?jin)一个单独方法v名:(x)void run()。这个v名和Thread的run()Ҏ(gu)|名一样ƈ作ؓ(f)U程的执行入口服务。因为Runnable是一个接口,Mc都能通过一个implements子句包含q类头和提供一个适当的run()Ҏ(gu)实现接口。在执行旉Q程序代码能从那个类创徏一个对象或runnableq将runnable的引用传递给一个适当的Thread构造器。构造器和Thread对象一起存贮这个引用ƈ保一个新U程在调用Thread对象的start()Ҏ(gu)后调用runnable的run()Ҏ(gu)。示范如?Q?
?.RunnableDemo.java
// RunnableDemo.java
class RunnableDemo
{
public static void main (String [] args)
{
Rectangle r = new Rectangle (5, 6);
r.draw ();
//用随机选择的宽度和高度M同的长方?
new Rectangle ();
}
}
abstract class Shape
{
abstract void draw ();
}
class Rectangle extends Shape implements Runnable
{
private int w, h;
Rectangle ()
{
//创徏一个绑定这个runnable的新Thread对象q开始一个将调用q个runnable?
//run()Ҏ(gu)的线E?
new Thread (this).start ();
}
Rectangle (int w, int h)
{
if (w < 2)
throw new IllegalArgumentException ("w value " + w + " < 2");
if (h < 2)
throw new IllegalArgumentException ("h value " + h + " < 2");
this.w = w;
this.h = h;
}
void draw ()
{
for (int c = 0; c < w; c++)
System.out.print ('*');
System.out.print ('\n');
for (int r = 0; r < h - 2; r++)
{
System.out.print ('*');
for (int c = 0; c < w - 2; c++)
System.out.print (' ');
System.out.print ('*');
System.out.print ('\n');
}
for (int c = 0; c < w; c++)
System.out.print ('*');
System.out.print ('\n');
}
public void run ()
{
for (int i = 0; i < 20; i++)
{
w = rnd (30);
if (w < 2)
w += 2;
h = rnd (10);
if (h < 2)
h += 2;
draw ();
}
}
int rnd (int limit)
{
//?<=x<界限范围内返回一个随机数字x
return (int) (Math.random () * limit);
}
}
RunnableDemoqRunnableDemoQShape和Rectanglel成。类RunnableDemo通过创徏一个Rectangle对象驱动应用E序—通过调用对象的draw()Ҏ(gu)—和通过创徏W二个什么都不做的RectanglecR相反,Shape和Rectanglel成?jin)一个基于shape层次的类。Shape是抽象的因ؓ(f)它提供一个抽象的draw()Ҏ(gu)。各Ushapec,比如RectangleQ扩展Shape和描q它们如何画它们自己的重载draw()。以后,我可能决定引入一些另外的shapec,创徏一个Shape数组Q通过调用Shape的draw()Ҏ(gu)要求每一个Shape元素d自己?
RunnableDemo 作ؓ(f)一个不带多U程的简单程序生。后面我军_引入多线E到RectangleQ这h能够用各U宽度和高度ȝU矩形。因为Rectangle扩展Shape (Z(jin)以后的多态性原?Q我没有其它选择只有让Rectangle实现Runnable。同P在Rectangle()构造器内,我不得不一个Rectangle runnablel定C个新的Thread对象q调用Thread的start()Ҏ(gu)开始一个新的线E调用Rectangle的run()Ҏ(gu)ȝ形?
因ؓ(f)包括在这文章中的RunnableDemo的新输出太长?jin),我徏议你自己~译q运行程序?
技?
当你面对一个类不是能扩展Thread是能实现Runnable的情形时Q你选择哪种Ҏ(gu)Q如果这个类已经扩展?jin)其它类Q你必须实现Runnable。然而,如果q个cL有扩展其它类Q考虑一下类的名U。名U将暗示q个cȝ对象不是U极的就是消极的。例如,名称Ticker暗示它的对象是积极的。因此,Tickercd扩展ThreadQƈ且Ticker对象被作ؓ(f)专门的Thread对象。相反,Rectangle暗示消极对象—Rectangle对象对于它们自己什么也不做。因此,Rectanglecd实现Runnable,q且Rectangle 对象用Thread对象(Z(jin)试或其它意?代替成ؓ(f)专门的Thread对象?
回顾
用户期望E序辑ֈ优异的性能。一U办法是用线E完成那些Q务。一个线E是一条程序代码的独立执行通道。线E有益于ZGUI的程序,因ؓ(f)它们允许那些E序当执行其它Q务时仍对用户保持响应。另外,带线E的E序比它们没带线E的副本E序完成的快。这对于q行在多处理器机器上的情形尤其明显,在这里每一个线E有它自q处理器。Thread和Thread子类对象描述?jin)线Eƈ与那些实体相兟뀂对于那些不能扩展Thread的类Q你必须创徏一个runnable以利用多U程的优ѝ?img src ="http://m.tkk7.com/nighthawk/aggbug/9219.html" width = "1" height = "1" />

nighthawk 2005-08-04 14:59 发表评论
]]>
谈Java的输入输出流http://m.tkk7.com/nighthawk/articles/9218.htmlnighthawknighthawkThu, 04 Aug 2005 06:57:00 GMThttp://m.tkk7.com/nighthawk/articles/9218.htmlhttp://m.tkk7.com/nighthawk/comments/9218.htmlhttp://m.tkk7.com/nighthawk/articles/9218.html#Feedback0http://m.tkk7.com/nighthawk/comments/commentRss/9218.htmlhttp://m.tkk7.com/nighthawk/services/trackbacks/9218.html

Java语言的输入输出功能是十分强大而灵zȝQ美中不的是看上去输入输出的代码ƈ不是很简z,因ؓ(f)你往(xin)往(xin)需要包装许多不同的对象。在Javacd中,IO部分的内Ҏ(gu)很庞大的Q因为它涉及(qing)的领域很q泛:标准输入输出Q文件的操作Q网l上的数据流Q字W串,对象,zip文g?...本文的目的是为大家做一个简要的介绍?/P>

是一个很形象的概念,当程序需要读取数据的时候,׃(x)开启一个通向数据源的,q个数据源可以是文gQ内存,或是|络q接。类似的Q当E序需要写入数据的时候,׃(x)开启一个通向目的地的。这时候你可以想象数据好像在q其中“流”动一P如下图:(x)

Java中的分ZU,一U是字节,另一U是字符,分别由四个抽象类来表C(每种包括输入和输出两种所以一共四个)(j):InputStreamQOutputStreamQReaderQW(xu)riter。Java中其他多U多样变化的均是由它们z出来?

 

在这其中InputStream和OutputStream在早期的Java版本中就已经存在?jin),它们是基于字节流的,而基于字W流的Reader和W(xu)riter是后来加入作充的。以上的层次图是Javacd中的一个基本的层次体系Q如果你感兴想?jin)解更多内容的话Q可以到Sun公司主页获取更多信息?/P>

在这四个抽象cMQInputStream和Reader定义?jin)完全相同的接口Q?/P>
int read()
int read(char cbuf[])
int read(char cbuf[], int offset, int length)

而OutputStream和W(xu)riter也是如此Q?/P>
int write(int c)
int write(char cbuf[])
int write(char cbuf[], int offset, int length)

q六个方法都是最基本的,read()和write()通过Ҏ(gu)的重载来d一个字节,或者一个字节数l?/P>

更多灉|多变的功能是由它们的子类来扩充完成的。知道了(jin)Java输入输出的基本层ơ结构以后,本文在这里想l大家一些以后可以反复应用例子,对于所有子cȝl节?qing)其功能q不详细讨论?/P>

import java.io.*;

    public class IOStreamDemo {

          public void samples() throws IOException {

               //1. q是从键盘读入一行数?q回的是一个字W串
               BufferedReader stdin =new BufferedReader(new InputStreamReader(System.in));
               System.out.print("Enter a line:");
               System.out.println(stdin.readLine());

               //2. q是从文件中逐行d数据

               BufferedReader in = new BufferedReader(new FileReader("IOStreamDemo.java"));
               String s, s2 = new String();
               while((s = in.readLine())!= null)
                          s2 += s + "\n";
               in.close();

               //3. q是从一个字W串中逐个d字节
               StringReader in1 = new StringReader(s2);
               int c;
               while((c = in1.read()) != -1)
                          System.out.print((char)c);

               //4. q是一个字W串写入文g
               try {
                          BufferedReader in2 = new BufferedReader(new StringReader(s2));
                          PrintWriter out1 = new PrintWriter(new BufferedWriter(new FileWriter("IODemo.out")));
                          int lineCount = 1;
                          while((s = in2.readLine()) != null )
                                     out1.println(lineCount++ + ": " + s);
                          out1.close();
               } catch(EOFException e) {
                          System.err.println("End of stream");
               }
          }

    }

对于上面的例子,需要说明的有以下几点:(x)

1. BufferedReader?FONT color=#ff0000>Reader的一个子c,它具有缓冲的作用Q避免了(jin)频繁的从物理讑֤中读取信息。它有以下两个构造函敎ͼ(x)

BufferedReader(Reader in)
BufferedReader(Reader in, int sz)

q里的sz是指定缓冲区的大?/P>

它的基本Ҏ(gu)Q?/P>
void close() //关闭?

           void mark(int readAheadLimit) //标记当前位置

           boolean markSupported() //是否支持标记

           int read() //l承自Reader的基本方?/FONT>

           int read(char[] cbuf, int off, int len) //l承自Reader的基本方?/FONT>

           String readLine() //d一行内容ƈ以字W串形式q回

           boolean ready() //判断是否已l做好读入的准备

           void reset() //重设到最q的一个标?/FONT>

           long skip(long n) //跌指定个数的字W读?/FONT>

2. InputStreamReader?FONT color=#000000>InputStream?FONT color=#000000>Reader之间的桥梁,׃System.in是字节流Q需要用它来包装之后变(sh)ؓ(f)字符供l?            BufferedReader使用?BR>

3. PrintWriter out1 = new PrintWriter(new BufferedWriter(new FileWriter("IODemo.out")));

q句话体C(jin)Java输入输出pȝ的一个特点,Z(jin)辑ֈ某个目的Q需要包装好几层。首先,输出目的地是文gIODemo.outQ所以最内层包装的是FileWriterQ徏立一个输出文件流Q接下来Q我们希望这个流是缓冲的Q所以用BufferedWriter来包装它以达到目的,最后,我们需要格式化输出l果Q于是将PrintWriter包在最外层?BR>

Java提供?jin)这样一个功能,标准的输入输出{向,也就是说Q我们可以将某个其他的流设ؓ(f)标准输入或输出流Q看下面q个例子Q?/P>

import java.io.*;

public class Redirecting {

       public static void
main(String[] args) throws IOException {
              PrintStream console = System.out;
              BufferedInputStream in = new BufferedInputStream( new FileInputStream( "Redirecting.java"));
              PrintStream out = new PrintStream( new BufferedOutputStream( new FileOutputStream("test.out")));
              System.setIn(in);
              System.setOut(out);

              BufferedReader br = new BufferedReader( new InputStreamReader(System.in));
              String s;
              while((s = br.readLine()) != null)
                     System.out.println(s);
              out.close();
              System.setOut(console);
      }
}

在这里java.lang.System的静(rn)态方?/P>
static void setIn(InputStream in)
static void setOut(PrintStream out)

提供?jin)重新定义标准输入输出流的方法,q样做是很方便的Q比如一个程序的l果有很多,有时候甚臌页昄Q这样不便于观看l果Q这是你可以将标准输出定义ؓ(f)一个文件流Q程序运行完之后打开相应的文件观看结果,q观了(jin)许多?/P>

Java有着另一个重要的用途,那就是利用对象流对对象进行序列化。下面将开始介l这斚w的问题?/P>

在一个程序运行的时候,其中的变量数据是保存在内存(sh)的,一旦程序结束这些数据将不会(x)被保存,一U解决的办法是将数据写入文gQ而Java中提供了(jin)一U机Ӟ它可以将E序中的对象写入文gQ之后再从文件中把对象读出来重新建立。这是所谓的对象序列化Java中引入它主要是ؓ(f)?jin)RMIQRemote Method InvocationQ和Java Bean所用,不过在^时应用中Q它也是很有用的一U技术?/P>

所有需要实现对象序列化的对象必首先实现Serializable接口。下面看一个例子:(x)

import java.io.*;
import java.util.*;

public class Logon implements Serializable {

       private Date date = new Date();
       private String username;
       private transient String password;


       Logon(String name, String pwd) {
              username = name;
              password = pwd;
       }


       public String toString() {
              String pwd = (password == null) ? "(n/a)" : password;
              return "logon info: \n " + "username: " + username + "\n date: " + date + "\n password: " + pwd;
       }


       public static void
main(String[] args) throws IOException, ClassNotFoundException {
              Logon a = new Logon("Morgan", "morgan83");
              System.out.println( "logon a = " + a);
              ObjectOutputStream o = new ObjectOutputStream( new FileOutputStream("Logon.out"));
              o.writeObject(a);
              o.close();

              int seconds = 5;
              long t = System.currentTimeMillis() + seconds * 1000;
              while(System.currentTimeMillis() < t) ;

              ObjectInputStream in = new ObjectInputStream( new FileInputStream("Logon.out"));
              System.out.println( "Recovering object at " + new Date());
              a = (Logon)in.readObject();
              System.out.println("logon a = " + a);
       }
}

cLogon是一个记录登录信息的c,包括用户名和密码。首先它实现?jin)接口SerializableQ这标志着它可以被序列化。之后再mainҎ(gu)?FONT color=#ff0000>ObjectOutputStream o = new ObjectOutputStream( new FileOutputStream("Logon.out"));新徏一个对象输出流包装一个文件流Q表C对象序列化的目的地是文件Logon.out。然后用Ҏ(gu)writeObject开始写入。想要还原的时候也很简?FONT color=#ff0000>ObjectInputStream in = new ObjectInputStream( new FileInputStream("Logon.out"));新徏一个对象输入流以文件流Logon.out为参敎ͼ之后调用readObjectҎ(gu)可以了(jin)?/P>

需要说明一点,对象序列化有一个神奇之处就是,它徏立了(jin)一张对象网Q将当前要序列化的对象中所持有的引用指向的对象都包含v来一起写入到文gQ更为奇妙的是,如果你一ơ序列化?jin)好几个对象Q它们中相同的内容将?x)被׃n写入。这的确是一个非常好的机制。它可以用来实现深层拯Q有x(chng)层拷贝的问题?A >JavaWorld上有一文章做?jin)几U实现方法的介绍和比较,有兴者可以去看看?/P>

关键字transient在这里表C当前内容将不被序列化,比如例子中的密码Q需要保密,所以没有被写入文g?/P>

对Java的输入输出功能,浅的介绍到这里,本文的目的只是开一个好_(d)希望能让大家对Java输入输出有个基本的认识Q更多更为全面的信息在http://java.sun.com有权威的说明?/P>



nighthawk 2005-08-04 14:57 发表评论
]]>
վ֩ģ壺 һ| ҰһƵ| ͽXXXX| Ů18ëƬƵ| ëƬѹۿƵȫ| ˮĻӰ| 1000žžʮδֹۿ| ޾Ʒɫ| Ʒ| ҹƵ| һaɫƬþٸһHƬѷ| ҹҹƵ| Ļþ| ձĻ| avav߲| ޹˾ƷŮ˾þþ | ˬָ߳Ƶ| ޹Ʒҹ߹ۿ| ѹۿһëƬ| һɫëƬ| þþƷ| ߿ƬvѹۿƵ777| ɫͼѧ| һһƬѲi| Ʒϵ߹ۿ| ͵v͵v޸| 99þþùƷţţĴ| ҹɼӰԺ| Ů˾ƷƵ| 㽶Ƶѿ| ޾ƷƵѿ| 777777| 99þþù| þþþAVר| һ߹ۿƵ| 鵺̳Ƶ| AV˾Ʒ߹ۿ| ëɫëƬѹۿ| þþƷר| ձҺ| 벻޳?Ƭ|