天天看点

多线程同步方法<一>数据库悲观锁(for update)

     悲观锁,正如其名,具有强烈的独占和排他特性。上来就锁住,把事情考虑的比较悲观,它是采用数据库机制实现的,数据库被锁之后其它用户将无法查看,直到提交或者回滚,锁释放之后才可查看。所以悲观锁具有其霸道性。

     简单说其悲观锁的功能就是,锁住读取的记录,防止其它事物读取和更新这些记录,而其他事物则会一直堵塞,知道这个事物结束。

我们可以在dos窗口中来简单测试测试:

     1)语句:sqlplus c##drp1/drp1(数据库名和密码)

进入之后写入sql语句  select * from t_table_id;(启动两个窗口),会发现两个结果会一模一样:

多线程同步方法<一>数据库悲观锁(for update)

     2)在一个窗口中写入语句:select * from t_table_id where table_name='t_client' for update;则出现下边的结果,你会发现第一个将字段结果查询出来了,但是在第二个窗口中却毫无反应,这就是“for update",已经加入了悲观锁。

多线程同步方法<一>数据库悲观锁(for update)

     3)使用commit语句提交事物:如下图,会发现第二个可以查看了,为什么呢?因为commint已经完成了提交事物,释放了其权限。

多线程同步方法<一>数据库悲观锁(for update)

     所以我们可以采用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)

     运行之后的测试效果:

多线程同步方法<一>数据库悲观锁(for update)

     运行之后数据库值:

多线程同步方法<一>数据库悲观锁(for update)

     所以通过实例可以发现悲观锁是可以胜任其工作任务的,但是胜任归胜任,还得考虑效率的问题,就比如我一个小时将整个工作任务全部完成,但是仅仅一个事物就占用好长时间,并且还一直占用,不给其他事物的发展空间,这种状况则也没有办法完成任务,纵然你满足的多线程同步,但是却依旧没有完成任务。所以另一种情况就是自己工作的同时尽量不打扰其他事物的运行,并且能够满足多线程同步,这样高效率的情况才是现在大部分的公司所需要的效果。但是对于"for update"而言,只能放到查询语句中,因为只有查询对于数据库锁住才有意义。

继续阅读