多執行緒同步方法資料庫悲觀鎖(for update)
悲觀鎖,正如其名,具有強烈的獨佔和排他特性。上來就鎖住,把事情考慮的比較悲觀,它是採用資料庫機制實現的,資料庫被鎖之後其它使用者將無法檢視,直到提交或者回滾,鎖釋放之後才可檢視。所以悲觀鎖具有其霸道性。
簡單說其悲觀鎖的功能就是,鎖住讀取的記錄,防止其它事物讀取和更新這些記錄,而其他事物則會一直堵塞,知道這個事物結束。
我們可以在dos視窗中來簡單測試測試:
1)語句:sqlplus c##drp1/drp1(資料庫名和密碼)
進入之後寫入sql語句 select * from t_table_id;(啟動兩個視窗),會發現兩個結果會一模一樣:
2)在一個視窗中寫入語句:select * from t_table_id where table_name='t_client' for update;則出現下邊的結果,你會發現第一個將欄位結果查詢出來了,但是在第二個視窗中卻毫無反應,這就是“for
update",已經加入了悲觀鎖。
3)使用commit語句提交事物:如下圖,會發現第二個可以查看了,為什麼呢?因為commint已經完成了提交事物,釋放了其許可權。
所以我們可以採用for update對其多執行緒保持同步,就比如我們對資料庫進行相加的操作,執行一次,資料增加一,demo如下:
方法一:對資料庫操作進行鎖住:
因為一般情況下,事物是自動提交的,因為悲觀鎖我們採用主動提交事物,由悲觀鎖的狀態來控制,所以我們採用放來來控制一下:public static int generate(String tableName){ // 使用資料庫的悲觀鎖for update String sqlString="select value from t_table_id where table_name=? for update"; Connection conn=null; PreparedStatement pstmt=null; ResultSet rSet=null; int value=0; try{ conn=DbUtil.getConnection(); // 手動開啟事物,不讓其自動提交 DbUtil.beginTransaction(conn); pstmt=conn.prepareStatement(sqlString); pstmt.setString(1, tableName); // 事物提交 rSet=pstmt.executeQuery(); if(!rSet.next()){ throw new RuntimeException(); } value= rSet.getInt("value"); value++; //自加 modifyValueField(conn,tableName,value); // 事物手動提交 DbUtil.commitTransaction(conn); }catch(Exception e){ e.printStackTrace(); // 簡單異常丟擲 // 如果事物提交失敗,則回滾事物 DbUtil.rollbackTransaction(conn); throw new RuntimeException(); }finally{ DbUtil.close(rSet); DbUtil.close(pstmt); // 釋放的時候,回覆其初始狀態,重置connection狀態 DbUtil.resetConnection(conn); DbUtil.close(conn); } return value; }
方法二:數值加1的操作,根據資料表明更新資料欄位的值// 手動開啟事物方法 public static void beginTransaction(Connection conn) { try { if (conn != null) { if (conn.getAutoCommit()) { conn.setAutoCommit(false); //手動提交 } } }catch(SQLException e) {} } // 事物提交方法 public static void commitTransaction(Connection conn){ try { if (conn != null) { if (!conn.getAutoCommit()) { conn.commit(); } } }catch(SQLException e) {} } // 如果手動提交事物,遇到問題,回滾方法 public static void rollbackTransaction(Connection conn){ try { if (conn != null) { if (!conn.getAutoCommit()) { conn.rollback(); } } }catch(SQLException e) {} } // 狀態設定,因為之前手動是預設為false的 public static void resetConnection(Connection conn){ try { if (conn != null) { if (conn.getAutoCommit()) { conn.setAutoCommit(false); }else { conn.setAutoCommit(true); } } }catch(SQLException e) {} }
public static void modifyValueField(Connection conn, String tableName, int value) throws SQLException{
String sql = "update t_table_id set value=? where table_name=?";
PreparedStatement pstmt = null;
try {
pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, value);
pstmt.setString(2, tableName);
pstmt.executeUpdate();
}finally {
DbUtil.close(pstmt);
}
}
最後我們寫一個工作臺來測試一下:
public static void main(String[] args){
int retValue=IdGenerator.generate("t_client");
System.out.println(retValue);
}
未執行之前資料庫值:
執行之後的測試效果:
執行之後資料庫值:
所以通過例項可以發現悲觀鎖是可以勝任其工作任務的,但是勝任歸勝任,還得考慮效率的問題,就比如我一個小時將整個工作任務全部完成,但是僅僅一個事物就佔用好長時間,並且還一直佔用,不給其他事物的發展空間,這種狀況則也沒有辦法完成任務,縱然你滿足的多執行緒同步,但是卻依舊沒有完成任務。所以另一種情況就是自己工作的同時儘量不打擾其他事物的執行,並且能夠滿足多執行緒同步,這樣高效率的情況才是現在大部分的公司所需要的效果。但是對於"for update"而言,只能放到查詢語句中,因為只有查詢對於資料庫鎖住才有意義。