首页 > 技术文章 > 并发编程日记-线程不安全的危害

maitian2013 2021-03-10 10:26 原文

计数器

@WebServlet(name = "HelloServlet", urlPatterns = {"/hello"})
public class HelloServlet extends HttpServlet {

    private static final Logger LOG = Logger.getLogger(HelloServlet.class.getName());

    private Integer count = 0;

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        ++count;
        processRequest(request, response);
        LOG.info(String.format("计数 %d", count));
    }
}

按常理多少次请求后就会打印出计数多少。

ab -n300 -c20 http://localhost:8080/servlet-demo/hello

正常下,执行第一次 计数应为300

事实上得到的却不是

再执行..

2 ~ 590
3 ~ 879
4 ~ 1170

几乎没有规律,并不是预期的 300的倍数。

解释

实际上++count是一个读取-修改-写入的操作。

假设线程A、B 同时读取到了值是m,同时写入+1后的值m+1。结果count = m+1,则事实上整个计数就少了1,应该为m+2。

处理

count声明为AtomicInter

    private Integer count = 0;
    private AtomicInteger atomicCount = new AtomicInteger(0);

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        ++count;
        processRequest(request, response);
        LOG.info(String.format("计数count %d", count));
        LOG.info(String.format("计数atomicCount %d", atomicCount.incrementAndGet()));
    }

可以看到,AtomicInteger统计的是正确的了。

推荐阅读