悲观锁与乐观锁是并发控制中的两种主要策略,在Java中,这两种锁策略都有各自的实现方式和应用场景,本文将详细介绍悲观锁和乐观锁的概念、原理、实现以及它们在Java中的应用场景。
悲观锁
1、概念
悲观锁是一种基于数据完整性约束的并发控制策略,它假设多个事务同时访问同一数据时会发生冲突,因此在事务开始执行之前就对可能受到冲突的数据加锁,以防止其他事务对其进行修改,悲观锁的实现方式主要是通过数据库的排他锁(X锁)来实现的。
2、原理
悲观锁的原理是通过在数据表中添加一个唯一标识符(如主键),当一个事务要访问某个数据时,首先会查询该数据是否被其他事务锁定,如果被锁定,则当前事务需要等待;如果没有被锁定,则当前事务可以对该数据加上排他锁,防止其他事务对其进行修改,这样,在事务提交之前,其他事务无法对受悲观锁保护的数据进行修改,从而保证了数据的完整性和一致性。
3、实现
在Java中,悲观锁可以通过以下几种方式实现:
(1)使用数据库的排他锁(X锁):在SQL语句中使用SELECT ... FOR UPDATE
或LOCK IN SHARE MODE
来加锁。
SELECT * FROM users WHERE id = 1 FOR UPDATE;
或者
SELECT * FROM users WHERE id = 1 LOCK IN SHARE MODE;
(2)使用Java的JDBC API:通过调用Connection对象的setAutoCommit(false)
方法关闭自动提交,然后调用PreparedStatement对象的executeQuery()
方法执行查询并加上排他锁,调用Connection对象的commit()
方法提交事务。
Connection conn = null; PreparedStatement pstmt = null; try { conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "password"); conn.setAutoCommit(false); pstmt = conn.prepareStatement("SELECT * FROM users WHERE id = ? FOR UPDATE"); pstmt.setInt(1, 1); ResultSet rs = pstmt.executeQuery(); // 对查询结果进行处理 } catch (SQLException e) { e.printStackTrace(); } finally { if (conn != null) { try { conn.commit(); } catch (SQLException e) { e.printStackTrace(); } finally { conn.close(); } } }
乐观锁
1、概念
乐观锁是一种基于版本号的并发控制策略,它假设多个事务在同一时间访问同一数据时不会发生冲突,因此不需要在事务开始执行之前就对可能受到冲突的数据加锁,乐观锁的实现方式主要是通过对数据表中每个记录的版本号字段进行更新来实现的,当一个事务要更新某个数据时,会检查该数据的版本号是否与自己读取到的版本号一致,如果一致,则更新数据并将版本号加一;如果不一致,则说明其他事务已经修改了该数据,此时可以选择重新获取数据或抛出异常,这样,在事务提交之前,其他事务只能看到已提交的数据版本,从而避免了数据的不一致性。
2、原理
乐观锁的原理是通过为每个数据记录添加一个版本号字段,并在每次更新数据时递增版本号,当一个事务要更新某个数据时,会先查询该数据的当前版本号,然后将自己的版本号设置为该值,接下来,事务会再次查询该数据的新版本号,如果新版本号与自己的版本号一致,则更新数据并将版本号加一;如果新版本号大于自己的版本号,则说明其他事务已经修改了该数据,此时可以选择重新获取数据或抛出异常,这样,在事务提交之前,其他事务只能看到已提交的数据版本,从而避免了数据的不一致性。
3、实现
在Java中,乐观锁可以通过以下几种方式实现:
(1)使用数据库的支持:许多关系型数据库都支持乐观锁机制,例如InnoDB和MyISAM等,这些数据库通常会在插入或更新数据时自动生成一个唯一的行ID(如MySQL的AUTO_INCREMENT),并将其作为版本号。
INSERT INTO users (name, age) VALUES ('张三', 25) ON DUPLICATE KEY UPDATE name='李四', age=26; -如果插入成功,则不返回任何值;如果插入失败(因为id重复),则返回1;如果更新成功(且没有其他事务修改该数据),则返回0;如果更新失败(或有其他事务修改该数据),则返回2;如果更新失败(但有其他事务插入了相同的id),则返回3。
(2)使用Java的JDBC API:通过调用PreparedStatement对象的executeUpdate()
方法执行更新操作。
Connection conn = null; PreparedStatement pstmt = null; try { conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "password"); int affectedRows = pstmt = conn.prepareStatement("INSERT INTO users (name, age) VALUES (?, ?) ON DUPLICATE KEY UPDATE name=?, age=?") // 如果插入成功,则不返回任何值;如果插入失败(因为id重复),则返回1;如果更新成功(且没有其他事务修改该数据),则返回0;如果更新失败(或有其他事务修改该数据),则返回2;如果更新失败(但有其他事务插入了相同的id),则返回3,pstmt.setString(1, "张三"); pstmt.setInt(2, 25); pstmt.setString(3, "李四"); pstmt.setInt(4, 26); affectedRows = pstmt.executeUpdate(); // affectedRows为受影响的行数或相应的错误代码。 } catch (SQLException e) { e.printStackTrace(); } finally { if (conn != null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } finally { if (affectedRows == SQLErrorCode.ER_DUP_ENTRY_ON_INDEX) { // 如果受影响的行数为2或3,则表示发生了行级锁定或死锁,可以在应用程序中根据具体情况选择重试或终止操作。 } else if (affectedRows == SQLErrorCode.ER_ROW_IS_REFERENCED_BY_OTHER_TABLE) { // 如果受影响的行数为4或5,则表示发生了外键约束冲突,可以在应用程序中根据具体情况选择重试或终止操作。 } else if (affectedRows == SQLErrorCode.ER_NO_SUCH_TABLE || affectedRows == SQLErrorCode.ER_BAD_NULL_ERROR || affectedRows == SQLErrorCode.ER_ACCESS_DENIED || affectedRows == SQLErrorCode.ER_HOST_IS_BLOCKED) { // 如果受影响的行数为0、1、7或8,则表示发生了连接错误或其他未知错误,可以在应用程序中根据具体情况选择重试或终止操作。 } else if (affectedRows == SQLErrorCode.ACQUIRED) { // 如果受影响的行数为9或10,则表示发生了死锁,可以在应用程序中根据具体情况选择重试或终止操作,else if (affectedRows == SQLErrorCode.COMMITTED) { // 如果受影响的行数为11或12,则表示更新成功且没有其他事务修改该数据,可以在应用程序中根据具体情况选择继续操作或提交事务,else if (affectedRows == SQLErrorCode.ROLLBACKED || affectedRows == SQLErrorCode.UNKNOWN) { // 如果受影响的行数为13或14,则表示更新失败或发生了未知错误,可以在应用程序中根据具体情况选择重试或回滚事务,else if (affectedRows == SQLErrorCode.SERIALIZATION_FAILED || affectedRows == SQLErrorCode.GENERIC_FAILURE || affectedRows == SQLErrorCode.DATA_READONLY || affectedRows == SQLErrorCode.DATA_WRONG_TYPE || affectedRows == SQLErrorCode.FEATURE_NOT_SUPPORTED || affectedRows == SQLErrorCode.CONNECTION_DOES_NOT_EXIST || affectedRows == SQLErrorCode.CONNECTION_FAILURE || affectedRows == SQLErrorCode.CONNECTION_BROKEN || affectedRows == SQLErrorCode.TRANSACTION_CONFLICT || affectedRows == SQLErrorCode.TRANSACTION_UNCOMPLETED || affectedRows == SQLErrorCode.TRANS
原创文章,作者:K-seo,如若转载,请注明出处:https://www.kdun.cn/ask/261386.html