1.2.4 Lock鎖接口實現(xiàn) -- ReentrantLock和ReadWriteLock

Lock的核心API

方法 描述
lock 獲取鎖的方法,若鎖被其他線程獲取,則等待(阻塞)
lockInterruptibly 在鎖的獲取過程中可以中斷當前線程
tryLock 嘗試非阻塞地獲取鎖,立即返回
unlock 釋放鎖

提示:根據(jù)Lock接口的源碼注釋,Lock接口的實現(xiàn),具備和同步關(guān)鍵字同樣的內(nèi)存語言。

首先我們根據(jù)方法的內(nèi)容自己來實現(xiàn)一個簡單的Lock

public class kfLock implements Lock {
    //當前鎖的使用者
    volatile AtomicReference<Thread> owner = new AtomicReference<>();
    // java q 線程安全
    volatile LinkedBlockingQueue<Thread> waiters = new LinkedBlockingQueue<>();

    public boolean tryLock(){
        return owner.compareAndSet(null,Thread.currentThread());
    }

    public void lock(){
        boolean addQ = true;
        while (!tryLock()){
            if (addQ){
                //塞到等待線程隊列中
                waiters.offer(Thread.currentThread());
                addQ = false;
            }else {
                //掛起這個線程
                LockSupport.park();
                // 后續(xù),等待其他線程釋放鎖,收到通知之后繼續(xù)循環(huán)
            }
        }
        waiters.remove(Thread.currentThread());
    }

    @Override
    public void unlock(){
        // cas 修改 owner 擁有者
        if (owner.compareAndSet(Thread.currentThread(), null)) {
            Iterator<Thread> iterator = waiters.iterator();
            while (iterator.hasNext()) {
                Thread waiter = iterator.next();
                LockSupport.unpark(waiter); // 喚醒線程繼續(xù) 搶鎖
            }
        }
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {
    }

    @Override
    public Condition newCondition() {
        return null;
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return false;
    }
}

ReentrantLock

特征:獨享鎖;支持公平鎖、非公平鎖兩種模式;可重入鎖。

默認的ReentrantLock實現(xiàn)是非公平鎖,因為相比公平鎖,非公平鎖性能更好。當然公平鎖能防止饑餓,某些情況下也很有用。在創(chuàng)建ReentrantLock的時候通過傳進參數(shù)true創(chuàng)建公平鎖,如果傳入的是false或沒傳參數(shù)則創(chuàng)建的是非公平鎖。

ReentrantLock lock = new ReentrantLock(true);

繼續(xù)跟進看下源碼

/**
 * Creates an instance of {@code ReentrantLock} with the
 * given fairness policy.
 *
 * @param fair {@code true} if this lock should use a fair ordering policy
 */
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}
//可以看到公平鎖和非公平鎖的實現(xiàn)關(guān)鍵在于成員變量sync的實現(xiàn)不同,這是鎖實現(xiàn)互斥同步的核心。

公平鎖和非公平鎖該如何選擇?

大部分情況下我們使用非公平鎖,因為其性能比公平鎖好很多。但是公平鎖能夠避免線程饑餓,某些情況下也很有用。

lockInterruptibly()的作用

當使用synchronized實現(xiàn)鎖時,阻塞在鎖上的線程除非獲得鎖否則將一直等待下去,也就是說這種無限等待獲取鎖的行為無法被中斷。而ReentrantLock給我們提供了一個可以響應中斷的獲取鎖的方法lockInterruptibly()。該方法可以用來解決死鎖問題。

// 可響應中斷
public class LockInterruptiblyDemo1 {
    private Lock lock = new ReentrantLock();

    public static void main(String[] args) throws InterruptedException {
        LockInterruptiblyDemo1 demo1 = new LockInterruptiblyDemo1();
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                try {
                    demo1.test(Thread.currentThread());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        Thread thread1 = new Thread(runnable);
        Thread thread2 = new Thread(runnable);
        thread1.start();
        Thread.sleep(500); // 等待0.5秒,讓thread1先執(zhí)行

        thread2.start();
        Thread.sleep(2000); // 兩秒后,中斷thread2

        thread2.interrupt();
    }

    public void test(Thread thread) throws InterruptedException {
        System.out.println(Thread.currentThread().getName() + ", 想獲取鎖");
        lock.lockInterruptibly();   //注意,如果需要正確中斷等待鎖的線程,必須將獲取鎖放在外面,然后將InterruptedException拋出
        try {
            System.out.println(thread.getName() + "得到了鎖");
            Thread.sleep(10000); // 搶到鎖,10秒不釋放
        } finally {
            System.out.println(Thread.currentThread().getName() + "執(zhí)行finally");
            lock.unlock();
            System.out.println(thread.getName() + "釋放了鎖");
        }
    }
}

獲取鎖時限時等待

ReentrantLock還給我們提供了獲取鎖限時等待的方法tryLock(),可以選擇傳入時間參數(shù),表示等待指定的時間,無參則表示立即返回鎖申請的結(jié)果:true表示獲取鎖成功,false表示獲取鎖失敗。我們可以使用該方法配合失敗重試機制來更好的解決死鎖問題。

Condition

在這里插入圖片描述

Condition由ReentrantLock對象創(chuàng)建,并且可以同時創(chuàng)建多個

static Condition notEmpty = lock.newCondition();

static Condition notFull = lock.newCondition();

Condition接口在使用前必須先調(diào)用ReentrantLock的lock()方法獲得鎖。之后調(diào)用Condition接口的await()將釋放鎖,并且在該Condition上等待,直到有其他線程調(diào)用Condition的signal()方法喚醒線程。使用方式和wait,notify類似。

  • 一個使用condition的簡單例子
// condition 實現(xiàn)隊列線程安全。
public class QueueDemo {
    final Lock lock = new ReentrantLock();
    // 指定條件的等待 - 等待有空位
    final Condition notFull = lock.newCondition();
    // 指定條件的等待 - 等待不為空
    final Condition notEmpty = lock.newCondition();

    // 定義數(shù)組存儲數(shù)據(jù)
    final Object[] items = new Object[100];
    int putptr, takeptr, count;

    // 寫入數(shù)據(jù)的線程,寫入進來
    public void put(Object x) throws InterruptedException {
        lock.lock();
        try {
            while (count == items.length) // 數(shù)據(jù)寫滿了
                notFull.await(); // 寫入數(shù)據(jù)的線程,進入阻塞
            items[putptr] = x;
            if (++putptr == items.length) putptr = 0;
            ++count;
            notEmpty.signal(); // 喚醒指定的讀取線程
        } finally {
            lock.unlock();
        }
    }
    // 讀取數(shù)據(jù)的線程,調(diào)用take
    public Object take() throws InterruptedException {
        lock.lock();
        try {
            while (count == 0)
                notEmpty.await(); // 線程阻塞在這里,等待被喚醒
            Object x = items[takeptr];
            if (++takeptr == items.length) takeptr = 0;
            --count;
            notFull.signal(); // 通知寫入數(shù)據(jù)的線程,告訴他們?nèi)∽吡藬?shù)據(jù),繼續(xù)寫入
            return x;
        } finally {
            lock.unlock();
        }
    }
}

ReadWriteLock

維護一對關(guān)聯(lián)鎖,一個用于只讀操作,一個用于寫入;讀鎖可以由多個讀線程同時持有,寫鎖是排他的。

適合讀取線程比寫入線程多的場景,改進互斥鎖的性能,示例場景:緩存組件、集合的并發(fā)線程安全改造。

鎖降級:指的是寫鎖降級成為讀鎖。把持住當前擁有的寫鎖的同時,再獲取到讀鎖,隨后釋放寫鎖的過程。

寫鎖是線程獨占,讀鎖是線程共享,所以寫 -> 讀是升級。(讀 -> 寫,是不能實現(xiàn)的)

// 讀寫鎖(既保證了讀數(shù)據(jù)的效率,也保證數(shù)據(jù)的一致性)
public class ReentrantReadWriteLockDemo {
    ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    public static void main(String[] args) {
        final ReentrantReadWriteLockDemo readWriteLockDemo = new ReentrantReadWriteLockDemo();
        // 多線程同時讀/寫
        new Thread(() -> {
            readWriteLockDemo.read(Thread.currentThread());
        }).start();

        new Thread(() -> {
            readWriteLockDemo.read(Thread.currentThread());
        }).start();

        new Thread(() -> {
            readWriteLockDemo.write(Thread.currentThread());
        }).start();
    }

    // 多線程讀,共享鎖
    public void read(Thread thread) {
        readWriteLock.readLock().lock();
        try {
            long start = System.currentTimeMillis();
            while (System.currentTimeMillis() - start <= 1) {
                System.out.println(thread.getName() + "正在進行“讀”操作");
            }
            System.out.println(thread.getName() + "“讀”操作完畢");
        } finally {
            readWriteLock.readLock().unlock();
        }
    }

    /**
     * 寫
     */
    public void write(Thread thread) {
        readWriteLock.writeLock().lock();
        try {
            long start = System.currentTimeMillis();
            while (System.currentTimeMillis() - start <= 1) {
                System.out.println(thread.getName() + "正在進行“寫”操作");
            }
            System.out.println(thread.getName() + "“寫”操作完畢");
        } finally {
            readWriteLock.writeLock().unlock();
        }
    }
}
// 緩存示例
public class CacheDataDemo {
    // 創(chuàng)建一個map用于緩存
    private Map<String, Object> map = new HashMap<>();
    private static ReadWriteLock rwl = new ReentrantReadWriteLock();

    public static void main(String[] args) {
        // 1 讀取緩存里面的數(shù)據(jù)
        // cache.query()
        // 2 如果換成沒數(shù)據(jù),則取數(shù)據(jù)庫里面查詢  database.query()
        // 3 查詢完成之后,數(shù)據(jù)塞到塞到緩存里面 cache.put(data)
    }

    public Object get(String id) {
        Object value = null;
        // 首先開啟讀鎖,從緩存中去取
        rwl.readLock().lock();
        try {
            if (map.get(id) == null) {
                // TODO database.query();  全部查詢數(shù)據(jù)庫 ,緩存雪崩
                // 必須釋放讀鎖
                rwl.readLock().unlock();
                // 如果緩存中沒有釋放讀鎖,上寫鎖。如果不加鎖,所有請求全部去查詢數(shù)據(jù)庫,就崩潰了
                rwl.writeLock().lock(); // 所有線程在此處等待  1000  1  999 (在同步代碼里面再次檢查是否緩存)
                try {
                    // 雙重檢查,防止已經(jīng)有線程改變了當前的值,從而出現(xiàn)重復處理的情況
                    if (map.get(id) == null) {
                        // TODO value = ...如果緩存沒有,就去數(shù)據(jù)庫里面讀取
                    }
                    rwl.readLock().lock(); // 加讀鎖降級寫鎖,這樣就不會有其他線程能夠改這個值,保證了數(shù)據(jù)一致性
                } finally {
                    rwl.writeLock().unlock(); // 釋放寫鎖@
                }
            }
        } finally {
            rwl.readLock().unlock();
        }
        return value;
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容