Java Web 高级编程 - 第三章 创建第一个Servlet

 本章内容

 

创建Servlet类

java.lang.Object

  javax.servlet.GenericServlet

    javax.servlet.http.HttpServlet

目前Java 7 EE支持的唯一Servlet协议就是HTTP。

HttpServletRequestHttpServletResponse

import javax.servlet.http.HttpServlet;

public class HelloServlet extends HttpServlet
{
}

通过以上的方式,该Servlet已经可以接受任何HTTP请求,并返回一个405 Method Not Allowed错误。

任何未重写HTTP Servlet方法都将返回一个HTTP状态405作为响应。

使用初始化方法和销毁方法

initdestroy

当Web容器启动Servlet时,将会调用Servlet的init方法。有时但不总是,在部署应用程序时也会调用该方法。

稍后再Web容器关闭Servlet时,它将调用Servlet的destroy方法。

@Override
public void init() throws ServletException
{
    System.out.println("Servlet " + this.getServletName() + " has started.");
}

@Override
public void destroy()
{
    System.out.println("Servlet " + this.getServletName() + " has stopped.");
}

init方法在Servlet构造完成之后调用,但在响应第一个请求之前。与构造器不同,在调用init方法时,Servlet中的所有属性都已经设置完成,并提供对ServletConfigServletContext对象的访问。

如果将Servlet配置为在Web应用程序部署和启动时自动启动,那么它的init方法也将会被调用。否则init方法将在第一次请求访问它接收的Serv时调用。

同样地,destroy在Servlet不再接收请求之后立即调用。这通常发生在Web应用程序被停止或卸载,或者Web容器关闭时。

因为它将在卸载或关闭时立即调用,所以不需要等待垃圾回收在清理资源之前触发终止化器。

 

配置可部署的Servlet

向描述符中添加Servlet

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                             http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">

    <display-name>Hello World Application</display-name>
</web-app>

上面的示例中,标记的代码表示应用程序在应用服务器中显示的名字。Tomcat管理器页面中将显示出<display-name>标签中配置的名字。

标签<web-app>中的version特性表示应用程序使用的是哪一个Servlet API版本。

添加Servlet标签:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                             http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">

    <display-name>Hello World Application</display-name>

    <servlet>
        <servlet-name>helloServlet</servlet-name>
        <servlet-class>com.wrox.HelloServlet</servlet-class>
    </servlet>

</web-app>

可以对Servlet配置做简单的调整,使它在Web应用程序启动之后立即启动:

<servlet>
    <servlet-name>helloServlet</servlet-name>
    <servlet-class>com.wrox.HelloServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>

如果多个Servlet配置都包含了该标签,它们将按照标签内的值得大小顺序启动,上面代码中使用的1表示第一个启动,数字越大启动越晚。

如果两个或多个Servlet的<load-on-startup>配置相同,那么将按照它们在描述符文件中的出现顺序启动,其他Servlet仍然按照大小顺序依次启动。

将Servlet映射到URL

在告诉应用服务器如何启动Servlet之后,接着需要告诉该Servlet应该对那些请求URL作出响应。

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                             http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">

    <display-name>Hello World Application</display-name>

    <servlet>
        <servlet-name>helloServlet</servlet-name>
        <servlet-class>com.wrox.HelloServlet</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>helloServlet</servlet-name>
        <url-pattern>/greeting</url-pattern>
    </servlet-mapping>

</web-app>

使用了以上配置后,所有访问应用程序相对URL/greeting的请求都将有helloServlet处理。

<servlet>和<servlet-mapping>标签内的<servlet-name>标签应该一致。

也可以将多个URL映射到相同的Servlet:

<servlet-mapping>
    <servlet-name>helloServlet</servlet-name>
    <url-pattern>/greeting</url-pattern>
    <url-pattern>/salutation</url-pattern>
    <url-pattern>/wazzup</url-pattern>
</servlet-mapping>

在以上配置中,三个URL都将被映射到相同的Servlet:helloServlet。

那么为什么要先创建一个Servlet实例,然后再通过名字将URL映射到该Servlet呢?为什么不直接将URL映射到Servlet类呢?

如果在一个在线商店应用程序中,有两个不同仓库的Servlet。它们可能有着相同的逻辑,但是链接到不同的数据库。那么可以通过以下的方式实现:

<servlet>
    <servlet-name>oddsStore</servlet-name>
    <servlet-class>com.wrox.StroeServlet</servlet-class>
</servlet>

<servlet>
    <servlet-name>endsStore</servlet-name>
    <servlet-class>com.wrox.StroeServlet</servlet-class>
</servlet>

<servlet-mapping>
    <servlet-name>oddsStore</servlet-name>
    <url-pattern>/odds</url-pattern>
</servlet-mapping>

<servlet-mapping>
    <servlet-name>endssStore</servlet-name>
    <url-pattern>/ends</url-pattern>
</servlet-mapping>

现在创建了两个实例,它们都使用相同的Servlet类,但是名字不同,并且被映射到了不同的URL上。那么这两个示例如何知道自己使用的是哪个仓库呢?

在Servlet代码的任何位置调用getServletName即可区分出这两个实例。

运行和调试Servlet

 

了解doGet、doPost和其他方法

使用HttpServletRequest

HttpServletRequest接口是对ServletRequest的扩展,它将提供关于收到请求的额外的与HTTP协议相关的信息。

它指定了多个可以获得HTTP请求的详细信息的方法。它也允许设置请求特性。

1.获取请求参数

无论参数是作为查询参数(get)还是post变量传入,都可以调用请求对象中与参数相关的方法来获取它们。

getParameter将返回参数的单个值。如果参数有多个值,getParameter将返回第一个值。

getParameterValues将返回参数的值得数组。如果参数只有一个值,该方法将返回只有一个元素的数组。

getParameterMap将返回一个包含了所有参数名值对的Map<String,String[]>。

getParameterNames将返回所有可用参数的名字的枚举。

2.确定与请求内容相关的信息

getContentType返回请求的MIME内容类型。

getContentLengthgetContentLengthLong都返回了请求正文的长度,以字节为单位。getContentLengthLong用于那些内容长度超过2GB的请求。

当请求中包含字符类型的内容时,getCharacterEncoding返回请求内容的字符编码。

3.读取请求的内容

getInputStream返回ServletInputStream

getReader返回BufferedReader

getInputStreamgetReader都可以用于读取请求的内容。

使用哪种方法取决于上下文-所需读取的请求的内容类型。

如果请求是基于字符编码的,那么使用BufferedReader通常是最简单的方式,因为它可以帮助你轻松地读取字符数据。

如果请求数据是二进制格式的,那么就必须使用ServletInputStream,这样才可以访问字节格式的请求内容。

永远不要在同一个请求上同时使用getReadergetInputStream。在调用其中一个方法后,再调用另一个将会触发IllegalStateException异常。

任何时候在使用含有post变量的请求时,最好只使用参数方法,不要使用getReadergetInputStream方法。

4.获取请求特有的数据,例如URL、URI和头

getRequestURL:返回客户端用于创建请求的完整URL。

Reconstructs the URL the client used to make the request. The returned URL contains a protocol, server name, port number, and server path, but it does not include query string parameters.

getRequestURI:与getRequestURL稍有不同,它只返回URL中的服务器路径部分。

First line of HTTP request Returned Value
POST /some/path.html HTTP/1.1  /some/path.html
GET http://foo.bar/a.html HTTP/1.0  /a.html
HEAD /xyz?a=b HTTP/1.1  /xyz

 

 

 

getServletPath:类似于getRequestURI,它将返回更少的URL。如果请求访问的时/hello-world/greeting?foo=world,Servlet映射为/greeting。getServletPath将只返回用于匹配Servlet映射的URL部分:/greeting。

getHeader(String name):返回指定名称的头数据。

getHeaderNames:返回请求中所有头数据的名字和枚举。

getIntHeader(String name):如果有某个特定的头的值一直是数字,那么可以调用该方法返回一个数字。NumberFormatException - If the header value can't be converted to an int。

getDateHeader(String name):对于可以表示有效时间戳的头数据,该方法将返回一个Unix时间戳(毫秒)。IllegalArgumentException - If the header value can't be converted to a date

5.会话和Cookies

 

使用HttpServletResponse

HttpServletResponse接口继承了ServletResponse,所以HttpServletResponse也提供了对响应中与HTTP协议相关属性的访问。

可以使用响应对象完成设置响应头、编写响应正文、重定向请求、设置HTTP状态码以及将Cookies返回到客户端等任务。

1.编写响应正文

getOutputStream将返回一个ServletOutputStream

getWriter将返回一个PrintWriter

使用PrintWriter可以轻松地向响应中输出编码字符串和字符。但是如果要返回二进制数据,就必须使用getOutputStream

同样地,如果两种方法被同一个响应对象同时使用,也会触发IllegalStateException

2.设置头和其他响应属性

setHeadersetIntHeadersetDateHeaderaddDateHeaderaddHeaderaddIntHeader

getHeadergetHeaderNamesgetHeaderscontainsHeader

getStatussetStatussendErrorsendRedirect

 

使用参数和接受表单提交 

doGet

private static final String DEFAULT_USER = "Guest";

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
  //检测请求中是否包含user参数,如果未包含,就使用DEFAULT_USER常量。
  String user = request.getParameter("user");
  if(user == null)
    user = HelloServlet.DEFAULT_USER;
  //将响应的内容设置为text/html,并将字符编码设置为UTF-8
  response.setContentType("text/html");
  response.setCharacterEncoding("UTF-8");
  //从响应中获得一个PrintWriter,并输出一个兼容与HTML5的文档
  PrintWriter writer = response.getWriter();
  writer.append("<!DOCTYPE html>\r\n")
    .append("<html>\r\n")
    .append("<head>\r\n")
    .append("<title>Hello User Application</title>\r\n")
    .append("</head>\r\n")
    .append("<body>\r\n")
    .append("Hello, ").append(user).append("!<br/><br/>\r\n")
    .append("<form action=\"greeting\" method=\"POST\">\r\n")
    .append("Enter your name:<br/>\r\n")
    .append("<input type=\"text\" name=\"user\"/><br/>\r\n")
    .append("<input type=\"submit\" value=\"Submit\"/>\r\n")
    .append("</form>\r\n")
    .append("</body>\r\n")
    .append("</html>\r\n");
}

 

doPost

@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
  throws ServletException, IOException
{
  //如果表单的方法设置为POST时,简单的调用doGet实现对请求的处理
  this.doGet(request, response);
}

 

最后一处需要注意的是Servlet声明上的注解:

@WebServlet(
        name = "helloServlet",
        urlPatterns = {"/greeting", "/salutation", "/wazzup"},
        loadOnStartup = 1
)
public class HelloServlet extends HttpServlet
{
}

 

注解可以用来代替之前web.xml文件中编写的描述符:

 

<servlet>
  <servlet-name>helloServlet</servlet-name>
  <servlet-class>com.wrox.HelloServlet</servlet-class>
</servlet>

<servlet-mapping>
  <servlet-name>helloServlet</servlet-name>
    <url-pattern>/greeting</url-pattern>
    <url-pattern>/salutation</url-pattern>
    <url-pattern>/wazzup</url-pattern>
</servlet-mapping>

 

多值参数的例子:

Servlet中的doGet方法将输出一个含有5个复选框的简单表单。用户选择任意数目的复选框并单击Submit(将由doPost方法处理)。

该方法将获得所有的水果值并在页面中使用无序列表显示出来。

@WebServlet(
        name = "multiValueParameterServlet",
        urlPatterns = {"/checkboxes"}
)
public class MultiValueParameterServlet extends HttpServlet
{
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException
    {
        response.setContentType("text/html");
        response.setCharacterEncoding("UTF-8");

        PrintWriter writer = response.getWriter();
        writer.append("<!DOCTYPE html>\r\n")
              .append("<html>\r\n")
              .append("    <head>\r\n")
              .append("        <title>Hello User Application</title>\r\n")
              .append("    </head>\r\n")
              .append("    <body>\r\n")
              .append("        <form action=\"checkboxes\" method=\"POST\">\r\n")
              .append("Select the fruits you like to eat:<br/>\r\n")
              .append("<input type=\"checkbox\" name=\"fruit\" value=\"Banana\"/>")
              .append(" Banana<br/>\r\n")
              .append("<input type=\"checkbox\" name=\"fruit\" value=\"Apple\"/>")
              .append(" Apple<br/>\r\n")
              .append("<input type=\"checkbox\" name=\"fruit\" value=\"Orange\"/>")
              .append(" Orange<br/>\r\n")
              .append("<input type=\"checkbox\" name=\"fruit\" value=\"Guava\"/>")
              .append(" Guava<br/>\r\n")
              .append("<input type=\"checkbox\" name=\"fruit\" value=\"Kiwi\"/>")
              .append(" Kiwi<br/>\r\n")
              .append("<input type=\"submit\" value=\"Submit\"/>\r\n")
              .append("        </form>")
              .append("    </body>\r\n")
              .append("</html>\r\n");
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException
    {
        String[] fruits = request.getParameterValues("fruit");

        response.setContentType("text/html");
        response.setCharacterEncoding("UTF-8");

        PrintWriter writer = response.getWriter();
        writer.append("<!DOCTYPE html>\r\n")
              .append("<html>\r\n")
              .append("    <head>\r\n")
              .append("        <title>Hello User Application</title>\r\n")
              .append("    </head>\r\n")
              .append("    <body>\r\n")
              .append("        <h2>Your Selections</h2>\r\n");

        if(fruits == null)
            writer.append("        You did not select any fruits.\r\n");
        else
        {
            writer.append("        <ul>\r\n");
            for(String fruit : fruits)
            {
                writer.append("        <li>").append(fruit).append("</li>\r\n");
            }
            writer.append("        </ul>\r\n");
        }

        writer.append("    </body>\r\n")
              .append("</html>\r\n");
    }
}
MultiValueParameterServlet

 

使用初始化参数配置应用程序

1.使用上下文初始化参数

在web.xml文件中使用<context-param>标签声明上下文初始化参数。

<context-param>
    <param-name>settingOne</param-name>
    <param-value>foo</param-value>
</context-param>
<context-param>
    <param-name>settingTwo</param-name>
    <param-value>bar</param-value>
</context-param> 

以上代码创建了两个上下文初始化参数:值为foo的settingOne和值为bar的settingTwo。在Servlet代码的任何地方都可以轻松地获得和使用这些参数。

@WebServlet(
        name = "contextParameterServlet",
        urlPatterns = {"/contextParameters"}
)
public class ContextParameterServlet extends HttpServlet
{
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException
    {
        ServletContext c = this.getServletContext();
        PrintWriter writer = response.getWriter();

        writer.append("settingOne: ").append(c.getInitParameter("settingOne"))
              .append(", settingTwo: ").append(c.getInitParameter("settingTwo"));
    }
}

应用程序中的所有Servlet都将共享这些初始化参数,在所有的Servlet中它们的值也都是相同的。

有时需要使某个设置只作用于某一个Servlet,那么就需要用到Servlet初始化参数。

2.使用Servlet初始化参数

以下代码基本功能与前面的ContextParameterServlet一致,但是它不是从ServletContext对象中获取初始化参数,而是从ServletConfig中。

public class ServletParameterServlet extends HttpServlet
{
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException
    {
        ServletConfig c = this.getServletConfig();
        PrintWriter writer = response.getWriter();

        writer.append("database: ").append(c.getInitParameter("database"))
              .append(", server: ").append(c.getInitParameter("server"));
    }
}

 

当然,只有Servlet代码是不够的。将下面的XML添加到部署描述符中,它将声明和映射Servlet,并完成一些额外的工作。

<servlet>
  <servlet-name>servletParameterServlet</servlet-name>
  <servlet-class>com.wrox.ServletParameterServlet</servlet-class>
  <init-param>
    <param-name>database</param-name>
    <param-value>CustomerSupport</param-value>
  </init-param>
  <init-param>
    <param-name>server</param-name>
    <param-value>10.0.12.5</param-value>
  </init-param>
</servlet>

 

标签<init-param>如同Servlet上下文的<context-param>一样,创建了专属于该Servlet的初始化参数。

如何使用注解的方式完成Servlet初始化参数的设置呢?

@WebServlet(
        name = "servletParameterServlet",
        urlPatterns = {"/servletParameters"},
        initParams = {
                @WebInitParam(name = "database", value = "CustomerSupport"),
                @WebInitParam(name - "server", value = "10.0.12.5")
        }
)
public class ServletParameterServlet extends HttpServlet {
}

 

不过,这样做有一个缺点,那就是在修改了Servlet初始化参数之后必须重新编译应用程序。

 

通过表单上传文件

Commons FileUpload

1.配置Servlet支持文件上传

@WebServlet(
        name = "ticketServlet",
        urlPatterns = {"/tickets"},
        loadOnStartup = 1
)
@MultipartConfig(
        fileSizeThreshold = 5_242_880, //5MB
        maxFileSize = 20_971_520L, //20MB
        maxRequestSize = 41_943_040L //40MB
)
public class TicketServlet extends HttpServlet
{
}

 

Annotation Type MultipartConfig

注解@MultipartConfig告诉Web容器这个Servlet提供文件上传支持。

location:该特性告诉浏览器应该在哪里存储临时文件,不过大多数情况下,都可以忽略该字段,让应用服务器使用它的默认临时目录即可。

fileSizeThreshold:告诉Web容器文件必须达到多大才能写入到临时目录中。

在本例中,小于5MB的上传文件将保存在内存中,知道请求完成,然后由垃圾回收器回收。对于超过5MB的文件,容器将把该文件保存在location指向的目录中,在请求完成之后,容器将从磁盘中删除该文件。

maxFileSize:设置指定了最大上传文件的大小。本例中为20MB。

maxRequestSize:则会禁止大小超过40MB的请求,不论它上传了多少个文件。

也可以使用部署描述符取代@WebServlet和@MultipartConfig。在<servlet>标签中,可以添加一个<multipart-config>标签,在该标签中则可以使用<location>;<file-size-threshold>;<max-file-size>;<max-request-size>标签。

doGet代码实现:

private volatile int TICKET_ID_SEQUENCE = 1;

private Map<Integer, Ticket> ticketDatabase = new LinkedHashMap<>();

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
  throws ServletException, IOException
{
  String action = request.getParameter("action");
  if(action == null)
    action = "list";
  switch(action)
  {
    case "create":
      this.showTicketForm(response);
      break;
    case "view":
      this.viewTicket(request, response);
      break;
    case "download":
      this.downloadAttachment(request, response);
      break;
    case "list":
    default:
      this.listTickets(response);
      break;
  }
}

 

doPost代码实现:

@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
  throws ServletException, IOException
{
  String action = request.getParameter("action");
  if(action == null)
    action = "list";
  switch(action)
  {
    case "create":
      this.createTicket(request, response);
      break;
    case "list":
    default:
      response.sendRedirect("tickets");
      break;
  }
}

 

doPost方法中一个新的变化是:使用了重定向方法。如果客户端执行了一个不含action参数或者含有无效action参数的POST请求,浏览器页面将会被重定向至显示票据的页面。

2.接收文件上传 

最后查看方法createTicket,以及他所使用的processAttachment方法。

方法processAttachment将从multipart请求中获得InputStream,并将它复制到Attachment对象中。

它使用了Servlet 3.1中新增的getSubmittedFileName方法,用于识别文件在上传之前的原始名称。

方法createTicket将使用该方法和其他请求参数填充Ticket对象,并将该对象添加到数据库中。

private void createTicket(HttpServletRequest request,
                          HttpServletResponse response)
  throws ServletException, IOException
{
  Ticket ticket = new Ticket();
  ticket.setCustomerName(request.getParameter("customerName"));
  ticket.setSubject(request.getParameter("subject"));
  ticket.setBody(request.getParameter("body"));

  Part filePart = request.getPart("file1");
  if(filePart != null && filePart.getSize() > 0)
  {
    Attachment attachment = this.processAttachment(filePart);
    if(attachment != null)
      ticket.addAttachment(attachment);
  }

  int id;
  synchronized(this)
  {
    id = this.TICKET_ID_SEQUENCE++;
    this.ticketDatabase.put(id, ticket);
  }

  response.sendRedirect("tickets?action=view&ticketId=" + id);
}

private Attachment processAttachment(Part filePart)
  throws IOException
{
  InputStream inputStream = filePart.getInputStream();
  ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

  int read;
  final byte[] bytes = new byte[1024];

  while((read = inputStream.read(bytes)) != -1)
  {
    outputStream.write(bytes, 0, read);
  }

  Attachment attachment = new Attachment();
  attachment.setName(filePart.getSubmittedFileName());
  attachment.setContents(outputStream.toByteArray());

  return attachment;
}

 

 

编写多线程安全的应用程序

理解请求、线程和方法执行

Web容器通常会包含某种类型的线程池,它们被成为连接池或执行池。

当容器收到请求时,它将在池中寻找可用的线程。如果找不到可用的线程,并且线程池已经达到了最大线程数,那么该请求将会被放入一个队列中 - 先进先出 - 等待获得可用的线程。一旦出现可用线程,浏览器将从线程池中借出线程,并将请求传递给线程,由线程进行处理。此时,该线程对于其他请求是不可用的。在普通请求中,线程和请求的关联将会贯穿请求的整个生命周期。只要请求正在由应用程序代码处理,该线程就只属于这个请求。只有在请求完成,相应内容已经发送到客户端后,该线程才会变成可用状态并返回到线程池中,用于处理下一个请求。

创建和销毁线程会产生许多开销,这可能会降低应用程序的运行速度,所以采用由可复用线程组成的线程池可以减少这种开销,提高性能。

线程池有一个可以配置的大小属性,通过它可以决定一次可以创建多少连接。

Tomcat中的最大线程池大小默认为200,可以增加或减少这个数目。必须明确这一点,因为它意味着在最糟糕的情况下,200个不同的线程可能同时在同一个实例上执行着相同的方法。因此,必须考虑代码的运行方式,避免代码在多个线程中并发执行的情况下出现异常行为。

保护共享资源

在编写多线程应用程序是最典型的问题就是对共享资源的访问。方法中创建的对象和变量在方法执行过程中都是安全的 - 其他线程无法访问它们。

不过,Servlet中的静态变量和实例变量都可以被多个线程同时访问。对这些共享资源进行同步是非常重要的,只有这样才能避免损坏资源的内容,也能比曼可能由应用程序引起的错误。

有多种技术可以用于保护共享资源:

private volatile int TICKET_ID_SEQUENCE = 1;

 

synchronized(this)
{
    id = this.TICKET_ID_SEQUENCE++;
    this.ticketDatabase.put(id, ticket);
}

Java 理论与实践: 正确使用 Volatile 变量

Java™ 语言包含两种内在的同步机制:同步块(或方法)和 volatile 变量。这两种机制的提出都是为了实现代码线程的安全性。其中 Volatile 变量的同步性较差(但有时它更简单并且开销更低),而且其使用也更容易出错。与锁相比,Volatile 变量是一种非常简单但同时又非常脆弱的同步机制,它在某些情况下将提供优于锁的性能和伸缩性。如果严格遵循 volatile 的使用条件 —— 即变量真正独立于其他变量和自己以前的值 —— 在某些情况下可以使用 volatile 代替 synchronized 来简化代码。然而,使用 volatile 的代码往往比使用锁的代码更加容易出错。

 

Chapter 3
Updated on 9/4/14.
104.17 KB Click to Download

 

 

 

 

摘录自:[美]Nicholas S.Williams著,王肖峰译 Java Web高级编程 [M]、清华大学出版社,2015、35-62、

 

posted @ 2016-11-13 18:25  guqiangjs  阅读(218)  评论(0)    收藏  举报