1. 程式人生 > >多執行緒同步方法資料庫悲觀鎖(for update)

多執行緒同步方法資料庫悲觀鎖(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;
		}
     因為一般情況下,事物是自動提交的,因為悲觀鎖我們採用主動提交事物,由悲觀鎖的狀態來控制,所以我們採用放來來控制一下:
// 手動開啟事物方法
	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) {}
	}
方法二:數值加1的操作,根據資料表明更新資料欄位的值
	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"而言,只能放到查詢語句中,因為只有查詢對於資料庫鎖住才有意義。