首頁  >  文章  >  資料庫  >  Java與Mysql鎖相關知識點有哪些

Java與Mysql鎖相關知識點有哪些

王林
王林轉載
2023-05-27 10:18:171075瀏覽

    鎖定的定義

    在電腦程式中鎖定用於獨佔資源,取得到鎖定才可以操作對應的資源。

    鎖定的實作

    鎖定在電腦底層的實現,依賴CPU提供的CAS指令(compare and swsp),對於記憶體位址,會比較原值以及嘗試去修改的值,透過值是否修改成功,來表示是否強佔到了這個鎖。

    JVM中的鎖

    jvm中,有2個常用的鎖

    synchronized

    synchronized是java提供的關鍵字鎖,可以鎖對象,類,方法。
    在JDK1.6以後,對synchronized進行了最佳化,增加了偏向鎖定和輕量鎖定模式,現在synchronized鎖定的運行邏輯如下:

    • 在初始加鎖時,會增加偏向鎖,即“偏向上一次取得該鎖的線程”,在偏向鎖下,會直接CAS取得該鎖。該模式大大提高了單線程反覆獲取同一個鎖的吞吐情況,在Java官方看來,大部分鎖的爭搶都發生在同一個線程上。

    • 如果偏向鎖定CAS取得失敗,表示目前執行緒與偏向鎖偏向的執行緒不同,偏向鎖就會升級成輕量鎖,輕量鎖的特點就是透過自旋CAS去取得鎖。

    • 如果自旋獲取失敗,那麼鎖就會升級成重量鎖,所有等待鎖的執行緒將會被JVM掛起,在鎖釋放後,再由JVM統一通知喚醒,再去試試CAS鎖,如果失敗,繼續掛起。

    很顯然,偏向鎖設計的目的是「在Java官方看來,對同一個鎖的爭搶大部分都發生在同一個執行緒上」。
    輕量鎖定設計的目的是「在短期內,鎖的爭搶透過自旋CAS就可以獲得,短時間內的CPU自旋消耗小於執行緒掛起再喚醒的消耗」。
    重量鎖就是最初優化前的synchronized的邏輯了。

    ReentrantLock

    說到ReentrantLock,就不得不說到JUC裡的AQS了。
    AQS全名為AbstractQueueSynchronizer,幾乎JUC裡所有的工具類,都依賴AQS實作。
    AQS在java裡,是一個抽象類,但是本質上是一種思路在java中的實作而已。
    AQS的實作邏輯如下:

    • 建構一個佇列

    • 佇列中維護需要等待鎖定的執行緒

    • #頭結點永遠是持有鎖(或持有資源)的節點,等待的節點在頭結點之後依序連接。

    • 頭結點釋放鎖定後,會依照順序去喚醒那些等待的節點,然後那些節點會再去嘗試取得鎖定。

    在synchronized鎖定優化以後,AQS的本質與synchronized並沒有太大不同,兩者的性能也並沒有太大差距了,所以AQS現在的特點是:

    • 是在java api層面實現的鎖,所以可以實現各種並發工具類,操作也更加靈活

    • 因為提供了超時時間等機制,操作靈活,所以不易死鎖。如果發生死鎖,它將更難以排查,因為jstack將不會顯示死鎖標識。

    • 可以實現公平鎖,而synchronized必定是非公平鎖。

    • 因為是JavaApi層實作的鎖,所以可以回應中斷。

    到這裡會發現,其實ReentrantLock可以說是synchronized在JavaApi層的實作。

    Mysql 鎖定

    共享鎖定(S) 與排它鎖定(X)

    作用範圍

    這兩個鎖定都包括行級鎖定和表級鎖。
    取得共用鎖定時,如果該資料被其他交易的排它鎖鎖住,則無法獲取,需要等待排它鎖釋放。

    意向鎖定

    作用範圍

    意向鎖定為表鎖定,在取得表格鎖定之前,一定會檢查意向鎖定。

    意圖鎖定協定如下:

    在交易取得表格中某行的共用鎖定之前,它必須先取得表格上的 IS 鎖定或更強的鎖定。

    在交易取得表中行的排他鎖定之前,它必須先取得表格的 IX 鎖定。

    在取得任意表鎖的共用鎖定或排它鎖定之前,一定會檢查該表上的共用鎖定。

    表鎖以及意向鎖的互斥規則如下:
    X IX S IS
    X Conflict Conflict Conflict Conflict
    IX Conflict Compatible Conflict Compatible
    S Conflict
    IX Conflict Compatible Conflict Compatible

    S Conflict Conflic#IX Conflict Compatible Conflict Compatible

    S Conflict Conflic#IX Conflict Compatible Conflict CompatibleS Conflict Conflic#IX Conflict Compatible Conflict CompatibleS Conflict Conflic#IX Compatible Compatibleible

    #IS Conflict Compatible Compatible Compatible

    意向鎖的作用在於:在取得表鎖時,可以透過意向鎖來快速判斷能否取得。

    ######因為取得行級鎖定時,會先取得對應的意向鎖定,這樣另外的交易在取得表格鎖定時就可以透過意向鎖定快速的判斷,而不需要每行去掃描。 ######特別注意的是,意向鎖是可以疊加的,即會存在多個,如T1事務獲取了意向鎖IX1和行級鎖X1,T2事務依舊可以獲取意向鎖IX2和行級鎖X2,所以只有在取得表級鎖之前,才會檢查意向鎖。 ###

    Record lock

    Record lock takes effect on the index to protect the row data from being changed by other transactions when SELECT c1 FROM t WHERE c1 = 10 FOR UPDATE.

    Record locks will still take effect when there is no index, because innodb will create a hidden index for each table.

    Record lock is the most basic row lock.

    Gap lock

    Gap lock takes effect on the index and is used to lock the row after the index value to prevent insertion. It will take effect when selecting from table where index=? for update, such as index= 1, the rows related to the index node with index=1 will be locked to prevent other transactions from inserting data.

    But it will not prevent the update statement, even if the updated data does not exist.

    Next-Key Locks

    This lock is a combination of record lock and gap lock. In short, when selecting from table where index=? for update, there will be a gap The lock prevents insert, and there is also a record lock on the index to prevent the update and delete of this piece of data. This Next-key is just a generalization of these two locks, because these two locks usually appear together when selecting for update.

    Insert Intention Locks

    Insert intention lock, similar to intention lock. It is a special gap lock, which does not occur in select for update, but occurs when insert occurs at the same time. For example, when two transactions insert the index range [4,7] at the same time, they obtain the intention lock of the range at the same time. This When the transaction is blocked, for example, A: insert-5, B: insert-7, the two transactions will not be blocked at this time.

    The insertion intention lock is a special gap lock, which is designed to prevent frequent blocking of insert in the case of normal gap lock lock interval, such as A: insert-5, B: insert-7, if not Insert the intention lock, then both 5 and 7 will try to obtain the gap lock. At this time, the second transaction will be blocked. However, by inserting the intention lock, the second transaction will not be blocked, and only the inserted rows will indeed conflict. , will be blocked.

    AUTO-INC Locks

    Auto-increment lock, this lock is obviously a table-level insert lock, in order to ensure that the primary key of the table with auto-increment primary key maintains atomic auto-increment.

    Regarding locks, everyone should understand more about the principles and models of various lock designs and operations, so that after deepening their understanding, they can use them more deeply and thoroughly.

    Common lock usage scenarios and usage

    double check

    As we all know, mysql transactions are of no use in preventing repeated insertions, and unique indexes have many shortcomings. Business It is best not to use it, so generally speaking, the common way to prevent repeated insertion is to use distributed locks. This is a more commonly used way of writing.

    final WeekendNoticeReadCountDO weekendNoticeReadCountDO = weekendNoticeReadRepositoryService.selectByNoticeId(noticeRequestDTO.getNoticeId());
    if (weekendNoticeReadCountDO == null) {
        final String lockKey = RedisConstant.LOCK_WEEKEND_READ_COUNT_INSERT + ":" + noticeRequestDTO.getNoticeId();
        ClusterLock lock = clusterLockFactory.getClusterLockRedis(
            RedisConstant.REDIS_KEY_PREFIX,
            lockKey
        );
        if (lock.acquire(RedisConstant.REDIS_LOCK_DEFAULT_TIMEOUT)) {
            //double check
            final WeekendNoticeReadCountDO weekendNoticeReadCountDO = weekendNoticeReadRepositoryService.selectByNoticeId(noticeRequestDTO.getNoticeId());
            if (weekendNoticeReadCountDO == null) {
                try {
                    lock.execute(() -> {
                        WeekendNoticeReadCountDO readCountDO = new WeekendNoticeReadCountDO();
                        readCountDO.setNoticeId(noticeRequestDTO.getNoticeId());
                        readCountDO.setReadCount(1L);
                        readCountDO.setCreateTime(new Date());
                        readCountDO.setUpdateTime(new Date());
                        weekendNoticeReadRepositoryService.insert(readCountDO);
                        return true;
                    });
                } catch (ApiException err) {
                    throw err;
                } catch (Exception e) {
                    log.error("插入", e);
                    throw new ApiException(ErrorEnum.SERVER_ERROR.getCode(), "服务端出错");
                }
            } else {
                weekendNoticeReadRepositoryService.noticeCountAdd(weekendNoticeReadCountDO);
            }
        } else {
            log.warn("redis锁获取超时,key:{}", lockKey);
            throw new ApiException(ErrorEnum.SERVER_ERROR.getCode(), "服务器繁忙,请稍后重试");
        }
    }

    After acquiring the lock, the lock may be acquired after waiting. At this time, the previous thread that released the lock may have inserted data, so inside the lock, the data still needs to be verified again. does it exist.
    This writing method is suitable for most writing scenarios that require uniqueness.

    Avoid deadlock

    How to avoid deadlock? The simplest and most effective method is: **Don't get the lock inside the lock. In short, it is best to use the lock alone, not as a nesting doll.
    Also pay attention to some implicit locks, such as databases.
    Transaction A:

    • Insert [5,7] and insert the intention lock.

    • select for update update [100,150], gap lock.
      Transaction B:

    • select for update update [90,120], gap lock.

    • Insert [4,6] and insert the intention lock.

    At this time, in a concurrent scenario, it may happen that A holds the gap lock of [5,7] and is waiting for the gap lock of transaction B[90,120]. Transaction B also Same, it's deadlocked.
    **

    By the way, let’s talk about common problems in concurrency scenarios

    Reading and writing confusion

    When writing business code and defining some tool classes or cache classes, It is easy to be careless and cause similar problems.
    For example, when building a static cache, methods such as putIfAbsent in ConcurrentHashMap are not used, and no lock is used to build it. As a result, the thread below is deleted as soon as the thread above puts it, or the cache is built twice.

    Redis or some concurrent operations release locks or resources without checking whether the current thread holds them.

    This is also mentioned in the example code of Redis lock.
    Thread A acquires the lock. At this time, B and C are waiting. Then A's execution time is too long, causing the lock to be automatically released due to timeout. At this time, B acquires the lock and executes happily. Then after A completes execution, When releasing the lock, it was not judged whether it was still held by itself, causing the lock held by B to be deleted. At this time, C acquired the lock again, and BC was executed at the same time.

    以上是Java與Mysql鎖相關知識點有哪些的詳細內容。更多資訊請關注PHP中文網其他相關文章!

    陳述:
    本文轉載於:yisu.com。如有侵權,請聯絡admin@php.cn刪除