Java 中的鎖分類以及鎖的原理

鎖的分類

鎖的分類及基本概念

樂觀鎖:假定沒有衝突,在修改數據如果發現數據和之前獲取的不一致,則讀取最新的數據,再次嘗試修改。
悲觀鎖:假定會發生衝突,同步所有的數據的相關操作,從讀數據就開始上鎖。

獨佔鎖:給資源加上寫鎖,當前線程可以修改資源,其它線程不能再加鎖(單寫)
共享鎖:給資源加上讀鎖後只能讀不能改,不能加寫鎖(多讀)

重入鎖:同時加兩次鎖,不會出現死鎖(再次拿到鎖)
可重入鎖:同時加兩次鎖,會出現死鎖(阻塞)

公平鎖:搶到鎖的順序和搶鎖順序相同則爲公平鎖
非公平鎖:搶到鎖的順序和搶鎖順序無關。

synchronized 的特性
1. 用於實例方法,靜態方法時,隱式指定鎖對象
2. 用於代碼塊時,顯示指定鎖對象
3. 鎖的作用域:對象鎖,類鎖,分佈式鎖
4. 是可重入鎖,也是獨享鎖和悲觀鎖

/**
*	測試 synchronized  的可重入性
*/
public class RecursiveTest {

    public static void main(String[] args) throws InterruptedException {
        recursive();
    }

    public static synchronized void recursive() throws InterruptedException {

        System.out.println("here i am ...");
        Thread.sleep(1000L);
        
        recursive();
    }
}

Java對象在內存中的佈局:

示例代碼:

public class Demo {
    public static void main(String[] args) {
        int a = 1;

        SalesOrder order = new SalesOrder();
        order.cust = new Customer();
    }
}

class SalesOrder {
    Long id = 1001L;
    int age = 50;

    Customer cust;

    public void discount() {
        //do something
    }
}

class Customer {
    String name = "Julie";
    int age = 18;
    boolean gender = false;
}

在這裏插入圖片描述

對象頭部
在這裏插入圖片描述
輕量級鎖:
在這裏插入圖片描述
當沒有成功的線程自旋達到一定次數時,鎖升級爲重量級鎖:
在這裏插入圖片描述
在這裏插入圖片描述

鎖的升級過程:

根據上述,鎖有四個狀態,分別爲:

  1. 未鎖定
  2. 偏向鎖
  3. 輕量級鎖
  4. 重量級鎖
    下面爲鎖之間升級的狀態圖:
    在這裏插入圖片描述

鎖的本質:

爭搶能夠訪問資源的一種權限。

Lock接口API:

public interface Lock {
 
 //獲取鎖(不死不休)
  void lock();

//嘗試獲取鎖(淺嘗輒止)
  boolean tryLock();
    
 //獲取鎖(過時不候)
  boolean tryLock(long time, TimeUnit unit) throws InterruptedException;


//獲取鎖(任人擺佈)
  void lockInterruptibly() throws InterruptedException;

  //釋放鎖
   void unlock();

	//返回一個新Condition綁定到該實例Lock實例。
   Condition newCondition();
}

Lock 包類之間的關係:
在這裏插入圖片描述

lock() 最常用
lockInterruptibly() 方法一般更昂貴,有的實現類中可能沒有實現lockInterruptibly() ,只有真的需要中斷時才使用,使用之前看看實現該方法的描述。

Condition
Object 中的wait(),notify(),notifyAll() 只能和synchronized配合使用,可以喚醒一個或者全部(單個等待集);而Condition需要與Lock配合使用,提供多個等待集合,更精確的控制。以下是使用Condition的示例:

public class Demo {

    private static Lock lock = new ReentrantLock();

    private static Condition condition = lock.newCondition();


    public static void main(String[] args) throws InterruptedException {

        Thread thread = new Thread(() -> {
            lock.lock();

            try {
                System.out.println("子線程調用condition.await()...");
                //子線程調用await()後,會自動釋放鎖以備主線程調用。
                condition.await();

                System.out.println("子線程condition.await() 後面的代碼...");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        });

        thread.start();

        Thread.sleep(5000L);
        lock.lock();

        condition.signalAll();

        lock.unlock();

    }
}

Condition中使用時應避免出現死鎖場景:在未調用await()之前調用了signal() 或signalAll() :

public class Demo {

    private static Lock lock = new ReentrantLock();

    private static Condition condition = lock.newCondition();


    public static void main(String[] args) {

        Thread thread = new Thread(() -> {

            try {
                Thread.sleep(2000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("子線程調用lock()");
            lock.lock();

            try {
                System.out.println("獲得鎖,調用condition.await()\n");
                condition.await();
                System.out.println("喚醒了...\n");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }

        });

        thread.start();
        lock.lock();

        condition.signalAll();
        System.out.println("主線程調用signalAll()");
        lock.unlock();
    }
}

synchronized 與Lock 的區別:

synchronized
優點:
1. 使用簡單,語義清晰。
2. 由JVM提供,提供了多種優化方案(鎖粗化,鎖消除,偏向鎖,輕量級鎖)
3. 鎖的釋放由JVM來完成,不用人工干預,也降低死鎖的可能性。
缺點:
4. 無法實現一些鎖的高級功能,如公平鎖,中斷鎖,讀寫鎖,共享鎖等。
Lock
優點: 所有synchronized的缺點,可以實現更多的功能 。
缺點:需要釋放鎖,使用不當可能造成死鎖。

互斥鎖的原理:

在這裏插入圖片描述
步驟說明:

  1. t1,t2,t3,t4分別都排隊搶鎖
  2. 假如t1 線程搶到鎖,這時將count用CAS(0,1)改爲1,將owner 改爲t1
  3. t2,t3,t4 未搶到鎖,依次按順序放入等待隊列waiters 中
  4. t1 用完後釋放鎖,這時count = 0,owner = null;喚醒等待隊列中的第一個t2;
  5. t2 拿到鎖後將owner改爲t2,count 進行CAS(0,1) 修改。

1.讀寫鎖

讀寫鎖:在讀與寫的操作中,讀可以併發讀,寫只能用一個線程寫。但是讀鎖與寫鎖之間是互斥的。

ReadWriteLock:
維護一對關聯鎖,一個只用於讀操作,一個只用於寫操作;讀鎖可以由多個讀線程同時持有,寫鎖是排他鎖。同一時間,讀鎖和寫鎖不能被不同的線程持有。
讀寫鎖改造HashMap:

public class Demo {

    private final Map<String, Object> map = new HashMap<>();

    private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    private final Lock readLock = readWriteLock.readLock();
    private final Lock writeLock = readWriteLock.writeLock();


    public Object get(String key) {
        readLock.lock();
        try {
            return map.get(key);
        } finally {
            readLock.unlock();
        }
    }

    public Object[] allKeys() {

        readLock.lock();
        try {
            return map.keySet().toArray();
        } finally {
            readLock.unlock();
        }
    }

    public Object put(String key, Object value) {

        writeLock.lock();
        try {
            return map.put(key, value);
        } finally {
            writeLock.unlock();
        }
    }

    public void clear() {

        writeLock.lock();
        try {
            map.clear();
        } finally {
            writeLock.unlock();
        }

    }


    public static void main(String[] args) throws InterruptedException {

        CountDownLatch countDownLatch = new CountDownLatch(5);
        Demo demo= new Demo ();

        for (int i = 0; i < 100000; i++) {
            int n = i;

            new Thread(() -> {
                if (n % 1 == 0)
                    demo.put(n + "", "n % 1 == 0");
                else
                    demo.put(n + "", n + "_123");
                countDownLatch.countDown();
            }).start();
        }

        countDownLatch.await();
        System.out.println(demo.get("39333"));

    }

}

2.讀寫鎖的原理

在這裏插入圖片描述

3.Java 中讀寫鎖的最好實現:ReentrantReadWriteLock

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章