一个高并发买票的实例

 马克- to-win:马克 java社区:防盗版实名手机尾号: 73203。
马克-to-win:我们现在回到春节高并发买票的问题。我们假设有一百万个人买一百张票,其中买票程序一百万个线程同时运行。不用改变mysql的缺省事务隔离级别。任何人在买之前都用普通的select * from table来访问数据库获得目前的票数。假如现在是一百,之后大家一起点“下单”钮。这个钮所对应的程序可以这样:先select * from table for update,这样所有别人的select * from table for update这句话都会被挡住,这个时刻选出的数据库的票的存量是准确的。你可以加一个判断,比如如果存量大于1,我就买一张票。(有很多高并发程序,会在这里加一个乐观锁版本的判断,如果还是老版本就做更新。马克-to-win:原理和目的和我们的例子是一样的)注意这里加判断,虽然耗时,但至关重要,(这也是很多公司的通用做法)而且必须像这样独占排他挡住别人大张旗鼓的做。假如你不下决心独占排他的去做判断,当你真正更新的时候,也许数据已经被别人更改了。也许一秒前看存量是一百,一秒之后已经变成零了。不判断就直接更新的话,数据库票数也许会变成负数。完成判断之后就是更新数据库票数减一张,当然还需做一些其他的工作,比如订单表中需要增加一行记录是谁买的之类的,最后提交。之后队列中下一个事务就会被开始执行。这只是程序的一个总的思路,真正做项目还需考虑用户体验比如超时问题,(connection query有超时timeout异常)或用户等得不耐烦,主动关闭窗口。这时数据库服务器就会照顾下一个select * from table for update。马克-to-win:真正做项目时,我们可以选择用select * from t for update nowait (不等待行锁释放,提示锁冲突,不返回结果)或select * from t for update wait 5 (等待5秒,若行锁仍未释放,则提示锁冲突,不返回结果)给用户提供三个选择,可以死等,不等,或等5秒。同时告诉用户现在多少人在队列中你的前面(每有一个人发出请求,在ServletContext中就加1,完成就减1),大概多长时间可以到你,因为数据库完成一个用多长时间可以算出来。下面我们就给出一个并发买票的简单实现。(本例子我们还用上章的register数据库表,用age变量代表车票数,道理是一样的)


例 1.2.1

package com;
import java.sql.*;
public class ConcurBuy_MarkToWin {
    void concurBuy() {
        Connection con = null;
        Statement s = null;
        try {
            con = DatabaseConn.getConnection();
            s = con.createStatement();
            System.out.println("11111111"+Thread.currentThread().getName());
(购买完整教程)
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        } finally {
            try {
                s.close();
                con.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
            System.out.println("successfully in finally"+ con+Thread.currentThread().getName());
        }
    }
}



package com;
class MulThreMarkToWin extends Thread {
    public void run() {
(购买完整教程)
    }
}
public class TestConcurBuy_MarkToWin {
    public static void main(String[] args) {
        Thread t1 = new MulThreMarkToWin();
        Thread t2 = new MulThreMarkToWin();
        t1.start();
        t2.start();
    }
}





即使两个线程同时运行,但里面的事务对数据库的改变是序列化进行的。select Age from register where Id=1 for update这步以后,就把别的事务给挡住了。输出结果:


11111111Thread-0
22222222Thread-0
33333333Thread-0
11111111Thread-1
22222222Thread-1
age is 24Thread-0
444444444Thread-0
33333333Thread-1
con = com.mysql.jdbc.Connection@10b9d04Thread-0
successfully in finallycom.mysql.jdbc.Connection@10b9d04Thread-0
age is 23Thread-1
444444444Thread-1
con = com.mysql.jdbc.Connection@171732bThread-1
successfully in finallycom.mysql.jdbc.Connection@171732bThread-1


下面我们给出例 1.2.1的Servlet版本:

例 1.2.2

<%@ page contentType="text/html; charset=GBK" %>
<html>
<body>
<center><h3>这里应先用普通select,选出结果,准备下单买票---by马克-to-win</h3></center>
<form action="MarkToWinServlet" method="post">
<input type="submit" name="Submit" value="提交">
</form>
</body>
</html>





package com;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.*;

public class ServletHello1 extends HttpServlet {
    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        ConcurBuy_MarkToWin concurBuy = new ConcurBuy_MarkToWin();
        concurBuy.concurBuy();
    }
}

当运行这个jsp的时候,一定要在两个不同的浏览器里,代表两个不同的用户,当然得同时点“更新”按钮。根据结果你会发现,即使同时按按钮,对数据库的改变是序列化进行的。

11111111http-8080-Processor24
22222222http-8080-Processor24
33333333http-8080-Processor24
11111111http-8080-Processor25
22222222http-8080-Processor25
age is 21http-8080-Processor24
444444444http-8080-Processor24
33333333http-8080-Processor25
con = com.mysql.jdbc.Connection@169dd64http-8080-Processor24
successfully in finallycom.mysql.jdbc.Connection@169dd64http-8080-Processor24
age is 20http-8080-Processor25
444444444http-8080-Processor25
con = com.mysql.jdbc.Connection@170984chttp-8080-Processor25
successfully in finallycom.mysql.jdbc.Connection@170984chttp-8080-Processor25