代码安全系列(2) - Race Condition

Race Condion 中文名不知道怎么翻译,竞态条件?紊乱条件?冲突条件?在多线程编程过程中,我们常常关心的是我们的线程是否会出现死锁(deadlock)的情况,而忽视Race Condion。到底Race Condtion是什么呢?

Race Condion是在多线程(或者说多个处理过程)情况下,对有些共享资源进行混乱操作,导致整个处理过程变得混乱,引发BUG。

有一个英文解释:

A race condition is any case where the results can be different depending on the order that processes arrive or are scheduled or depending on the order that specific competing instructions are executed.

一个早期传统的Race Condion的例子:

在早期的UNIX版本中,存在一个叫UNIX login的攻击方式。当一个新用户登陆到系统后,需要从root权限切换到user权限,假如在切换过程中一直按ESC键,则会导致权限切换不成功,使得登陆的用户一直具有root权限,从而控制整个计算机。

下面我们用C#来做一个多线程情况下的Race Contion的例子:

static DateTime curTime;
static void Main(string[] args)
{
    
for (int i = 0; i < 10; i++)
    {
        Thread aThread 
= new Thread(new ParameterizedThreadStart(Run));
        aThread.Start(
"Thread" + i.ToString());
        Thread.Sleep(
1000);
    }
}
static void Run(object threadName)
{
    curTime 
= DateTime.Now;
    
for (int i = 0; i < 10; i++)
    {
        
if (threadName.ToString() == "Thread0")
        {
            Console.WriteLine(
"{0}, Current time is {1}", threadName.ToString(), curTime.ToLongTimeString());
        }
        Thread.Sleep(
1000);
    }
}

上面的例子中,curTime其实是所有线程实例所同享的,而每个线程的执行函数内又对curTime进行了赋值,因此会引发混乱,导致线程内的curTime被其他线程所修改。我们看输出的结果如下:

Thread0, Current time is 11:51:56
Thread0, Current time is 
11:51:57
Thread0, Current time is 
11:51:58
Thread0, Current time is 
11:51:59
Thread0, Current time is 
11:52:00
Thread0, Current time is 
11:52:01
Thread0, Current time is 
11:52:02
Thread0, Current time is 
11:52:03
Thread0, Current time is 
11:52:04
Thread0, Current time is 
11:52:05
请按任意键继续. . .

 

上面的例子看上去好像可笑,因为稍微有点经验的程序员都不会犯那样简单的错误。然而在实际项目中,在不知不觉中,由于对某些东西有所忽视,则可能导致RaceCondtion。下面我们来看看一个简单的Web程序,我们分别用C#和JAVA来实现。该代码的功能是:

  1. 访问该页面时,若不传入参数c,显示字符串"Empty"
  2. 访问该页面时,若传入了参数c,则显示参数c的内容。

功能很简单,下面是C#版本的实现:

public partial class _Default : System.Web.UI.Page 
{
    
private string userName = "Empty";
    
protected void Page_Load(object sender, EventArgs e)
    {
        
string name = Request["c"];
        
if (!String.IsNullOrEmpty(name))
        {
            userName 
= name;
        }
        Label1.Text 
= userName;
    }
}

 

接着是Java的Servlet实现(其他不重要代码省略):


public class HelloServlet extends HttpServlet {

    
private String userName = "Empty";
    
public void doGet(HttpServletRequest request, HttpServletResponse response)
            
throws ServletException, IOException {
        
        String name 
= request.getParameter("c");
        
if (name != null)
        {
            userName 
= name;
        }
        response.setContentType(
"text/html");
        PrintWriter out 
= response.getWriter();
        out.println(userName);
        out.flush();
        out.close();
    }

 

上面两个版本看上去是那么的一样,真的是一样吗?不是的!C#版本是正确的,而JAVA版本恰恰出现了RaceCondion的问题!很多Servlet开发者常常忽视了,Servlet实际上是一个单件,除非Servlet实现SingleThreadModel接口。当多线程访问时(即多个用户一起访问时),每个线程得到的实际上是同一个Servlet实例,这样的话,他们对实例的成员变量的修改其实会影响到别人。下面是Servlet的多线程机制:

当客户端第一次请求某个Servlet时,Servlet容器将会根据 web.xml配置文件实例化这个Servlet类。当有新的客户端请求该Servlet时,一般不会再实例化该Servlet类,也就是有多个线程在使用这个实例。Servlet容器会自动使用线程池等技术来支持系统的运行,如下图:


因此,我们上面的例子中的userName成员变量其实被所有线程共享,其中某一个线程修改了userName,则其他线程的userName也同样修改。最后我们来试验一下:

首先,我们访问测试页面,不输入任何参数,然后再开一个窗口,测试页面中传入参数c=CoderZh,两个页面显示结果如下:

 

然后,我们刷新不带参数的页面,看看显示的结果:


posted @ 2008-12-31 17:15  CoderZh  阅读(6612)  评论(6编辑  收藏  举报