有时您根本无法避免:通过SQL进行悲观锁定。 实际上,当您要在共享的全局锁上同步多个应用程序时,它是一个很棒的工具。

有些人可能认为这是在滥用数据库。 如果可以解决您遇到的问题,我们认为可以使用您拥有的工具。 例如, RDBMS可能是消息队列的完美实现 。

假设您确实有悲观的锁定用例,并且您确实想选择RDBMS。 现在,如何正确处理? 因为产生死锁真的很容易。 想象一下以下设置(我正在为此使用Oracle):

CREATE TABLE locks (v NUMBER(18));INSERT INTO locks
SELECT level
FROM dual
CONNECT BY level <= 10;

这将生成10条记录,我们将其用作10个不同的行级锁。

现在,让我们从两个sqlplus客户端连接到数据库:

实例1

SQL> SELECT *2  FROM locks3  WHERE v = 14  FOR UPDATE;V
----------1

实例2

SQL> SELECT *2  FROM locks3  WHERE v = 24  FOR UPDATE;V
----------2

现在,我们已经从两个不同的会话中获得了两个不同的锁。

然后,让我们反过来:

实例1

SQL> SELECT *2  FROM locks3  WHERE v = 24  FOR UPDATE;

实例2

SQL> SELECT *2  FROM locks3  WHERE v = 14  FOR UPDATE;

现在这两个会话都被锁定,幸运的是,Oracle将检测到这并使其中一个会话失败:

ORA-00060: deadlock detected while waiting for resource

避免死锁

这是一个非常明确的示例,在此示例中,很容易看到它发生的原因以及潜在的避免方法。 避免死锁的一种简单方法是建立一条规则,即始终必须按升序获取所有锁。 如果您知道需要1号和2号锁,则必须按顺序购买它们。 这样,您仍然会产生锁定并因此产生争用,但是一旦负载减少,至少争用将最终(可能)得到解决。 这是一个示例,显示了当您有更多客户时会发生什么。 这次,编写为Java线程。

在示例中,我们将jOOλ用于更简单的lambda表达式(例如lambdas抛出检查的异常)。 当然,我们将大量滥用Java 8!

Class.forName("oracle.jdbc.OracleDriver");// We want a collection of 4 threads and their
// associated execution counters
List<Tuple2<Thread, AtomicLong>> list =
IntStream.range(0, 4)// Let's use jOOλ here to wrap checked exceptions// we'll map the thread index to the actual tuple.mapToObj(Unchecked.intFunction(i -> {final Connection con = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:xe", "TEST", "TEST");final AtomicLong counter = new AtomicLong();final Random rnd = new Random();return Tuple.tuple(// Each thread acquires a random number of// locks in ascending ordernew Thread(Unchecked.runnable(() -> {for (;;) {String sql =" SELECT *"+ " FROM locks"+ " WHERE v BETWEEN ? AND ?"+ " ORDER BY v"+ " FOR UPDATE";try (PreparedStatement stmt = con.prepareStatement(sql)) {stmt.setInt(1, rnd.nextInt(10));stmt.setInt(2, rnd.nextInt(10));stmt.executeUpdate();counter.incrementAndGet();con.commit();}}})),counter);})).collect(Collectors.toList());// Starting each thread
list.forEach(tuple -> tuple.v1.start());// Printing execution counts
for (;;) {list.forEach(tuple -> {System.out.print(String.format("%1s:%2$-10s",tuple.v1.getName(),tuple.v2.get()));});System.out.println();Thread.sleep(1000);
}

在程序运行时,您可以看到它逐渐地继续运行,每个线程承担与其他线程大致相同的负载:

Thread-1:0         Thread-2:0         Thread-3:0         Thread-4:0
Thread-1:941       Thread-2:966       Thread-3:978       Thread-4:979
Thread-1:2215      Thread-2:2206      Thread-3:2244      Thread-4:2253
Thread-1:3422      Thread-2:3400      Thread-3:3466      Thread-4:3418
Thread-1:4756      Thread-2:4720      Thread-3:4855      Thread-4:4847
Thread-1:6095      Thread-2:5987      Thread-3:6250      Thread-4:6173
Thread-1:7537      Thread-2:7377      Thread-3:7644      Thread-4:7503
Thread-1:9122      Thread-2:8884      Thread-3:9176      Thread-4:9155

现在,为了论证,让我们来做禁忌ORDER BY DBMS_RANDOM.VALUE

String sql =" SELECT *"
+ " FROM locks"
+ " WHERE v BETWEEN ? AND ?"
+ " ORDER BY DBMS_RANDOM.VALUE"
+ " FOR UPDATE";

用不了多长时间,您的应用程序就会爆炸:

Thread-1:0         Thread-2:0         Thread-3:0         Thread-4:0
Thread-1:72        Thread-2:79        Thread-3:79        Thread-4:90
Thread-1:72        Thread-2:79        Thread-3:79        Thread-4:90
Thread-1:72        Thread-2:79        Thread-3:79        Thread-4:90
Exception in thread "Thread-3" org.jooq.lambda.UncheckedException:
java.sql.SQLException: ORA-00060: deadlock detected while waiting for resourceThread-1:72        Thread-2:79        Thread-3:79        Thread-4:93
Thread-1:72        Thread-2:79        Thread-3:79        Thread-4:93
Thread-1:72        Thread-2:79        Thread-3:79        Thread-4:93
Exception in thread "Thread-1" org.jooq.lambda.UncheckedException:
java.sql.SQLException: ORA-00060: deadlock detected while waiting for resourceThread-1:72        Thread-2:1268      Thread-3:79        Thread-4:1330
Thread-1:72        Thread-2:3332      Thread-3:79        Thread-4:3455
Thread-1:72        Thread-2:5691      Thread-3:79        Thread-4:5841
Thread-1:72        Thread-2:8663      Thread-3:79        Thread-4:8811
Thread-1:72        Thread-2:11307     Thread-3:79        Thread-4:11426
Thread-1:72        Thread-2:12231     Thread-3:79        Thread-4:12348
Thread-1:72        Thread-2:12231     Thread-3:79        Thread-4:12348
Thread-1:72        Thread-2:12231     Thread-3:79        Thread-4:12348
Exception in thread "Thread-4" org.jooq.lambda.UncheckedException:
java.sql.SQLException: ORA-00060: deadlock detected while waiting for resourceThread-1:72        Thread-2:13888     Thread-3:79        Thread-4:12348
Thread-1:72        Thread-2:17037     Thread-3:79        Thread-4:12348
Thread-1:72        Thread-2:20234     Thread-3:79        Thread-4:12348
Thread-1:72        Thread-2:23495     Thread-3:79        Thread-4:12348

最后,由于死锁异常,除一个线程外,所有线程都被杀死了(至少在我们的示例中)。

不过要当心竞争

在显示悲观锁定(或一般来说锁定)的其他负面影响方面,上述示例也令人印象深刻:争用。 在“不良示例”中继续执行的单个线程几乎与之前的四个线程一样快。 我们使用随机锁定范围的愚蠢示例导致这样一个事实,即平均而言,几乎每次获取锁定的尝试至少都会产生一些阻塞 。 您如何解决这个问题? 通过查找enq:TX –您会话中的行锁争用事件。 例如:

SELECT blocking_session, event
FROM v$session
WHERE username = 'TEST'

上面的查询返回灾难性的结果,在这里:

BLOCKING_SESSION   EVENT
-------------------------------------
48                 enq: TX - row lock contention
54                 enq: TX - row lock contention
11                 enq: TX - row lock contention
11                 enq: TX - row lock contention

结论

结论只能是:谨慎使用悲观锁定,并始终期待意外发生 。 在进行悲观锁定时,死锁和大量争用都是您可能遇到的问题。 作为一般经验法则,请遵循以下规则(按顺序):

  • 如果可以,请避免悲观锁定
  • 如果可以,请避免每个会话锁定多于一行
  • 如果可以,请避免以随机顺序锁定行
  • 避免去上班看看发生了什么

翻译自: https://www.javacodegeeks.com/2015/04/how-to-avoid-the-dreaded-dead-lock-when-pessimistic-locking-and-some-awesome-java-8-usage.html

悲观锁定时如何避免可怕的死锁-以及Java 8的一些用法!相关推荐

  1. 悲观锁 引起死锁_悲观锁定时如何避免可怕的死锁-以及Java 8的一些用法!

    悲观锁 引起死锁 有时您根本无法避免:通过SQL进行悲观锁定. 实际上,当您要在共享的全局锁上同步多个应用程序时,它是一个很棒的工具. 有人可能认为这是在滥用数据库. 如果可以解决您遇到的问题,我们认 ...

  2. java定义属性时用this_(转载)深入Java关键字this的用法的总结

    合它的含义并不完全相同,使用不当还会出现错误, 本文对this的几种用法和出现的问题进行了分析详解. 关键词:类:对象:this:成员变量:方法:构造方法 中,Java语言提供了丰富的类(Class) ...

  3. oracle中悲观锁定_如何使用悲观锁定修复乐观锁定竞争条件

    oracle中悲观锁定 回顾 在我以前的文章中 ,我解释了使用显式乐观锁定的好处. 然后我们发现,在很短的时间范围内,并发交易仍可以在我们当前交易被提交之前立即提交产品价格更改. 此问题可以描述如下: ...

  4. 如何使用悲观锁定修复乐观锁定竞争条件

    概括 在我以前的文章中 ,我解释了使用显式乐观锁定的好处. 然后我们发现,在很短的时间范围内,并发交易仍可以在我们当前交易被提交之前立即提交产品价格更改. 此问题可以描述如下: 爱丽丝拿产品 然后,她 ...

  5. SQL的「悲观锁定」与「乐观锁定」

          今天在做设计书的时候,遇到了这两个词:「悲观锁定」与「乐观锁定」,于是回了总结一下. 悲观锁定方式: 当我们在对数据库进行更新操作的时候,有时候我们为了防止冲突,使用数据库为我们 提供的, ...

  6. java屏幕锁定_Java android FCM,当应用程序最小化或屏幕锁定时我听不到声音和颤音...

    我使用FCM,当我的应用程序最小化或手机锁定时,我听不到声音和颤音.当某件事来自服务器时(当服务器向我的手机发送消息时),我会创建一个通知.当我的应用程序是在上面我听到一个声音和vibratteJav ...

  7. 单击事件开启线程时,再次单击就会出现 java.lang.IllegalThreadStateException: Thread already started. 错误

    第一种解决方案:单击事件开启线程时,再次单击就会出现 java.lang.IllegalThreadStateException: Thread already started. 错误 解决办法: 创 ...

  8. 错误记录一:线程通信时抛出Exception in thread “Thread-0” java.lang.IllegalMonitorStateException异常

    错误记录一:线程通信时抛出Exception in thread "Thread-0" java.lang.IllegalMonitorStateException异常 完整异常: ...

  9. 详解各种锁:CAS、共享锁、排它锁、互斥锁、悲观锁、乐观锁、行级锁、表级锁、页级锁、死锁、JAVA对CAS的支持、ABA问题、AQS原理

    共享锁(S锁) 又称为读锁,可以查看但无法修改和删除的一种数据锁.如果事务T对数据A加上共享锁后,则其他事务只能对A再加共享锁,不能加排它锁.获准共享锁的事务只能读数据,不能修改数据. 共享锁下其它用 ...

最新文章

  1. centos下tomcat6.0.28配置
  2. sql with(lock) 与事务
  3. instance的用法 php,php面向对象之instanceof关键字的用法
  4. 接口之间传递inputstream_接口测试 | 接口测试入门
  5. 一步步在SAP Cloud Platform上创建HANA实例并使用
  6. 教你自制.NET Core Global Tools
  7. ipython notebook超级好用
  8. JavaSE生成随机数
  9. 05 Confluent_Kafka权威指南 第五章: kafka内部实现原理
  10. STM32+DWM1000开发uwb测距系列教程之三:使用官方例程实现p2p双向 twr测距
  11. 专业绘图(Visio 2016)实战视频课程-专题视频课程
  12. SAEJ3061汽车信息安全指南文档
  13. 信息学奥赛一本通 1296:开餐馆 | OpenJudge NOI 2.6 6045:开餐馆
  14. 法院判了!耿美玉起诉饶毅名誉侵权,驳回请求!但对饶毅方也应给予批评......
  15. 不需要登录微信,发送消息python
  16. 在服务器系统Windows 2003安装Avira AntiVir小红伞免费个人版
  17. 从FragmentPagerAdapter看Fragment 生命周期
  18. 2020 最新的 65 道 Java 经典面试题及答案
  19. BeanDefinition介绍
  20. Android+jenkins自动打包教程

热门文章

  1. java中实现客户姓名添加和显示
  2. C#中的序列化和反序列化案例
  3. 2020蓝桥杯省赛---java---B---4( 合并检测)
  4. 集成SpringSecurity---SpringBoot
  5. Mybatis框架的连接池配置
  6. Myeclipse创建第一个web项目
  7. maven项目 报错 java.lang.ClassNotFoundException: org.springframework.web.filter.HiddenHttpMethodFilter
  8. java泛型程序设计——调用遗留代码
  9. 使用SpringBoot搭建一个简单的webSocket服务
  10. java 读取 文本块_Java 13:文本块