Java開發(fā)中的各種鎖概念

前言:
本文不會深入不會深入!科普文,就是歸納一下平時我們遇到的各種鎖,這樣聽到也不會太懵逼。真正深入的還是要看書的~
在Java開發(fā)中,特別是并發(fā)編程的時候我們會和很多的鎖打交道。寫下一篇筆記記錄一下各種鎖的概念。

1.死鎖

在學(xué)習(xí)操作系統(tǒng),或者并發(fā)編程中的時候我們經(jīng)常會遇到死鎖的概念。什么是死鎖?
死鎖是指兩個或兩個以上的進(jìn)程在執(zhí)行過程中,由于競爭資源或者由于彼此通信而造成的一種阻塞的現(xiàn)象,若無外力作用,它們都將無法推進(jìn)下去。此時稱系統(tǒng)處于死鎖狀態(tài)或系統(tǒng)產(chǎn)生了死鎖,這些永遠(yuǎn)在互相等待的進(jìn)程稱為死鎖進(jìn)程。
舉個簡單的例子:
一個線程需要搶占A鎖去執(zhí)行某段代碼,搶占到了A鎖后,要搶占B鎖去繼續(xù)執(zhí)行代碼。另外一個線程則需要搶占B鎖,然后搶占A鎖去執(zhí)行代碼。這樣就會造成彼此之間阻塞。兩個線程一起卡死。
如何解決?

  • 避免寫嵌套鎖(廢話);
  • 規(guī)范嵌套鎖的順序,多個線程用戶同一個順序并且放在同一個方法里;
  • 引入超時機(jī)制(需要用到顯示鎖Lock)

2.顯示鎖,公平鎖,可重入鎖

標(biāo)題中的說的三種鎖說的就是Lock這個接口下的鎖,Lock具有這三種概念。
顯示鎖:非Java給我們提供的關(guān)鍵字去操作。而是我們自己定義鎖,然后顯示的獲取,顯示的釋放。
公平鎖:一定程度上保證線程獲得這個鎖的機(jī)會是公平的。但是這樣會大大消耗性能。
可重入鎖:看下面的代碼。這兩個是同步代碼塊。他們獲取的是同一把鎖。因此獲取鎖的是線程,不是對象。因此在線程沒有結(jié)束之前,這把鎖可以一直使用。
PS:Lock下的ReentrantLock也具有可重入性。

public class Widget {
    // 獲得了鎖
    public synchronized void doSomething() {
        ...
    }
}

public class LoggingWidget extends Widget {

    // 獲得了鎖
    public synchronized void doSomething() {
        System.out.println(toString() + ": calling doSomething");
        super.doSomething();
    }
}

3.Lock的介紹

其實synchronize從jdk一直發(fā)展到現(xiàn)在,性能已經(jīng)非常好。如果不是很有必要還是建議synchronize;因為簡單易用。不過這里還是要說一下Lock。下面是這個接口的方法。

image.png

  • lock:獲得鎖

  • lockInterruptibly:可打斷鎖

  • tryLock:嘗試獲取鎖。

  • tryLock帶時間:引入超時機(jī)制。很大程度上可以避免死鎖

  • unlock():釋放鎖

看上去是非常爽的。也非常的優(yōu)雅,畢竟啥時候加鎖上鎖的控制權(quán)在我們這里了。但是如果我們忽略了釋放鎖,或者說程序出了點(diǎn)問題,這個鎖沒有釋放。那就麻煩大了~因此還是要酌情使用!

怎么用呢?這里給一個demo

class LockDemo {
    private final ReentrantLock lock = new ReentrantLock();
    // ...

    public void m() { 
        lock.lock();  // block until condition holds
        try {
            // ... method body
        } finally {
            lock.unlock()
        }
    }
}


4.ReentrantReadWriteLock

ReentrantReadWriteLock是一個讀寫鎖

  • 取數(shù)據(jù)的時候,可以多個線程同時進(jìn)入到到臨界區(qū)(被鎖定的區(qū)域)
  • 數(shù)據(jù)的時候,無論是讀線程還是寫線程都是互斥
    當(dāng)我們讀的操作比較多。寫的操作比較少的時候可以用ReentrantReadWriteLock。
    ReentrantReadWriteLock就兩個方法:
  • readLock():得到一個可以被多個讀操作共用的毒鎖。但是用會排斥所有寫操作
  • writeLock():得到一個寫鎖。排斥其他所有讀操作和寫操作
    舉個例子
    讀鎖:
package com.lock;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class LockTest2 {
    private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    public static void main(String[] args) {
        final LockTest2 lockTest2 = new LockTest2();
        new Thread(new Runnable() {
            @Override
            public void run() {
                lockTest2.get(Thread.currentThread());
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                lockTest2.get(Thread.currentThread());
            }
        }).start();
    }

    public void get(Thread thread) {
        try {
            lock.readLock().lock();
            long start = System.currentTimeMillis();
            while (System.currentTimeMillis() - start <= 1) {
                System.out.println(thread.getName() + "正在進(jìn)行讀操作");
            }
            System.out.println(thread.getName() + "操作完畢");

        }catch (Exception e) {

        }
        finally {
            lock.readLock().unlock();
        }
    }

}
image.png

我們可以看到是交替進(jìn)行的~
寫鎖:
寫鎖只有將readLock改成writeLock就好了。這里不貼代碼了。直接觀察結(jié)果可以看出是互斥的。必須一個個來


image.png

5.悲觀鎖,樂觀鎖

悲觀鎖,樂觀鎖也是我們并發(fā)編程中常遇到的問題。在數(shù)據(jù)庫層面有為體現(xiàn)。
下面舉例子說明:

image.png

假設(shè)現(xiàn)在有兩個線程:
A線程來了,讀到了這個數(shù)據(jù),status為0;B線程改為了1了。由于數(shù)據(jù)庫的事務(wù)隔離機(jī)制。并發(fā)修改了這個數(shù)據(jù)。那數(shù)據(jù)就混亂了。
這個時候就要保持互斥性,同一個時間只能有一個線程來操作這行數(shù)據(jù)。
解決方案:
樂觀鎖:
加一個字段版本字段:
A線程讀出來的時候版本號位1,然后更新的時候加個條件update xxx where version =1;但是B線程已經(jīng)改為了2,所以會更新失敗;這樣就可以控制并發(fā)修改。一般是給用戶提示,有人已經(jīng)操作了數(shù)據(jù)。但是還是可以讀取這一行數(shù)據(jù)
悲觀鎖
SQL關(guān)鍵字FOR_UPDATE
select * from XXXX FOR_UPDATE
把查詢出來的數(shù)據(jù)鎖住了,其他人讀這條數(shù)據(jù)會阻塞。在用戶界面上會轉(zhuǎn)圈會等待。
舉例:
比如外賣。用戶下了一個單,這個時候訂單的數(shù)據(jù)異步到配送端了。此時,用戶突然申請退單了。此時數(shù)據(jù)庫訂單狀態(tài)應(yīng)該是退單狀態(tài)。然而由于時差,訂單的數(shù)據(jù)才到配送端。此時配送人員又搶單成功將訂單狀態(tài)重新改為了配送中(實際上這張訂單應(yīng)該是退單狀態(tài)的。)

6.分布式鎖

我們知道樂觀鎖,悲觀鎖是數(shù)據(jù)庫層面(單個)。如果我們分庫分表的話我們怎么處理呢?這個時候我們就要用分布式鎖。
我們可以用zookeeper,redis實現(xiàn)~具體實現(xiàn)大家可以baidu一下!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容