Java中的可重入鎖


所謂重入鎖,指的是以線程為單位,當一個線程獲取對象鎖之后,這個線程可以再次獲取本對象上的鎖,而其他的線程是不可以的。

synchronized 和   ReentrantLock 都是可重入鎖。

可重入鎖的意義在於防止死鎖。

可重入鎖簡單演示

什么是 “可重入”,可重入就是說某個線程已經獲得某個鎖,可以再次獲取鎖而不會出現死鎖。

ReentrantLock 和 synchronized 不一樣,需要手動釋放鎖,所以使用 ReentrantLock的時候一定要手動釋放鎖,並且加鎖次數和釋放次數要一樣

例如

//演示可重入鎖是什么意思,可重入,就是可以重復獲取相同的鎖,synchronized和ReentrantLock都是可重入的 //可重入降低了編程復雜性
public class WhatReentrant {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (this) {
                    System.out.println("第1次獲取鎖,這個鎖是:" + this);
                    int index = 1;
                    while (true) {
                        synchronized (this) {
                            System.out.println("第" + (++index) + "次獲取鎖,這個鎖是:" + this);
                        }
                        if (index == 10) {
                            break;
                        }
                    }
                }
            }
        }).start();
    }
}

//演示可重入鎖是什么意思
public class WhatReentrant2 {
    public static void main(String[] args) {
        ReentrantLock lock = new ReentrantLock();
        
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    lock.lock();
                    System.out.println("第1次獲取鎖,這個鎖是:" + lock);

                    int index = 1;
                    while (true) {
                        try {
                            lock.lock();
                            System.out.println("第" + (++index) + "次獲取鎖,這個鎖是:" + lock);
                            
                            try {
                                Thread.sleep(new Random().nextInt(200));
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            
                            if (index == 10) {
                                break;
                            }
                        } finally {
                            lock.unlock();
                        }

                    }

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

 可以發現沒發生死鎖,可以多次獲取相同的鎖

可重入鎖有

  • synchronized
  • ReentrantLock

使用ReentrantLock的注意點

ReentrantLock 和 synchronized 不一樣,需要手動釋放鎖,所以使用 ReentrantLock的時候一定要手動釋放鎖,並且加鎖次數和釋放次數要一樣

以下代碼演示,加鎖和釋放次數不一樣導致的死鎖

public class WhatReentrant3 {
    public static void main(String[] args) {
        ReentrantLock lock = new ReentrantLock();
        
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    lock.lock();
                    System.out.println("第1次獲取鎖,這個鎖是:" + lock);

                    int index = 1;
                    while (true) {
                        try {
                            lock.lock();
                            System.out.println("第" + (++index) + "次獲取鎖,這個鎖是:" + lock);
                            
                            try {
                                Thread.sleep(new Random().nextInt(200));
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            
                            if (index == 10) {
                                break;
                            }
                        } finally {
// lock.unlock();// 這里故意注釋,實現加鎖次數和釋放次數不一樣
                        }

                    }

                } finally {
                    lock.unlock();
                }
            }
        }).start();
        
        
        new Thread(new Runnable() {
            
            @Override
            public void run() {
                try {
                    lock.lock(); for (int i = 0; i < 20; i++) {
                        System.out.println("threadName:" + Thread.currentThread().getName());
                        try {
                            Thread.sleep(new Random().nextInt(200));
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                } finally {
                    lock.unlock();
                }
            }
        }).start();
        
        
    }
}

 可以看出,由於加鎖次數和釋放次數不一樣,第二個線程始終無法獲取到鎖,導致一直在等待

稍微改一下,在外層的finally里頭釋放9次,讓加鎖和釋放次數一樣,就沒問題了

try {
    lock.lock();
    System.out.println("第1次獲取鎖,這個鎖是:" + lock);

    int index = 1;
    while (true) {
        try {
            lock.lock();
            System.out.println("第" + (++index) + "次獲取鎖,這個鎖是:" + lock);
            
            ... 代碼省略節省篇幅...
        } finally {
//                            lock.unlock();// 這里故意注釋,實現加鎖次數和釋放次數不一樣
        }

    }

} finally {
    lock.unlock();
    
    // 在外層的finally里頭釋放9次,讓加鎖和釋放次數一樣,就沒問題了
    for (int i = 0; i < 9; i++) { lock.unlock(); }
}

實現原理

  實現原理是通過為每個鎖關聯一個請求計數器和一個占有它的線程。

當計數為0時,認為鎖是未被占有的;線程請求一個未被占有的鎖時,JVM將記錄鎖的占有者,並且將請求計數器置為1 。

  如果同一個線程再次請求這個鎖,計數將遞增;

每次占用線程退出同步塊,計數器值將遞減。直到計數器為0,鎖被釋放。

 

關於父類和子類的鎖的重入:子類覆寫了父類的synchonized方法,然后調用父類中的方法,此時如果沒有重入的鎖,那么這段代碼將產生死鎖(很好理解吧)。

例子:

比如說A類中有個方法public synchronized methodA1(){

        methodA2();

}

而且public synchronized methodA2(){

                    //具體操作

}

也是A類中的同步方法,當當前線程調用A類的對象methodA1同步方法,如果其他線程沒有獲取A類的對象鎖,那么當前線程就獲得當前A類對象的鎖,

然后執行methodA1同步方法,方法體中調用methodA2同步方法,當前線程能夠再次獲取A類對象的鎖,而其他線程是不可以的,這就是可重入鎖

代碼演示:

不可重入鎖:

public class Lock{
    private boolean isLocked = false;
    public synchronized void lock() throws InterruptedException{
        while(isLocked){    
            wait();
        }
        isLocked = true;
    }
    public synchronized void unlock(){
        isLocked = false;
        notify();
    }

使用該鎖:

public class Count{
    Lock lock = new Lock();
    public void print(){
        lock.lock();
        doAdd();
        lock.unlock();
    }
    public void doAdd(){
        lock.lock();
        //do something
        lock.unlock();
    }
}

當前線程執行print()方法首先獲取lock,接下來執行doAdd()方法就無法執行doAdd()中的邏輯,必須先釋放鎖。這個例子很好的說明了不可重入鎖。

可重入鎖:

接下來,我們設計一種可重入鎖

public class Lock{
    boolean isLocked = false; Thread lockedBy = null; int lockedCount = 0;
    public synchronized void lock()
            throws InterruptedException{
        Thread thread = Thread.currentThread();
        while(isLocked && lockedBy != thread){//鎖已經被獲取了,並且不是這個線程獲取的,就等待
            wait();
        }
        isLocked = true;
        lockedCount++;
        lockedBy = thread;
    }
    public synchronized void unlock(){
        if(Thread.currentThread() == this.lockedBy){
            lockedCount--;
            if(lockedCount == 0){
                isLocked = false;
                notify();
            }
        }
    }
}

所謂可重入,意味着線程可以進入它已經擁有的鎖的同步代碼塊兒

我們設計兩個線程調用print()方法,第一個線程調用print()方法獲取鎖,進入lock()方法,

由於初始lockedBy是null,所以不會進入while掛起當前線程,而是是增量lockedCount並記錄lockBy為第一個線程。

接着第一個線程進入doAdd()方法,由於是同一線程,所以不會進入while掛起線程,接着增量lockedCount,

第二個線程嘗試lock,由於isLocked=true,所以他不會獲取該鎖,直到第一個線程調用兩次unlock()將lockCount遞減為0,才將標記為isLocked設置為false

  可重入鎖的概念和設計思想大體如此,Java中的可重入鎖ReentrantLock設計思路也是這樣

synchronized和ReentrantLock 都是可重入鎖。

ReentrantLock與synchronized比較

  1. 前者使用靈活,但是必須手動開啟和釋放鎖
  2. 前者擴展性好,有時間鎖等候(tryLock( )),可中斷鎖等候(lockInterruptibly( )),鎖投票等,適合用於高度競爭鎖和多個條件變量的地方
  3. 前者提供了可輪詢的鎖請求,可以嘗試去獲取鎖(tryLock( )),如果失敗,則會釋放已經獲得的鎖。有完善的錯誤恢復機制,可以避免死鎖的發生。

 

https://blog.csdn.net/w8y56f/article/details/89554060

https://blog.csdn.net/qq_39101581/article/details/82144499


免責聲明!

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



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