AIML的一些标签说明

转自:http://blog.sina.com.cn/s/blog_71302a170101a8kz.html

1.think元素
例如:<think><set name=”topic”>Me</set></think>
放置在template元素里面,表示一旦用户的输入匹配到该category时,再回复应答的同时,给一个变量赋值,这里也就是把Me记再脑子里,以后就可以用<get name=”topic”/>来取出先前记住的内容。

2.<star/>表示*
比如有一个匹配模式是<pattern>* 你 好 *<pattern>;这里pattern元素里的匹配模式是用*号表示任意匹配的,但在其他元素里面不能用*号,而用<star/>这个元素来表示。
已经不推荐使用,只针对0。9版本,目前一般要加index属性来使用,例如:<star index=”1″/>,看如下对话:
用户:我的名字叫黑山!

3.<srai>元素
刺激回复人工智能,表示<srai>里面的话会被当作是用户输入,从新查找匹配模式,直到找到非<srai>定义的回复
例如:<srai>我 是 <star/></srai>,那么机器人会把“我 是 *”当作是用户输入来从新查找匹配模式。

4.<condition>元素
放在template元素里面,可以有多个condition元素,但不能嵌套(目前还不支持),有3种形式:

Name是预先定义的变量,第一种表示name变量的值如果和value相等,回复内容就包括”你好”;
第二种表示name变量的值如果里面包含value这个字符串,回复内容就包括“你好”;
第三种表示name变量的值如果存在value的值,回复内容就包括“你好”
举个使用例子:

情况一:如果事先用think设置了变量“用户名字”,而且等于“张三”,那么用户接受到的回复内容是:你好阿!你又来了阿;)
情况二:如果事先没有定义或者变量“用户名字”的值不等于“张三”,那么用户接受到的回复内容是:你好阿!
写法有以下几种:

5.<formal>元素,用来格式化输出
例如:<formal>jon baer</formal> 那么回复将被格式化成首字母大写输出:Jon Baer,对中文无效。

6.<gender>元素,替换性别以及代名词
例如: <gender>She told him to take a hike.</gender>
将被替换成:He told her to take a hike,跟性别有关的单词都将被替换

7.<get name=”名字” />,即得到name的值。

8.<gossip>元素用来把改元素里面的内容保存到gossip.log文件里。

9.<if>元素,判断元素,有以下形式:
<if name=”topic” value=”cars”></if>
<if name=”topic” contains=”cars”></if>
<if name=”topic” exists=”true”></if>
例子:

10. <input>表示用户输入,
例如: <input index=”2″/>将输出用户倒数第2次的输入,看如下对话:
用户:好阿
机器人:你也好,你叫什么名字?
用户:我叫黑山
机器人:呵呵,你好黑山。
如果有aiml文件里有片断:

如果这个时候用户输入“嘿嘿”,那么机器人将回复:你刚才说:“我叫黑山”?
Index属性的数字表示倒数第几句话,1:倒数第一句,2:倒数第2句,依此类推。

11.<learen filename=”xxx.aiml”>元素表示让机器人学习某个aiml文件。

12.<li></li>元素可以在random元素或condition等元素当中使用;表示一个列表;

13.<lowcase></lowcase>表示把中间的内容变成小写,对应的是<upcase>把内容变成大写

14.<pattern>表示匹配模式
里面的内容必须大写,可以有星号* 或下划线_,但必须空格隔开,星号表示匹配所有,任意情况;下划线的意义跟星号一样,除了不能匹配字典里面Z后面的字母。

15.<person>和<person2> 第一个是把第一人称转换成第3人称;第2个是把第一人称转成第2人称
<person/>等于 <person><star/></person>

16.<random>随机元素,一般和<li>一起使用,表示从列表里随机取一个。

17.<sentence>元素用来格式化句子,
比如: <sentence>this is some kind of sentence test.</sentence>
可以格式化成:This is some kind of sentence test.即把句子首字母大写。

18. <system><system>元素表示调用系统函数,
例如: <system>date</system>表示取系统当前日期
警告:使用这个元素要千万小心,因为有可能使用户运行你的系统命令。

19.<template>定义回复的模板。

20.<that>元素表示先前机器人说的话,例如:

即:如果机器人先前问用户“一起聊聊电影好吗?”,而且现在用户回答了“好”,那么匹配正确,回复内容为:“那你喜欢什么电影那?”
如果要取前面的前面机器人的话,可以用:<that index=”nx,ny”>,例如:<that index=”2,1”>
表示取机器人倒数第2句的话,<that index=”2,1”>也等于<justbeforethat/>

21.<thatstar index=”n”>元素先前第n个机器人说过的星号部分的话,<thatstar/>等于<thatstar index=”1″/>.
例如:

对话场景:
用户:你好
机器人:计算机 的 型 号 是 什 么
用户:p4
机器人:p4这个型号是计算机里面很好的商品

22.<think>记录用户输入且里面的内容不回复给用户
例如:

这里把female这个值保存到gender这个变量里,且回复内容不包括female。

23. <Topic name=”film”>元素用来设置主题,
例如:

只有当前的topic系统变量是“功夫”时才会匹配到这里。

24.<topicstar index=”n”>元素用来得到先前倒数第n次谈论的主题。

25.<upcase>xiaoxie</upcase>元素用来把xiaoxie转换成大写形式:XIAOXIE

注意:
aiml所有元素属性都是大小写敏感的!!!
aiml的pattern元素里面的英文必须大写!!!

AIML机器人

AIML,全名为Artificial Intelligence Markup Language(人工智能标记语言),是一种创建自然语言软件代理的XML语言,是由Richard Wallace和世界各地的自由软件社区在1995年至2002年发明的。它的雏形是一个名为”A.L.I.C.E.” (“Artificial Linguistic Internet Computer Entity”)的高度扩展的Eliza机器人。ALICE总共赢得3次每年度的Loebner奖,并且在2004年获得了Chatterbox Challenge的冠军。由于A.L.I.C.E. 的AIML设置是在GNU GPL协议下发布的,所以已经有许多基于该程序和AIML库的“克隆ALICE”出现。目前AIML已经有了Java,Ruby,Python, C ,C#,Pascal等语言的版本。

例子:http://alice.mpabo.com

AIML文件就是一个xml兼容的文本文档

AIML的常用标签:
1:pattern tag:支持模式匹配(正则表达式,模糊匹配),及基于template的返回
2:random tag:支持随机回答(一对多)
4:think,system tag: 支持简单逻辑记忆及自定义函数
5:javascript tag: 支持嵌入js脚本(适用于web chat开发,比如根据情绪改变表情等)。
6:srai tag: 支持多对一回答.
详细内容请参加AIML的官方文档:
http://alicebot.org/TR/2005/WD-aiml/WD-aiml-1.0.1-008.html

chatterbean的例子,需要bsh包。
Alice工厂: AliceBotMother

命令行聊天程序:

需要说明的是:

context.xml:设置application的属性, 及时间格式等可变属性

如上属性,都可以用AIML的<bot>标签及<get>标签访问得到。

splitters.xml:定义什么是句子,即句子的结束符。

substitutions.xml:定义容错规则及特殊字符映射等。

AIML文件:

system标签中的learn方法实现了Alice的学习功能,<input/>标签记住了之前对方的聊天记录, 通过index可以得到(倒序索引)

转自:http://lcllcl987.iteye.com/blog/473256
中文的实现:https://code.google.com/p/ameliebot/ (很久没更新了)

Sql Server2005 Row_Numerb分页

转自:http://bbs.csdn.net/topics/340134909

1、为什么要使用row方案:
在oracle里有row_number虚列,mySql有limit关键字分页,他们都有一个比较通用的分页方案,使得hibernate等类似的程序可以拼接sql字符串提供通用的分页。
而sqlserver却没有这样的分页方案。
于是乎,本人稍稍改装row_number()over(order by )用法,获得了一个通用的分页方案。
如提供了sql如下:

被row方案的分页程序处理后变成(在select 后面添加 top 开始位置 0 __tc,在外层嵌套固定模式的查询sql)

这样就得到了拼接出通用的分页sql方案了。
并且经过本人测试发现,这套方案的运行速度不逊于任何一套其他方案。
其余各方面效率还有待考察,忘高人指点了。

2、row方案的排序:
row方案可以任意排序,只要修改最内层的select排序即可,应该来说是很简单易用的。
参考【追加说明1、】和【#80楼】

3、row方案和普通row_number()方案的区别:
一般的row方案:

使用了over(order by 表中的列),照成了必须由用户提供这个列,而不容易使用分页程序生成分页sql(如hibernate分页)。
而row方案使用的是一个常数列tempColumn,值永远是0。

这个列是静态的,只是为了使用row_number()函数,并不是真正的order by 依据,order by 实际看最内层。

我分析是因为row方案使用一个静态的列tempColumn,
这样可能被sql分析程序认为是无需排序的,省下了排序过程的开销。

4、数据测试:
现只在我一台机子上试过,希望路过的各位随手帮忙测试一下。
这也是我迟迟不结贴的缘故。[已结贴]
举手之劳,复制sql运行即可:

运行测试代码:

———————————————————–
以下为原帖:
———————————————————–
这套方案(下面简称row方案)是本人借鉴Oracle的row_number分页方法和sqlServerrow_number结合+上top分页方案合体版,经过本人初步测试。
效率非常快。(本人测试非常业余,还望高人帮忙测试。)
row方案的具体操作方法在这章帖子里:
一套原创的sqlserver通用分页方案 忘高人测试效率 先阿里嘎多了
比较了3种分页方式,分别是max方案,top方案,row方案

效率:
第1:row
第2:max
第3:top

缺点:
max:必须用户编写复杂Sql,不支持非唯一列排序
top:必须用户编写复杂Sql,不支持复合主键
row:不支持sqlServer2000

测试数据:
共320万条数据,每页显示10条数据,分别测试了2万页、15万页和32万页。

页码,top方案,max方案,row方案
2万,60ms,46ms,33ms
15万,453ms,343ms,310ms
32万,953ms,720ms,686ms

具体操作sql代码如下:
top方案:

max:

row:

关于JAVA中变量的初始化及类属性的默认值问题

转自:http://hi.baidu.com/yioopayczwgnsye/item/e28120f4ae4a3eee1b111fa9

先看两个例子:
1.VariableInitialization.java

利用JAVAC编译后提示:Variable x may not be initialized.显示在第四行中用到的x可能未被初始化,不可参与运算。

2.ClassAttribute.java

运行结果为:
半径为:0
面积为:0.0
非但没有报错,还给出了可用的运行结果。Circle类中的radius并没有赋初值,但却可以正常使用,而例1里main()方法中的x未赋初值却被告有错。其实问题就在这里,在JAVA中:Local variables must be initialized before they can be used.
而在类定义中的属性若不赋予初值,则JAVA会自动赋予这个属性一个默认值,以下为类的属性的默认值表:

  • 数据类型                        默认值
  • boolean                         false
  • char                           ‘/u0000′(null)
  • 整形(byte,short,int,long)      0
  • 浮点型(float, double)         +0.0f或+0.0d
  • 参考型                           null

总结为一句话便是类里定义的数据成员称为属性,属性可不赋初值,若不赋初值则JAVA会按上表为其添加默认值;方法里定义的数据成员称为变量,变量在参与运算之前必须赋初值。
1.一个变量作为类成员使用的时候,如果没有被初始化,java会为其分配默认值;
2.如果在一个方法中定义一个变量,java不会给其分配默认值,就必须我们来给他初始化,否则将得到编译错误的提示;

Java中使用Runtime和Process类运行外部程序

转自:http://www.cnblogs.com/xxpal/articles/824963.html

使用Runtime.getRuntime().exec()方法可以在java程序里运行外部程序。
1. exec(String command)
2. exec(String command, String envp[], File dir)
3. exec(String cmd, String envp[])
4. exec(String cmdarray[])
5. exec(String cmdarray[], String envp[])
6. exec(String cmdarray[], String envp[], File dir)
一般的应用程序可以直接使用第一版本,当有环境变量传递的时候使用后面的版本。其中2和6版本可以传递一个目录,标识当前目录,因为有些程序是使用相对目录的,所以就要使用这个版本。

cmd.exe /c start <FileName>  (注: 为什么有这句?)

使用DOS命令(比如dir)时也要使用到调用。如果想与调用的程序进行交互,那么就要使用该方法的返回对象Process了,通过Process的getInputStream(),getOutputStream()和getErrorStream()方法可以得到输入输出流,然后通过InputStream可以得到程序对控制台的输出信息,通过OutputStream可以给程序输入指令,这样就达到了程序的交换功能。

用Java编写应用时,有时需要在程序中调用另一个现成的可执行程序或系统命令,这时可以通过组合使用Java提供的Runtime类和Process类的方法实现。下面是一种比较典型的程序模式:

在上面的程序中,第一行的“.\\p.exe”是要执行的程序名,Runtime.getRuntime()返回当前应用程序的Runtime对象,该对象的exec()方法指示Java虚拟机创建一个子进程执行指定的可执行程序,并返回与该子进程对应的Process对象实例。通过Process可以控制该子进程的执行或获取该子进程的信息。第二条语句的目的等待子进程完成再往下执行。
但在windows平台上,如果处理不当,有时并不能得到预期的结果。下面是笔者在实际编程中总结的几种需要注意的情况:
1、执行DOS的内部命令
如果要执行一条DOS内部命令,有两种方法。一种方法是把命令解释器包含在exec()的参数中。例如,执行dir命令,在NT上,可写成exec(“cmd.exe /c dir”),在windows95/98下,可写成“command.exe /c dir”,其中参数“/c”表示命令执行后关闭DOS立即关闭窗口。另一种方法是,把内部命令放在一个批命令my_dir.bat文件中,在Java程序中写成exec(“my_dir.bat”)。如果仅仅写成exec(“dir”),Java虚拟机则会报运行时错误。前一种方法要保证程序的可移植性,需要在程序中读取运行的操作系统平台,以调用不同的命令解释器。后一种方法则不需要做更多的处理。
2、打开一个不可执行的文件
打开一个不可执行的文件,但该文件存在关联的应用程序,则可以有两种方式。以打开一个word文档a.doc文件为例,Java中可以有以下两种写法:

显然,前一种方法更为简捷方便。
3、执行一个有标准输出的DOS可执行程序
在Windows平台上,运行被调用程序的DOS窗口在程序执行完毕后往往并不会自动关闭,从而导致Java应用程序阻塞在waitfor()语句。导致该现象的一个可能的原因是,该可执行程序的标准输出比较多,而运行窗口的标准输出缓冲区不够大。解决的办法是,利用Java中Process类提供的方法让Java虚拟机截获被调用程序的DOS运行窗口的标准输出,在waitfor()命令之前读出窗口的标准输出缓冲区中的内容。一段典型的程序如下:

 

注:

ASP过程Sub的定义和调用

转自:http://hi.baidu.com/xutt88/item/1aa9b4262449348d6e2cc3bf

在编写ASP代码的时候我们常常会自己编写一部分函数或过程,当我们不返回值时我们常常用过程sub来定义我们的函数,通常情况下我们的参数往往只有一个,这是我们一般不会发现问题,在使用多个参数的时候我们往往就要注意了,比较容易发生错误。
sub的定义如下:

这里的括号是必须的,请特别注意。
调用的时候如下:
函数名 arg1,arg2,…argn
或call 函数名(arg1,arg2,…argn)
注意这里两者的区别,一个需要括号,一个不需要括号。
当然只有一个参数的时候调用的时候两种情况都可以带括号,但是定义的时候是必须带括号的。

逆波兰表达式

摘自:https://zh.wikipedia.org/wiki/%E9%80%86%E6%B3%A2%E5%85%B0%E8%A1%A8%E7%A4%BA%E6%B3%95

逆波兰表示法(Reverse Polish notation,RPN,或逆波兰记法),是一种是由波兰数学家扬·武卡谢维奇1920年引入的数学表达式方式,在逆波兰记法中,所有操作符置于操作数的后面,因此也被称为后缀表示法。逆波兰记法不需要括号来标识操作符的优先级。

逆波兰记法中,操作符置于操作数的后面。例如表达“三加四”时,写作“3 4 +”,而不是“3 + 4”。如果有多个操作符,操作符置于第二个操作数的后面,所以常规中缀记法的“3 – 4 + 5”在逆波兰记法中写作“3 4 – 5 +”:先3减去4,再加上5。使用逆波兰记法的一个好处是不需要使用括号。例如中缀记法中“3 – 4 * 5”与“(3 – 4)*5”不相同,但后缀记法中前者写做“3 4 5 * -”,无歧义地表示“3 (4 5 *) −”;后者写做“3 4 – 5 *”。
逆波兰表达式的解释器一般是基于堆栈的。解释过程一般是:操作数入栈;遇到操作符时,操作数出栈,求值,将结果入栈;当一遍后,栈顶就是表达式的值。因此逆波兰表达式的求值使用堆栈结构很容易实现,和能很快求值。
注意:逆波兰记法并不是简单的波兰表达式的反转。因为对于不满足交换律的操作符,它的操作数写法仍然是常规顺序,如,波兰记法“/ 6 3”的逆波兰记法是“6 3 /”而不是“3 6 /”;数字的数位写法也是常规顺序。

代码[编辑]

  • while有输入符号
    • 读入下一个符号
    • IF是一个操作数
      • 入栈
    • ELSE IF是一个操作符
      • 有一个先验的表格给出该操作符需要n个参数
      • IF堆栈中少于n个操作数
        • (错误)用户没有输入足够的操作数
      • Else,n个操作数出栈
      • 计算操作符。
      • 将计算所得的值入栈
  • IF栈内只有一个值
    • 这个值就是整个计算式的结果
  • ELSE多于一个值
    • (错误) 用户输入了多余的操作数

Java计算器(使用逆波兰表达式算法)

转自:http://space.itpub.net/23071790/viewspace-716661

逆波兰表达式算法实现(Java):

 

Sql Server 日期格式化函数

转自:http://blog.163.com/yjdd005@126/blog/static/502426682007112895813356/

Sql Server 中一个非常强大的日期格式化函数(部分格式显示和系统语言有关)
Select CONVERT(varchar(100), GETDATE(), 0); –05 16 2006 10:57AM
Select CONVERT(varchar(100), GETDATE(), 1); –05/16/06
Select CONVERT(varchar(100), GETDATE(), 2); –06.05.16
Select CONVERT(varchar(100), GETDATE(), 3); –16/05/06
Select CONVERT(varchar(100), GETDATE(), 4); –16.05.06
Select CONVERT(varchar(100), GETDATE(), 5); –16-05-06
Select CONVERT(varchar(100), GETDATE(), 6); –16 05 06
Select CONVERT(varchar(100), GETDATE(), 7); –05 16, 06
Select CONVERT(varchar(100), GETDATE(), 8); –10:57:46
Select CONVERT(varchar(100), GETDATE(), 9); –05 16 2006 10:57:46:827AM
Select CONVERT(varchar(100), GETDATE(), 10); –05-16-06
Select CONVERT(varchar(100), GETDATE(), 11); –06/05/16
Select CONVERT(varchar(100), GETDATE(), 12); –060516
Select CONVERT(varchar(100), GETDATE(), 13); –16 05 2006 10:57:46:937
Select CONVERT(varchar(100), GETDATE(), 14); –10:57:46:967
Select CONVERT(varchar(100), GETDATE(), 20); –2006-05-16 10:57:47
Select CONVERT(varchar(100), GETDATE(), 21); –2006-05-16 10:57:47.157
Select CONVERT(varchar(100), GETDATE(), 22); –05/16/06 10:57:47 AM
Select CONVERT(varchar(100), GETDATE(), 23); –2006-05-16
Select CONVERT(varchar(100), GETDATE(), 24); –10:57:47
Select CONVERT(varchar(100), GETDATE(), 25); –2006-05-16 10:57:47.250
Select CONVERT(varchar(100), GETDATE(), 100); –05 16 2006 10:57AM
Select CONVERT(varchar(100), GETDATE(), 101); –05/16/2006
Select CONVERT(varchar(100), GETDATE(), 102); –2006.05.16
Select CONVERT(varchar(100), GETDATE(), 103); –16/05/2006
Select CONVERT(varchar(100), GETDATE(), 104); –16.05.2006
Select CONVERT(varchar(100), GETDATE(), 105); –16-05-2006
Select CONVERT(varchar(100), GETDATE(), 106); –16 05 2006
Select CONVERT(varchar(100), GETDATE(), 107); –05 16, 2006
Select CONVERT(varchar(100), GETDATE(), 108); –10:57:49
Select CONVERT(varchar(100), GETDATE(), 109); –05 16 2006 10:57:49:437AM
Select CONVERT(varchar(100), GETDATE(), 110); –05-16-2006
Select CONVERT(varchar(100), GETDATE(), 111); –2006/05/16
Select CONVERT(varchar(100), GETDATE(), 112); –20060516
Select CONVERT(varchar(100), GETDATE(), 113); –16 05 2006 10:57:49:513
Select CONVERT(varchar(100), GETDATE(), 114); –10:57:49:547
Select CONVERT(varchar(100), GETDATE(), 120); –2006-05-16 10:57:49
Select CONVERT(varchar(100), GETDATE(), 121); –2006-05-16 10:57:49.700
Select CONVERT(varchar(100), GETDATE(), 126); –2006-05-16T10:57:49.827
Select CONVERT(varchar(100), GETDATE(), 130); –18 ???? ?????? 1427 10:57:49:907AM
Select CONVERT(varchar(100), GETDATE(), 131); –18/04/1427 10:57:49:920AM

–常用:
Select CONVERT(varchar(100), GETDATE(), 8); –10:57:46
Select CONVERT(varchar(100), GETDATE(), 24); –10:57:47
Select CONVERT(varchar(100), GETDATE(), 108); –10:57:49
Select CONVERT(varchar(100), GETDATE(), 12); –060516
Select CONVERT(varchar(100), GETDATE(), 23); –2006-05-16

深入浅出Swing事件分发线程

转自:http://space.itpub.net/13685345/viewspace-374940

《FilthyRichClients》读书笔记-SwingのEDT

《FilthyRichClients》读完了前几个章节,现将我的体会结合工作以来从事Swing桌面开发的经验,对本书的一些重要概念进行一次分析,对书中的一些遗漏与模糊的地方及时补充,同时使读者消除长期以来“Swing性能低、界面丑陋”诸如此类的旧观念。读书笔记仅谈谈我对Swing的 理解,难免会犯错误,还望广大读者指教。
书中第二章-Swing渲染基本原理 中对Swing的线程做了系统地介绍。相比其他同类Swing教程,已经讲得非常深入了。但是如果读者之前对线程的掌握程度有限,尤其是编写代码比较随意的coder们,动辄就大量编写类似下面这样的代码:

这样的代码可能是netBeans这样的工具生成的“杰作”。但是如果这个人再懒惰一点,可能会直接在TODO下面写上长长一堆代码,还伴随着不可预知的 I/O操作,很多人指责界面被僵住是Swing性能的问题。在新式的JDK中,Swing已经在性能方面改进了很多,完全可以这么说:与应用程序自身的业务计算相比,界面上的耗时可以忽略。但是如果上述恶习改不掉的话,Swing永远“快”不起来,SWT也同样如此,因为它们都是单线程图形工具包。

(注:任何java程序都是由主线程中的main()方法开始执行,当main()方法中的JFrame调用setVisiable(true) 后,引发了另一个线程——事件分派线程,而actionPerformed()方法及paintComponent()方法都是在事件分派Swing线程中被调用,所以如果在actionPerformed()方法中有大量的耗时操作,将会导致用户界面无法响应用户请求,造成用户界面锁死。)

书上有这样一段话:“EventQueue的派发机制由单独的一个线程管理,这个线程称为事件派发线程(EDT)”。和其他很多桌面API一 样,Swing将GUI请求放入一个事件队列中执行。如果不明白什么是事件队列、EDT,它们是如何运作的,那么首先必须澄清四个重要的概念:分别是同步与异步、串行与并行、生产者消费者模式、事件队列。(不同领域串行与并行的含义可能是不同的)
同步与异步:同步是程序在发起请求后开始处理事件并等待处理的结果或等待请求执行完毕,在此之前程序被block住直到请求完成。而异步是当前程序发起请求后立即返回,当前程序不会立即处理该事件并等待处理的结果,请求是在稍后的某一时间才被处理。
串行与并行: 所谓串行是指多个要处理请求顺序执行,处理完一个再处理下一个;并行可以理解为并发,是同时处理多个请求(实际上我们只能理解为是这样,特别是CPU数目少于线程数的机器而言,真正意义的并发是不存在的,各个线程只是断断续续地交替地执行)。下图演示了串行与并行的机制。可以这么说,在引入多线程之前,对于同一进程或者程序而言执行的都是串行操作。
串行:
并行:
生产者/消费者模式:可以想象这样一副场景,某车间的一条传送带,有一个或多个入口不断产生待加工的货物,这种不断产生货物的称为生产者;传送带的末端是一个或多个工人在加工货物,称作消费者。有时由于传送带上没有足够的货物使得某一工人暂时空闲,有时又由于部分货物需加工的时间较长出现传送带上待加工的货物堆积。
如果用Java实现一个简单的生产者消费者模型,利用线程的等待/通知机制很容易实现。给出最基本的同步队列的参考实现

在JDK 5中新出现了许多具有并发性的数据结构在java.util.concurrent包中,它们适合于特殊的场合,本帖不作解释。

事件队列:在计算机数据结构中,队列是一个特殊的数据结构。其一、它是线性的;其二、元素是先进先出的,也就是说进入队列的元素必须从末端进入,先入队的元素先得到执行,后入队的元素等待前面的元素执行完毕出队后才 能执行,队列的处理方式是执行完一个再执行下一个。队列与线程安全是两个不同的概念,如果要将队列加上线程安全的特性,只需要仿照上述生产者/消费者加上 线程的等待/通知即可。
而Swing的事件队列就类似(基本原理相似,但是Swing内部实现会做些优化)于上述的事件队列,说它是单线程图形工具包指的是仅有单一消费者,也就 是常说的事件分发线程(EDT),一般来讲,除非你的应用程序停止,否则EDT会永不间断地徘徊在处理请求与等待请求之间。下图是Swing事件队列的实现机制:

很显然,如果在加工某一个货物上花费很长的时间,那么后续的货物只好等待。对于单一线程的事件队列来说有两个非常突出的特性:一、将同步操作转为异步操作。二、将并行处理转换为串行顺序处理
如果你能理解上述图,那么你就应该意识到:EDT要处理所有GUI操作,它是职责分明且非常忙碌的。也就是说你要记住两条原则:一、职责分明,任何GUI请求都应该在EDT中调用。二、需要处理的GUI请求非常多,包括窗口移动、组件自动重绘、刷新,它很忙,所以任何与GUI无关的处理不要由EDT来负责,尤其是I/O这种耗时的操作。
书中还讲到Swing不是一个“安全线程”的API,为什么要这样设计,再回看上图就会明白:Swing的线程安全不是靠自身组件的API来保 障,虽然repaint方法是这样,但是大多数Swing API是非线程安全的,也就是说不能在任意地方调用,它应该只在EDT中调用。Swing的线程安全靠事件队列和EDT来保障。
invokeLater和invokeAndWait:前文提到,Swing自身不是线程安全,对非EDT的并发调用需通过 invokeLater(runnable)和invokeAndWait(runnable)使请求插入到队列中等待EDT去执行。 invokeLater(runnable)方法是异步的,它会立即返回,具体何时执行请求并不确定,所以命名invokeLater是稍后调用。invokeAndWait(runnable)方法是同步的,它被调用结束会立即block当前线程(调用invokeAndWait的那个线程)直到EDT处理完那个请求。invokeAndWait一般的应用是取得Swing组件的数据,例如取得JSlider组件的当前值:

而外部非EDT线程可以这样调用:

当线程运行到EventQueue.invokeAndWait(task)时会立即被block至少1秒,待invokeAndWait返回时已经可以安全地取到值了。invokeAndWait被这样命名也反映了使用的意图:调用并等待结果。invokeAndWait有非常重要的一条准则是它不能在 EDT中被调用,否则程序会抛出Error,请求也不会去执行。

为什么要有这样一条限制?结合前文不难得出-防止死锁。如果invokeAndWait在EDT中调用,那么首先将请求压进队列,然后EDT便被 block(因为它就是调用invokeAndWait的当前线程)等待请求结束通知它继续运行,而实际上请求将永远得不到执行,因为它在等待队列的调度使EDT执行它,这就陷入一个僵局-EDT等待请求先执行,请求又等待EDT对队列的调度。彼此等待对方释放锁是造成死锁的四类条件之一。Swing有意地避免了这类情况的发生。
书中也提到了同步的绘制请求,作为队列,一条基本原则就是先进先出。那么paintImmediately到底是怎样的呢?显然这个调用请求不会稍后去 执行,也就是说不会插入到队列的末尾等到排在它前面的请求执行完再去执行它,而是“破坏”顺序性原则优先去执行,前面提到,Swing的事件队列相对基础的同步队列做了很多优化,那么这么说它是否被插入到队列最前面呢,也就是0这个位置?貌似也不是,书上说“已经在EDT中调用的方法中间…”,那么就是比当前正在处理的绘制请求还要优先,因为它是当前绘制请求的一部分,所以当前绘制请求(EDT正在处理的那个请求)要等它处理完成后再继续处理。(好好体会吧)
SwingWorker:推荐一篇Blog,http://blog.sina.com.cn/s/blog_4b6047bc010007so.html,作者是原Sun中国工程研究院的陈维雷先生,他对Swing的造诣非浅,他的Blog中有3篇介绍这一主题的文章,详尽程度要比该书详细得多。
最后,谈一下理解EDT对设计模式的帮助。通过上述对事件队列和EDT的分析,有这样一种体会:事件队列是一个非常好的处理并发设计模型,不仅 Swing用它来处理后台,Java的很多地方都在用,只不过对于处理服务器端的并发请求有多个处理线程在等候处理请求,也就是常说的线程池。而对于单用户的桌面应用,单线程调用要比多现成API更简单,“Swing后台这样做是为了保证事件的顺序和可预见性”,而且相对于服务器,客户端桌面层的请求要少得多,所以单线程就足够应对了。
单一Thread化的访问
通过EDT,使得不具备线程安全的Swing函数库避开了并发访问的问题。如果你也有一个不具备thread安全性的函数库并想在multithreaded环境下使用应该怎么办?只要你是从单一的thread来访问这个函数库,程序就不会遭遇到任何数据同步的问题。