主頁 > 後端開發 > 什么是Lock執行緒鎖?與synchronized區別在哪?Lock鎖是如何實作等待通知的?如何實作執行緒順序執行?

什么是Lock執行緒鎖?與synchronized區別在哪?Lock鎖是如何實作等待通知的?如何實作執行緒順序執行?

2021-02-01 12:16:29 後端開發

文章目錄

  • 前言
  • 一.synchronized的缺陷
  • 二.Lock介面的特性
  • 三.Lock介面基本的方法:
  • 四、Lock介面的實作類:ReentrantLock
  • 五、ReadWriteLock
  • 六、Condition介面
    • 簡介
    • Condition介面的常見方法:
    • . 使用單個Condition實體實作等待/通知機制:
    • 使用多個Condition實體實作等待/通知機制:
    • 使用Condition實作順序執行
  • 七、ReentrantReadWriteLock
  • 八、公平鎖與非公平鎖
  • 九、可重入鎖


前言

本章內容并沒有特別之處,主要目的是為了整理自己學習筆記,作為一個鞏固和更深刻的認識,方便后面自己查閱,也供一些朋友參考
當然了本章的內容學習,需要先了解執行緒同步,執行緒鎖的內容后再學習,
也就是上一期的篇章,可供參考,
什么是執行緒同步?什么是執行緒安全?什么是執行緒鎖?synchronized怎么用?如何理解wait()和sleep()的區別?超詳細例程講解-------手摸手教會小白


一.synchronized的缺陷

上一章我們提到synchronized使用的幾個案例,它們特性是這樣的,當一個執行緒在執行帶鎖的方法時,其他需要使用這個方法的執行緒只能等待 ,等待獲取鎖的執行緒釋放鎖,

序號獲取鎖的執行緒釋放鎖的兩種情況:
1獲取鎖的執行緒執行完了該代碼塊,然后執行緒釋放對鎖的占有.
2執行緒執行發生例外,此時JVM會讓執行緒自動釋放鎖,

如果獲取鎖的執行緒由于要等待IO或者其他原因(比如呼叫sleep方法)被阻塞了,但是又沒有釋放鎖,其他執行緒便只能干巴巴地等待,這十分影響程式執行效率,因此就需要有一種機制可以不讓等待的執行緒一直無期限地等待下去(比如只等待一定的時間或者能夠回應中斷),通過Lock就可以辦到,通過Lock可以知道執行緒有沒有成功獲取到鎖,這個是synchronized無法辦到的,
  總結一下:Lock提供了比synchronized更多的功能,但是要注意以下兩點:
  (1)Lock不是Java語言內置的,synchronizedJava語言的關鍵字,因此是內置特性,Lock是一個類,通過這個類可以實作同步訪問;
  (2)Lock和synchronized有一點非常大的不同,采用synchronized不需要用戶去手動釋放鎖,當synchronized方法或者synchronized代碼塊執行完之后,系統會自動讓執行緒釋放對鎖的占用;而Lock則必須要用戶去手動釋放鎖,如果沒有主動釋放鎖,就有可能導致出現死鎖現象,

二.Lock介面的特性

特性描述
嘗試非阻塞地獲取鎖當前執行緒嘗試獲取鎖,如果這一時刻鎖沒有被其他執行緒獲取到,則成功獲取并持有鎖
能被中斷地獲取鎖獲取到鎖的執行緒能夠回應中斷,當獲取到鎖的執行緒被中斷時,中斷例外將會被拋出,同時鎖會被釋放
超時獲取鎖在指定的截止時間之前獲取鎖, 超過截止時間后仍舊無法獲取則回傳

三.Lock介面基本的方法:

方法名稱描述
void lock()獲得鎖,如果鎖不可用,則當前執行緒將被禁用以進行執行緒調度,并處于休眠狀態,直到獲取鎖,
void lockInterruptibly()獲取鎖,如果可用并立即回傳,如果鎖不可用,那么當前執行緒將被禁用以進行執行緒調度,并且處于休眠狀態,和lock()方法不同的是在鎖的獲取中可以中斷當前執行緒(相應中斷,
Condition newCondition()獲取等待通知組件,該組件和當前的鎖系結,當前執行緒只有獲得了鎖,才能呼叫該組件的wait()方法,而呼叫后,當前執行緒將釋放鎖,
boolean tryLock()只有在呼叫時才可以獲得鎖,如果可用,則獲取鎖定,并立即回傳值為true;如果鎖不可用,則此方法將立即回傳值為false ,
boolean tryLock(long time, TimeUnit unit)超時獲取鎖,當前執行緒在一下三種情況下會回傳: 1. 當前執行緒在超時時間內獲得了鎖;2.當前執行緒在超時時間內被中斷;3.超時時間結束,回傳false.
void unlock()釋放鎖,

由于在前面講到如果采用Lock,必須主動釋放鎖,并且在發生例外時,不會自動釋放鎖,因此一般來說,使用Lock必須在try{}catch{}塊中進行,并且將釋放鎖的操作放在finally塊中進行,以保證鎖一定被被釋放防止死鎖的發生,通常使用Lock來進行同步的話,是以下面這種形式去使用的:

Lock lock = ...;
lock.lock();
try{
    //處理任務
}catch(Exception ex){
     
}finally{
    lock.unlock();   //釋放鎖
}

tryLock()方法是有回傳值的,它表示用來嘗試獲取鎖,如果獲取成功,則回傳true,如果獲取失敗(即鎖已被其他執行緒獲取),則回傳false,也就說這個方法無論如何都會立即回傳,在拿不到鎖時不會一直在那等待
tryLock(long time, TimeUnit unit)方法和tryLock()方法是類似的,只不過區別在于這個方法在拿不到鎖時會等待一定的時間,在時間期限之內如果拿不到鎖,就回傳false,如果如果一開始拿到鎖或者在等待期間內拿到了鎖,則回傳true,
  所以,一般情況下通過tryLock來獲取鎖時是這樣使用的:

Lock lock = ...;
if(lock.tryLock()) {
     try{
         //處理任務
     }catch(Exception ex){
         
     }finally{
         lock.unlock();   //釋放鎖
     } 
}else {
    //如果不能獲取鎖,則直接做其他事情
}

lockInterruptibly()方法比較特殊,當通過這個方法去獲取鎖時,如果執行緒正在等待獲取鎖,則這個執行緒回應中斷,即中斷執行緒的等待狀態,也就使說,當兩個執行緒同時通過lock.lockInterruptibly()想獲取某個鎖時,假若此時執行緒A獲取到了鎖,而執行緒B只有在等待,那么對執行緒B呼叫threadB.interrupt()方法能夠中斷執行緒B的等待程序
由于lockInterruptibly()的宣告中拋出了例外,所以lock.lockInterruptibly()必須放在try塊中或者在呼叫lockInterruptibly()的方法外宣告拋出InterruptedException,
因此lockInterruptibly()一般的使用形式如下:

public void method() throws InterruptedException {
    lock.lockInterruptibly();
    try {  
     //.....
    }
    finally {
        lock.unlock();
    }  
}

當一個執行緒獲取了鎖之后,是不會被interrupt()方法中斷的,

四、Lock介面的實作類:ReentrantLock

ReentrantLock,意思是“可重入鎖”,ReentrantLock是唯一實作了Lock介面的類,并且ReentrantLock提供了更多的方法,
ReentrantLock類常見方法:
構造方法:

方法名稱描述
ReentrantLock()創建一個 ReentrantLock的實體,
ReentrantLock(boolean fair)創建一個特定鎖型別(公平鎖/非公平鎖)的ReentrantLock的實體

ReentrantLock類常見方法(不包括介面中的方法):

方法名稱描述
int getHoldCount()查詢當前執行緒保持此鎖定的個數,也就是呼叫lock()方法的次數,
protected Thread getOwner()回傳當前擁有此鎖的執行緒,如果不擁有,則回傳 null
protected Collection getQueuedThreads()回傳包含可能正在等待獲取此鎖的執行緒的集合
int getQueueLength()回傳等待獲取此鎖的執行緒數的估計,
protected Collection getWaitingThreads(Condition condition)回傳包含可能在與此鎖相關聯的給定條件下等待的執行緒的集合,
int getWaitQueueLength(Condition condition)回傳與此鎖相關聯的給定條件等待的執行緒數的估計,
boolean hasQueuedThread(Thread thread)查詢給定執行緒是否等待獲取此鎖,
boolean hasQueuedThreads()查詢是否有執行緒正在等待獲取此鎖,
boolean hasWaiters(Condition condition)查詢任何執行緒是否等待與此鎖相關聯的給定條件
boolean isFair()如果此鎖的公平設定為true,則回傳 true ,
boolean isHeldByCurrentThread()查詢此鎖是否由當前執行緒持有,
boolean isLocked()查詢此鎖是否由任何執行緒持有,

五、ReadWriteLock

ReentrantLock(排他鎖)具有完全互斥排他的效果,即同一時刻只允許一個執行緒訪問,這樣做雖然雖然保證了實體變數的執行緒安全性,但效率非常低下,ReadWriteLock介面就是為了解決這個問題,
讀寫鎖維護了兩個鎖,一個是讀操作相關的鎖也成為共享鎖,一個是寫操作相關的鎖 也稱為排他鎖,通過分離讀鎖和寫鎖,其并發性比一般排他鎖有了很大提升,
ReadWriteLock介面,在它里面只定義了兩個方法:

public interface ReadWriteLock {
    Lock readLock();
    Lock writeLock();
}

一個用來獲取讀鎖,一個用來獲取寫鎖,也就是說將檔案的讀寫操作分開,分成2個鎖來分配給執行緒,從而使得多個執行緒可以同時進行讀操作,

六、Condition介面

簡介

synchronized關鍵字與 wait()notify/notifyAll() 方法相結合可以實作 等待/通知機制,ReentrantLock類當然也可以實作,但是需要借助于Condition介面與newCondition() 方法,Condition是JDK1.5之后才有的,它具有很好的靈活性,比如可以實作多路通知功能也就是在一個Lock物件中可以創建多個Condition實體(即物件監視器),執行緒物件可以注冊在指定的Condition中,從而可以有選擇性的進行執行緒通知,在調度執行緒上更加靈活,
在使用notify/notifyAll()方法進行通知時,被通知的執行緒是由JVM選擇的,使用ReentrantLock類結合Condition實體可以實作“選擇性通知”,這個功能非常重要,而且是Condition介面默認提供的,
而synchronized關鍵字就相當于整個Lock物件中只有一個Condition實體,所有的執行緒都注冊在它一個身上,如果執行notifyAll()方法的話就會通知所有處于等待狀態的執行緒這樣會造成很大的效率問題,而Condition實體的signalAll()方法 只會喚醒注冊在該Condition實體中的所有等待執行緒# 五、Condition介面

Condition介面的常見方法:

方法名稱描述
void await()相當于Object類的wait方法
boolean await(long time, TimeUnit unit)相當于Object類的wait(long timeout)方法
signal()相當于Object類的notify方法
signalAll()相當于Object類的notifyAll方法

. 使用單個Condition實體實作等待/通知機制:

public class UseSingleConditionWaitNotify {
public static void main(String[] args) throws InterruptedException {
        MyService service = new MyService();
        ThreadA a = new ThreadA(service);
        a.start();
        Thread.sleep(3000);
        service.signal();
        }
static public class MyService {
    private Lock lock = new ReentrantLock();
    public Condition condition = lock.newCondition();
    public void await() {
        lock.lock();
        try {
            System.out.println(" await時間為" + System.currentTimeMillis());
            condition.await();
            System.out.println("這是condition.await()方法之后的陳述句,condition.signal()方法之后我才被執行");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void signal() throws InterruptedException {
        lock.lock();
        try {
            System.out.println("signal時間為" + System.currentTimeMillis());
            condition.signal();
            Thread.sleep(3000);
            System.out.println("這是condition.signal()方法之后的陳述句");
        } finally {
            lock.unlock();
        }
    }
}
static public class ThreadA extends Thread {
    private MyService service;
    public ThreadA(MyService service) {
        super();
        this.service = service;
    }
    @Override
    public void run() {
        service.await();
    }
}
}

執行結果

await時間為1612080501270
signal時間為1612080504272
這是condition.signal()方法之后的陳述句
這是condition.await()方法之后的陳述句,condition.signal()方法之后我才被執行

可以看到,await()方法和signal()方法實作了與wait()、notify()同樣的功能,
注意: 必須在condition.await()方法呼叫之前呼叫lock.lock()代碼獲得同步監視器,不然會報錯,

使用多個Condition實體實作等待/通知機制:

public class UseMoreConditionWaitNotify {
    public static void main(String[] args) throws InterruptedException {
        MyserviceMoreCondition service = new MyserviceMoreCondition();
        ThreadA a = new ThreadA(service);
        a.setName("A");
        a.start();
        ThreadB b = new ThreadB(service);
        b.setName("B");
        b.start();
        Thread.sleep(3000);
        service.signalAll_A();
    }
    static public class ThreadA extends Thread {
        private MyserviceMoreCondition service;
        public ThreadA(MyserviceMoreCondition service) {
            super();
            this.service = service;
        }
        @Override
        public void run() {
            service.awaitA();
        }
    }
    static public class ThreadB extends Thread {
        private MyserviceMoreCondition service;
        public ThreadB(MyserviceMoreCondition service) {
            super();
            this.service = service;
        }
        @Override
        public void run() {
            service.awaitB();
        }
    }

}

MyserviceMoreCondition

public class MyserviceMoreCondition {
    private Lock lock = new ReentrantLock();
    public Condition conditionA = lock.newCondition();
    public Condition conditionB = lock.newCondition();
    public void awaitA() {
        lock.lock();
        try {
            System.out.println("begin awaitA時間為" + System.currentTimeMillis()
                    + " ThreadName=" + Thread.currentThread().getName());
            conditionA.await();
            System.out.println("  end awaitA時間為" + System.currentTimeMillis()
                    + " ThreadName=" + Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void awaitB() {
        lock.lock();
        try {
            System.out.println("begin awaitB時間為" + System.currentTimeMillis()
                    + " ThreadName=" + Thread.currentThread().getName());
            conditionB.await();
            System.out.println("  end awaitB時間為" + System.currentTimeMillis()
                    + " ThreadName=" + Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void signalAll_A() {
        lock.lock();
        try {
            System.out.println("  signalAll_A時間為" + System.currentTimeMillis()
                    + " ThreadName=" + Thread.currentThread().getName());
            conditionA.signalAll();
        } finally {
            lock.unlock();
        }
    }
    public void signalAll_B() {
        lock.lock();
        try {
            System.out.println("  signalAll_B時間為" + System.currentTimeMillis()
                    + " ThreadName=" + Thread.currentThread().getName());
            conditionB.signalAll();
        } finally {
            lock.unlock();
        }
    }
}

執行結果

begin awaitA時間為1612084608495 ThreadName=A
begin awaitB時間為1612084608512 ThreadName=B
signalAll_A時間為1612084611512 ThreadName=main
end awaitA時間為1612084611512 ThreadName=A

從結果上可以看出只有A執行緒被喚醒了,明顯的signalAll()與notifyAll()不一樣,它沒有喚醒全部wait()的執行緒,

使用Condition實作順序執行

執行緒的交叉呼叫可能產生死鎖,因此所有執行緒按順序使用鎖的話,是可以避免死鎖的,但是實際上不這么做,因為這需要知道所有需要排序的鎖,且對團隊協作開發帶來很大的難度,

public class ConditionSeqExec {
    volatile private static int nextPrintWho = 1;
    private static ReentrantLock lock = new ReentrantLock();
    final private static Condition conditionA = lock.newCondition();
    final private static Condition conditionB = lock.newCondition();
    final private static Condition conditionC = lock.newCondition();
    public static void main(String[] args) {
        Thread threadA = new Thread() {
            public void run() {
                try {
                    lock.lock();
                    //如果nextPrintWho不為1就等待
                    while (nextPrintWho != 1) {
                        conditionA.await();
                    }
                    for (int i = 0; i < 3; i++) {
                        System.out.print("ThreadA " + (i + 1)+"       " );
                    }
                    System.out.println();
                    nextPrintWho = 2;
                    //通知conditionB實體的執行緒運行
                    conditionB.signalAll();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        };
        Thread threadB = new Thread() {
            public void run() {
                try {
                    lock.lock();
                    //如果nextPrintWho不為2就等待
                    while (nextPrintWho != 2) {
                        conditionB.await();
                    }
                    for (int i = 0; i < 3; i++) {
                        System.out.print("ThreadB " + (i + 1)+"       " );
                    }
                    System.out.println();
                    nextPrintWho = 3;
                    //通知conditionC實體的執行緒運行
                    conditionC.signalAll();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        };
        Thread threadC = new Thread() {
            public void run() {
                try {
                    lock.lock();
                    //如果nextPrintWho不為3就等待
                    while (nextPrintWho != 3) {
                        conditionC.await();
                    }
                    for (int i = 0; i < 3; i++) {
                        System.out.print("ThreadC " + (i + 1)+"       " );
                    }
                    System.out.println();
                    nextPrintWho = 1;
                    //通知conditionA實體的執行緒運行
                    conditionA.signalAll();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        };
        Thread[] aArray = new Thread[5];
        Thread[] bArray = new Thread[5];
        Thread[] cArray = new Thread[5];
        for (int i = 0; i < 5; i++) {
            aArray[i] = new Thread(threadA);
            bArray[i] = new Thread(threadB);
            cArray[i] = new Thread(threadC);
            aArray[i].start();
            bArray[i].start();
            cArray[i].start();
        }
    }
}

運行結果

ThreadA 1       ThreadA 2       ThreadA 3       
ThreadB 1       ThreadB 2       ThreadB 3       
ThreadC 1       ThreadC 2       ThreadC 3       
ThreadA 1       ThreadA 2       ThreadA 3       
ThreadB 1       ThreadB 2       ThreadB 3       
ThreadC 1       ThreadC 2       ThreadC 3       
ThreadA 1       ThreadA 2       ThreadA 3       
ThreadB 1       ThreadB 2       ThreadB 3       
ThreadC 1       ThreadC 2       ThreadC 3       
ThreadA 1       ThreadA 2       ThreadA 3       
ThreadB 1       ThreadB 2       ThreadB 3       
ThreadC 1       ThreadC 2       ThreadC 3       
ThreadA 1       ThreadA 2       ThreadA 3       
ThreadB 1       ThreadB 2       ThreadB 3       
ThreadC 1       ThreadC 2       ThreadC 3   

可以看到這樣就實作了,執行緒按A->B->C->A的順序依次執行

七、ReentrantReadWriteLock

ReentrantReadWriteLock實作了上面講的ReadWriteLock介面
ReentrantReadWriteLock的特性:

特性說明
公平性選擇支持非公平(默認)和公平的鎖獲取方式,吞吐量上來看還是非公平優于公平
重進入該鎖支持重進入,以讀寫執行緒為例:讀執行緒在獲取了讀鎖之后,能夠再次獲取讀鎖,而寫執行緒在獲取了寫鎖之后能夠再次獲取寫鎖也能夠同時獲取讀鎖
鎖降級遵循獲取寫鎖、獲取讀鎖再釋放寫鎖的次序,寫鎖能夠降級稱為讀鎖

讀讀共享

/**
 * 兩個讀共享鎖
 */
public class ReadRead {
    private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    public static void main(String[] args)  {
        final ReadRead test = new ReadRead();
        new Thread(){
            public void run() {
                test.get(Thread.currentThread());
            };
        }.start();

        new Thread(){
            public void run() {
                test.get(Thread.currentThread());
            };
        }.start();
    }
    public void get(Thread thread) {
        rwl.readLock().lock();
        try {
            long start = System.currentTimeMillis();

            while(System.currentTimeMillis() - start <= 1) {
                System.out.println(thread.getName()+"正在進行讀操作");
            }
            System.out.println(thread.getName()+"讀操作完畢");
        } finally {
            rwl.readLock().unlock();
        }
    }
}

此時列印的結果為(每次結果不同):

Thread-0正在進行讀操作
Thread-0正在進行讀操作
Thread-0正在進行讀操作
Thread-0正在進行讀操作
Thread-0正在進行讀操作
Thread-0正在進行讀操作
Thread-0正在進行讀操作
Thread-0正在進行讀操作
Thread-0正在進行讀操作
Thread-1正在進行讀操作
Thread-0正在進行讀操作
Thread-1正在進行讀操作
Thread-1正在進行讀操作
Thread-0正在進行讀操作
Thread-0正在進行讀操作
Thread-0正在進行讀操作
Thread-0正在進行讀操作
Thread-0正在進行讀操作
Thread-1正在進行讀操作
Thread-1正在進行讀操作
Thread-1正在進行讀操作
Thread-1正在進行讀操作
Thread-1正在進行讀操作
Thread-1讀操作完畢
Thread-0正在進行讀操作
Thread-0讀操作完畢

說明thread1和thread2在同時進行讀操作,這樣就大大提升了讀操作的效率,
不過要注意的是,如果有一個執行緒已經占用了鎖,則此時其他執行緒如果要申請寫鎖,則申請鎖的執行緒會一直等待釋放讀鎖,
如果有一個執行緒已經占用了鎖,則此時其他執行緒如果申請寫鎖或者讀鎖,則申請的執行緒會一直等待釋放寫鎖,

// An highlighted block
var foo = 'bar';
// An highlighted block
var foo = 'bar';
// An highlighted block
var foo = 'bar';

八、公平鎖與非公平鎖

Lock鎖分為:公平鎖 和 非公平鎖,
公平鎖:表示執行緒獲取鎖的順序是按照執行緒加鎖的順序來分配的,即先來先得的FIFO先進先出順序,
非公平鎖:是隨機獲取鎖的,和公平鎖不一樣的就是先來的不一定先的到鎖,這樣可能造成某些執行緒一直拿不到鎖,結果也就是不公平的了,

/**
 * 根據ReentrantLock創建時給定不同的引數
 */
public class FairorNofairLock {
    public static void main(String[] args) throws InterruptedException {
        final Service service = new Service(true);//true為公平鎖,false為非公平鎖
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println("★執行緒" + Thread.currentThread().getName()
                        + "運行了");
                service.serviceMethod();
            }
        };
        Thread[] threadArray = new Thread[10];
        for (int i = 0; i < 10; i++) {
            threadArray[i] = new Thread(runnable);
        }
        for (int i = 0; i < 10; i++) {
            threadArray[i].start();
        }
    }
    static public class Service {
        private ReentrantLock lock;
        public Service(boolean isFair) {
            super();
            lock = new ReentrantLock(isFair);
        }
        public void serviceMethod() {
            lock.lock();
            try {
                System.out.println("ThreadName=" + Thread.currentThread().getName()
                        + "獲得鎖定");
            } finally {
                lock.unlock();
            }
        }

    }
}

使用公平鎖時的結果

★執行緒Thread-0運行了
★執行緒Thread-4運行了
ThreadName=Thread-0獲得鎖定
★執行緒Thread-1運行了
★執行緒Thread-2運行了
★執行緒Thread-3運行了
★執行緒Thread-7運行了
★執行緒Thread-9運行了
★執行緒Thread-8運行了
★執行緒Thread-6運行了
ThreadName=Thread-4獲得鎖定
ThreadName=Thread-1獲得鎖定
★執行緒Thread-5運行了
ThreadName=Thread-2獲得鎖定
ThreadName=Thread-3獲得鎖定
ThreadName=Thread-7獲得鎖定
ThreadName=Thread-9獲得鎖定
ThreadName=Thread-8獲得鎖定
ThreadName=Thread-6獲得鎖定
ThreadName=Thread-5獲得鎖定

可以看到執行緒開啟的順序時
0-4-1-2-3-7-9-8-6-5
獲得鎖的順序也是
0-4-1-2-3-7-9-8-6-5
使用非公平鎖時的結果

★執行緒Thread-0運行了
ThreadName=Thread-0獲得鎖定
★執行緒Thread-4運行了
★執行緒Thread-1運行了
ThreadName=Thread-4獲得鎖定
ThreadName=Thread-1獲得鎖定
★執行緒Thread-2運行了
★執行緒Thread-3運行了
ThreadName=Thread-2獲得鎖定
★執行緒Thread-6運行了
ThreadName=Thread-6獲得鎖定
★執行緒Thread-5運行了
ThreadName=Thread-5獲得鎖定
★執行緒Thread-9運行了
ThreadName=Thread-9獲得鎖定
ThreadName=Thread-3獲得鎖定
★執行緒Thread-7運行了
ThreadName=Thread-7獲得鎖定
★執行緒Thread-8運行了
ThreadName=Thread-8獲得鎖定

很明顯執行緒開啟的順序和獲得鎖定的順序不一致,

九、可重入鎖

如果鎖具備可重入性,則稱作為可重入鎖,像synchronized和ReentrantLock都是可重入鎖,可重入性實際上表明了鎖的分配機制:基于執行緒的分配,而不是基于方法呼叫的分配,舉個簡單的例子,當一個執行緒執行到某個synchronized方法時,比如說method1,而在method1中會呼叫另外一個synchronized方法method2,此時執行緒不必重新去申請鎖,而是可以直接執行方法method2,

class Demo{
    public synchronized void method1() {
        method2();
    }
    public synchronized void method2() {
         //........
    }
}

上述代碼中的兩個方法method1和method2都用synchronized修飾了,假如某一時刻,執行緒A執行到了method1,此時執行緒A獲取了這個物件的鎖,而由于method2也是synchronized方法,假如synchronized不具備可重入性,此時執行緒A需要重新申請鎖,但是這就會造成一個問題,因為執行緒A已經持有了該物件的鎖,而又在申請獲取該物件的鎖,這樣就會執行緒A一直等待永遠不會獲取到的鎖,


本章例程審核通過后將附上,
下一張將介紹一下樂觀鎖Atomic原理,

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/255204.html

標籤:java

上一篇:阿里云天池在線編程1月30號 No.56周賽

下一篇:利用Arcpy和Geopandas進行多規比對

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more