今天的中间件课程上,Dash老师给出了一段代码,用于统计一个Servlet被访问的次数,代码如下:
咋看上去,没有任何问题,好的,我们来实际运行一下,连续访问如下url五次: ![](http://www.java3z.com/cwbwebhome/article/article8/img5/1117522_1270566260MdpF.jpg) 连续访问五次,页面显示访问次数正确。但这并不代表程序没有问题,请考虑以下情况,客户端A和客户端B同时对Counter Servlet进行访问,那么必然需要同时对counter进行自增操作,竞争条件出现了!大家想一想,这和操作系统课程中的经典的竞争条件是不是一模一样。 要理解上面的话,首先要先理解Servlet的线程模型: ![](http://www.java3z.com/cwbwebhome/article/article8/img5/1117522_1270566567XGVH.jpg) 图一:Servlet线程模型,来自Java Eye 我们知道,当Counter类被初始化之后,Servlet容器中便保存了一个该对象的实例(注意有且仅有一个实例,还有一个条件,Counter类没有实现SingleThreadModel接口),如果存在两个客户端同时访问该Servlet,那么Servlet容器会从该Servlet对象的线程池中分配两个线程分别处理两个客户端的请求。在Dash老师给出的代码中,如果多个客户端同时访问这个Servlet,相应的必然存在多个线程同时访问Counter类的实例变量count,必然会导致数据竞争问题,为了更明显的演示这一问题,我先来给Dash老师的代码加点料,修改后的代码如下: 做如下的实验,打开两个浏览器窗口,输入相同的url访问Counterbeta1,首先在第一个窗口中敲回车确认访问,再迅速的在第二个浏览器中窗口中敲回车,结果如下: ![](http://www.java3z.com/cwbwebhome/article/article8/img5/1117522_1270566261Htkk.jpg) 显然,结果出错,两个浏览器窗口中显示的访问次数都是2,显然正确的结果应该是一个为1,另外一个为2。数据竞争出现了,好了,让我再玩的损一点,给Dash老师的代码再加点料: 再实验一下,开两个浏览器窗口,方法同上,结果如下: 这下子错的更离谱了,两个页面显示的访问次数都是1。 下面给出线程安全版的计数器: ![](http://www.java3z.com/cwbwebhome/article/article8/img5/1117522_1270566266u1jd.jpg) 注意,上面给出的线程安全版依旧是为了放大问题而加入了线程睡眠的语句,请注意判断。 |