一個高并發(fā)買票的實例

 馬克- to-win:馬克 java社區(qū):防盜版實名手機尾號: 73203。
馬克-to-win:我們現(xiàn)在回到春節(jié)高并發(fā)買票的問題。我們假設(shè)有一百萬個人買一百張票,其中買票程序一百萬個線程同時運行。不用改變mysql的缺省事務(wù)隔離級別。任何人在買之前都用普通的select * from table來訪問數(shù)據(jù)庫獲得目前的票數(shù)。假如現(xiàn)在是一百,之后大家一起點“下單”鈕。這個鈕所對應(yīng)的程序可以這樣:先select * from table for update,這樣所有別人的select * from table for update這句話都會被擋住,這個時刻選出的數(shù)據(jù)庫的票的存量是準確的。你可以加一個判斷,比如如果存量大于1,我就買一張票。(有很多高并發(fā)程序,會在這里加一個樂觀鎖版本的判斷,如果還是老版本就做更新。馬克-to-win:原理和目的和我們的例子是一樣的)注意這里加判斷,雖然耗時,但至關(guān)重要,(這也是很多公司的通用做法)而且必須像這樣獨占排他擋住別人大張旗鼓的做。假如你不下決心獨占排他的去做判斷,當你真正更新的時候,也許數(shù)據(jù)已經(jīng)被別人更改了。也許一秒前看存量是一百,一秒之后已經(jīng)變成零了。不判斷就直接更新的話,數(shù)據(jù)庫票數(shù)也許會變成負數(shù)。完成判斷之后就是更新數(shù)據(jù)庫票數(shù)減一張,當然還需做一些其他的工作,比如訂單表中需要增加一行記錄是誰買的之類的,最后提交。之后隊列中下一個事務(wù)就會被開始執(zhí)行。這只是程序的一個總的思路,真正做項目還需考慮用戶體驗比如超時問題,(connection query有超時timeout異常)或用戶等得不耐煩,主動關(guān)閉窗口。這時數(shù)據(jù)庫服務(wù)器就會照顧下一個select * from table for update。馬克-to-win:真正做項目時,我們可以選擇用select * from t for update nowait (不等待行鎖釋放,提示鎖沖突,不返回結(jié)果)或select * from t for update wait 5 (等待5秒,若行鎖仍未釋放,則提示鎖沖突,不返回結(jié)果)給用戶提供三個選擇,可以死等,不等,或等5秒。同時告訴用戶現(xiàn)在多少人在隊列中你的前面(每有一個人發(fā)出請求,在ServletContext中就加1,完成就減1),大概多長時間可以到你,因為數(shù)據(jù)庫完成一個用多長時間可以算出來。下面我們就給出一個并發(fā)買票的簡單實現(xiàn)。(本例子我們還用上章的register數(shù)據(jù)庫表,用age變量代表車票數(shù),道理是一樣的)


例 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();
    }
}







即使兩個線程同時運行,但里面的事務(wù)對數(shù)據(jù)庫的改變是序列化進行的。select Age from register where Id=1 for update這步以后,就把別的事務(wù)給擋住了。輸出結(jié)果:


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>這里應(yīng)先用普通select,選出結(jié)果,準備下單買票---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的時候,一定要在兩個不同的瀏覽器里,代表兩個不同的用戶,當然得同時點“更新”按鈕。根據(jù)結(jié)果你會發(fā)現(xiàn),即使同時按按鈕,對數(shù)據(jù)庫的改變是序列化進行的。

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