【資料庫】使用悲觀鎖實現執行緒同步,實現秒殺效果
阿新 • • 發佈:2019-02-14
一、前言
小編在最近的專案中遇到了要對資料庫中同一個欄位進行操作的一個功能,少數人操作的話,還體現不出來執行緒的問題,當很多人同時使用,資料量變大,就會出現執行緒的問題。如何保持執行緒同步,是小編這篇部落格要達到的目的。
二、引入
其實在我們生活中有很多功能使用了執行緒同步,小編向大家舉一個例子:京東秒殺。
上面這張圖就是小編在京東官網上面的京東秒殺模組,他的主要功能是在一個固定的時間點,比如16:00,釋出1000件瀘州酒,頁面上回顯示出還剩下百分之多少。這樣使用者會搶這些商品,很多人同時搶,就會在資料庫中出現多個執行緒對資料庫一個欄位同時操作的現象,如果資料庫不做到執行緒單一的話,資料就會變得不準確。
為了解決這個問題就引入了鎖的問題,對資料庫進行加鎖操作,實現執行緒單一。
三、實現執行緒同步
解決共享資源的情況,必須使用執行緒同步,可以使用兩種方案:
方法一:關鍵字synchronized
在java中使用synchronized關鍵欄位對方法同步
/**
* 根據表名生成該表的序列
* @param tableName
* @return 返回生成的序列
*/
public static synchronized int generate(String tableName) {
String sql = "select value from t_table_id where table_name=? " ;
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
int value = 0;
try {
conn = DbUtil.getConnection();
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, tableName);
rs = pstmt.executeQuery();
if (!rs.next()) {
throw new RuntimeException();
}
value = rs.getInt("value");
value++; //自加
modifyValueField(conn, tableName, value);
}catch(Exception e) {
e.printStackTrace();
throw new RuntimeException();
}finally {
DbUtil.close(rs);
DbUtil.close(pstmt);
DbUtil.close(conn);
}
return value;
}
方法二 使用悲觀鎖實現執行緒同步
悲觀鎖是採用資料庫機制實現的,資料被鎖住之後其他使用者將無法檢視,只到鎖釋放,只有提交或回滾事務鎖才會釋放。
for update語句只能放到select語句中,因為查詢時把資料鎖住才有意義。
/**
* 根據表名生成該表的序列
* @param tableName
* @return
* @throws SQLException
* synchronized程序同步,加鎖
*/
public static int generate(String tableName) throws SQLException{
//使用資料庫的悲觀鎖for update,只能用在查詢上
String sql= "select value from t_table_id where table_name=? for update";
Connection conn =null;
PreparedStatement pstmt = null;
ResultSet rs = null;
int value =0;
try {
conn=DbUtil.getConnection();
//開啟事務
DbUtil.beginTransaction(conn);
pstmt=conn.prepareStatement(sql);
pstmt.setString(1, tableName);
rs = pstmt.executeQuery();
if (!rs.next()) {
throw new RuntimeException();
}
value = rs.getInt("value");
value++;
modifyValueField(conn,tableName,value);
//提交事務
DbUtil.commitTranction(conn);
}catch (Exception e) {
e.printStackTrace();
//回滾事務
DbUtil.rollbackTransaction(conn);
throw new RuntimeException();
}finally{
DbUtil.close(rs);
DbUtil.close(pstmt);
DbUtil.resetConnection(conn);// 重置Connection的狀態
DbUtil.close(conn);
}
return value;
}
對事務的管理函式:
/**
* 手動提交
* @param conn
*/
public static void beginTransaction(Connection conn){
try {
if (conn!=null) {
if (conn.getAutoCommit()) {
conn.setAutoCommit(false); //手動提交
}
}
} catch (Exception e) {
}
}
/**
* 提交事務
* @param conn
*/
public static void commitTranction(Connection conn) {
try {
if (conn!=null) {
if (conn.getAutoCommit()) {
conn.commit(); //手動提交
}
}
} catch (Exception e) {
}
}
/**
* 回滾事務
* @param conn
*/
public static void rollbackTransaction(Connection conn){
try {
if (conn!=null) {
if (conn.getAutoCommit()) {
conn.rollback() ; //手動提交
}
}
} catch (Exception e) {
}
}
/**
* 重置Connection的狀態
* @param conn
*/
public static void resetConnection(Connection conn){
try {
if (conn!=null) {
if (conn.getAutoCommit()) {
conn.setAutoCommit(false);
}else {
conn.setAutoCommit(true); }
}
} catch (Exception e) {
}
}
通過這樣我們可以在還沒有提交一個程序之前,其他的程序都要等待,只有當前的程序完成了提交,完成了事務,悲觀鎖解除了,才會有一個新的程序對資料程序操作,這樣便完成了執行緒同步的效果。
四、小結
可以這麼理解,在咱們的生活中,有很多的功能都已經非常的實用,非常的常見了,只要我們對其中的核心知識點做核心的研究,我們就可以實現這個功能,大家都是程式設計師,要深入研究還是有必要的。加油!~~