2014-05-06 39 views
0

我看到一个来自网站的servlet示例代码,这段代码据说是线程不安全的,但是我不能说出为什么它是线程不安全的,当我使用这段代码时会发生什么。这段代码是一个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引起,它是一个共享数据结构。由于字符串visitorName是一个局部变量,每个线程都会在它自己的调用堆栈中创建一个副本,对?

回答

2

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

请注意,HashSet的实施不同步。如果多个线程同时访问散列集,并且至少有一个线程修改了该集,则它必须在外部同步。

因此,要同步HashSet我们使用Collections.synchronizedSet()。现在HashSet是同步的,并且访问这个特定的HashSet进行写入操作一次只能控制一个。

如果想了解更多关于线程的信息,线程安全的方法为什么以及如何等等,最好读一下这个http://www.tutorialspoint.com/java/java_thread_synchronization.htm。顺便说一句,当它允许两个或两个以上的人同时获得同一件东西时,就会出现竞争条件或不稳定的情况。如果你的代码允许这种情况发生,它的线程不安全:-)

还有一点,这一切都取决于你如何在代码中处理它。如果您认为没有2个线程可以覆盖或删除数据结构中的相同值,那么您不需要介意线程安全性。请注意为什么线程安全很重要,在哪些情况下最重要。

+0

为什么对不安全的HashSet进行简单的添加操作可能会导致一些问题?请给我一个例子吗? – wuchang

+0

那么现在你问的是线程和同步的问题。我正在编辑我的答案,在此包含更多信息 – Kris

3

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

A HashSet不被设计为用作共享资源,因此可能存在像尝试添加值而另一个线程仍在迭代Set等问题。这就是为什么你需要在你自己的解决方案中提到的某种同步Set。

0

我知道如果这段代码是线程不安全的,它必须由visitorSet引起,它是一个共享数据结构。

是的,它是你的servlet的成员。 servlet容器创建一个servlet实例,但它可能在不同的线程中同时处理多个http请求,这些线程在同一个UnsafeGuestbookServlet对象上调用您的doGet()方法 - 这意味着可能有多个线程操纵HashSet。 A HashSet不是线程安全的。

由于String visitorName是一个局部变量,每个线程都会在自己的调用堆栈中创建一个副本,>对吗?

是的。

+0

什么使我困惑的是,会发生什么问题?我正在做的只是一个插入操作,其他注意事项.. – wuchang

+0

@Vico_Wu任何事情都可能发生。竞争条件导致数据不一致,例如你会得到重复的条目,条目会消失。或者对于HashMap,它可能会进入[infinte循环](http://stackoverflow.com/questions/13695832/explain-the-timing-causing-hashmap-put-to-execute-an-infinite-loop) – nos

1

由于字符串visitorName是一个局部变量,每个线程将使 副本在自己的调用堆栈中,对吗?

指针是复印件,但String类可以重用的对象(因为它们是不能修改)为了节省内存,所以理论上你可以有两个线程尝试存储同一对象的共享设置。即使对象是不同的,Set将使用equals来知道对象是否已经添加,所以你的代码仍然暴露在不安全的检查和添加操作中,迭代器在添加新元素的同时通过Set ...

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()); 

该设置将使用ConcurrentHashMapkeySet使用的条纹图案,它提供了线程安全的迭代器和更好的“并发性”。

相关问题