和风细雨

世上本无难事,心以为难,斯乃真难。苟不存一难之见于心,则运用之术自出。

#

使用JavaScript操作表单


获取表单的引用

在开始对表单进行编程前,必须先获取表单<form>的引用.有以下方法可以来完成这一操作。
1)采用典型的DOM树中的定位元素的方法getElementById(),只要传入表单的id即可获得表单的引用:
var vform=document.getElementById(“form1”);

2)还可以用document的forms集合,并通过表单在form集合中的位置或者表单的name特性来进行引用:
var oform=document.forms[0];
var oform=document.forms[“formZ”];

访问表单字段

每个表单字段,不论它是按钮,文本框还是其它内容,均包含在表单的elements集合中.可以用它们的name特性或者它们在集合中的位置来访问不同的字段:
Var oFirstField=oForm.elements[0];
Var oTextBox1=oForm.elements[“textBox1”];
此外还可以通过名字来直接访问字段,如:
Var oTextBox1=oForm.textbox1;
如果名字中有标记,则可以使用方括号标记:
Var oTextBox1=oForm[“text box 1”];

最常见的访问表单字段的方法

最简单常用的访问表单元素的方法自然是document.getElementById(),举例如下:
<input type="text" name="count"
      value="" />
在JS中取得此元素内容的代码为:
var name=document.getElementById("name").value
这种方法无论表单元素处于那个表单中甚至是不在表单中都能凑效,一般情况下是我们用JS访问表单元素的首选.
鉴于document.getElementById比较长,你可以用如下函数代替它:
function $(id){
     return document.getElementById(id);
}
把这个函数放在共有JS库中,在jsp页面通过如下方法引用它:
<head>
<title>"記賬系统"添加资源页面</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script src="web/js/check.js" type="text/javascript"></script>
<link rel="stylesheet" rev="stylesheet" href="web/css/style.css"
 type="text/css" />
</head>
此后你就可以直接使用$访问表单元素中的内容:
var name=$("name").value; 

表单字段的共性

以下是所有表单字段(除了隐藏字段)
Disabled可以用来获取或设置表单控件是否被禁用.
Form特性用来指向字段所在的表单.
Blur()方法使表单字段失去焦点.
Focus()方法使表单字段获得焦点.
当字段失去焦点是,发生blur事件,执行onblur事件处理程序.
当字段获取焦点时,发生focus事件,执行onfocus事件处理函数.

当页面载入时将焦点放在第一个字段

在body代码中如此书写:
<body onload=“focusOnFirstElm()”>
JS函数如下书写:
Fucntion focusOnFirstElm(){
     document.forms[0].elements[0].focus();
}
如果第一个字段不是隐藏字段此方法就是凑效的,如果是的话把elements的下标改成非隐藏字段的下标即可.

控制表单只被提交一次

由于Web的响应问题,用户有可能会点击多次提交按钮从而创建重复数据或是导致错误,我们可以使用JS对提交按钮进行设置以让表单只被提交一次。
<input type=“submit” value=“提交” onclick=“this.disabled=true;this.form.submit()”/>
这里在点击提交按钮时执行了两句JS代码,一次是this.disabled=true;这是让提交按钮被禁用;一次是this.form.submit()这是提交这个按钮所在的表单。

检查用户在表单元素中的按键

为控件添加 onkeydown事件处理,然后在函数查看keyCode,就能知道用户的按键,代码如下:
<input type="text" name="test"
value="" onkeydown="testkey(this,event)"/>
JS代码如下:
function testkey(obj,event){
     alert(event.keyCode);
}

这种技巧在改善用户体验如按回车键提交表单时很常用。

posted @ 2008-03-02 15:23 和风细雨 阅读(2253) | 评论 (2)编辑 收藏

Servlet与JSP综合讲述

Servlet与JSP综合讲述

Servlet和JSP的概念

Servlet是Sun推出的用于实现CGI(通用网关接口)的java语言版本,它不但具有跨平台的特性,而且还以多线程的方式为用户提供服务而不必为每个请求都启动一个线程,因此其效率要比传统的CGI程序要高很多.
JSP和MS的ASP类似,它把JSP标签嵌入到HTML格式的网页中,这样对程序员和网页编辑人员都很方便,JSP天生就是为表现层设计的.
实际上,Servlet只是继承了HttpRequest的Java类,而JSP最终也会被Servlet引擎翻译成Servlet并编译执行,JSP的存在主要是为了方便表现层.
Servlet与JSP之间的区别,决定了Servlet与JSP在MVC构架模式中的不同角色.Servlet一般作为MVC中的控制器,JSP一般作为MVC中的视图.

Servlet的生命周期

Servlet有三个生命周期:初始化,执行和结束,它们分别对应Servlet接口中的init,service和destroy三个函数.
初始化时期:当servlet被servlet容器(如tomcat)载入后,servlet的init函数就会被调用,在这个函数可以做一些初始化工作.init函数只会在servlet容器载入servlet执行一次,以后无论有多少客户端访问这个Servlet,init函数都不会被执行.
执行期:servlet采用多线程方式向客户提供服务,当有客户请求来到时, service会被用来处理它.每个客户都有自己的service方法,这些方法接受客户端请求,并且发挥相应的响应.程序员在实现具体的Servlet时,一般不重载service方法,服务器容器会调用service方法以决定doGet,doPost,doPut,doDelete中的一种或几种,因此应该重载这些方法来处理客户端请求.]
结束期:该时期服务器会卸载servlet,它将调用destroy函数释放占用的资源,注意Web服务器是有可能崩溃的,destroy方法不一定会被执行.

如何开发和部署一个Servlet

1)从java.servlet.http.HttpServlet继承自己的servlet类.
2)重载doGet或doPost方法来处理客户请求(一般是doPost,其安全性较好),如果要在加载Servlet时被加载时进行初始化操作,可以重载init方法.
3)在web.xml中配置这个servlet.其中servlet-class用来制定这个servlet的类全名,servlet-name用来标识这个servlet,它可以是任意的字符串,不一定要和类文件名一致.url-pattern用来表示servlet被映射到的url模式.

 <!– Servlet在Web.xml中的配置示例 -->
  <servlet>
    <servlet-name>ShowPageServlet</servlet-name>
    <servlet-class>
      com.sitinspring.action.ShowPageServlet
    </servlet-class>
  </servlet>

  <servlet-mapping>
    <servlet-name>ShowPageServlet</servlet-name>
    <url-pattern>/ShowPage</url-pattern>
  </servlet-mapping>

开发一个启动时就被执行的Servlet

一般的Servlet都是在有来自客户端请求时才会执行,要让它在启动时就执行需要在配置中进行一些特殊设置,如右.
右边的代码中, load-on-startup说明了服务器一启动就加载并初始化它,0代表了加载它的优先级,注意它必须是一个正数,而且值小的要比值大的先加载.debug制定输出调试信息的级别,0为最低.
这样的servlet在用于读取WebApp的一些初始化参数很有用处,如取得配置文件的地址,设置log4j和得到WebApp的物理路径等.右边配置的servlet就是用来初始化log4j的.

<!-- InitServlet -->
<servlet>
   <servlet-name>log4j-init</servlet-name>
   <servlet-class>
    com.sitinspring.action.Log4jInit
   </servlet-class>
   <init-param>
     <param-name>log4j</param-name>
     <param-value>WEB-INF/classes/log4j.properties</param-value>
   </init-param>
   <load-on-startup>1</load-on-startup>
</servlet>

Serlet中出现的线程不安全的问题

Servlet是运行在多线程的服务器上的,它对每个用户的请求创建的是线程而不是进程,因此在高效的同时也带来了数据同步和一致性的问题.
服务器值实例化一个Servlet/JSP实例,然后在多个处理线程中调用该实例的相关方法来处理请求,因此servlet的成员变量可能会被多个线程调用该实例的相关方法改变,将有可能带来问题.这也是大家在众多的Servlet中很少看到成员变量的原因.



public class ThreadUnsafeServlet extends HttpServlet {
  private String unsafeString="";

  public void doPost(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, java.io.IOException {
    request.setCharacterEncoding("UTF-8");
   
    unsafeString=request.getParameter("str");
   
    try{
      Thread.sleep(5000);
    }
    catch(Exception ex){
      ex.printStackTrace();
    }
   
    PrintWriter out=response.getWriter();
    out.println(unsafeString);
  }
   
  public void doGet(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, java.io.IOException {
    doPost(request, response);
  }
}

关于重定向的两种方法

在servlet中,重定向一般是通过HttpResponse的sendRedirect()方法或RequestDispatcher的forward方法来实现的.
sendRedirect的参数可以是相对或绝对地址,如果以/开头容器将认为相对于当前Web引用的根.这种请求将导致客户端浏览器请求URL跳转,而且从Browser的地址栏中可以看到新的Url地址.另外使用这个方法时,前一个页面的状态不会被带到下一个页面,通过request.getAttribute(“XXXX”)方法将得到空值.
RequestDispatcher是一个Web资源的包装器,可以用来把当前请求传递到该资源,而且客户端浏览器的地址栏也不会显示为转向后的地址.另外此方法还可以将请求发送到任意一个服务器资源.
如果两种方法都能达到要求,最好使用forward方法,它比sendRedirect安全而高效.

获取当前绝对路径

Servlet/JSP有时需要对拥有的资源进行操作,这就要求得到它们所在的绝对路径.此时可以使用ServletContext接口提供的方法来得到当前应用所在的绝对路径,代码如下:
ServletContext sct = getServletContext();
String realPath=sct.getRealPath("/");

注: ServletContext 用于Servlet和Servlet容器交换信息.

posted @ 2008-03-02 00:13 和风细雨 阅读(456) | 评论 (0)编辑 收藏

XML(Extensible Markup Language )综述

XML的由来
XML是eXtensible Markup Language的缩写。扩展标记语言XML是一种简单的数据存储语言,使用一系列简单的标记描述数据,而这些标记可以用方便的方式建立,虽然XML占用的空间比二进制数据要占用更多的空间,但XML极其简单易于掌握和使用
XML是现代程序中一个必不可少的组成部分,也是世界上发展最快的技术之一。它的主要目的是以结构化的方式来表示数据,在某些方面,XML也类似于数据库,提供数据的结构化视图。
XML(可扩展标记语言)是从称为SGML(标准通用标记语言)发展而来的,SGML的主要目的是定义使用标签来表示数据的标记语言的语法。基于SGML的重要语言之一就是著名的HTML.
标签由包围在一个小于号<和一个大于号>之间的文本组成,起始标签(tag)表示一个特定区域的开始,例如<start>;结束标签定义了一个区域的结束,除了在小于号之后紧跟一个斜线外和起始标签一致,例如</end>.举例说明标签如下:
<member id=“007”>邦德</member>中,左边的<member id=“007”>是起始标签,邦德是标签中的文字,007是属性Attribute, </member >是结束标签.

XML的发展

由于SGML中存在特殊而随意的语法(如标签的非嵌套使用),使得建立一个SGML语言的解析器成了一项艰巨的任务,这些困难导致了SGML一直停步不前.
XML通过相对严格的语法规定使得建立一个XML解析器要容易得多,这些语法包括:
1)任何起始标签都必须有一个结束标签。
2)可以采用另一种简化语法,可以在一个标签中同时表示起始和结束标签。这种语法是在大于符号前紧跟一个斜线/.如<tag />等同于<tag></tag>.
3)标签必须按照合适的顺序进行嵌套,在没有关闭内部节点之前不能关闭外部节点。
4)所有的特性都必须有值,特性的值周围应该加上双引号。

XML文档示例

<?xml version="1.0" encoding="GBK"?>

<members>
  <member name="Andy">
    <age>25</age>
    <title>JSE</title>
  </member>
  <member name="Bill">
    <age>35</age>
    <title>SSE</title>
  </member>
  <member name="Cindy">
    <age>45</age>
    <title>PM</title>
  </member>
  <member name="Douglas">
    <age>45</age>
    <title>GM</title>
  </member>
</members>

<?xml version=“1.0” encoding=“GBK”?>是XML序言,这一行代码告诉解析器文件将按XML规则进行解析, GBK制定了此文件的编码方式。
<members>是文档的根节点,一个XML中有且只有一个根节点,否则会造成解析失败。
<member name=“Andy”>。。。</member>是根节点下面的子节点,name是其特性,特性的值为Andy。这个子节点下面有age和title两个子节点。

XML的用途

以文本的形式存储数据,这样的形式适于机器阅读,对于人阅读也相对方便.
作为程序的配置文件使用,如著名的web.xml,struts-config.xml
Ajax程序传递数据的载体.
WebService,SOAP的基础.

针对XML的API

将XML定义为一种语言之后,就出现了使用常见的编程语言(如Java)来同时表现和处理XML代码的需求。

首先出现的是Java上的SAX(Simple API for XML)项目。SAX提供了一个基于事件的XML解析的API。从其本质上来说,SAX解析器从文件的开头出发,从前向后解析,每当遇到起始标签或者结束标签、特性、文本或者其他的XML语法时,就会触发一个事件。然后,当事件发生时,具体要怎么做就由开发人员决定。

因为SAX解析器仅仅按照文本的方式来解析它们,所以SAX更轻量、更快速。而它们的主要缺点是在解析中无法停止、后退或者不从文件开始,直接访问XML结构中的指定部分。

DOM是针对XML的基于树的API。它关注的不仅仅是解析XML代码,而是使用一系列互相关联的对象来表示这些代码,而这些对象可以被修改且无需重新解析代码就能直接访问它们。

使用DOM,只需解析代码一次来创建一个树的模型;某些时候会使用SAX解析器来完成它。在这个初始解析过程之后,XML已经完全通过DOM模型来表现出来,同时也不再需要原始的代码。尽管DOM比SAX慢很多,而且,因为创建了相当多的对象而需要更多的开销,但由于它使用上的简便,因而成为Web浏览器和JavaScript最喜欢的方法。

最方便的XML解析利器-dom4j

Dom4j是一个易用的、开源的库,用于XML,XPath和XSLT。它应用于Java平台,采用了Java集合框架并完全支持DOM,SAX和JAXP.
sax和dom本身的api都比较复杂,不易使用,而开源包dom4j却综合了二者的优点,屏蔽了晦涩的细节,封装了一系列类和接口以方便用户使用它来读写XML.

Dom4j下载

要使用dom4j读写XML文档,需要先下载dom4j包,dom4j官方网站在 http://www.dom4j.org/ 目前最新dom4j包下载地址:http://nchc.dl.sourceforge.net/sourceforge/dom4j/dom4j-1.6.1.zip
解开后有两个包,仅操作XML文档的话把dom4j-1.6.1.jar加入工程就可以了,如果需要使用XPath的话还需要加入包jaxen-1.1-beta-7.jar.

使用dom4j读写xml的一些常用对象

1.Document:文档对象,它代表着整篇xml文档.
2.Element:节点元素,它代表着xml文档中的一个节点元素,如前面的<age>25</age>就是一个Element.其值(文本值)为25.
3.Attribute:节点属性,如前面的节点元素<member name=“Andy”>…< /member >中, name就是节点元素的一个属性,其值(文本值)为Andy.

与Document对象相关的API

1.读取XML文件,获得document对象.
SAXReader reader = new SAXReader();
Document  document = reader.read(new File("input.xml"));

2.解析XML形式的文本,得到document对象.
String text = "<members></members>";
Document document = DocumentHelper.parseText(text);

3.主动创建document对象.
Document document = DocumentHelper.createDocument();
Element root = document.addElement("members");// 创建根节点

与Element有关的API

1.获取文档的根节点.
Element rootElm = document.getRootElement();

2.取得某节点的单个子节点.
Element memberElm=root.element(“member”);// “member”是节点名

3.取得节点的文字
String text=memberElm.getText();
也可以用:
String text=root.elementText("name");这个是取得根节点下的name字节点的文字.

4.取得某节点下名为"member"的所有字节点并进行遍历.
List nodes = rootElm.elements("member");

for (Iterator it = nodes.iterator(); it.hasNext();) {
   Element elm = (Element) it.next();
   // do something
}

5.对某节点下的所有子节点进行遍历.
for(Iterator it=root.elementIterator();it.hasNext();){
    Element element = (Element) it.next();
    // do something
}

6.在某节点下添加子节点.
Element ageElm = newMemberElm.addElement("age");

7.设置节点文字.
ageElm.setText("29");
8.删除某节点.
parentElm.remove(childElm);// childElm是待删除的节点,parentElm是其父节点

与Attribute相关的API

1.取得某节点下的某属性
    Element root=document.getRootElement();   
    Attribute attribute=root.attribute("size");// 属性名name

2.取得属性的文字
    String text=attribute.getText();
也可以用:
    String text2=root.element("name").attributeValue("firstname");这个是取得根节点下name字节点的属性firstname的值.

3.遍历某节点的所有属性
    Element root=document.getRootElement();   
    for(Iterator it=root.attributeIterator();it.hasNext();){
 Attribute attribute = (Attribute) it.next();
 String text=attribute.getText();
 System.out.println(text);
    }

4.设置某节点的属性和文字.
    newMemberElm.addAttribute("name", "sitinspring");

5.设置属性的文字
    Attribute attribute=root.attribute("name");
    attribute.setText("sitinspring");
6.删除某属性
    Attribute attribute=root.attribute("size");// 属性名name
    root.remove(attribute);

将document的内容写入XML文件

1.文档中全为英文,不设置编码,直接写入的形式.
    XMLWriter writer = new XMLWriter(new FileWriter("output.xml"));
    writer.write(document);
    writer.close();

2.文档中含有中文,设置编码格式写入的形式.
    OutputFormat format = OutputFormat.createPrettyPrint();
    format.setEncoding("GBK");    // 指定XML编码       
    XMLWriter writer = new XMLWriter(new FileWriter("output.xml"),format);
   
    writer.write(document);
    writer.close();

字符串与XML的转换

1.将字符串转化为XML
    String text = "<members> <member>sitinspring</member> </members>";
    Document document = DocumentHelper.parseText(text);

2.将文档或节点的XML转化为字符串.
    SAXReader reader = new SAXReader();
    Document  document = reader.read(new File("input.xml"));           
    Element root=document.getRootElement();               
    String docXmlText=document.asXML();
    String rootXmlText=root.asXML();
    Element memberElm=root.element("member");
    String memberXmlText=memberElm.asXML();

使用XPath快速找到节点.

读取的XML文档示例
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
  <name>MemberManagement</name>
  <comment></comment>
  <projects>
    <project>PRJ1</project>
    <project>PRJ2</project>
    <project>PRJ3</project>
    <project>PRJ4</project>
  </projects>
  <buildSpec>
    <buildCommand>
      <name>org.eclipse.jdt.core.javabuilder</name>
      <arguments>
      </arguments>
    </buildCommand>
  </buildSpec>
  <natures>
    <nature>org.eclipse.jdt.core.javanature</nature>
  </natures>
</projectDescription>

使用XPath快速找到节点project.
 public static void main(String[] args){
    SAXReader reader = new SAXReader();
   
    try{
      Document  doc = reader.read(new File("sample.xml"));
     
      List projects=doc.selectNodes("/projectDescription/projects/project");
     
      Iterator it=projects.iterator();
     
      while(it.hasNext()){
        Element elm=(Element)it.next();      
        System.out.println(elm.getText());
      }
     
    }
    catch(Exception ex){
       ex.printStackTrace();
    }
  }

 

posted @ 2008-03-02 00:09 和风细雨 阅读(812) | 评论 (0)编辑 收藏

Socket基础

本文内容

Socket概述
Socket的重要API
一个Socket通信的例子

Socket是什么?

Socket通常也称作“套接字”,用于描述IP地址和端口,是一个通信链的句柄。应用程序通常通过“套接字”向网络发出请求或者应答网络请求。
在java中,Socket和ServerSocket类库位于java.net包中。ServerSocket用于服务器端,Socket是建立网络连接时使用的。在连接成功时,应用程序两端都会产生一个Socket实例,操作这个实例,完成所需的会话。
对于一个网络连接来说,套接字是平等的,并没有差别,不因为在服务器端或在客户端而产生不同级别。

Socket的直观描述

Socket的英文原义是“孔”或“插座”。在这里作为进程通信机制,取后一种意义。socket非常类似于电话插座。以一个国家级电话网为例。电话的通话双方相当于相互通信的2个进程,区号是它的网络地址;区内一个单位的交换机相当于一台主机,主机分配给每个用户的局内号码相当于socket号。任何用户在通话之前,首先要占有一部电话机,相当于申请一个socket;同时要知道对方的号码,相当于对方有一个固定的socket。然后向对方拨号呼叫,相当于发出连接请求(假如对方不在同一区内,还要拨对方区号,相当于给出网络地址)。对方假如在场并空闲(相当于通信的另一主机开机且可以接受连接请求),拿起电话话筒,双方就可以正式通话,相当于连接成功。双方通话的过程,是一方向电话机发出信号和对方从电话机接收信号的过程,相当于向socket发送数据和从socket接收数据。通话结束后,一方挂起电话机相当于关闭socket,撤消连接。
在电话系统中,一般用户只能感受到本地电话机和对方电话号码的存在,建立通话的过程,话音传输的过程以及整个电话系统的技术细节对他都是透明的,这也与socket机制非常相似。socket利用网间网通信设施实现进程通信,但它对通信设施的细节毫不关心,只要通信设施能提供足够的通信能力,它就满足了。
 至此,我们对socket进行了直观的描述。抽象出来,socket实质上提供了进程通信的端点。进程通信之前,双方首先必须各自创建一个端点,否则是没有办法建立联系并相互通信的。正如打电话之前,双方必须各自拥有一台电话机一样。

socket 是面向客户/服务器模型而设计的

socket 是面向客户/服务器模型而设计的,针对客户和服务器程序提供不同的socket 系统调用。客户随机申请一个socket (相当于一个想打电话的人可以在任何一台入网电话上拨号呼叫),系统为之分配一个socket号;服务器拥有全局公认的 socket ,任何客户都可以向它发出连接请求和信息请求(相当于一个被呼叫的电话拥有一个呼叫方知道的电话号码)。
socket利用客户/服务器模式巧妙地解决了进程之间建立通信连接的问题。服务器socket为全局所公认非常重要。读者不妨考虑一下,两个完全随机的用户进程之间如何建立通信?假如通信双方没有任何一方的socket 固定,就好比打电话的双方彼此不知道对方的电话号码,要通话是不可能的。

Socket的应用

Socket 接口是访问 Internet 使用得最广泛的方法。 如果你有一台刚配好TCP/IP协议的主机,其IP地址是202.120.127.201, 此时在另一台主机或同一台主机上执行ftp 202.120.127.201,显然无法建立连接。因"202.120.127.201" 这台主机没有运行FTP服务软件。同样, 在另一台或同一台主机上运行浏览软件 如Netscape,输入"http://202.120.127.201",也无法建立连接。现在,如果在这台主机上运行一个FTP服务软件(该软件将打开一个Socket, 并将其绑定到21端口),再在这台主机上运行一个Web 服务软件(该软件将打开另一个Socket,并将其绑定到80端口)。这样,在另一台主机或同一台主机上执行ftp 202.120.127.201,FTP客户软件将通过21端口来呼叫主机上由FTP 服务软件提供的Socket,与其建立连接并对话。而在netscape中输入"http://202.120.127.201"时,将通过80端口来呼叫主机上由Web服务软件提供的Socket,与其建 立连接并对话。 在Internet上有很多这样的主机,这些主机一般运行了多个服务软件,同时提供几种服务。每种服务都打开一个Socket,并绑定到一个端口上,不同的端口对应于不同的服务。Socket正如其英文原意那样,象一个多孔插座。一台主机犹如布满各种插座的房间,每个插座有一个编号,有的插座提供220伏交流电, 有的提供110伏交流电,有的则提供有线电视节目。 客户软件将插头插到不同编号的插座,就可以得到不同的服务。

重要的Socket API

accept方法用于产生“阻塞”,直到接受到一个连接,并且返回一个客户端的Socket对象实例。“阻塞”是一个术语,它使程序运行暂时“停留”在这个地方,直到一个会话产生,然后程序继续;通常“阻塞”是由循环产生的。
getInputStream方法获得网络连接输入,同时返回一个IutputStream对象实例。
getOutputStream方法连接的另一端将得到输入,同时返回一个OutputStream对象实例。 注意:其中getInputStream和getOutputStream方法均会产生一个IOException,它必须被捕获,因为它们返回的流对象,通常都会被另一个流对象使用。

一个Server-Client模型的程序的开发原理

服务器,使用ServerSocket监听指定的端口,端口可以随意指定(由于1024以下的端口通常属于保留端口,在一些操作系统中不可以随意使用,所以建议使用大于1024的端口),等待客户连接请求,客户连接后,会话产生;在完成会话后,关闭连接。
客户端,使用Socket对网络上某一个服务器的某一个端口发出连接请求,一旦连接成功,打开会话;会话完成后,关闭Socket。客户端不需要指定打开的端口,通常临时的、动态的分配一个1024以上的端口。

服务器端代码

public class ResponseThread implements Runnable {
  private static Logger logger = Logger.getLogger(ResponseThread.class);
 
  // 用于与客户端通信的Socket
  private Socket incomingSocket;

  /**
   * 构造函数,用以将incomingSocket传入
   * @param incomingSocket
   */
  public ResponseThread(Socket incomingSocket) {
    this.incomingSocket = incomingSocket;
  }

  public void run() {
    try {
      try {
        // 输入流
        InputStream inStream = incomingSocket.getInputStream();
       
        // 输出流
        OutputStream outStream = incomingSocket.getOutputStream();

        // 文本扫描器
        Scanner in = new Scanner(inStream);
       
        // 输出流打印器
        PrintWriter out = new PrintWriter(outStream,true);
       
        while (in.hasNextLine()) {
          String line = in.nextLine();
          logger.info("从客户端获得文字:"+line);
         
          String responseLine=line+" 门朝大海 三河合水万年流";
          out.println(responseLine);
          logger.info("向客户端送出文字:"+responseLine);
        }
      } finally {
        incomingSocket.close();
      }
    } catch (IOException ex) {
      ex.printStackTrace();
    }
  }
 public static void main(String[] args) {
    try {
      // 建立一个对2009端口进行监听的ServerSocket并开始监听
      ServerSocket socket=new ServerSocket(2009);
      logger.info("开始监听");
     
      while(true){
        // 产生阻塞,直到客户端连接过来才会往下执行
        logger.info("阻塞中,等待来自客户端的连接请求");
        Socket incomingSocket=socket.accept();       
       
        // 执行到这里客户端已经连接过来了,incomingSocket就是创建出来和远程客户端Socket进行通信的Socket
        logger.info("获得来自"+incomingSocket.getInetAddress()+"的请求."); 
       
        // 开辟一个线程,并把incomingSocket传进去,在这个线程中服务器和客户机开始通信
        ResponseThread responseThread=new ResponseThread(incomingSocket);
       
        // 启动线程
        Thread thread=new Thread(responseThread);
        thread.start();
      }
     
    } catch (IOException ex) {
      ex.printStackTrace();
    }
  }
}

客户端代码

/**
 * Socket客户端类
 * @author sitinspring
 *
 * @date 2008-2-26
 */
public class SocketClient{
  private static Logger logger = Logger.getLogger(ResponseThread.class);
 
  public static void main(String[] args){
    try {
      // 建立一个Socket,试图连接到位于127.0.0.1的主机的2009端口,如果服务器已经在监听则会接收到这个请求,accept方法产生一个和这边通信的Socket
      Socket socket=new Socket("127.0.0.1",2009);
      logger.info("客户端向服务器发起请求.");
     
      try {
        // 输入流
        InputStream inStream = socket.getInputStream();
       
        // 输出流
        OutputStream outStream = socket.getOutputStream();

        /**
         * 向服务器发送文字
         */
        // 输出流打印器
        PrintWriter out = new PrintWriter(outStream);
        out.println("地震高岗 一派溪山千古秀");
        out.flush();
       
        /**
         * 接收服务器发送过来的文字
         */
        // 文本扫描器
        Scanner in = new Scanner(inStream);
        while (in.hasNextLine()) {
          String line = in.nextLine();
          logger.info("客户端获得响应文字="+ line);
        }
      } finally {
        // 关闭Socket
        socket.close();
      }
    } catch (IOException ex) {
      ex.printStackTrace();
    }
  }
}

posted @ 2008-02-26 13:53 和风细雨 阅读(426) | 评论 (0)编辑 收藏

工序任务的线程模拟

     摘要: 工序任务指流水线式的工作场景,下一组工人的工件就是上一组工人的成品,一个活做完自己的部分就往下传,直到活完成为止。 为了编程方便简化了各个工序的工时,把它们都当成一样的了。 代码: package com.sitinspring.autotask.domain; import com.sitinspring.autotask.util.IdUtil; ...  阅读全文

posted @ 2008-02-25 22:40 和风细雨 阅读(261) | 评论 (0)编辑 收藏

常见JavaScript验证

/**
* Positive Integer Check
*/

function isPositiveInteger(str){
    
var regex=new RegExp("^[1-9]+\\d*$");
    
return regex.test(str);
}


/**
* Negative Integer Check
*/

function isNegativeInteger(str){
    
var regex=new RegExp("^-{1}\\d+$");
    
return regex.test(str);
}


/**
* Nonnegative Integer Check
*/

function isNonnegativeInteger(str){
    
var regex=new RegExp("^\\d+$");
    
return regex.test(str);
}


/**
* Integer Check
*/

function isInteger(str){
    
var regex=new RegExp("^-?\\d+$");
    
return regex.test(str);
}


/**
* Rational number Check
*/

function isRationalNumber(str){
    
var regex=new RegExp("^-?\\d+(\\.*)(\\d*)$");
    
return regex.test(str);
}


/**
* Letter Check
*/

function isLetter(str){
    
var regex=new RegExp("^[a-zA-Z]+$");
    
return regex.test(str);
}


/**
* Letter Integer Check
*/

function isLetterOrInteger(str){
    
var regex=new RegExp("^[a-zA-Z0-9]+$");
    
return regex.test(str);
}


/**
* Email Check
*/

function isEmail(str){
    
var regex=new RegExp("^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$");
    
return regex.test(str);
}


/**
* Character Check
*/

function isCharacter(str){
    
var regex=new RegExp("^[\u4E00-\u9FA5]+$");
    
return regex.test(str);
}


/**
* Currency Check
*/

function isCurrency(str){
    
return str.search("^\\d+(\\.\\d{0,2})*$")==0;
}

posted @ 2008-02-23 18:34 和风细雨 阅读(362) | 评论 (0)编辑 收藏

读写锁的使用

本文内容

何时该使用读写锁.
读写锁的写法.
理解读写锁和线程互斥的区别。

复习-同步化的概念

当一个方法或代码块被声明成synchronized,要执行此代码必须先取得一个对象实例或this的锁定,这个锁定要在synchronized修饰的方法或代码块执行完后才能释放掉(无论这段代码是怎样返回的,是正常运行还是异常运行)。每个对象只有一个锁定,如果有两个不同的线程试图同时调用同一对象的同步方法,最终只会有一个能运行此方法,另外一个要等待第一个线程释放掉锁定后才能运行此方法。

读写锁应用的场合

我们有时会遇到对同一个内存区域如数组或者链表进行多线程读写的情况,一般来说有以下几种处理方式: 1.不加任何限制,多见于读取写入都很快的情况,但有时也会出现问题. 2.对读写函数都加以同步互斥,这下问题是没了,但效率也下去了,比如说两个读取线程不是非要排队进入不可. 3.使用读写锁,安全和效率都得到了解决,特别合适读线程多于写线程的情况.也就是下面将要展现的模式.

读写锁的意图

读写锁的本意是分别对读写状态进行互斥区分,有互斥时才加锁,否则放行.互斥的情况有: 1.读写互斥. 2.写写互斥. 不互斥的情况是:读读,这种情况不该加以限制. 程序就是要让锁对象知道当前读写状态,再根据情况对读写的线程进行锁定和解锁。

读写线程都要操作的数据类

读写线程都要操作的数据是链表datas。
注意其中try...finally 的写法,它保证了加锁解锁过程是成对调用的

 

lpublic class DataLib {
     private List<String> datas;
 
     private ReadWriteLock lock;
 
     public DataLib() {
         datas = new ArrayList<String>();
         lock = new ReadWriteLock();
     }
 
     // 写入数据,这时不能读取
     public void writeData(List<String> newDatas) {
         try {
             lock.writeLock();
             Test.sleep(2);
             datas=newDatas;
         } finally {
             lock.writeUnlock();
         }
     }
 
     // 读取数据,这时不能写入
     public List<String> readData() {
         try {
             lock.readLock();
             Test.sleep(1);            
             return datas;
         } finally {
             lock.readUnlock();
         }
 
     }
 
 }



 

读写锁ReadWriteLock类

public class ReadWriteLock{
    // 读状态
    private boolean isRead;
   
    // 写状态
    private boolean isWrite;
   
    public synchronized void readLock(){
        // 有写入时读取线程停止
        while(isWrite){
            try{   
                System.out.println("有线程在进行写入,读取线程停止,进入等待状态");
                wait();
            }
            catch(InterruptedException ex){
                ex.printStackTrace();
            }
        }
       
        System.out.println("设定锁为读取状态");
        isRead=true;
    }
   
    public synchronized void readUnlock(){
        System.out.println("解除读取锁");
        isRead=false;
        notifyAll();
    }

     public synchronized void writeLock(){
        // 有读取时读取线程停止
        while(isRead){
            try{   
                System.out.println("有线程在进行读取,写入线程停止,进入等待状态");
                wait();
            }
            catch(InterruptedException ex){
                ex.printStackTrace();
            }
        }
       
        // 有写入时写入线程也一样要停止
        while(isWrite){
            try{   
                System.out.println("有线程在进行写入,写入线程停止,进入等待状态");
                wait();
            }
            catch(InterruptedException ex){
                ex.printStackTrace();
            }
        }
       
        System.out.println("设定锁为写入状态");
        isWrite=true;
    }
   
    public synchronized void writeUnlock(){
        System.out.println("解除写入锁");
        isWrite=false;
        notifyAll();
    }
}

写线程类Writer -它用于往DataLib类实例中的datas字段写数据

分析其中dataLib字段的用意。
注意并记住其中持续调用及使用随机数的方法。

 

lpublic class Writer implements Runnable{
     private DataLib dataLib;
     private static final Random random=new Random();
     private String[] mockDatas={"","","","","","","","","",""};    
     
     public Writer(DataLib dataLib,String[] mockDatas){
         this.dataLib=dataLib;
         this.mockDatas=mockDatas;
         
         Thread thread=new Thread(this);
         thread.start();
     }
     
     public void run(){
         while(true){
             Test.sleep(random.nextInt(3));
             
             int startIndex=random.nextInt(mockDatas.length);
             
             ArrayList<String> newDatas=new ArrayList<String>();
             for(int i=startIndex;i<mockDatas.length;i++){
                 newDatas.add(mockDatas[i]);
             }
             
             dataLib.writeData(newDatas);
         }
     }
 }

读线程类Reader  -它用于从DataLib类实例中的datas字段读取数据

分析其中dataLib字段的用意。
注意并记住其中持续调用及使用随机数的方法。

public class Reader implements Runnable{
    private DataLib dataLib;
    private static final Random random=new Random();
   
    public Reader(DataLib dataLib){
        this.dataLib=dataLib;
   
        Thread thread=new Thread(this);
        thread.start();
    }
   
    public void run(){
        while(true){
            Test.sleep(random.nextInt(2));           
            List<String> datas=dataLib.readData();
           
            System.out.print(">>取得数组为:");
            for(String data:datas){
                System.out.print(data+",");
            }
            System.out.print("\n");
        }
    }
}

将代码运行起来

右边的代码创建了两个写线程和三个读线程,它们都是对dataLib实例进行操作的。
五个线程都有一个dataLib字段,都提供了一个带参构造函数以给datas字段赋值,这就保证了五个线程操作的都是一个实例的同一字段,也就是同一片内存。
读写锁就是对这五个线程进行控制的。
当有一个读线程在操作时,其它的写线程无法进行操作,读线程可以正常操作,互不干扰。
当有一个写线程在操作时,其它的读线程无法进行操作。

 

 public class Test{
     public static void main(String[] args){
         DataLib dataLib=new DataLib();
         
         String[] mockDatas1={"","","","","","","","","",""};
         Writer writer1=new Writer(dataLib,mockDatas1);
         
         String[] mockDatas2={"","","","","","","","","","","",""};
         Writer writer2=new Writer(dataLib,mockDatas2);
         
         Reader reader1=new Reader(dataLib);
         Reader reader2=new Reader(dataLib);
         Reader reader3=new Reader(dataLib);
     }
     
     
     // 用于延时
     public static void sleep(int sleepSecond){
         try{
             Thread.sleep(sleepSecond*1000);
         }
         catch(Exception ex){
             ex.printStackTrace();
         }
     }
 }

小结

当多个线程试图对同一内容进行读写操作时适合使用读写锁。
请理解并记住ReadWriteLock类读写锁的写法.
读写锁相对于线程互斥的优势在于高效,它不会对两个读线程进行盲目的互斥处理,当读线程数量多于写线程尤其如此,当全是写线程时两者等效。

posted @ 2008-02-22 14:28 和风细雨 阅读(2589) | 评论 (2)编辑 收藏

线程锁的使用

本文内容

何时该使用线程锁.
线程锁的写法.
以线程锁的例子来理解线程的调度。

使用线程锁的场合

程序中经常采用多线程处理,这可以充分利用系统资源,缩短程序响应时间,改善用户体验;如果程序中只使用单线程,那么程序的速度和响应无疑会大打折扣。
但是,程序采用了多线程后,你就必须认真考虑线程调度的问题,如果调度不当,要么造成程序出错,要么造成荒谬的结果。

一个讽刺僵化体制的笑话

前苏联某官员去视察植树造林的情况,现场他看到一个人在远处挖坑,其后不远另一个人在把刚挖出的坑逐个填上,官员很费解于是询问陪同人员,当地管理人员说“负责种树的人今天病了”。
上面这个笑话如果发生在程序中就是线程调度的问题,种树这个任务有三个线程:挖坑线程,种树线程和填坑线程,后面的线程必须等前一个线程完成才能进行,而不是按时间顺序来进行,否则一旦一个线程出错就会出现上面荒谬的结果。

用线程锁来处理两个线程先后执行的情况

在程序中,和种树一样,很多任务也必须以确定的先后秩序执行,对于两个线程必须以先后秩序执行的情况,我们可以用线程锁来处理。
线程锁的大致思想是:如果线程A和线程B会执行实例的两个函数a和b,如果A必须在B之前运行,那么可以在B进入b函数时让B进入wait set,直到A执行完a函数再把B从wait set中激活。这样就保证了B必定在A之后运行,无论在之前它们的时间先后顺序是怎样的。

线程锁的代码

如右,SwingComponentLock的实例就是一个线程锁,lock函数用于锁定线程,当完成状态isCompleted为false时进入的线程会进入SwingComponentLock的实例的wait set,已完成则不会;要激活SwingComponentLock的实例的wait set中等待的线程需要执行unlock函数。

public class SwingComponentLock {
  // 是否初始化完毕
  boolean isCompleted = false;

  /**
   * 锁定线程
   */
  public synchronized void lock() {
    while (!isCompleted) {
      try {
        wait();
      } catch (Exception e) {
        e.printStackTrace();
        logger.error(e.getMessage());
      }
    }
  }

  /**
   * 解锁线程
   *
   */
  public synchronized void unlock() {
    isCompleted = true;
    notifyAll();
  }
}

线程锁的使用

public class TreeViewPanel extends BasePanel {
  // 表空间和表树
  private JTree tree;

  // 这个是防树还未初始化好就被刷新用的
  private SwingComponentLock treeLock;

  protected void setupComponents() {
    // 初始化锁
    treeLock = new SwingComponentLock();

    // 创建根节点
    DefaultMutableTreeNode root = new DefaultMutableTreeNode("DB");
    tree = new JTree(root);

    // 设置布局并装入树
    setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
    add(new JScrollPane(tree));

    // 设置树节点的图标
    setupTreeNodeIcons();

    // 解除对树的锁定
    treeLock.unlock();
  }

 /**
   * 刷新树视图
   *
   * @param schemas
   */
  public synchronized void refreshTree(List<SchemaTable> schemas) {
    treeLock.lock();

    DefaultTreeModel model = (DefaultTreeModel) tree.getModel();

    DefaultMutableTreeNode root = (DefaultMutableTreeNode) model.getRoot();
    root.removeAllChildren();

    for (SchemaTable schemaTable : schemas) {
      DefaultMutableTreeNode schemaNode = new DefaultMutableTreeNode(
          schemaTable.getSchema());

      for (String table : schemaTable.getTables()) {
        schemaNode.add(new DefaultMutableTreeNode(table));
      }

      root.add(schemaNode);
    }

    model.reload();
  }

讲解

上页中,setupComponents函数是Swing主线程执行的,而refreshTree函数是另外的线程执行(初始化时程序开辟一个线程执行,其后执行由用户操作决定)。 refreshTree函数必须要等setupComponents函数把tree初始化完毕后才能执行,而tree初始化的时间较长,可能在初始化的过程中执行refreshTree的线程就进入了,这就会造成问题。
程序使用了一个SwingComponentLock来解决这个问题,setupComponents一开始就创建SwingComponentLock的实例treeLock,然后执行refreshTree的线程以来就会进入treeLock的wait set,变成等待状态,不会往下执行,这是不管tree是否初始化完毕都不会出错;而setupComponents执行到底部会激活treeLock的wait set中等待的线程,这时再执行refreshTree剩下的代码就不会有任何问题,因为setupComponents执行完毕tree已经初始化好了。
让线程等待和激活线程的代码都在SwingComponentLock类中,这样的封装对复用很有好处,如果其它复杂组件如table也要依此办理直接创建SwingComponentLock类的实例就可以了。如果把wait和notifyAll写在TreeViewPanel类中就不会这样方便了。

总结

线程锁用于必须以固定顺序执行的多个线程的调度。
线程锁的思想是先锁定后序线程,然后让线序线程完成任务再接触对后序线程的锁定。
线程锁的写法和使用一定要理解记忆下来。

 

posted @ 2008-02-22 14:20 和风细雨 阅读(4538) | 评论 (0)编辑 收藏

线程调度实例

本文内容

本文将从一个现实例子来实际说明线程调度方法wait,notify和notifyAll的使用。

工厂中任务的领受和执行

某工厂执行这样的机制:当生产任务下达到车间时会统一放在一个地方,由工人们来取活。
工人取活如此执行:一个工人手头只能有一个活,如果没做完不能做下一个,如果做完了则可以到公共的地方去取一个;如果没有活可取则闲着直到来活为止。

本文就是讲述怎样使用线程的调度三方法wait,notify和notifyAll来实现这一现实活动的。

任务类Task-它用来实现一个”活”,其中关键的成员是完成需消耗的工时数manHour和已经完成的工时数completed

public class Task implements Comparable {
  private String id;

  private String name;

  // 完成需消耗的工时数
  private int manHour;

  // 已经完成的工时数
  private int completed;

  // 优先级
  private int priority;

  // 接受任务者
  private Worker worker;

  public Task(String name, int manHour) {
    this(name, manHour, 0);
  }

  public Task(String name, int manHour, int priority) {
    id = IdUtil.generateId();
    this.name = name;
    this.manHour = manHour;
    this.priority = priority;
    this.completed = 0;
  }
 // 任务是否完成
  public boolean isCompleted() {
    return completed >= manHour;
  }

  // 添加完成度
  public void addCompleted(int n) {
    completed += n;

    if (isCompleted()) {
      completed = manHour;

      if (worker != null) {
        System.out.println("任务"+this+"处理完毕!");
      }
    }
  }

  public int compareTo(Object obj) {
    Task another = (Task) obj;
    return (another.priority) - this.priority;
  }

  public String toString() {
    return "任务名:" + name + " 工人名:" + worker.getName() + " 完成度:" + completed
        * 100 / manHour + "%";
  }
 
  public String getCompletedRatio() {
    return " 完成度:" + completed * 100 / manHour + "%";
  }
...getter/setter方法省略..

}

任务库类TaskLibrary

这个类对应现实中的取活的地方,每个活Task放在这个类的成员tasks中,有两个方法来添加单个任务和多个任务,还有一个fetchTask方法来供工人领受任务.

public class TaskLibrary {
  private List<Task> tasks;

  public TaskLibrary() {
    tasks = new LinkedList<Task>();
  }

  // 添加单个任务
  public synchronized void addTask(Task task) {
    tasks.add(task);
    notifyAll();
  }

  // 添加多个任务
  public synchronized void addTasks(List<Task> moreTasks) {
    tasks.addAll(moreTasks);
    notifyAll();
  }

  public int getTaskSize() {
    return tasks.size();
  }

  // 工人领受任务
  public synchronized Task fetchTask(Worker worker) {
    while (tasks.size() == 0) {
      try {
        System.out.println("任务告罄");
        System.out.println("工人:" + worker.getName() + "进入闲置状态");
        wait();
      } catch (InterruptedException ex1) {
        ex1.printStackTrace();
      }
    }

    Task task = tasks.get(0);
    System.out.println("工人:" + worker.getName() + "取得任务:" + task.getName());
    tasks.remove(task);
    return task;
  }
}

工人类Worker

public class Worker implements Runnable {
  private String id;

  private String name;

  private Task currTask;

  private TaskLibrary taskLibrary;

  // 工作速度
  private int speed;

  public Worker(String name, int speed, TaskLibrary taskLibrary) {
    id = IdUtil.generateId();
    this.currTask = null;
    this.name = name;
    this.speed = speed;
    this.taskLibrary = taskLibrary;

    doWork();
  }

  // 开始干活
  public void doWork() {
    Thread thread = new Thread(this);
    thread.start();
  }
 // 真正干活
  public void run() {
    while (true) {
      if (currTask == null || currTask.isCompleted()) {
        currTask = taskLibrary.fetchTask(this);
        currTask.setWorker(this);
      }

      try {
        Thread.sleep(1000);
        System.out.println("正在处理的任务" + currTask + " 完成度"
            + currTask.getCompletedRatio() + "个.");
        currTask.addCompleted(speed);
      } catch (Exception ex) {
        ex.printStackTrace();
      }
    }
  }
。。。
}

运行代码

  TaskLibrary taskLibrary=new TaskLibrary();  
  
  taskLibrary.addTask(new Task("培训",8));
  
  List<Task> moreTasks=new LinkedList<Task>();
  moreTasks.add(new Task("锻造",4));
  moreTasks.add(new Task("打磨",5));
  moreTasks.add(new Task("车阶梯",6));
  moreTasks.add(new Task("热处理",7));
  moreTasks.add(new Task("去皮",8));
  moreTasks.add(new Task("镗孔",60));
  moreTasks.add(new Task("钻孔",10));
  moreTasks.add(new Task("拉槽",11));
  
  taskLibrary.addTasks(moreTasks); 
  
  Worker worker01=new Worker("王进喜",1,taskLibrary);
  Worker worker02=new Worker("时传详",2,taskLibrary);
  Worker worker03=new Worker("张秉贵",3,taskLibrary);
  Worker worker04=new Worker("徐虎",3,taskLibrary);
  
  taskLibrary.addTask(new Task("铸造",8));
  sleep(1);
  taskLibrary.addTask(new Task("校验",9));
  sleep(2);
  taskLibrary.addTask(new Task("内务",10));
  sleep(3);

运行情况分析

一开始先初始化任务库,然后进行给任务库中添加任务,初始化工人实例时会把任务库实例的地址传入,工人实例初始化完毕后会调用doWork函数去任务库取任务开始做,这会进入TaskLibrary类的fetchTask函数,这时如果没有则会让工人等待,有则把第一个任务给他,然后周而复始进行这一过程.

运行结果示例

 工人:王进喜取得任务:培训 工人:时传详取得任务:锻造 工人:张秉贵取得任务:打磨 工人:徐虎取得任务:车阶梯 正在处理的任务任务名:培训 工人名:王进喜 完成度:0% 完成度 完成度:0%个. 正在处理的任务任务名:锻造 工人名:时传详 完成度:0% 完成度 完成度:0%个. 正在处理的任务任务名:打磨 工人名:张秉贵 完成度:0% 完成度 完成度:0%个. 正在处理的任务任务名:车阶梯 工人名:徐虎 完成度:0% 完成度 完成度:0%个. 正在处理的任务任务名:培训 工人名:王进喜 完成度:12% 完成度 完成度:12%个. 正在处理的任务任务名:锻造 工人名:时传详 完成度:50% 完成度 完成度:50%个. 任务任务名:锻造 工人名:时传详 完成度:100%处理完毕! 工人:时传详取得任务:热处理 正在处理的任务任务名:打磨 工人名:张秉贵 完成度:60% 完成度 完成度:60%个. 任务任务名:打磨 工人名:张秉贵 完成度:100%处理完毕! 正在处理的任务任务名:车阶梯 工人名:徐虎 完成度:50% 完成度 完成度:50%个. 任务任务名:车阶梯 工人名:徐虎 完成度:100%处理完毕! 工人:徐虎取得任务:去皮 工人:张秉贵取得任务:镗孔 正在处理的任务任务名:培训 工人名:王进喜 完成度:25% 完成度 完成度:25%个. 正在处理的任务任务名:热处理 工人名:时传详 完成度:0% 完成度 完成度:0%个. 正在处理的任务任务名:去皮 工人名:徐虎 完成度:0% 完成度 完成度:0%个. 正在处理的任务任务名:镗孔 工人名:张秉贵 完成度:0% 完成度 完成度:0%个. 正在处理的任务任务名:培训 工人名:王进喜 完成度:37% 完成度 完成度:37%个. 正在处理的任务任务名:热处理 工人名:时传详 完成度:28% 完成度 完成度:28%个. 正在处理的任务任务名:去皮 工人名:徐虎 完成度:37% 完成度 完成度:37%个. 正在处理的任务任务名:镗孔 工人名:张秉贵 完成度:5% 完成度 完成度:5%个. 正在处理的任务任务名:培训 工人名:王进喜 完成度:50% 完成度 完成度:50%个. 正在处理的任务任务名:热处理 工人名:时传详 完成度:57% 完成度 完成度:57%个. 正在处理的任务任务名:去皮 工人名:徐虎 完成度:75% 完成度 完成度:75%个. 任务任务名:去皮 工人名:徐虎 完成度:100%处理完毕! 工人:徐虎取得任务:钻孔

 

posted @ 2008-02-22 14:17 和风细雨 阅读(309) | 评论 (0)编辑 收藏

线程的协调

本文内容

Java进行线程调度的方法.
如何让线程进入等待.
wait set概念的理解.
如何激活等待状态的线程.

进行线程协调的方法

从上一章《线程的互斥》中我们知道,当方法体或代码块被synchronized方法修饰时,有一个线程在执行这部分代码时,其它线程无法同时执行这部分代码。但如果我们想进行更高效的处理如主动调整线程而不是让线程被动等待和盲目竞争时该怎么处理呢?
在Java中,有三个方法可以对线程进行主动高效的控制,它们是wait,notify和notifyAll。
wait是主动让线程等待的方法,而notify和notifyAll则是激活等待中的线程的方法。他们都是Object类的方法,也就是说任何对象都可以使用这三个方法。

Wait Set-线程的休息室

在学习使用wait,notify和notifyAll这三个方法之前,我们可以先理解一下Wait Set的概念,它是一个在某实例执行wait方法时,停止操作的线程的集合,类似于线程的休息室,每个实例都拥有这样一个休息室。
Wait方法是用来把线程请入这个休息室的,而notify和notifyAll这两个方法是用来将进入休息室休息的线程激活的。
wait Set是一个虚拟的概念,它既不是实例的字段,也不是可以获取在实例上wait中线程的列表的方法.它只是用来帮助我们理解线程的等待和激活的。

Wait方法,将线程放入Wait Set

使用Wait方法时,线程即进入Wait set。如线程在执行代码时遇到这样的语句:xxObj.wait();则目前的线程会暂时停止运行,进入实例xxObj的wait Set.
当线程进入Wait Set时,即释放了对该实例的锁定.也就是说,即使是被synchronized修饰的方法和代码块,当第一个线程进入实例的wait Set等待后,其它线程就可以再进入这部分代码了.
wait()前如果不写某对象表示其前面的对象是this, wait()=this.wait();

notify方法-从wait set中激活一个线程

使用notify方法时,程序会从处于等待的实例的休息室中激活一个线程.代码如下:
xxObj.notify();程序将从xxObj的wait set中挑出一个激活.这个线程即准备退出wait set.当当前线程释放对xxObj的锁定后,这个线程即获取这个锁定,从上次的停止点-执行wait的地方开始运行。
线程必须有调用的实例的锁定,才能执行notify方法.
Wait set中处于等待状态的线程有多个,具体激活那一个依环境和系统而变,事先无法辩明.我们大致可理解为随机挑选了一个.

notifyAll方法-激活wait set中所有等待的线程

当执行notifyAll方法时,实例中所有处于等待状态的线程都会被激活.代码为:
xxObj.notifyAll();执行此代码后xxObj的wait set中所有处于等待中的线程都会被激活.但具体那个线程获取执行wait方法时释放的锁定要靠竞争,最终只能有一个线程获得锁定,其它的线程只能继续回去等待.
notifyAll与notify的差异在于notifyAll激活所有wait set中等待的线程,而notify只激活其中的一个.

该使用notify还是notifyAll

建议:
1) 选择notifyAll比notify稳当安全,如果notify处理得不好,程序会有隐患.
2) 选择notifyAll比notify可靠,是大多数程序的首选.
3) 当你对代码已经很清楚,对线程理解也很透彻时,你可以选择使用notify,发挥其处理速度高的优势.

当前线程必须持有欲调用实例的锁定,才能调用wait,notify和notifyAll这三个方法.

如果代码是xxObj.notifyAll(或wait, notify)(),则这行代码必须处于synchronized(xxObj){…}代码块中.
如果代码是this.notifyAll(或wait, notify)(),则这行代码必须处于synchronized修饰的方法中.
前面说过, notifyAll和notify会激活线程去获得进入wait时释放的锁定,但这个锁定要等刚才执行notifyAll或notify方法的线程释放这个锁定才能获取.

总结

1) wait,notify和notifyAll都是java.lang.Object的方法,它们用来对线程进行调度.
2) obj.wait()是把执行这句的线程放入obj的wait set中.
3) obj.notify()是从wait set中唤醒一个线程,这个线程在当前线程释放对obj的锁定后即获取这个锁定.
4) obj.notifyAll()是唤醒所有在obj的wait set中的线程,这批线程在当前线程释放obj的锁定后去竞争这个锁定,最终只有一个能获得,其它的又重新返回wait set等待下一次激活.
5) 执行这个wait,notify和notifyAll这三个方法前,当前线程(即执行obj.wait(), obj.notify()和obj.notifyAll()代码的线程)必须持有obj的锁定.

posted @ 2008-02-22 14:13 和风细雨 阅读(316) | 评论 (0)编辑 收藏

仅列出标题
共10页: 上一页 1 2 3 4 5 6 7 8 9 下一页 Last