1. 程式人生 > >synchronized和lock比較

synchronized和lock比較

ide ads 能力 nal reads throw 很多 訪問 aqs

一、synchronized的實現方案

  1.synchronized能夠把任何一個非null對象當成鎖,實現由兩種方式:

  a.當synchronized作用於非靜態方法時,鎖住的是當前對象的事例,當synchronized作用於靜態方法時,鎖住的是class實例,又因為Class的相關數據存儲在永久帶,因此靜態方法鎖相當於類的一個全局鎖。

  b.當synchronized作用於一個對象實例時,鎖住的是對應的代碼塊。

  2.synchronized鎖又稱為對象監視器(object)。

3.當多個線程一起訪問某個對象監視器的時候,對象監視器會將這些請求存儲在不同的容器中。

  >Contention List:競爭隊列,所有請求鎖的線程首先被放在這個競爭隊列中

  >Entry List:Contention List中那些有資格成為候選資源的線程被移動到Entry List中

  >Wait Set:哪些調用wait方法被阻塞的線程被放置在這裏

  >OnDeck:任意時刻,最多只有一個線程正在競爭鎖資源,該線程被成為OnDeck

  >Owner:當前已經獲取到所資源的線程被稱為Owner

  > !Owner:當前釋放鎖的線程

  下圖展示了他們之前的關系

技術分享圖片

二、lock的實現方案

  與synchronized不同的是lock是純java手寫的,與底層的JVM無關。在java.util.concurrent.locks包中有很多Lock的實現類,常用的有ReenTrantLock、ReadWriteLock(實現類有ReenTrantReadWriteLock)

,其實現都依賴java.util.concurrent.AbstractQueuedSynchronizer類(簡稱AQS),實現思路都大同小異,因此我們以ReentrantLock作為講解切入點。

分析之前我們先來花點時間看下AQS。AQS是我們後面將要提到的CountDownLatch/FutureTask/ReentrantLock/RenntrantReadWriteLock/Semaphore的基礎,因此AQS也是Lock和Excutor實現的基礎。它的基本思想就是一個同步器,支持獲取鎖和釋放鎖兩個操作。

  技術分享圖片

  要支持上面鎖獲取、釋放鎖就必須滿足下面的條件:

  1、 狀態位必須是原子操作的

  2、 阻塞和喚醒線程

  3、 一個有序的隊列,用於支持鎖的公平性

  場景:可定時的、可輪詢的與可中斷的鎖獲取操作,公平隊列,或者非塊結構的鎖。

  使用用法介紹

  

public class Test {
    private ArrayList<Integer> arrayList = new ArrayList<Integer>();
    private Lock lock = new ReentrantLock();    //註意這個地方
    public static void main(String[] args)  {
        final Test test = new Test();
         
        new Thread(){
            public void run() {
                test.insert(Thread.currentThread());
            };
        }.start();
         
        new Thread(){
            public void run() {
                test.insert(Thread.currentThread());
            };
        }.start();
    }  
     
    public void insert(Thread thread) {
        lock.lock();
        try {
            System.out.println(thread.getName()+"得到了鎖");
            for(int i=0;i<5;i++) {
                arrayList.add(i);
            }
        } catch (Exception e) {
            // TODO: handle exception
        }finally {
            System.out.println(thread.getName()+"釋放了鎖");
            lock.unlock();
        }
    }
}

tryLock()的使用方法

public class Test {
    private ArrayList<Integer> arrayList = new ArrayList<Integer>();
    private Lock lock = new ReentrantLock();    //註意這個地方
    public static void main(String[] args)  {
        final Test test = new Test();
         
        new Thread(){
            public void run() {
                test.insert(Thread.currentThread());
            };
        }.start();
         
        new Thread(){
            public void run() {
                test.insert(Thread.currentThread());
            };
        }.start();
    }  
     
    public void insert(Thread thread) {
        if(lock.tryLock()) {
            try {
                System.out.println(thread.getName()+"得到了鎖");
                for(int i=0;i<5;i++) {
                    arrayList.add(i);
                }
            } catch (Exception e) {
                // TODO: handle exception
            }finally {
                System.out.println(thread.getName()+"釋放了鎖");
                lock.unlock();
            }
        } else {
            System.out.println(thread.getName()+"獲取鎖失敗");
        }
    }
}

lockInterruptibly()

public class Test {
    private Lock lock = new ReentrantLock();   
    public static void main(String[] args)  {
        Test test = new Test();
        MyThread thread1 = new MyThread(test);
        MyThread thread2 = new MyThread(test);
        thread1.start();
        thread2.start();
         
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread2.interrupt();
    }  
     
    public void insert(Thread thread) throws InterruptedException{
        lock.lockInterruptibly();   //註意,如果需要正確中斷等待鎖的線程,必須將獲取鎖放在外面,然後將InterruptedException拋出
        try {  
            System.out.println(thread.getName()+"得到了鎖");
            long startTime = System.currentTimeMillis();
            for(    ;     ;) {
                if(System.currentTimeMillis() - startTime >= Integer.MAX_VALUE)
                    break;
                //插入數據
            }
        }
        finally {
            System.out.println(Thread.currentThread().getName()+"執行finally");
            lock.unlock();
            System.out.println(thread.getName()+"釋放了鎖");
        }  
    }
}
 
class MyThread extends Thread {
    private Test test = null;
    public MyThread(Test test) {
        this.test = test;
    }
    @Override
    public void run() {
         
        try {
            test.insert(Thread.currentThread());
        } catch (InterruptedException e) {
            System.out.println(Thread.currentThread().getName()+"被中斷");
        }
    }
}

  

ReentrantReadWriteLock

public class Test {
    private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
     
    public static void main(String[] args)  {
        final Test test = new Test();
         
        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();
        }
    }
}

 另外在ReentrantLock類中定義了很多方法,比如:

  isFair() //判斷鎖是否是公平鎖

  isLocked() //判斷鎖是否被任何線程獲取了

  isHeldByCurrentThread() //判斷鎖是否被當前線程獲取了

  hasQueuedThreads() //判斷是否有線程在等待該鎖

  在ReentrantReadWriteLock中也有類似的方法,同樣也可以設置為公平鎖和非公平鎖。不過要記住,ReentrantReadWriteLock並未實現Lock接口,它實現的是ReadWriteLock接口。

  主要從以下幾個特點介紹:

  1.可重入鎖

    如果鎖具備可重入性,則稱作為可重入鎖。像synchronized和ReentrantLock都是可重入鎖,可重入性在我看來實際上表明了鎖的分配機制:基於線程的分配,而不是基於方法調用的分配。

  2.可中斷鎖

    可中斷鎖:顧名思義,就是可以相應中斷的鎖。

    在Java中,synchronized就不是可中斷鎖,而Lock是可中斷鎖。

    如果某一線程A正在執行鎖中的代碼,另一線程B正在等待獲取該鎖,可能由於等待時間過長,線程B不想等待了,想先處理其他事情,我們可以讓它中斷自己或者在別的線程中中斷它,這種就是可中斷鎖。

  3.公平鎖和非公平鎖

    公平鎖以請求鎖的順序來獲取鎖,非公平鎖則是無法保證按照請求的順序執行。synchronized就是非公平鎖,它無法保證等待的線程獲取鎖的順序。而對於ReentrantLock和ReentrantReadWriteLock,它默認情況下是非公平鎖,但是可以設置為公平鎖。

    參數為true時表示公平鎖,不傳或者false都是為非公平鎖。

ReentrantLock lock = new ReentrantLock(true);

  4.讀寫鎖

  讀寫鎖將對一個資源(比如文件)的訪問分成了2個鎖,一個讀鎖和一個寫鎖。

  正因為有了讀寫鎖,才使得多個線程之間的讀操作不會發生沖突。

  ReadWriteLock就是讀寫鎖,它是一個接口,ReentrantReadWriteLock實現了這個接口。

  可以通過readLock()獲取讀鎖,通過writeLock()獲取寫鎖。

三、總結

  1.synchronized

  優點:實現簡單,語義清晰,便於JVM堆棧跟蹤,加鎖解鎖過程由JVM自動控制,提供了多種優化方案,使用更廣泛

  缺點:悲觀的排他鎖,不能進行高級功能

  2.lock

  優點:可定時的、可輪詢的與可中斷的鎖獲取操作,提供了讀寫鎖、公平鎖和非公平鎖  

  缺點:需手動釋放鎖unlock,不適合JVM進行堆棧跟蹤

  3.相同點 

  都是可重入鎖

ReenTrantLock獨有的能力:

1. ReenTrantLock可以指定是公平鎖還是非公平鎖。而synchronized只能是非公平鎖。所謂的公平鎖就是先等待的線程先獲得鎖。

2. ReenTrantLock提供了一個Condition(條件)類,用來實現分組喚醒需要喚醒的線程們,而不是像synchronized要麽隨機喚醒一個線程要麽喚醒全部線程。

3. ReenTrantLock提供了一種能夠中斷等待鎖的線程的機制,通過lock.lockInterruptibly()來實現這個機制。

什麽情況下使用ReenTrantLock:

答案是,如果你需要實現ReenTrantLock的三個獨有功能時。

參考:

https://www.cnblogs.com/jiangds/p/6476293.html

https://www.cnblogs.com/handsomeye/p/5999362.html

https://www.cnblogs.com/baizhanshi/p/7211802.html

synchronized和lock比較