美好的一天,所有。
我有一个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(" "); // Spacers.
out.println("<button name = \"depButton\" value = \"Deposit\">Deposit</button>"); // "Deposit" button.
out.println(" "); // 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(" "); // Spacers.
out.println("<button name = \"depButton\" value = \"Deposit\">Deposit</button>"); // "Deposit" button.
out.println(" "); // 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的浏览器窗口)时,一个线程覆盖的余额成为另一个线程读取的余额。这当然是不正确的。我需要每个并发线程都有自己的变量副本以获得一致的结果。
据我所知,与将余额存储为类(实例)变量不同,将其存储为本地并分配给会话属性是线程安全的。但如果是这样,为什么线程会更新彼此的变量?什么编码不正确?
非常感谢!
答案 0 :(得分:0)
浏览器可能与此有点不一致,但最终您看到的是单个浏览器与您的服务器通过单个会话cookie(通常名为JSESSIONID)进行交互。从服务器和代码的角度来看,由于单个cookie,它不知道有多个选项卡或浏览器。
在这种情况下,一个线程访问会话中的值,更新并存储它。另一个线程 - 为不同的浏览器选项卡提供服务 - 做同样的事情。但是会话中只有一个会话和一个变量,因此它最终在浏览器选项卡之间共享。
以跨平台的方式解决这个问题可能是一个挑战。一种方法是在浏览器中使用一些JavaScript在启动时生成唯一的数字或代码。因此,举例来说,您可以在HTML代码中创建onload
方法,并使用简单的Math.random()
JavaScript调用。然后,对于每个请求,您将此数字传递给后端。在后端,您可以创建一个Map,将JavaScript生成的数字映射到另一个参数Map并将其存储在会话中。
您的代码使实现起来有点困难,因为您在一个地方同时拥有显示和后端逻辑(通常它将是显示器的JSP和后端的servlet)但它可能是完成。