悲观锁,正如其名,具有强烈的独占和排他特性。上来就锁住,把事情考虑的比较悲观,它是采用数据库机制实现的,数据库被锁之后其它用户将无法查看,直到提交或者回滚,锁释放之后才可查看。所以悲观锁具有其霸道性。
简单说其悲观锁的功能就是,锁住读取的记录,防止其它事物读取和更新这些记录,而其他事物则会一直堵塞,知道这个事物结束。
我们可以在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"而言,只能放到查询语句中,因为只有查询对于数据库锁住才有意义。