|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有帐号?立即注册
x
用java开发web只要两本书:一本是关于java基础的,一本是关于jsp、servlet的就可以了。开发周期长,我就来讲句题外话,现在有很多思想都是通过java来展现。平安SwingAPI的计划方针是壮大、天真和易用。出格地,我们但愿能让程序员们便利地创建新的Swing组件,不管是重新入手下手仍是经由过程扩大我们所供应的一些组件。
出于这个目标,我们不请求Swing组件撑持多线程会见。相反,我们向组件发送哀求并在单一线程中实行哀求。
本文会商线程和Swing组件。目标不但是为了匡助你以线程平安的体例利用SwingAPI,并且注释了我们为何会选择如今如许的线程计划。
本文包含以下内容:
单线程划定规矩:Swing线程在统一时候仅能被一个线程所会见。一样平常来讲,这个线程是事务派发线程(event-dispatchingthread)。
划定规矩的破例:有些操纵包管是线程平安的。
事务分发:假如你必要处置件处置(event-handling)或绘制代码之外的中央会见UI,那末你可使用SwingUtilities类的invokeLater()或invokeAndWait()办法。
创立线程:假如你必要创立一个线程――好比用来处置一些泯灭大批盘算才能或受I/O才能限定的事情――你可使用一个线程工具类如SwingWorker或Timer。
为何我们如许完成Swing:我们将用一些关于Swing的线程平安的背景材料来停止这篇文章。
Swing的划定规矩是:
一旦Swing组件被具现化(realized),一切大概影响或依附于组件形态的代码都应当在事务派发线程中实行。
这个划定规矩大概听起来有点吓人,但对很多复杂的程序来讲,你用不着为线程成绩费心。在我们深切怎样撰写Swing代码之前,让我们先来界说两个术语:具现化(realized)和事务派发线程(event-dispatchingthread)。
具现化的意义是组建的paint()办法已或大概会被挪用。一个作为顶级窗口的Swing组件当挪用以下办法时将被具现化:setVisible(true)、show()或(大概令你惊异)pack()。当一个窗口被具现化,它包括的一切组件都被具现化。另外一个具现化一个组件的办法是将它放进到一个已具现化的容器中。稍后你会看到一些对组件具现化的例子。
事务派发线程是实行绘制和事务处置的线程。比方,paint()和actionPerformed()办法会主动在事务派发线程中实行。另外一个将代码放到事务派发线程中实行的办法是利用SwingUtilities类的invokeLater()办法。
一切大概影响一个已具现化的Swing组件的代码都必需在事务派发线程中实行。但这个划定规矩有一些破例:
有些办法是线程平安的:在SwingAPI的文档中,线程平安的办法用以下笔墨标志:
Thismethodisthreadsafe,althoughmostSwingmethodsarenot.
(这个办法是线程平安的,只管年夜多半Swing办法都不是。)
一个使用程序的GUI经常能够在主线程中构建和显现:上面的典范代码是平安的,只需没有(Swing或其他)组件被具现化:
publicclassMyApplication
{
publicstaticvoidmain(String[]args)
{
JFramef=newJFrame("Labels");//在这里将各组件
//到场到主框架……
f.pack();
f.show();
//不要再做任何GUI事情……
}
}
下面所示的代码全体在“main”线程中运转。对f.pack()的挪用使得JFrame以下的组件都被具现化。这意味着,f.show()挪用是不平安的且应当在事务派发线程中实行。只管云云,只需程序还没有一个看失掉的GUI,JFrame或它的内里的组件就几近不成能在f.show()前往前收到一个paint()挪用。由于在f.show()挪用以后不再有任何GUI代码,因而一切GUI事情都从主线程转到了事务派发线程,因而后面所会商的代码实践上是线程平安的。
一个applet的GUI能够在init()办法中机关和显现:现有的扫瞄器都不会在一个applet的init()和start()办法被挪用前绘制它。因此,在一个applet的init()办法中机关GUI是平安的,只需你不合错误applet中的对象挪用show()或setVisible(true)办法。
要特地一提的是,假如applet中利用了Swing组件,就必需完成为JApplet的子类。而且,组件应当增加到的JApplet内容窗格(contentpane)中,而不要间接增加到JApplet。对任何applet,你都不该该在init()或start()办法中实行费时的初始化操纵;而应当启动一个线程来实行费时的义务。
下述JComponent办法是平安的,能够从任何线程挪用:repaint()、revalidate()、和invalidate()。repaint()和revalidate()办法为事务派发线程对哀求列队,并分离挪用paint()和validate()办法。invalidate()办法只在必要确认时标志一个组件和它的一切间接先人。
监听者列表能够由任何线程修正:挪用addListenerTypeListener()和removeListenerTypeListener()办法老是平安的。对监听者列表的增加/删除操纵不会对举行中的事务派发有任何影响。
注重:revalidate()和旧的validate()办法之间的主要区分是,revalidate()会缓存哀求并组分解一次validate()挪用。这和repaint()缓存并组合绘制哀求相似。
年夜多半初始化后的GUI事情天然地产生在事务派发线程。一旦GUI成为可见,年夜多半程序都是由事务驱动的,如按钮举措或鼠标点击,这些老是在事务派发线程中处置的。
不外,总有些程序必要在GUI成为可见后实行一些非事务驱动的GUI事情。好比:
在成为可用前必要举行长工夫初始化操纵的程序:这类程序一般应当在初始化时代就显现出GUI,然后更新或改动GUI。初始化历程不该该在事务派发线程中举行;不然,重绘组件和事务派发会中断。只管云云,在初始化以后,GUI的更新/改动仍是应当在事务派发线程中举行,来由是线程平安。
必需呼应非AWT事务来更新GUI的程序:比方,设想一个服务器程序从大概运转在其他呆板上的程序失掉哀求。这些哀求大概在任什么时候刻抵达,而且会引发在一些大概未知的线程中对服务器的办法挪用。这个办法挪用如何更新GUI呢?在事务派发线程中实行GUI更新代码。
SwingUtilities类供应了两个办法来匡助你在事务派发线程中实行代码:
invokeLater():请求在事务派发线程中实行某些代码。这个办法会当即前往,不会守候代码实行终了。
invokeAndWait():举动与invokeLater()相似,除这个办法会守候代码实行终了。一样平常地,你能够用invokeLater()来取代这个办法。
上面是一些利用这几个API的例子。请同时参阅《TheJavaTutorial》中的“BINGOexample”,特别是以下几个类:CardWindow、ControlPane、Player和OverallStatusPane。
利用invokeLater()办法
你能够从任何线程挪用invokeLater()办法以哀求事务派发线程运转特定代码。你必需把要运转的代码放到一个Runnable对象的run()办法中,并将此Runnable对象设为invokeLater()的参数。invokeLater()办法会当即前往,不守候事务派发线程实行指定代码。这是一个利用invokeLater()办法的例子:
RunnabledoWorkRunnable=newRunnable()
{
publicvoidrun()
{
doWork();
}
};
SwingUtilities.invokeLater(doWorkRunnable);
利用invokeAndWait()办法
invokeAndWait()办法和invokeLater()办法很类似,除invokeAndWait()办法会等事务派发线程实行了指定代码才前往。在大概的情形下,你应当只管用invokeLater()来取代invokeAndWait()。假如你真的要利用invokeAndWait(),请确保挪用invokeAndWait()的线程不会在挪用时代持有任何其他线程大概必要的锁。
这是一个利用invokeAndWait()的例子:
voidshowHelloThereDialog()throwsException
{
RunnableshowModalDialog=newRunnable()
{
publicvoidrun()
{
JOptionPane.showMessageDialog(myMainFrame,"HelloThere");
}
};
SwingUtilities.invokeAndWait(showModalDialog);
}
相似地,假定一个线程必要对GUI的形态举行存取,好比文本域的内容,它的代码大概相似如许:
voidprintTextField()
throwsException{
finalString[]myStrings=newString[2];
RunnablegetTextFieldText=newRunnable(){
publicvoidrun(){
myStrings[0]=textField0.getText();
myStrings[1]=textField1.getText();
}
};
SwingUtilities.invokeAndWait(getTextFieldText);
System.out.println(myStrings[0]+""+myStrings[1]);}
假如你能制止利用线程,最好如许做。线程大概难于利用,并使得程序的debug更坚苦。一样平常来讲,关于严厉意义下的GUI事情,线程是不用要的,好比对组件属性的更新。
不论怎样说,偶然候线程是需要的。以下情形是利用线程的一些典范情形:
实行一项费时的义务而不用将事务派发线程锁定。例子包含实行大批盘算的情形,会招致大批类被装载的情形(如初始化),和为收集或磁盘I/O而堵塞的情形。
反复地实行一项操纵,一般在两次操纵间距离一个预定的工夫周期。
要守候来自客户的动静。
你可使用两个类来匡助你完成线程:
SwingWorker:创立一个背景线程来实行费时的操纵。
Timer:创立一个线程来实行或屡次实行某些代码,在两次实行间距离用户界说的提早。
利用SwingWorker类
SwingWorker类在SwingWorker.java中完成,这个类其实不包括在Java的任何刊行版中,以是你必需独自下载它。
SwingWorker类做了一切完成一个背景线程所需的邋遢事情。固然很多程序都不必要背景线程,背景线程在实行费时的操纵时仍旧是很有效的,它能进步程序的功能不雅感。
SwingWorkersget()method.HeresanexampleofusingSwingWorker:
要利用SwingWorker类,你起首要完成它的一个子类。在子类中,你必需完成construct()办法还包括你的长工夫操纵。当你实例化SwingWorker的子类时,SwingWorker创立一个线程但其实不启动它。你要挪用你的SwingWorker对象的start()办法来启动线程,然后start()办法会挪用你的construct()办法。当你必要construct()办法前往的对象时,能够挪用SwingWorker类的get()办法。这是一个利用SwingWorker类的例子:
...//在main办法中:
finalSwingWorkerworker=newSwingWorker(){
publicObjectconstruct(){
returnnewexpensiveDialogComponent();
}
};
worker.start();
...
//在举措事务处置办法中:
JOptionPane.showMessageDialog(f,worker.get());
当程序的main()办法挪用start()办法,SwingWorker启动一个新的线程来实例化ExpensiveDialogComponent。main()办法还机关了由一个窗口和一个按钮构成的GUI。
当用户点击按钮,程序将堵塞,假如需要,堵塞到ExpensiveDialogComponent创立完成。然后程序显现一个包括ExpensiveDialogComponent的形式对话框。你能够在MyApplication.java找到全部程序。
利用Timer类
Timer类经由过程一个ActionListener来实行或屡次实行一项操纵。你创立准时器的时分能够指定操纵实行的频次,而且你能够指定准时器的举措事务的监听者(actionlistener)。启动准时器后,举措监听者的actionPerformed()办法会被(屡次)挪用来实行操纵。
准时器举措监听者(actionlistener)界说的actionPerformed()办法将在事务派发线程中挪用。这意味着你不用在个中利用invokeLater()办法。
这是一个利用Timer类来完成动画轮回的例子:
publicclassAnimatorApplicationTimer
extendsJFrameimplementsActionListener{
...//在这里界说实例变量
Timertimer;
publicAnimatorApplicationTimer(...){
...//创立一个准时器来
//来挪用此对象actionhandler。
timer=newTimer(delay,this);
timer.setInitialDelay(0);
timer.setCoalesce(true);
...
}
publicvoidstartAnimation(){
if(frozen){
//甚么都不做。使用户请求
//中断变更图象。
}else{
//启动(或重启动)动画!
timer.start();
}
}
publicvoidstopAnimation(){
//中断动画线程。
timer.stop();
}
publicvoidactionPerformed(ActionEvente)
{
//进到下一帧动画。
frameNumber++;
//显现。
repaint();
}
...
}
在一个线程中实行一切的用户界面代码有如许一些长处:
组件开辟者不用对线程编程有深切的了解:像ViewPoint和Trestle这类工具包中的一切组件都必需完整撑持多线程会见,使得扩大十分坚苦,特别对不精晓线程编程的开辟者来讲。比来的一些工具包如SubArctic和IFC,都接纳和Swing相似的计划。
事务以可预知的序次派发:invokeLater()列队的runnable对象从鼠标和键盘事务、准时器事务、绘制哀求的统一个行列派发。在一些组件完整撑持多线程会见的工具包中,组件的改动被变更无常的线程调剂程序交叉到事务处置过程当中。这使得周全测试变得坚苦乃至不成能。
更低的价值:实验当心锁住临界区的工具包要消费实足的工夫和空间在锁的办理上。每当工具包中挪用某个大概在客户代码中完成的办法时(如public类中的任何public和protected办法),工具包都要保留它的形态并开释一切锁,以便客户代码能在需要时取得锁。当把持权交回到工具包,工具包又必需从头捉住它的锁并恢复形态。一切使用程序都不能不包袱这一价值,即便年夜多半使用程序其实不必要对GUI的并发会见。
这是的SubArcticJavaToolkit的作者对在工具包中撑持多线程会见的成绩的形貌:
我们的基础信条是,当计划和制作多线程使用程序,特别是那些包含GUI组件的使用程序时,必需包管极度当心。线程的利用大概会很有棍骗性。在很多情形下,它们体现得可以极好的简化编成,使得计划“专注于单一义务的复杂自治实体”成为大概。在一些情形下它们切实其实简化了计划和编码。但是,在几近一切的情形下,它们都使得调试、测试和保护的坚苦年夜年夜增添乃至成为不成能。不管年夜多半程序员所受的练习、他们的履历和理论,仍是我们用来匡助本人的工具,都不是可以用来凑合非决意论的。比方,周全测试(这老是坚苦的)在bug依附于工夫时是几近不成能的。特别关于Java来讲,一个程序要运转在很多分歧范例的呆板的操纵体系平台上,而且每一个程序都必需在争先和非争先式调剂下都能一般事情。
因为这些固有的坚苦,我们力劝你三思是不是相对有利用线程的需要。只管云云,有些情形下利用线程是需要的(大概是被其他软件包强加的),以是subArctic供应了一个线程平安的会见机制。本章会商了这一机制和如何在一个自力线程中平安地操纵交互树。
他们所说的线程平安机制十分相似于SwingUtilities类供应的invokeLater()和invokeAndWait()办法。
对于一个大型项目,如果用java来作,可能需要9个月,并且可能需要翻阅10本以上的书,但如果用ruby来作,3个月,3本书就足够了,而.net也不过3,4本书足以,这就是区别。 |
|