我在网站上看到了一个servlet示例代码,这段代码被认为是线程不安全的,但是我不知道为什么它是线程不安全的,当我使用这个代码时会发生什么。这段代码是一个servlet代码,用于保存每位访客的姓名。
public class UnsafeGuestbookServlet extends HttpServlet {
private Set visitorSet = new HashSet();
protected void doGet(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse) throws ServletException, IOException {
String visitorName = httpServletRequest.getParameter("NAME");
if (visitorName != null)
visitorSet.add(visitorName);
}
}
Peoper说如果我改变这样的代码:
private Set visitorSet = Collections.synchronizedSet(new HashSet());
线程不安全问题将得到解决。
我知道如果这段代码是线程不安全的,它必须是由visitorSet引起的,它是一个共享数据结构。因为String visitorName是一个局部变量,每个线程都会在自己的调用堆栈中复制,对吧?
答案 0 :(得分:3)
第一个示例中的HashSet
是Servlet的一个字段,它只会初始化一次(因为Servlet也会被初始化一次)。每个请求都将由您的Servlet的这个实例处理,因此您的示例中提到的线程不安全性存在问题。
HashSet
并非设计用作共享资源,因此可能存在诸如尝试添加值而另一个线程仍在迭代Set的问题。这就是您需要某种同步Set的原因,如您自己的解决方案中所述。
答案 1 :(得分:2)
简单,UnsafeGuestbookServlet
初始化一次,doGet
或doPost
方法由多个并行线程调用。
请注意HashSet
的实现未同步。如果多个线程同时访问哈希集,并且至少有一个线程修改了该集,则必须在外部进行同步。
因此要同步我们使用的HashSet
Collections.synchronizedSet()
。现在HashSet被同步,并且一次一个地控制对该特定HashSet的访问以进行写入。
如果你想要更多关于线程的信息,线程安全方法为何以及如何等等,请更好地阅读这个http://www.tutorialspoint.com/java/java_thread_synchronization.htm。顺便说一句,简单来说,当两个或两个以上的人被允许同时获得同一个东西时,就会出现竞争状况或不稳定状况。如果你的代码允许它发生它的线程不安全:-)
还有一点,这一切都取决于你在代码中如何处理它。如果您认为,没有2个线程可以覆盖或删除数据结构中的相同值,那么您不想介意线程安全性。请注意为什么线程安全很重要,在哪些情况下最重要。
答案 2 :(得分:1)
由于String visitorName是一个局部变量,因此每个线程都会生成 在自己的调用堆栈中的副本,对吗?
指针的副本是,但String类可以重用对象(因为它们不可修改)以节省内存,所以理论上你可以有两个线程试图在共享集中存储同一个对象。即使对象不同,Set也会使用equals
来确定对象是否已经添加,因此您的代码仍会暴露给不安全的检查和添加操作,迭代器会在添加新的时通过Set元件...
答案 3 :(得分:1)
为了使这个servlet线程安全,我会做两件事。首先,引用集合final
:
public class UnsafeGuestbookServlet extends HttpServlet {
private final Set visitorSet = new HashSet();
在对象构建时初始化的最终引用,并安全发布。没有它,doGet
方法可能看不到构造集,尽管 已正确初始化。
其次,要么在Collections.synchronizedSet()
中进行换行设置,这实际上不是最佳解决方案,因为它的迭代器易于ConcurrentModificationException
,并且所有方法都需要对该集进行独占锁定,或者使用为并发使用:
private final Set visitorSet = Collections.newSetFromMap(new ConcurrentHashMap());
这个集合将使用ConcurrentHashMap
keySet
使用的条带化模式,它提供了线程安全的迭代器和更好的“并发”。
答案 4 :(得分:0)
我知道如果这段代码是线程不安全的,它必须由visitorSet引起,它是一个共享的数据结构。
是的,它是您的servlet的成员。 servlet容器创建了一个servlet实例,但是它可以在不同的线程中同时处理几个http请求,这些线程都在同一个doGet()
对象上调用UnsafeGuestbookServlet
方法 - 这意味着可能有多个线程在操作visitorSet
HashSet。 HashSet
不是线程安全的。
由于String visitorName是一个局部变量,每个线程都会在自己的调用堆栈中创建一个副本,>对吗?
是