带有静态字符串的线程安全 Servlet
Thread Safe Servlet With a Static String
我在 http://ahoj.io/nodejs-and-websocket-simple-chat-tutorial 上查看了带有 Node JS 和套接字 IO 的聊天服务器示例。在该示例中,服务器使用了一个简单的历史变量来保存聊天历史数据。由于 Node Js 是单线程,所以一切正常。 (如果你对 node js 不感兴趣,你可以忽略上面的 node JS 示例:)我将在下面的 java 中解释它)
考虑下面的 servlet,它从请求中获取 message
字符串并将其添加到字符串中。此代码可以是聊天服务器的示例。它从请求中获取用户消息并将其全部转换为 history
字符串,其他客户端可以读取它。
public class ChatServlet implements Servlet {
private static String history = "";
public void service(ServletRequest request, ServletResponse response)
history = history.concat(request.getParameter("message"));
}
}
理论上,此代码不是线程安全的,因为它使用了 global static
变量 (How do servlets work? Instantiation, sessions, shared variables and multithreading)。
但是,我已经用 jMeter 测试了上面的代码,有很多并发请求,历史字符串总是存储所有消息(所以没有客户端消息丢失或覆盖),没有出错!
我没有使用线程,所以我想知道我是否在这里遗漏了什么!上面的代码线程安全吗,可信吗
它不是线程安全的。非线程安全的代码不能保证会失败,但也不能保证能正常工作。
不,不是。线程安全错误可能很难触发 - 也许您的程序会错过十亿分之一的消息,或者它可能永远不会错过消息巧合。但是,如果它是线程安全的,则可以保证它永远不会发生。
您可以简单地使用 synchronized
块来确保一次只有一个线程访问 history
,如下所示:
synchronized(ChatServlet.class) {
history = history.concat(request.getParameter("message"));
}
这意味着:锁定ChatServlet.class
,将消息添加到历史记录,然后解锁ChatServlet.class
。
你永远不能让两个线程同时锁定同一个对象 - 如果他们尝试,其中一个将继续,其余的将等待第一个解锁对象(然后另一个 一个会继续,其余的会等待它解锁对象,以此类推。
还要确保在 synchronized(ChatServlet.class)
块中仅 read history
- 否则,不能保证阅读线程会看到最新更新。
正如其他人所证实的,这确实不是线程安全的,因为它不可信。 JVM 实现中的一些怪癖 可能 使其成为一个可用的 servlet,但不能保证它可以在另一个 JVM 甚至另一个时间工作。
为了增加提议的实现的多样性,这里有一个带有 AtomicReference 的实现:
AtomicReference<String> history = new AtomicReference<>("");
public void service(ServletRequest request, ServletResponse response)
history.updateAndGet(h -> h.concat("123"));
}
我在 http://ahoj.io/nodejs-and-websocket-simple-chat-tutorial 上查看了带有 Node JS 和套接字 IO 的聊天服务器示例。在该示例中,服务器使用了一个简单的历史变量来保存聊天历史数据。由于 Node Js 是单线程,所以一切正常。 (如果你对 node js 不感兴趣,你可以忽略上面的 node JS 示例:)我将在下面的 java 中解释它)
考虑下面的 servlet,它从请求中获取 message
字符串并将其添加到字符串中。此代码可以是聊天服务器的示例。它从请求中获取用户消息并将其全部转换为 history
字符串,其他客户端可以读取它。
public class ChatServlet implements Servlet {
private static String history = "";
public void service(ServletRequest request, ServletResponse response)
history = history.concat(request.getParameter("message"));
}
}
理论上,此代码不是线程安全的,因为它使用了 global static
变量 (How do servlets work? Instantiation, sessions, shared variables and multithreading)。
但是,我已经用 jMeter 测试了上面的代码,有很多并发请求,历史字符串总是存储所有消息(所以没有客户端消息丢失或覆盖),没有出错! 我没有使用线程,所以我想知道我是否在这里遗漏了什么!上面的代码线程安全吗,可信吗
它不是线程安全的。非线程安全的代码不能保证会失败,但也不能保证能正常工作。
不,不是。线程安全错误可能很难触发 - 也许您的程序会错过十亿分之一的消息,或者它可能永远不会错过消息巧合。但是,如果它是线程安全的,则可以保证它永远不会发生。
您可以简单地使用 synchronized
块来确保一次只有一个线程访问 history
,如下所示:
synchronized(ChatServlet.class) {
history = history.concat(request.getParameter("message"));
}
这意味着:锁定ChatServlet.class
,将消息添加到历史记录,然后解锁ChatServlet.class
。
你永远不能让两个线程同时锁定同一个对象 - 如果他们尝试,其中一个将继续,其余的将等待第一个解锁对象(然后另一个 一个会继续,其余的会等待它解锁对象,以此类推。
还要确保在 synchronized(ChatServlet.class)
块中仅 read history
- 否则,不能保证阅读线程会看到最新更新。
正如其他人所证实的,这确实不是线程安全的,因为它不可信。 JVM 实现中的一些怪癖 可能 使其成为一个可用的 servlet,但不能保证它可以在另一个 JVM 甚至另一个时间工作。
为了增加提议的实现的多样性,这里有一个带有 AtomicReference 的实现:
AtomicReference<String> history = new AtomicReference<>("");
public void service(ServletRequest request, ServletResponse response)
history.updateAndGet(h -> h.concat("123"));
}