servlet会话跟踪 - 会话属性的线程安全性



美好的一天,全部。

我有一个servlet,其中我在其中声明,启动并分配给会话属性。

我需要servlet是线程安全的,以便同一计算机上的多个浏览器窗口可以运行servlet,并且您可以在每个窗口中执行计算,但是各个线程中的操作不会互相影响,也就是说,帐户余额变量并未在线程之间共享,因此任何时间都会导致其不一致的状态。

以下是我的代码:

// Import servlet and HTTP functionality packages.
import javax.servlet.*;
import javax.servlet.http.*;
// Import packages to: handle user inputs and outputs, enable usage of decimal formatting of numbers.
import java.io.*;
import java.util.*;
import java.text.DecimalFormat;
public class SessionBank extends HttpServlet    // Define concrete class and extend HTTP functionality.
{
    public void init() throws ServletException  // Initialise variables at start of the servlet. Possible exception.
    {
    }
    // The method to output the initial HTML form to the screen, addressing also possible exceptions.
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
    {
        // Declare and initiate local variables.
        double balance = 0;                     // The current balance variable, un-formatted number, initiate to zero.
        String formattedBal = "";               // The current balance variable, formatted to show as currency amount, initially blank.
        // Set balance and formatted balance as session attributes.
        request.getSession().setAttribute("balance", balance);
        request.getSession().setAttribute("formattedBal", formattedBal);
        showBalance(request, response); // Call custom-defined method to display initial page.
    }
    // Method to respond to user's button inputs - output relevant HTML back to the screen. Possible exceptions.
    public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
    {
        HttpSession session = request.getSession(true);     // Establish session object.
        response.setContentType("text/html");       // Set response type to text/HTML.
        response.setHeader("Expires", "Tues, 01 Jan 1980 00:00:00 GMT");    // Set past date to forbid cache.
        // If user clicks "Deposit" button:
        if(request.getParameter("depButton") != null && request.getParameter("depButton").equals("Deposit"))
        {
            if(verifyAmount(request))   // If entered amount passes verification.
            {
                addToBalance(request, session);         // Add the amount to current balance.
                redisplayCurrentPage(response);     // Redisplay initial page.
            }
            else        // If entered amount does not pass verification.
            {
                showErrorPage(response);    // Display error page.
            }
        }
        // If user clicks "Withdraw" button:
        if(request.getParameter("withdrButton") != null && request.getParameter("withdrButton").equals("Withdraw"))
        {
            if(verifyAmount(request))   // If entered amount passes verification.
            {
                subtractFromBalance(request, session);  // Subtract the amount from current balance.
                redisplayCurrentPage(response);     // Redisplay initial page.
            }
            else        // If entered amount does not pass verification.
            {
                showErrorPage(response);    // Display error page.
            }
        }
        // If user clicks "Balance" button:
        if(request.getParameter("balButton") != null && request.getParameter("balButton").equals("Balance"))
        {
            showBalance(request, response);     // Display current formatted balance on page.
        }
    }
    private boolean verifyAmount(HttpServletRequest request)    // Method to verify entered amount, based on textbook criteria.
    {
        boolean amountValid = false;    // Declare and initiate a validity variable.
        // If entered amount is not blank and is greater than zero, return validity as true. Else, return false.
        if(request.getParameter("amount") != "" && Double.parseDouble(request.getParameter("amount")) > 0)
            amountValid = true;
        else
            amountValid = false;
        return amountValid;     // Return validity variable.
    }
    // Method to add amount to balance, addressing possible exception.
    private void addToBalance(HttpServletRequest request, HttpSession session) throws IOException
    {
        double userAmount = Double.parseDouble(request.getParameter("amount")); // Declare and assign entered amount variable.
        // Down-cast session attribute object to String, then parse into double type variable.
        double balOld = Double.parseDouble(String.valueOf(session.getAttribute("balance")));
        double balNew = balOld + userAmount;        // Add the amount to current balance and save the value.
        session.setAttribute("balance", balNew);    // Assign new balance to the session attribute.
    }
    // Method to subtract from balance. Possible exception.
    private void subtractFromBalance(HttpServletRequest request, HttpSession session) throws IOException
    {
        double userAmount = Double.parseDouble(request.getParameter("amount")); // Declare and assign entered amount value.
        // Down-cast session attribute object to String, then parse into a double type variable.
        double balOld = Double.parseDouble(String.valueOf(session.getAttribute("balance")));
        double balNew = balOld - userAmount;        // Subtract the amount from the balance and save the value.
        session.setAttribute("balance", balNew);    // Assign new balance value to the session attribute.
    }
    private void showBalance(HttpServletRequest request, HttpServletResponse response) throws IOException   // Method to output balance HTML page. Possible exception.
    {
        PrintWriter out = response.getWriter();     // Establish HTML writer object.
        formatBalance(request); // Format current balance for displaying.
        out.println("<html>");
            out.println("<hr>");        // Horizontal line.
            out.println("<title>Online Bank ATM Simulator</title>");        // Title to show on browser title bar.
            out.println("<h1 align = "center">Bank ATM Simulation</h1>"); // Page heading, centered on page.
            out.println("<body onLoad = "amount.focus()">");              // Set focus to the text-field.
                out.println("<form method = "POST" action = "../servlet/SessionBank">");    // Form method and submission address.
                    out.println("<center>");        // Tag to center the following output on page.
                    out.println("Amount: ");
                    out.println("<input type = "text" name = "amount" id = "amount" size = "20"><br><br>"); // Amount text field.
                    out.println("Balance: ");
                    out.println(request.getSession().getAttribute("formattedBal") + "<br><br>");    // Current formatted balance shown.
                    out.println("<button name = "balButton" value = "Balance">Balance</button>");   // "Balance" button.
                    out.println("&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;");  // Spacers.
                    out.println("<button name = "depButton" value = "Deposit">Deposit</button>");   // "Deposit" button.
                    out.println("&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;");  // Spacers.
                    out.println("<button name = "withdrButton" value = "Withdraw">Withdraw</button>");  // "Withdraw" button.
                    out.println("</center>");       // Tag to end centering of output on page.
                out.println("</form>");     // End of form.
            out.println("</body>");
            out.println("<br>");
            out.println("<hr>");        // Horizontal line.
        out.println("</html>");
    }
    // Method to redisplay form after deposit/withdrawal.
    private void redisplayCurrentPage(HttpServletResponse response) throws IOException
    {
        PrintWriter out = response.getWriter();     // Establish HTML writer object.
        out.println("<html>");
            out.println("<hr>");        // Horizontal line.
            out.println("<title>Online Bank ATM Simulator</title>");        // Title to show on browser title bar.
            out.println("<h1 align = "center">Bank ATM Simulation</h1>"); // Page heading, centered on page.
            out.println("<body onLoad = "amount.focus()">");              // Set focus to the text-field.
                out.println("<form method = "POST" action = "../servlet/SessionBank">");    // Form method and submission address.
                    out.println("<center>");        // Tag to center the following output on page.
                    out.println("Amount: ");
                    out.println("<input type = "text" name = "amount" id = "amount" size = "20"><br><br>"); // Amount text field.
                    out.println("Balance: ");
                    out.println("<br><br>");    // No formatted balance value shown.
                    out.println("<button name = "balButton" value = "Balance">Balance</button>");   // "Balance" button.
                    out.println("&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;");  // Spacers.
                    out.println("<button name = "depButton" value = "Deposit">Deposit</button>");   // "Deposit" button.
                    out.println("&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;");  // Spacers.
                    out.println("<button name = "withdrButton" value = "Withdraw">Withdraw</button>");  // "Withdraw" button.
                    out.println("</center>");       // Tag to end centering of output on page.
                out.println("</form>");     // End of form.
            out.println("</body>");
            out.println("<br>");
            out.println("<hr>");        // Horizontal line.
        out.println("</html>");
    }
    private void formatBalance(HttpServletRequest request)  // Method to format the current balance number to a currency amount.
    {
        DecimalFormat dollars = new DecimalFormat("$###,###.###");  // Construct new decimal format.
        // Down-cast session attribute to String, parse to double, then format using the above decimal format and save value.
        String formattedBal = dollars.format(Double.parseDouble(String.valueOf(request.getSession().getAttribute("balance"))));
        request.getSession().setAttribute("formattedBal", formattedBal);    // Assign new formatted balance to session attribute.
    }
    // Method to output error HTML page, if entered amount does not pass verification. Possible exception.
    private void showErrorPage(HttpServletResponse response) throws IOException
    {
        PrintWriter out = response.getWriter();     // Establish HTML writer object.
        out.println("<html>");
            out.println("<head>");
                out.println("<title>Amount Input Error</title>");   // Title to show in browser title bar.
            out.println("</head>");
            out.println("<body>");
                out.println("<h1>Error processing the input.</h1><br>");    // Heading text.
                out.println("Please ensure your input:<br><br>");
                out.println("- Is not blank.<br>");
                out.println("- Is strictly a number.<br>");
                out.println("- Is a positive, non-zero amount.");
            out.println("</body>");
        out.println("</html>");
    }
    public void destroy()   // Method to terminate the servlet.
    {
    }
}

现在,发生的事情是,当使用多个线程(浏览器窗口运行servlet)时,一个线程覆盖的平衡成为另一个线程读取的平衡。当然是不正确的。我需要每个同时线程才能拥有自己的此变量的副本以保持一致的结果。

据我所知,与将余额存储为类(实例)变量不同,将其存储为本地,并且分配给会话属性是线程安全。但是,如果是这样,为什么这些线程彼此更新的变量?什么是错误的编码?

非常感谢!

浏览器可能与此浏览器有点不一致,但最终您看到的是,单个浏览器正在与您的服务器与单个session cookie(通常命名为jsessionId)进行交互。从服务器和您的代码的角度来看,它不知道由于单个cookie而有多个选项卡或浏览器。

在这种情况下,一个线程从会话中访问值,更新并存储它。另一个线程 - 使用不同的浏览器选项卡 - 执行同一件事。但是,会话中只有一个会话和一个变量,因此最终在浏览器选项卡之间共享。

解决这可能是跨平台方式的挑战。一种方法是在浏览器中让一些JavaScript在启动时生成唯一的数字或代码。因此,例如,您可以在HTML代码中创建onload方法,并使用简单的Math.random() JavaScript调用。然后,对于每个请求,您将此号码传递给后端。在后端您创建一个映射JavaScript生成的数字到另一个参数地图并存储在会话中的地图。

您的代码使您在一个地方拥有显示和后端逻辑时更难实现(通常是显示屏的JSP,而是后端的servlet),但可以做到。

最新更新