一個(gè)高并發(fā)買票的實(shí)例

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







即使兩個(gè)線程同時(shí)運(yùn)行,但里面的事務(wù)對數(shù)據(jù)庫的改變是序列化進(jìn)行的。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é)果,準(zhǔn)備下單買票---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();
    }
}

當(dāng)運(yùn)行這個(gè)jsp的時(shí)候,一定要在兩個(gè)不同的瀏覽器里,代表兩個(gè)不同的用戶,當(dāng)然得同時(shí)點(diǎn)“更新”按鈕。根據(jù)結(jié)果你會(huì)發(fā)現(xiàn),即使同時(shí)按按鈕,對數(shù)據(jù)庫的改變是序列化進(jìn)行的。

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