天天看点

悲观锁解决高并发访问问题

如果程序存在并发访问问题,我们就要考虑通过加锁对一些资源的访问进行控制,加锁的两种方式为悲观锁和乐观锁,学习drp的时候,老师为我们展示的悲观锁的例子,这篇博客就来看一下这种锁的机制。

eg:在分销管理系统中,分销商的主键采用单独的表来生成,多个用于可以同时生成主键,所以存在并发访问的情况,需使用线程同步,需考虑锁的机制。

在看代码之前先简单了解一些基本的知识:

(1)synchronized

synchronized,Java语言的关键字,可用来给对象和方法或者代码块加锁,当它锁定一个方法或者一个代码块的时候,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。

(2)悲观锁

悲观锁利用数据库的机制实现,直接在sql语句后面加上“for update”即可,在整个过程中把数据锁住,只要事务不释放(提交或者回滚),任何人都不能查看或者修改数据,只能处于等待状态。

(3)事务

要理解接下来的代码,需要了解事务的概念,事务是恢复和并发控制的基本单位,事务应该具有4个属性:原子性、一致性、隔离性、持久性。事务保证多个操作的原子性,要么全部成功,要么全部失败。

代码部分:

悲观锁在执行数据库操作的时候把数据锁住,事务提交后就释放锁,所以按照这里的例子, executeQuery查完后马上就释放锁了,其他人就可以改了,这样不行。所以需要手动开始事务,直到所有的都修改完,没有错误,再提交事务,释放锁。所以在这里需要手动添加事务,完成事务的开始-提交-回滚-恢复几种状态。

//开始事务,如果手动提交修改为false  
    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) {}  

    }  
//状态恢复  
    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 int generate(String tableName){  
        //使用数据库的悲观锁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 = ;  
        try{  
            conn= DbUtil.getConnection();  
            //开始事务  
            DbUtil.beginTransaction(conn);            
            pstmt = conn.prepareStatement(sql);  
            pstmt.setString(, tableName);  
            rs = pstmt.executeQuery();  
            if(!rs.next()){  
                throw new RuntimeException();  
            }  
            value = rs.getInt("value");  
            value++;  
            modifyValueField(conn,tableName,value);  
            //提交事务  
            DbUtil.commitTransaction(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;  
    }  
           

总结:

乐观锁和悲观锁是来控制进程对数据库的操作,避免出现丢失更新,肮脏数据,不可重复的读取,虚读问题。在实际的业务逻辑中,是采用事物的隔离(根据事物和需求有四种隔离级别)和锁来达到数据的统一。

悲观锁数据一旦锁定,就只能等待释放,如果一直占用资源不释放,其他人就一直无法使用,并且悲观锁不管是否发生多线程冲突,只要存在这种可能,就每次访问都加锁,会造成资源的浪费。

乐观锁,就是通过标记值版本控制,每次操作前通过标记值(一般在数据库中加入一个version字段在读取数据的时候将version读出来)判断是否是最新内容,最新内容就可以操作,不是最新的就继续循环判断标记值,直到是最新内容。

在大量冲突发生时,悲观锁的锁消耗大,乐观锁的读取次数会多。