悲观锁定时如何避免可怕的死锁-以及Java 8的一些用法!
有時您根本無法避免:通過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的一些用法!的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 树莓派装安卓流畅吗(树莓派装安卓)
- 下一篇: 办事机构备案表怎么填(办事机构备案表)