Java鎖的理解


目錄:

  1.為什么要使用鎖?

  2.鎖的類型?

 

1.為什么要使用鎖?

  通俗的說就是多個線程,也可以說多個方法同時對一個資源進行訪問時,如果不加鎖會造成線程安全問題。舉例:比如有兩張票,但是有5個人進來買,買了一張票數就減1,在他們進門的時候會判斷是否還有票,但是在他們進門的那一刻,票還一張都沒有買走。但是他們都已經進門了,過了是否有票的校驗了,所以最后票數為被減成負3,顯然是不對的,因為票不能小於0,所以需要加一個鎖,在同一時刻只能有一個人進門去買票,也就是同一個資源同一個時刻只能有一個線程進行操作,這樣在第三個人進門的時候就能判斷出票數已經賣完了,不會產生票數成負數的情況。

2.鎖的類型

  1.重入鎖

  重入鎖也叫遞歸鎖,外層的函數獲取鎖后,如果里面的函數仍然有獲取鎖的代碼,里面的函數就不用重新獲取鎖了。 比如:ReentrantLock 和 synchronized

  比如:

  

public class Test implements Runnable {
    public  synchronized void get() {
        System.out.println("name:" + Thread.currentThread().getName() + " get();");
        set();
    }

    public synchronized  void set() {
        System.out.println("name:" + Thread.currentThread().getName() + " set();");
    }

    @Override
    public void run() {
        get();
    }

    public static void main(String[] args) {
        Test ss = new Test();
        new Thread(ss).start();
        new Thread(ss).start();
        new Thread(ss).start();
    }
}
public class Test02 extends Thread {
    ReentrantLock lock = new ReentrantLock();
    public void get() {
        lock.lock();
        System.out.println(Thread.currentThread().getId());
        set();
        lock.unlock();
    }
    public void set() {
        lock.lock();
        System.out.println(Thread.currentThread().getId());
        lock.unlock();
    }
    @Override
    public void run() {
        get();
    }
    public static void main(String[] args) {
        Test ss = new Test();
        new Thread(ss).start();
        new Thread(ss).start();
        new Thread(ss).start();
    }

}
View Code

   2.讀寫鎖

  讀寫鎖:既是排他鎖,又是共享鎖。讀鎖,共享鎖,寫鎖:排他鎖

  

public class Cache {
    static Map<String, Object> map = new HashMap<String, Object>();
    static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    static Lock r = rwl.readLock();
    static Lock w = rwl.writeLock();

    // 獲取一個key對應的value
    public static final Object get(String key) {
        r.lock();
        try {
            System.out.println("正在做讀的操作,key:" + key + " 開始");
            Thread.sleep(100);
            Object object = map.get(key);
            System.out.println("正在做讀的操作,key:" + key + " 結束");
            System.out.println();
            return object;
        } catch (InterruptedException e) {

        } finally {
            r.unlock();
        }
        return key;
    }

    // 設置key對應的value,並返回舊有的value
    public static final Object put(String key, Object value) {
        w.lock();
        try {

            System.out.println("正在做寫的操作,key:" + key + ",value:" + value + "開始.");
            Thread.sleep(100);
            Object object = map.put(key, value);
            System.out.println("正在做寫的操作,key:" + key + ",value:" + value + "結束.");
            System.out.println();
            return object;
        } catch (InterruptedException e) {

        } finally {
            w.unlock();
        }
        return value;
    }

    // 清空所有的內容
    public static final void clear() {
        w.lock();
        try {
            map.clear();
        } finally {
            w.unlock();
        }
    }

    public static void main(String[] args) {
        new Thread(new Runnable() {

            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    Cache.put(i + "", i + "");
                }

            }
        }).start();
        new Thread(new Runnable() {

            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    Cache.get(i + "");
                }

            }
        }).start();
    }
}
View Code

  3.悲觀鎖

  總是假設最壞的情況,每次取數據時都認為其他線程會修改,所以都會加鎖(讀鎖、寫鎖、行鎖等),當其他線程想要訪問數據時,都需要阻塞掛起。可以依靠數據庫實現,如行  鎖、讀鎖和寫鎖等,都是在操作之前加鎖,在Java中,synchronized的思想也是悲觀鎖。

  

  4.樂觀鎖

  在更新數據的時候會判斷其他線程是否修改過數據,一般通過version版本號控制。

  具體實現,就是在數據表加一個version字段,每次更新的時候都會與上一次取出數據的版本號做比較。

  ① 線程A 和 線程B 同時取出同一條數據,這是數據的version為0.

  ② 線程A 取出數據的時候version = 0 , 這時候線程A走完業務,需要更新數據,這是會 update tabel set version = version + 1 where id = {id} and version = #{取數  據時的version}

  ③ 這時線程B 也走完業務去更新這條數據,這時執行update tabel set version = version + 1 where id = {id} and version = #{取出數據時的version} 這時候取出數據時  的version為0但是線程這條數據的version已經為1了。所以會更新失敗。在這個情況下可以寫一個循環,重新去出該條數據進行更新,知道這條數據更新成功為止。

    

  5.CAS無鎖機制(Compare and Swap)

  這種模式在多核cpu的情況下,完全沒有鎖競爭帶來的系統開銷,也沒有線程間頻繁調度帶來的開銷,因此,它要比基於鎖的方式擁有更優越的性能。

  

  什么是CAS?

  CAS 操作包含三個操作數 —— 內存位置(V)、預期原值(A)和新值(B)。 當變量的內存位置的值 V 等於原值,說明在設值的時候沒有其他線程去改變他的值,這時候將值設置為新值B。

  

  存在的問題?

  ABA問題:當線程A在判斷內存位置的值V是否等於原值之前,線程B將值改為了B,線程C又將值改為了A。這時候A線程在比較內存位置的值V是否等於原值時,這個條件返回true,但是這個值中途已經改變過了,

  解決方法,在值改變的時候加上版本號。即線程A拿到的內存位置V的值為1A,線程B修改為了2B,線程C修改為了3A,這時候內存位置值1A不等於有3A,因此就不會存在ABA問題。

  

  循環時間長,開銷大:當長時間獲取不到資源的場景,自旋鎖會一直循環,比較占用cpu資源。 可以根據一定的業務場景,讓獲取資源的操作做一些延遲。

  

  對於多個共享變量的問題:對於多個共享變量,我可以把多個共享變量放於一個對象中,當做一個共享變量來處理。

 

  6.AQS  

 

  7.分布式鎖

  在分布式的場景下,如果是單數據庫的情況下,某些場景下還可以用樂觀鎖,大部分場景想在不用的jvm中保證數據的同步,安全問題,還是需要使用緩存,數據,zookepper等實現分布式鎖。

 

 

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM