【Java】Lock锁的细节内容-ORA-00060: 等待资源时检测到死锁

今天在全量上传模板的过程中遇到了“ORA-00060: 等待资源时检测到死锁”。

ERROR 22292 --- [default-pool-thread-2] com.common.utils.logger.ListenableLogger - [error,104] - [20230317 092835][ERROR][模板解析失败,批处理中出现错误: ORA-00060: 等待资源时检测到死锁
]
ERROR 22292 --- [default-pool-thread-2] com.thread.LoadTemplateRunable - [error,108] - LoadTemplateRunable.loadSubtemplateInfo error=批处理中出现错误: ORA-00060: 等待资源时检测到死锁
,从异常信息:批处理中出现错误: ORA-00060: 等待资源时检测到死锁

翻开代码一看,发现Lock锁被屏蔽……

//类中
private Lock setLock = new ReentrantLock();

//核心方法中
Connection conn = dao.getConnection();      
conn.setAutoCommit(false);
Statement sta= conn.createStatement();
//省略多处业务
sta.addBatch(mDelSql);//多处
//省略多处业务
setLock.lock();//被屏蔽
sta.executeBatch();//仅留下
setLock.unlock();//被屏蔽
conn.commit();//被屏蔽
conn.setAutoCommit(true);//被屏蔽
//发现上述死锁在于lock与unlock方法被屏蔽,由此导致多线程竞争中出现了问题,需要放开lock与unclock。

并发场景中,最简单的方式是直接添加同步关键字synchronized来实现多线程之间的同步互斥操作。另外一种高效的机制去完成”同步互斥”操作是使用Lock对象,比synchronized关键字更为强大功能,并且有嗅探锁定,多路分支等功能。

Lock 是 java.util.concurrent.locks 包下的接口,Lock 实现提供了比 synchronized 关键字更广泛的锁操作,能以更优雅的方式处理线程同步问题。

1.Lock和ReadWriteLock是两大锁的根接口
在java.util.concurrent.locks包中有很多Lock的实现类,其实现都依赖java.util.concurrent.AbstractQueuedSynchronizer类,实现思路都大同小异。
Lock代表实现类是ReentrantLock(可重入锁)。
ReadWriteLock(读写锁)的代表实现类是ReentrantReadWriteLock。

//基本代码
ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
    //目标代码
} finally {
    //在finally里释放锁防止死锁
    lock.unlock();
}

2.Lock、synchronized 对比总结
①Lock是JUC下的接口,而synchronized是Java关键字,是内置的语言实现。
②synchronized是隐示锁,出了作用域自动释放(同步方法或者同步代码块),lock是显示锁(手动开启和关闭锁,记得关闭锁)。
synchronized 发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而 Lock 在发生异常时,如果没有主动通过 unLock() 去释放锁,则很可能造成死锁现象,因此使用 Lock 时需要在 finally 块中释放锁。
③Lock 可以让等待锁的线程响应中断,而synchronized不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断。
④通过Lock可以知道有没有成功获取锁,而 synchronized 不行。
⑤Lock可以提高多个线程进行读操作的效率。Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题,使用lock锁,JVM将花费较少的时候来调度线程,性能更好,并且具有更好的扩展性(提供更多子类)。
⑥Lock锁只有代码块锁,synchronized有代码块锁和方法锁。
⑦使用synchronized 关键字进行多线程协同工作时,需要wait()、notify()等进行配合使用。补充: wait()、notify/notifyAll() 方法是Object的本地final方法,无法被重写。
使用Lock时可以使用新的等待/通知类Condition。Condition一定是针对某一把固定的锁,也就是说,只有在有锁的基础上才会产生Condition。

3.部分细节展开

//Lock接口
public interface Lock {
    void lock();
    void lockInterruptibly() throws InterruptedException;
    boolean tryLock();
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    void unlock();
    Condition newCondition();
}

tryLock() 方法是有返回值的,它表示用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false,也就说这个方法无论如何都会立即返回,在拿不到锁时不会一直在那等待。
tryLock(long time, TimeUnit unit) 方法和 tryLock() 方法是类似的,只不过区别在于这个方法在拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回 false。如果如果一开始拿到锁或者在等待期间内拿到了锁,则返回 true。

可重入指的是一个线程获得这把锁,那么它在它第一次的锁还没有释放时,它还能多次获得锁。
不可重入说的则是在第二次获取锁的时候就会被挡住。

ReentrantLock有2个构造方法,一个默认是一个不公平的锁,还有一个构建方法我们传入一个boolean值,true就是设置公平锁,false就是不公平的和默认的一样
在这里插入图片描述

ReentrantReadWriteLock
读写锁:实现读写分离,适用高并发下读多写少的场景。
synchronized关键字和ReentrantLock同一时间只能有一个线程进行访问被锁定的代码,而读写锁的机制则不同,它有两把锁,读锁和写锁,在读锁情况下,多个线程可以并发访问资源,只有当是写锁时只能一个一个的顺序执行。
规则是:读读共享,写写互斥,读写互斥

这篇文章讲解的比较细致,可以一并看看
https://blog.csdn.net/xyy1028/article/details/107333451
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,让我们来详细解释一下 ORA-00060:deadlock detected while waiting for resource 错误,并用 Java 程序进行举例。 首先,让我们先了解一下什么是死锁死锁是指两个或多个事务相互等待对方释放资源,导致彼此都无法继续执行的情况。当出现死锁Oracle 数据库会选择其中一个事务作为牺牲品来终止,以解除死锁。 在 Java 程序中,死锁通常发生在多个线程同访问共享资源的情况下。例如,多个线程同访问同一个数据库表中的数据,如果它们之间没有正确地同步,就可能会产生死锁错误。 下面是一个简单的 Java 程序,用于模拟多线程访问数据库表的情况: ```java import java.sql.*; public class DeadlockExample { public static void main(String[] args) { Connection conn1 = null; Connection conn2 = null; try { // 获取数据库连接 conn1 = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:orcl", "user1", "password1"); conn2 = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:orcl", "user2", "password2"); // 创建两个线程,分别执行查询操作和更新操作 Thread t1 = new Thread(new QueryTask(conn1, conn2)); Thread t2 = new Thread(new UpdateTask(conn2, conn1)); // 启动线程 t1.start(); t2.start(); // 等待线程结束 t1.join(); t2.join(); } catch (Exception e) { e.printStackTrace(); } finally { // 关闭数据库连接 try { if (conn1 != null) conn1.close(); if (conn2 != null) conn2.close(); } catch (SQLException e) { e.printStackTrace(); } } } } class QueryTask implements Runnable { private Connection conn1; private Connection conn2; public QueryTask(Connection conn1, Connection conn2) { this.conn1 = conn1; this.conn2 = conn2; } public void run() { try { // 在 conn1 上执行查询操作 Statement stmt1 = conn1.createStatement(); ResultSet rs1 = stmt1.executeQuery("SELECT * FROM table1 WHERE column1 = 1"); rs1.next(); System.out.println(rs1.getString("column1")); // 在 conn2 上执行查询操作 Statement stmt2 = conn2.createStatement(); ResultSet rs2 = stmt2.executeQuery("SELECT * FROM table2 WHERE column2 = 2"); rs2.next(); System.out.println(rs2.getString("column2")); // 关闭结果集和语句 rs1.close(); stmt1.close(); rs2.close(); stmt2.close(); } catch (Exception e) { e.printStackTrace(); } } } class UpdateTask implements Runnable { private Connection conn1; private Connection conn2; public UpdateTask(Connection conn1, Connection conn2) { this.conn1 = conn1; this.conn2 = conn2; } public void run() { try { // 在 conn2 上执行更新操作 Statement stmt2 = conn2.createStatement(); stmt2.executeUpdate("UPDATE table2 SET column2 = 3 WHERE column2 = 2"); stmt2.close(); // 在 conn1 上执行更新操作 Statement stmt1 = conn1.createStatement(); stmt1.executeUpdate("UPDATE table1 SET column1 = 2 WHERE column1 = 1"); stmt1.close(); } catch (Exception e) { e.printStackTrace(); } } } ``` 在这个例子中,我们创建了两个线程,一个线程执行查询操作,另一个线程执行更新操作。这两个线程分别使用不同的数据库连接(conn1 和 conn2),同访问两个不同的数据库表(table1 和 table2)。 在 QueryTask 中,我们在 conn1 上执行了一个查询操作,并在 conn2 上执行了另一个查询操作。在 UpdateTask 中,我们在 conn2 上执行了一个更新操作,并在 conn1 上执行了另一个更新操作。 这个程序的运行过程中,可能会导致死锁错误。例如,如果查询操作先获取了 table1 中的行,并等待更新操作释放 table2 中的行,而更新操作先获取了 table2 中的行,并等待查询操作释放 table1 中的行,就会产生死锁错误。 当程序出现死锁错误Oracle 数据库会选择其中一个事务作为牺牲品来终止,以解除死锁。在 Java 程序中,可以通过捕获 SQLException 异常来处理死锁错误,例如: ```java try { // 执行数据库操作 } catch (SQLException e) { if (e.getErrorCode() == 60) { // 出现死锁错误,进行相应的处理 } else { // 其他类型的 SQLException 错误,进行相应的处理 } } ``` 在捕获 SQLException 异常,可以通过调用 getErrorCode() 方法来获取错误码,如果错误码为 60,就说明出现了死锁错误。此,可以进行相应的处理,例如回滚事务、重试操作等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值