亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Java的Lock接口與讀寫鎖詳解

 更新時(shí)間:2023年12月08日 09:29:30   作者:GeorgiaStar  
這篇文章主要介紹了Java的Lock接口與讀寫鎖詳解,鎖是用來控制多個(gè)線程訪問共享資源的方式,一般來說,一個(gè)鎖能夠防止多個(gè)線程同時(shí)訪問共享資源,但是有些鎖可以允許多個(gè)線程并發(fā)的訪問共享資源,比如讀寫鎖,需要的朋友可以參考下

一、Lock接口與synchronized關(guān)鍵字

鎖是用來控制多個(gè)線程訪問共享資源的方式,一般來說,一個(gè)鎖能夠防止多個(gè)線程同時(shí)訪問共享資源(但是有些鎖可以允許多個(gè)線程并發(fā)的訪問共享資源,比如讀寫鎖)。

在Lock接口出現(xiàn)之前,Java程序是靠synchronized關(guān)鍵字實(shí)現(xiàn)鎖功能的,而Java SE 5之后,并發(fā)包中新增了Lock接口(以及相關(guān)實(shí)現(xiàn)類)用來實(shí)現(xiàn)鎖功能,它提供了與synchronized關(guān)鍵字類似的同步功能,只是在使用時(shí)需要顯式地獲取和釋放鎖。

雖然它缺少了(通過synchronized塊或者方法所提供的)隱式獲取釋放鎖的便捷性,但是卻擁有了鎖獲取與釋放的可操作性、可中斷的獲取鎖以及超時(shí)獲取鎖等多種synchronized關(guān)鍵字所不具備的同步特性。

使用synchronized關(guān)鍵字將會(huì)隱式地獲取鎖,但是它將鎖的獲取和釋放固化了,也就是先獲取再釋放。當(dāng)然,這種方式簡化了同步的管理,可是擴(kuò)展性沒有顯式的鎖獲取和釋放來的好。

synchronized關(guān)鍵字在使用的過程中會(huì)有如下幾個(gè)問題:

1. 不可控性,無法做到隨心的加鎖和釋放鎖;

2. 效率比較低下,比如我們現(xiàn)在并發(fā)的讀兩個(gè)文件,讀與讀之間是互不影響的,但如果給這個(gè)讀的對(duì)象使用synchronized來實(shí)現(xiàn)同步的話,那么只要有一個(gè)線程進(jìn)入了,那么其他的線程都要等待;

3. 無法知道線程是否獲取到了鎖;

Lock是一個(gè)上層的接口,其原型如下,總共提供了6個(gè)方法:

public interface Lock {
    // 用來獲取鎖,如果鎖已經(jīng)被其他線程獲取,則一直等待,直到獲取到鎖
    void lock();
    // 該方法獲取鎖時(shí),可以響應(yīng)中斷,比如現(xiàn)在有兩個(gè)線程,一個(gè)已經(jīng)獲取到了鎖,另一個(gè)線程調(diào)用這個(gè)方法正在等待鎖
    //但是此刻又不想讓這個(gè)線程一直在這死等,可以通過調(diào)用線程的Thread.interrupted()方法,來中斷線程的等待過程
    void lockInterruptibly() throws InterruptedException;
    // tryLock方法會(huì)返回bool值,該方法會(huì)嘗試著獲取鎖,如果獲取到鎖,就返回true,如果沒有獲取到鎖,就返回false,
    //但是該方法會(huì)立刻返回,而不會(huì)一直等待
    boolean tryLock();
    // 這個(gè)方法和上面的tryLock差不多是一樣的,只是會(huì)嘗試指定的時(shí)間,如果在指定的時(shí)間內(nèi)拿到了鎖,則會(huì)返回true,
    //如果在指定的時(shí)間內(nèi)沒有拿到鎖,則會(huì)返回false
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    // 釋放鎖
    void unlock();
    // 實(shí)現(xiàn)線程通信,相當(dāng)于wait和notify,后面會(huì)單獨(dú)講解
    Condition newCondition();
}

使用Lock是需要手動(dòng)釋放鎖的,但是如果程序中拋出了異常,那么就無法做到釋放鎖,有可能引起死鎖,所以我們在使用Lock的時(shí)候,有一種固定的格式,如下:

Lock l = ...;
l.lock();
try {
 // access the resource protected by this lock
} finally {// 必須使用try,最后在finally里面釋放鎖
 l.unlock();
}

在finally塊中釋放鎖,目的是保證在獲取到鎖之后,最終能夠被釋放。 不要將獲取鎖的過程寫在try塊中,因?yàn)槿绻讷@取鎖(自定義鎖的實(shí)現(xiàn))時(shí)發(fā)生了異常,異常拋出的同時(shí),也會(huì)導(dǎo)致鎖無故釋放。

Lock接口提供的synchronized關(guān)鍵字所不具備的主要特性有:

  1. 嘗試非阻塞地獲取鎖,當(dāng)前線程獲取鎖時(shí),如果鎖沒有被其他線程獲取到,則成功獲取并持有鎖;
  2. 被中斷地獲取鎖,與syncronized不同,獲取到鎖的線程能響應(yīng)中斷,當(dāng)獲取到鎖的線程被中斷時(shí),會(huì)拋出中斷異常,并釋放鎖;
  3. 超時(shí)獲取鎖,在指定的截止時(shí)間前獲取鎖,如果時(shí)間到了仍未獲取到鎖,則返回;

以多線程讀取文件來示例:

public class LockDemo {
    // new一個(gè)鎖對(duì)象,注意此處必須聲明成類對(duì)象,保持只有一把鎖,ReentrantLock是Lock的唯一實(shí)現(xiàn)類
    Lock lock = new ReentrantLock();
    public void readFile(String fileMessage){
        lock.lock();// 上鎖
        try {
            System.out.println(Thread.currentThread().getName()+"得到了鎖,正在讀取文件……");
            for (int i = 0; i< fileMessage.length(); i++){
                System.out.print(fileMessage.charAt(i));
            }
            System.out.println();
            System.out.println("文件讀取完畢!");
        } finally {
            System.out.println(Thread.currentThread().getName()+"釋放了鎖!");
            lock.unlock();
        }
    }
    public static void main(String[] args) {
        LockDemo demo = new LockDemo();
        String fileName = "H:/Java_Workspace_Console/test.txt";
        // 創(chuàng)建若干個(gè)線程
        new Thread(new Runnable() {
            @Override
            public void run() {
                demo.readFile(fileName);
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                demo.readFile(fileName);
            }
        }).start();
    }
}

如果先把鎖的那兩行代碼注釋掉,看下效果如何:

這里寫圖片描述

多個(gè)線程讀取到的內(nèi)容錯(cuò)亂。 然后我們把鎖的代碼加上,看下效果如何:

這里寫圖片描述

如果我們把上面的readFile方法前面加上synchronized關(guān)鍵字,然后把鎖去掉,效果是一樣的。 tryLock方法的使用和Lock方法的使用類似,不做過多的說明了,代碼如下:

public class TryLockDemo {
    // new一個(gè)鎖對(duì)象,注意此處必須聲明成類對(duì)象,保持只有一把鎖,ReentrantLock是Lock的唯一實(shí)現(xiàn)類
    Lock lock = new ReentrantLock();
    public void readFile(String fileMessage){
        // 上鎖
        if (lock.tryLock()) {
            try {
                System.out.println(Thread.currentThread().getName()+"得到了鎖,正在讀取文件……");
                for (int i = 0; i< fileMessage.length(); i++){
                    System.out.print(fileMessage.charAt(i));
                }
                System.out.println();
                System.out.println("文件讀取完畢!");
            } finally {
                System.out.println(Thread.currentThread().getName()+"釋放了鎖!");
                lock.unlock();
            }
        }
    }
    public static void main(String[] args) {
        TryLockDemo demo = new TryLockDemo();
        String fileName = "H:/Java_Workspace_Console/test.txt";
        // 創(chuàng)建若干個(gè)線程
        new Thread(new Runnable() {
            @Override
            public void run() {
                demo.readFile(fileName);
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                demo.readFile(fileName);
            }
        }).start();
    }
}

二、讀寫鎖

ReentrantLock是排他鎖,這些鎖在同一時(shí)刻只允許一個(gè)線程進(jìn)行訪問,而讀寫鎖在同一時(shí)刻可以允許多個(gè)讀線程訪問,但是在寫線程訪問時(shí),所有的讀線程和其他寫線程均被阻塞。讀寫鎖維護(hù)了一對(duì)鎖,一個(gè)讀鎖和一個(gè)寫鎖,通過分離讀鎖和寫鎖,使得并發(fā)性相比一般的排他鎖有了很大提升。

除了保證寫操作對(duì)讀操作的可見性以及并發(fā)性的提升之外,讀寫鎖能夠簡化讀寫交互場景的編程方式。假設(shè)在程序中定義一個(gè)共享的用作緩存數(shù)據(jù)結(jié)構(gòu),它大部分時(shí)間提供讀服務(wù)(例如查詢和搜索),而寫操作占有的時(shí)間很少,但是寫操作完成之后的更新需要對(duì)后續(xù)的讀服務(wù)可見。

一般情況下,讀寫鎖的性能都會(huì)比排它鎖好,因?yàn)榇蠖鄶?shù)場景讀是多于寫的。在讀多于寫的情況下,讀寫鎖能夠提供比排它鎖更好的并發(fā)性和吞吐量。Java并發(fā)包提供讀寫鎖的接口是ReadWriteLock:

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

該接口也有一個(gè)實(shí)現(xiàn)類ReentrantReadWriteLock,下面我們先看一下,多線程同時(shí)讀取文件時(shí),用synchronized實(shí)現(xiàn)的效果,代碼如下:

public class SyncReadDemo {
    public synchronized void get(Thread thread) {
        System.out.println("start time:"+System.currentTimeMillis());
        for(int i=0; i<5; i++){
            try {
                Thread.sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(thread.getName() + ":正在進(jìn)行讀操作……");
        }
        System.out.println(thread.getName() + ":讀操作完畢!");
        System.out.println("end time:"+System.currentTimeMillis());
    }

    public static void main(String[] args) {
        final SyncReadDemo lock = new SyncReadDemo();
        new Thread(new Runnable() {
            @Override
            public void run() {
                lock.get(Thread.currentThread());
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                lock.get(Thread.currentThread());
            }
        }).start();
    }
}

測試結(jié)果如下:

這里寫圖片描述

整個(gè)過程耗時(shí)200ms

在加了synchronized關(guān)鍵字之后,讀與讀之間,也是互斥的,也就是說,必須等待Thread-0讀完之后,才會(huì)輪到Thread-1線程讀,而無法做到同時(shí)讀文件,這種情況在大量線程同時(shí)都需要讀文件的時(shí)候,效率低下。 下面我們來測試ReadAndWriteLock的性能,代碼如下:

public class ReadAndWriteLockDemo {
    ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    public void get(Thread thread) {
        lock.readLock().lock();
        try{
            System.out.println("start time:"+System.currentTimeMillis());
            for(int i=0; i<5; i++){
                try {
                    Thread.sleep(20);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(thread.getName() + ":正在進(jìn)行讀操作……");
            }
            System.out.println(thread.getName() + ":讀操作完畢!");
            System.out.println("end time:"+System.currentTimeMillis());
        }finally{
            lock.readLock().unlock();
        }
    }

    public static void main(String[] args) {
        final ReadAndWriteLockDemo lock = new ReadAndWriteLockDemo();
        new Thread(new Runnable() {
            @Override
            public void run() {
                lock.get(Thread.currentThread());
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                lock.get(Thread.currentThread());
            }
        }).start();
    }
}

整個(gè)過程耗時(shí):100ms Thread-0和Thread-1是在同時(shí)讀取文件。不過要注意的是,如果有一個(gè)線程已經(jīng)占用了讀鎖,則此時(shí)其他線程如果要申請寫鎖,則申請寫鎖的線程會(huì)一直等待釋放讀鎖。如果有一個(gè)線程已經(jīng)占用了寫鎖,則此時(shí)其他線程如果申請寫鎖或者讀鎖,則申請的線程會(huì)一直等待釋放寫鎖。讀鎖和寫鎖是互斥的。

可重入鎖

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

舉個(gè)簡單的例子,當(dāng)一 個(gè)線程執(zhí)行到某個(gè)synchronized方法時(shí),比如說method1,而在method1中會(huì)調(diào)用另外一個(gè)synchronized方法 method2,此時(shí)線程不必重新去申請鎖,而是可以直接執(zhí)行方法method2。

可中斷鎖

可中斷鎖:顧名思義,就是可以相應(yīng)中斷的鎖。   

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

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

公平鎖

公平鎖即盡量以請求鎖的順序來獲取鎖。比如同時(shí)有多個(gè)線程在等待一個(gè)鎖,當(dāng)這個(gè)鎖被釋放時(shí),等待時(shí)間最久的線程(最先請求的線程)會(huì)獲得該所,這種就是公平鎖。   

非公平鎖即無法保證鎖的獲取是按照請求鎖的順序進(jìn)行的。這樣就可能導(dǎo)致某個(gè)或者一些線程永遠(yuǎn)獲取不到鎖。   

在Java中,synchronized就是非公平鎖,它無法保證等待的線程獲取鎖的順序。而對(duì)于ReentrantLock和ReentrantReadWriteLock,它默認(rèn)情況下是非公平鎖,但是可以在構(gòu)造函數(shù)中指定其為公平鎖。   

公平性與否是針對(duì)獲取鎖而言的,如果一個(gè)鎖是公平的,那么鎖的獲取順序就應(yīng)該符合請求的絕對(duì)時(shí)間順序,也就是FIFO。非公平性鎖可能使線程“饑餓”,為什么它又被設(shè)定成默認(rèn)的實(shí)現(xiàn)呢?

如果把每次不同線程獲取到鎖定義為1次切換,公平性鎖為了保證鎖的獲取按照FIFO原則,代價(jià)是進(jìn)行大量的線程切換。非公平性鎖雖然可能造成線程“饑餓”,但會(huì)有更少的線程切換,保證了其更大的吞吐量。

到此這篇關(guān)于Java的Lock接口與讀寫鎖詳解的文章就介紹到這了,更多相關(guān)Lock接口與讀寫鎖內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論