|
通常,每个servlet在Tomcat容器中只对应一个对象。所有的客户请求都由这个对象来处理,这就产生了线程安全的问题。
例如一个简单的计数器类:
public class Counting { int count = 0; public void doGet (..., ...) { ... ... count ++; out.println("This servlet has been accessed " + count + " times."); ... } }
如果有两个线程同时进入count()方法,那么,一种可能的代码执行顺序是:
count ++ //线程1 count ++ //线程2 out.println //线程1 out.println //线程2
结果就是,每个线程得到的都是 count + 2 的值。
解决办法是使得这个servlet具有线程安全性。就我目前所学到的,有三种理论上的方法:
第一种是给整个doGet()方法加上synchronized关键字,就像这样:
public synchronized void doGet (..., ...) { ... ... }
这会通过同步化整个方法来保证一致性。但问题是这样的servlet每次只能处理一个GET请求,而其它的客户就不得不在方法外面排队等候进入。假如google用这种方法处理搜索请求,它就一定要像银行的排队机那样,告诉每个新来的顾客:“目前在你的前面还有6833951个顾客……”那么当我提交了一个查询之后,我大可踏踏实实的去睡上一觉再来看看是否轮到我搜索了。
第二个方法是只同步化关键部分的代码,例如涉及数据读写的部分:
public class Counting { int count = 0; public void doGet (..., ...) { ... ... synchronized (this) { count ++; out.println("This servlet has been accessed " + count + " times."); } ... } }
这个方法就好很多了。因为他在保证了一致性的同时,限制了每个线程在同步代码块中执行的时间。不过像out.println这样的方法仍然有比较大的系统开销。因此我们还可以进一步改进它。
第三个方法,是在一个同步代码块中只放置所有需要连续运行关键代码,然后把结果输出到同步代码块之外。对于上面这个计数器程序,可以把count++的结果放到一个同步代码块之外的本地变量中。
public class Counting { int count = 0;
public void doGet (..., ...) { int local_count; ... ... synchronized (this) { count ++; local_count = count; } out.println("This servlet has been accessed " + local_count + " times."); ... } }
这种方法会将同步代码压缩到最小,不过一致性仍然有保障。
-------------------------- 参考:《Java Servlet编程》第二版,O'Reilly / 中国电力出版社
|
一共有 1 条评论