今天在全量上传模板的过程中遇到了“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