Servlet参数持久化时可能遇到的多线程问题以及解决之道

本文介绍了一种使用文件存储实现Servlet状态跨加载持久化的方案。通过覆盖init()和destroy()方法,确保了计数器能够在服务器重启后仍保留之前的状态。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  Up until now, the counter examples have demonstrated how servlet state persists between accesses. This solves only part of the problem. Every time the server is shut down or the servlet is reloaded, the count begins again. What we really want is persistence across loads—a counter that doesn't have to start over.

The init( ) and destroy( ) pair can accomplish this. Example 3-5 further extends the InitCounter example, giving the servlet the ability to save its state in destroy( ) and load the state again in init( ). To keep things simple, assume this servlet is not registered and is accessed only as http://server:port/servlet/InitDestroyCounter. If it were registered under different names, it would have to save a separate state for each name.

Example 3-5. A Fully Persistent Counter
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class InitDestroyCounter extends HttpServlet {

int count;

public void init() throws ServletException {
// Try to load the initial count from our saved persistent state
FileReader fileReader = null;
BufferedReader bufferedReader = null;
try {
fileReader = new FileReader("InitDestroyCounter.initial");
bufferedReader = new BufferedReader(fileReader);
String initial = bufferedReader.readLine();
count = Integer.parseInt(initial);
return;
}
catch (FileNotFoundException ignored) { } // no saved state
catch (IOException ignored) { } // problem during read
catch (NumberFormatException ignored) { } // corrupt saved state
finally {
// Make sure to close the file
try {
if (bufferedReader != null) {
bufferedReader.close();
}
}
catch (IOException ignored) { }
}

// No luck with the saved state, check for an init parameter
String initial = getInitParameter("initial");
try {
count = Integer.parseInt(initial);
return;
}
catch (NumberFormatException ignored) { } // null or non-integer value

// Default to an initial count of "0"
count = 0;
}

public void doGet(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
res.setContentType("text/plain");
PrintWriter out = res.getWriter();
count++;
out.println("Since the beginning, this servlet has been accessed " +
count + " times.");
}

public void destroy() {
super.destroy(); // entirely optional
saveState();
}

public void saveState() {
// Try to save the accumulated count
FileWriter fileWriter = null;
PrintWriter printWriter = null;
try {
fileWriter = new FileWriter("InitDestroyCounter.initial");
printWriter = new PrintWriter(fileWriter);
printWriter.println(count);
return;
}
catch (IOException e) { // problem during write
// Log the exception. See Chapter 5.
}
finally {
// Make sure to close the file
if (printWriter != null) {
printWriter.close();
}
}
}
}

Each time this servlet is unloaded, it saves its state in a file named InitDestroyCounter.initial. In the absence of a supplied path, the file is saved in the server process's current directory, usually the startup directory. Ways to specify alternate locations are discussed in Chapter 4.[4] This file contains a single integer, saved as a string, that represents the latest count.

[4] The location of the current user directory can be found with System.getProperty("user.dir").

Each time the servlet is loaded, it tries to read the saved count from the file. If, for some reason, the read fails (as it does the first time the servlet runs because the file doesn't yet exist), the servlet checks if an init parameter specifies the starting count. If that too fails, it starts fresh with zero. You can never be too careful in init( ) methods.

Servlets can save their state in many different ways. Some may use a custom file format, as was done here. Others may save their state as serialized Java objects or put it into a database. Some may even perform journaling, a technique common to databases and tape backups, where the servlet's full state is saved infrequently while a journal file stores incremental updates as things change. Which method a servlet should use depends on the situation. In any case, you should always be watchful that the state being saved isn't undergoing any change in the background.

Right now you're probably asking yourself, "What happens if the server crashes?" It's a good question. The answer is that the destroy( ) method will not be called.[5]

[5] Unless you're so unlucky that your server crashes while in the destroy( ) method. In that case, you may be left with a partially written state file—garbage written on top of your previous state. To be perfectly safe, a servlet should save its state to a temporary file and then copy that file on top of the official state file in one command.

This doesn't cause a problem for destroy( ) methods that only have to free resources; a rebooted server does that job just as well (if not better). But it does cause a problem for a servlet that needs to save its state in its destroy( ) method. For these servlets, the only guaranteed solution is to save state more often. A servlet may choose to save its state after handling each request, such as a "chess server" servlet should do, so that even if the server is restarted, the game can resume with the latest board position. Other servlets may need to save state only after some important value has changed—a "shopping cart" servlet needs to save its state only when a customer adds or removes an item from her cart. Last, for some servlets, it's fine to lose a bit of the recent state changes. These servlets can save state after some set number of requests. For example, in our InitDestroyCounter example, it should be satisfactory to save state every 10 accesses. To implement this, we can add the following line at the end of doGet( ):

if (count % 10 == 0) saveState();

Does this addition make you cringe? It should. Think about synchronization issues. We've opened up the possibility for data loss if saveState( ) is executed by two threads at the same time and the possibility for saveState( ) to not be called at all if count is incremented by several threads in a row before the check. Note that this possibility did not exist when saveState( ) was called only from the destroy( ) method: the destroy( ) method is called just once per servlet instance. Now that saveState( ) is called in the doGet( ) method, however, we need to reconsider. If by some chance this servlet is accessed so frequently that it has more than 10 concurrently executing threads, it's likely that two servlets (10 requests apart) will be in saveState( ) at the same time. This may result in a corrupted datafile. It's also possible the two threads will increment count before either thread notices it was time to call saveState( ). The fix is easy: move the count check into the synchronized block where count is incremented:

int local_count;
synchronized(this) {
local_count = ++count;
if (count % 10 == 0) saveState();
}
out.println("Since loading, this servlet has been accessed " +
local_count + " times.");

The moral of the story is harder: always be vigilant to protect servlet code from multithreaded access problems.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值