java並發-鎖-ReentrantLock(重入鎖)和ReentrantReadWriteLock(讀寫鎖)


  • 同步控制是並發程序必不可少的重要手段,synchronized關鍵字就是一種簡單的控制方式,除此之外,JDK內部並發包中也也提供了Lock接口,該接口中提供了lock()方法和unLock()方法對顯式加鎖和顯式釋放鎖操作進行支持。

ReentrantLock(重入鎖)

重入鎖可以完全替代synchronized關鍵字,在jdk5早期版本中重入鎖的性能遠遠好於synchronized,但從JDK6開始JDK在synchronized中做了大量的優化,是的兩者的性能差距不大,

public class Test {
    public static ReentrantLock lock = new ReentrantLock();
    public static int i = 0;
    public static void increase() {
        try {
            lock.lock();
            i++;
        }finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws Exception {
        Thread t1 = new TestThread();
        Thread t2 = new TestThread();
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(i);
    }
}
class TestThread extends Thread {
    @Override
    public void run() {
        for (int j = 0; j < 1000; j++) {
            Test.increase();
        }
    }
}

從這段代碼可以看到與synchronized相比,重入鎖有着顯示的操作過程,我們需要手動定義核實加鎖,核實釋放鎖,但也就是因為這樣,重入鎖對邏輯的控制靈活性要好於synchronized。

公平鎖

大多數情況下鎖的申請都是非公平的。如一個線程1先請求了鎖A,然后線程2頁也請求了鎖A,那么當鎖A可用時,是線程1可以獲得鎖還是線程2是不一定的,系統只是會從這個鎖的等待隊列中隨機挑選一個。

重入鎖允許我們對其公平性進行設置。公平鎖的一大特點是:它不會產生飢餓現象。只要排隊,最終你就可以獲得資源。可以使用如下構造函數創建公平鎖:

public ReentrantLock(boolean fair)

當參數fair為true,表示鎖的公平的,當然由於公平所需要維護有序隊列,因此公平鎖的實現成本比較高,性能相對也底下,所以默認都是非公平鎖

public class Test {
    public static ReentrantLock lock = new ReentrantLock(true);
    public static void main(String[] args) throws Exception {
        Thread t1 = new TestThread();
        Thread t2 = new TestThread();
        t1.start();
        t2.start();
    }
}
class TestThread extends Thread {
    @Override
    public void run() {
        while(true)
        try {
            Test.lock.lock();
            System.out.println(Thread.currentThread().getName()+"獲得鎖");
        } finally{
            Test.lock.unlock();
        }
    }
}

可以看到如上代碼制定公平鎖之后,兩個線程交替獲得鎖

Thread-1獲得鎖
Thread-0獲得鎖
Thread-1獲得鎖
Thread-0獲得鎖
Thread-1獲得鎖
Thread-0獲得鎖
Thread-1獲得鎖
Thread-0獲得鎖
Thread-1獲得鎖
Thread-0獲得鎖
Thread-1獲得鎖
Thread-0獲得鎖
Thread-1獲得鎖
...

ReentrantLock的一些其他方法:

public boolean tryLock();
//使用此方法,當前線程會嘗試獲得鎖,如果鎖未被其他線程占用,則申請鎖成功,返回true,否則會立即返回false.這種模式不會引起線程等待,因此也不會產生死鎖。

 

public boolean tryLock(long timeout, TimeUnit unit)
//在這里tryLock接收兩個參數,一個表示等待時長,一個表示計時單位,表示在一段時間范圍內如果得到鎖就返回true,否則直接返回false,不在繼續等待鎖。
public class Test implements Runnable {
    public static ReentrantLock lock = new ReentrantLock();
    @Override
    public void run() {
        try {
            if (lock.tryLock(5, TimeUnit.SECONDS)) {
                System.out.println(Thread.currentThread().getName());
                System.out.println("get lock success");
                Thread.sleep(6000);
            } else {
                System.out.println(Thread.currentThread().getName());
                System.out.println("get lock failed");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }
    public static void main(String args[]) {
        Test timeLock = new Test();
        Thread thread1 = new Thread(timeLock);
        Thread thread2 = new Thread(timeLock);
        thread1.start();
        thread2.start();
    }
}

輸出結果:

Thread-0
get lock success
Thread-1
get lock failed

 其他:

關於重入鎖的具體原理及這部分源碼的分析可以看下這篇文章http://www.cnblogs.com/xrq730/p/4979021.html

ReentrantReadWriteLock(讀寫鎖)

ReadWriteLock是JDK5開始提供的讀寫分離鎖。讀寫分離開有效的幫助減少鎖的競爭,以提升系統性能。用鎖分離的機制避免多個讀操作線程之間的等待。

讀寫鎖的訪問約束:

  • 讀-讀不互斥:讀讀之間不阻塞
  • 讀-寫互斥:讀堵塞寫,寫也阻塞讀
  • 寫-寫互斥:寫寫阻塞

如果在一個系統中讀的操作次數遠遠大於寫操作,那么讀寫鎖就可以發揮明顯的作用,提升系統性能

public class Test {
    private static Lock lock = new ReentrantLock();
    private static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
    private static Lock readLock = reentrantReadWriteLock.readLock();
    private static Lock writeLock = reentrantReadWriteLock.writeLock();
    private static int value;

    public static Object handleRead(Lock lock) throws InterruptedException {
        try {
            lock.lock();
            Thread.sleep(1000);// 模擬讀操作
            System.out.println("讀操作:" + value);
            return value;
        } finally {
            lock.unlock();
        }
    }

    public static void handleWrite(Lock lock, int index)
            throws InterruptedException {
        try {
            lock.lock();
            Thread.sleep(1000);// 模擬寫操作
            System.out.println("寫操作:" + value);
            value = index;
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws Exception {
        TestReadThread testReadThread = new TestReadThread();
        TestWriteThread testWriteThread = new TestWriteThread();
        for (int i = 0; i < 18; i++) {
            new Thread(testReadThread).start();
        }
        for (int i = 18; i < 20; i++) {
            new Thread(testWriteThread).start();
        }

    }
    
    private static class TestReadThread extends Thread {
        @Override
        public void run() {
            try {
                //Test.handleRead(lock);
                Test.handleRead(readLock);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    private static class TestWriteThread extends Thread {
        @Override
        public void run() {
            try {
                //Test.handleWrite(lock,new Random().nextInt(100));
                Test.handleWrite(writeLock,new Random().nextInt(100));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

 

這一段代碼可以清楚的表達讀寫鎖的作用,如果不使用讀寫鎖,那么整個程序執行時間大概是20s。換用讀寫鎖后只需要2s多,這里讀線程完全並行,節省了大部分時間。

 


免責聲明!

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



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