博主资料

留言 加为好友 收藏

用户名:  epengfei

个人统计

用户名: epengfei
等级: 初来乍到
威望: 60
积分: 125
在线时间: 0 小时
日志总数: 12
评论数量: 5
访问次数: 64276
建立时间: 2007-05-14
RSS订阅       手机访问

文章搜索

友情链接

最近访问的人:

日志文章

2007年05月17日 18:14:56

在Servlet中保证一致性的方法

通常,每个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) |  浏览(2191) |  收藏
1楼 [匿名]chenpengyi 2008年01月18日 14:10:05 Says:
建议不使用this的lock,使用byte作为lock开销小
发表评论