为什么这个servlet代码示例是线程不安全的?

时间:2014-05-06 08:40:13

标签: java servlets thread-safety

我在网站上看到了一个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是一个局部变量,每个线程都会在自己的调用堆栈中复制,对吧?

5 个答案:

答案 0 :(得分:3)

第一个示例中的HashSet是Servlet的一个字段,它只会初始化一次(因为Servlet也会被初始化一次)。每个请求都将由您的Servlet的这个实例处理,因此您的示例中提到的线程不安全性存在问题。

HashSet并非设计用作共享资源,因此可能存在诸如尝试添加值而另一个线程仍在迭代Set的问题。这就是您需要某种同步Set的原因,如您自己的解决方案中所述。

答案 1 :(得分:2)

简单,UnsafeGuestbookServlet初始化一次,doGetdoPost方法由多个并行线程调用。

请注意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是一个局部变量,每个线程都会在自己的调用堆栈中创建一个副本,>对吗?

相关问题