多線程之Lock接口

      之前寫了一下synchronized關鍵字的一點東西,那麼除了synchronized可以加鎖外,JUC(java.util.concurrent)提供的Lock接口也可以實現加鎖解鎖的功能。

       看完本文,希望您可以瞭解或者掌握:

       1:Lock接口的實現

        2:Condition的原理和概念

        3:ReentrantLock的實現原理,可以手寫一個簡單的ReentrantLock

         4:ReadWriteLock的概念和實現原理,可以手寫一個簡單的ReadWriteLock

         5:能夠瞭解到模板模型,AQS

        鎖的本質:

         1:加鎖,其實就是加了一種權限或者說是規則。

         2:獲得鎖,其實也就是獲得了訪問資源的權限。

一:Lock接口 

上圖是Lock接口中的一些方法,下面說下每個方法的作用:

1:lock(),lock接口是對資源進行加鎖,而且加鎖的時候是不死不休的,就是我加不到鎖,我就一直等待着,直到我加到鎖爲止。

2:tryLock(),tryLock接口是嘗試加鎖,它就是我嘗試一下去加鎖,若是鎖已經被佔用,就不會再去等了。

3:tryLock(long time, TimeUnit unit),這個接口有個超時限制,若是鎖已經被佔用,就等待一段時間,時間到了後,要是鎖還是被佔用,就放棄。

4:lockInterruptibly(),lockInterruptibly是任人擺佈的,就是說當有別的線程調用了interrupt打斷它之後,它就不會再去加鎖了。

下面是一個測試例子:

    //定義一個鎖
    public static volatile Lock lock = new ReentrantLock();

    public static void main(String[] args) throws InterruptedException {

        lock.lock();

        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("try lock start......");
                lock.tryLock();
                System.out.println("try lock end......");


                System.out.println("lock start......");
                lock.lock();
                System.out.println("lock end......");

                System.out.println("try lock start......");
                try {
                    lock.tryLock(5, TimeUnit.SECONDS);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("try lock end......");

                System.out.println("try lock start......");
                try {
                    lock.lockInterruptibly();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("try lock end......");
            }
        });
        thread.start();

        Thread.sleep(5000);
        thread.interrupt();

        Thread.sleep(2000);
        lock.unlock();
    }

二:Condition

      之前說了wait、notify,掛起和喚醒線程,那麼Lock接口中也提供了一個Condition,Condition的底層實現機制是park和unpark,我們知道park和unpark當先喚醒後掛起時不會發生死鎖,那麼Condition也是一樣,而且Condition中的掛起也有釋放鎖的語義,所以Condition在加鎖或者先喚醒後掛起兩種情況下都不會死鎖,下面看下栗子:

    //定義一個鎖
    static Lock lock = new ReentrantLock();

    static Condition condition = lock.newCondition();

    public static void main(String[] args) throws InterruptedException {

        test1();
    }

    public static void test1() throws InterruptedException {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                lock.lock();
                System.out.println("子線程獲取鎖。。。");
                try {
                    System.out.println("子線程掛起開始。。。");
                    condition.await();
                    System.out.println("子線程掛起結束。。。");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }

            }
        });
        thread.start();

        Thread.sleep(2000);
        lock.lock();
        System.out.println("主線程獲取鎖。。。");
        condition.signal();
        lock.unlock();
    }

    public static void test2() throws InterruptedException {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                lock.lock();
                System.out.println("子線程獲取鎖。。。");
                try {
                    Thread.sleep(8000);
                    System.out.println("子線程掛起開始。。。");
                    condition.await();
                    System.out.println("子線程掛起結束。。。");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }

            }
        });
        thread.start();

        Thread.sleep(1000);
        lock.lock();
        System.out.println("主線程獲取鎖。。。");
        condition.signal();
        lock.unlock();
    }

上面測試例子中,兩種情況都會執行完畢,不會死鎖。

wait/notify提供的等待集合是單個的,而Condition可以提供多個等待集,下面是測試例子:

public class ConditionDemo2 {

    public static void main(String[] args) throws InterruptedException {
        ThreadQueue queue = new ThreadQueue(5);

        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i=0;i<20;i++){
                    try {
                        queue.put("元素" + i);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        thread.start();

        Thread.sleep(3000);
        System.out.println("循環的從隊列拿元素。。。");
        for (int i=0;i<10;i++){
            queue.get();
            Thread.sleep(3000);
        }
    }

}

//定義一個阻塞隊列
//put元素時,若隊列已滿,就阻塞,直到再有空間,未滿就put元素進去
//take元素時,若隊列中沒有元素,就阻塞,直到再有元素,有元素就直接取
class ThreadQueue {

    //定義一個可重入鎖
    Lock lock = new ReentrantLock();
    Condition putCondition = lock.newCondition();
    Condition getCondition = lock.newCondition();
    //隊列長度
    private volatile int length;
    //用來存放元素的集合
    List<Object> list = new ArrayList<>();

    public ThreadQueue(int length) {
        this.length = length;
    }

    //往隊列中放元素
    public void put(Object obj) throws InterruptedException {
        lock.lock();
        for (;;) {
            //若隊列還有空間,就直接放入隊列,並且喚醒拿元素的等待集合,可以去拿元素了
            //若隊列空間已經滿了,就直接阻塞
            if (list.size() < length) {
                list.add(obj);
                System.out.println("put: " + obj);
                getCondition.signal();
                break;
            } else {
                putCondition.await();
            }
        }
        lock.unlock();
    }

    //從隊列中拿元素
    public Object get() throws InterruptedException {
        lock.lock();
        Object obj;
        for (;;) {
            //若隊列中有元素就直接取,然後把取走後的元素從隊列中移除,並且喚醒放元素的等待集 
             合,可以繼續放元素了
            //若隊列中沒有元素,就阻塞等待元素
            if (list.size() > 0) {
                obj = list.get(0);
                list.remove(0);
                System.out.println("get: " + obj);
                putCondition.signal();
                break;
            } else {
                getCondition.await();
            }
        }
        lock.unlock();
        return obj;
    }
}

三:ReentrantLock

        ReentrantLock是Lock接口的一個實現,它是一個可重入鎖,會有一個值去記錄重入的次數。若在一個線程中加鎖加了n次,那麼解鎖就要調用n次,如果加鎖次數大於解鎖次數,就不能完全釋放鎖,若加鎖次數小於解鎖次數,即解鎖多調了幾次,那麼就會報錯。下面是例子:

   /定義一個鎖
    static Lock lock = new ReentrantLock();

    public static void main(String[] args) {

        System.out.println("here i am 1.......");
        lock.lock();

        System.out.println("here i am 2.......");
        lock.lock();

        System.out.println("here i am 3.......");
        lock.unlock();

        lock.unlock();

        //會報java.lang.IllegalMonitorStateException異常
        //lock.unlock();

        new Thread(new Runnable() {
            @Override
            public void run() {
                lock.lock();
                System.out.println("子線程獲取鎖.......");
                lock.unlock();
            }
        }).start();
    }

下面簡單實現一個可重入鎖:

思考:

1. 實現可重入鎖需要哪些準備呢?

2. 需要實現哪些方法呢?

那麼,實現一個可重入鎖,需要以下內容:

1:需要知道那個線程獲取到了鎖。

2:如果獲取不到鎖,就放入等待隊列,那麼就需要一個隊列存放掛起的線程。

3:需要知道線程重入的次數,即需要一個變量去記錄線程重入的次數。

4:需要加鎖,解鎖的方法

下面提供一個簡單的實現,有大量註釋,可以方便查閱:

public class ThreadLock2 implements Lock {

    //用於顯示那個線程獲取到鎖
    public volatile AtomicReference<Thread> owner = new AtomicReference<>();

    //用於存放沒有獲取到鎖,掛起的線程
    public static BlockingDeque<Thread> waiter = new LinkedBlockingDeque<>();

    //鎖重入的次數
    volatile AtomicInteger count = new AtomicInteger(0);

    //嘗試加鎖
    @Override
    public boolean tryLock() {
        int ct = count.get();
        //ct!=0,說明鎖已被佔用
        if (ct != 0) {
            //判斷鎖的擁有者是不是當前線程,若是,就把重入次數加一
            if (owner.get() == Thread.currentThread()) {
                count.set(ct + 1);
                return true;
            }
        //鎖未被佔用,就去搶鎖
        } else {
            //CAS方式去搶鎖
            if (count.compareAndSet(ct, ct+1)) {
                owner.set(Thread.currentThread());
                return true;
            }
        }
        return false;
    }

    //加鎖
    @Override
    public void lock() {
        if (!tryLock()) {
            //搶鎖失敗,就加入等待隊列
            waiter.offer(Thread.currentThread());
            //循環的去搶鎖
            for (;;) {
                //取出隊列頭部的線程
                Thread head = waiter.peek();
                //若隊列頭部的元素是當前線程,就去搶鎖
                if (head == Thread.currentThread()) {
                    if (tryLock()) {
                        //搶到鎖就從等待隊列中取出
                        waiter.poll();
                        return;
                    } else {
                        //搶不到鎖就直接掛起
                        LockSupport.park();
                    }
                } else {
                    //不是隊列頭部的線程,就直接掛起
                    LockSupport.park();
                }
            }
        }
    }

    //解鎖
    @Override
    public void unlock() {
        if (tryUnlock()) {
            //釋放鎖成功後,就從等待隊列中喚醒線程
            Thread thread = waiter.peek();
            if (thread != null) {
                LockSupport.unpark(thread);
            }
        }

    }

    //嘗試解鎖
    public boolean tryUnlock() {
        //判斷當前線程是不是鎖擁有者
        //不是就拋出異常
        //是的話就釋放鎖
        if (owner.get() != Thread.currentThread()) {
            //測試類運行可能會進入這個錯誤,這和環境可能有關,但是代碼的具體實現思路應該是沒有 
            //問題的
            throw new IllegalMonitorStateException();
        } else {
            //釋放鎖,就把重入次數減一
            int ct = count.get();
            int nextCt = ct - 1;
            count.set(nextCt);
            //當重入次數爲0,就完全釋放鎖,把鎖擁有者置爲null
            if (nextCt == 0) {
                owner.set(null);
                return true;
            } else {
                return false;
            }
        }
    }


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

    @Override
    public void lockInterruptibly() throws InterruptedException {

    }

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

測試類:

    static ThreadLock2 lock = new ThreadLock2();

    static volatile int i = 0;

    public static void main(String[] args) throws InterruptedException {
        for (int i=0;i<6;i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i=0;i<100000;i++) {
                        add();
                    }
                }
            }).start();
        }
        Thread.sleep(2000);
        System.out.println(i);
    }

    public static void add() {
        lock.lock();
        i++;
        lock.unlock();
    }

四:synchronized和Lock的區別

4.1 synchronized

優點:

1. synchronized使用起來比較簡單,語義也比較清晰。

2. JVM爲synchronized提供了很多的優化,如鎖消除,鎖粗化,偏向鎖等。

3. synchronized可以自動釋放鎖,可以避免死鎖發生

缺點:

無法實現鎖的一些高級特性,如公平鎖,共享鎖等。

4.2 Lock接口

優點:

synchronized的缺點就是Lock的缺點,Lock接口可以實現一些鎖的高級特性。

缺點:

Lock接口需要手動的去釋放鎖,使用中不注意的話可能會造成死鎖。

五:ReadWriteLock

        ReadWriteLock讀寫鎖,提供了一個讀鎖,一個寫鎖,讀鎖是共享鎖,可以由多個線程持有,寫鎖是獨佔鎖,只能有一個線程可以獲得寫鎖,讀寫鎖適用於讀場景比較多的地方。


 

       讀寫鎖,一個線程獲取到了讀鎖後就不能再去獲取寫鎖,讀寫是互斥的,一個線程獲取到了寫鎖,可以鎖降級爲讀鎖,降級爲讀鎖後,就獲取不了寫鎖了;寫鎖只能由一個線程獲取(寫寫互斥),讀鎖可以由多個線程共同獲取,想要獲取寫鎖,只能等到所有的讀鎖全部釋放後,纔可以去獲取,想要獲取讀鎖,也要等到寫鎖釋放後纔可以獲取。

獲取寫鎖的過程:

獲取讀鎖的過程:

下面是實現ReadWriteLock的一個例子:

public class ReadWriteLock2 {

    //鎖擁有者
    private Thread owner = null;
    //等待隊列,存放阻塞掛起的線程
    private LinkedBlockingDeque<Node> waiters = new LinkedBlockingDeque<>();
    //讀鎖
    private AtomicInteger readCount = new AtomicInteger(0);
    //寫鎖
    private AtomicInteger writeCount = new AtomicInteger(0);
    class Node {
        //標識是讀鎖還是寫鎖,0爲讀鎖,1爲寫鎖
        int type = 0;
        //線程
        Thread thread = null;
        int arg = 0;

        public Node(int type, Thread thread, int arg) {
            this.type = type;
            this.thread = thread;
            this.arg = arg;
        }
    }

    //獲取寫鎖,即獨佔鎖
    public void lock() {
        int arg = 1;
        //嘗試獲取獨佔鎖,若成功則退出,若失敗繼續搶鎖
        if (!tryLock(arg)) {
            //搶鎖失敗,就放入等待隊列
            Node node = new Node(0, Thread.currentThread(), arg);
            waiters.offer(node);
            //自旋搶鎖
            for(;;) {
                Node head = waiters.peek();
                //判斷當前線程是否在隊列頭部,若在就嘗試搶鎖,否則就掛起
                if (head != null && head.thread == Thread.currentThread()) {
                    if (tryLock(arg)) {
                        //搶鎖成功就把線程從隊列中移除,然後返回
                        waiters.poll();
                        return;
                    } else {
                        //搶鎖失敗就掛起
                        LockSupport.park();
                    }
                } else {
                    //不是隊列頭部就掛起
                    LockSupport.park();
                }
            }
        }

    }

    //嘗試獲取寫鎖,即獨佔鎖
    public boolean tryLock(int arg) {
        //若讀鎖已被獲取,就返回false,因爲要獲取寫鎖,要等到所有的讀鎖釋放
        if (readCount.get() != 0) {
            return false;
        }
        int ct = writeCount.get();
        if (ct != 0) {
            //若寫鎖已被獲取,且是當前線程獲取,那麼就把writeCount加1
            if (owner == Thread.currentThread()) {
                writeCount.set(ct + arg);
                return true;
            }
        } else {
            //若寫鎖沒被獲取,就用CAS方式去搶鎖
            if (writeCount.compareAndSet(ct, ct + arg)){
                //搶鎖成功就把當前線程賦予owner
                owner = Thread.currentThread();
                return true;
            }
        }
        return false;
    }

    //釋放寫鎖,即獨佔鎖
    public boolean unLock() {
        int arg = 1;
        if (tryUnLock(arg)) {
            //釋放鎖成功,取出隊列頭部線程,喚醒
            Node headNode = waiters.peek();
            if (headNode != null) {
                LockSupport.unpark(headNode.thread);
            }
            return true;
        }
        return false;
    }

    //嘗試釋放寫鎖,即獨佔鎖
    public boolean tryUnLock(int arg) {
        //若鎖的擁有者不是當前線程就拋異常
        if (owner != Thread.currentThread()) {
            throw new IllegalMonitorStateException();
        } else {
            //若是當前線程擁有鎖,就把writeCount減一,直到writeCount爲0時,完全釋放鎖
            int ct = writeCount.get();
            int next = ct - arg;
            if (next == 0) {
                //把owner置爲null,釋放鎖成功
                owner = null;
                return true;
            } else {
                return false;
            }
        }
    }

    //獲取讀鎖,即共享鎖
    public void sharedLock() {
        int arg = 1;
        if (trySharedLock(arg) < 0) {
            //搶鎖失敗,就放入等待隊列
            Node node = new Node(1, Thread.currentThread(), arg);
            waiters.offer(node);

            for (;;) {
                Node headNode = waiters.peek();
                //判斷當前線程是否在隊列頭部,若在就嘗試搶鎖,否則就掛起
                if (headNode != null && headNode.thread == Thread.currentThread()) {
                    if (trySharedLock(arg) >= 0) {
                        //搶鎖成功就把線程從隊列中移除,然後返回
                        waiters.poll();
                        //若下一個線程是讀鎖,就把它喚醒
                        Node next = waiters.peek();
                        if (next != null && next.type == 0) {
                            LockSupport.unpark(next.thread);
                        }
                    } else {
                        //搶鎖失敗就掛起
                        LockSupport.park();
                    }
                } else {
                    //不是隊列頭部就掛起
                    LockSupport.park();
                }
            }
        }
    }

    //嘗試獲取讀鎖,即共享鎖
    public int trySharedLock(int arg) {
        //自旋搶鎖
        for (;;) {
            //若寫鎖沒有釋放,且不是當前線程持有,就返回錯誤值-1,因爲寫鎖釋放後,纔可以獲取讀鎖
            if (writeCount.get() != 0 && owner != Thread.currentThread()) {
                return -1;
            }
            int ct = readCount.get();
            if (readCount.compareAndSet(ct, ct + arg)) {
                return 1;
            }

        }

    }

    //嘗試釋放讀鎖,即共享鎖
    public boolean tryUnSharedLock(int arg) {
        for (;;) {
            int ct = readCount.get();
            int nextCt = ct - arg;
            //直到readCount爲0時纔是完全釋放鎖
            if (readCount.compareAndSet(ct, nextCt)) {
                return nextCt == 0;
            }
        }
    }

    //釋放讀鎖,即共享鎖
    public boolean unSharedLock() {
        int arg = 1;
        if (tryUnSharedLock(arg)) {
            //釋放鎖成功,取出隊列頭部線程,喚醒
            Node headNode = waiters.peek();
            if (headNode != null) {
                LockSupport.unpark(headNode.thread);
            }
            return true;
        }
        return false;
    }
}

測試類:

    static ReadWriteLock2 lock = new ReadWriteLock2();

    volatile static int i = 0;

    static void add() {
        i++;
    }

    public static void main(String[] args) throws InterruptedException {
        long startTime = System.currentTimeMillis();


        for (int a=1; a<=20000; a++){
            final int n = a;
            new Thread(new Runnable() {
                @Override
                public void run() {
                    if (n%5 ==0){
                        lock.lock();
                        add();
                        lock.unLock();
                    }else{
                        lock.sharedLock();
                        System.out.println("i=" +i);
                        int a = i;
                        lock.unSharedLock();
                    }
                }
            }).start();
        }


        while (true){
            System.out.println("目前耗時:" + (System.currentTimeMillis()-startTime) /1000 + "s");
            Thread.sleep(1000L);
            System.out.println("i=" + i);

        }
    }

下面提供另外兩個例子:改造hashMap爲安全的,和實現模擬一個緩存:

public class MapDemo {
    //將hashMap改造成線程安全的
    private Map<String, Object> map = new HashMap<>();

    private ReadWriteLock lock = new ReentrantReadWriteLock();
    //初始化讀鎖和寫鎖
    private Lock writeLock = lock.writeLock();
    private Lock readLock = lock.readLock();

    //根據key值獲取元素
    public Object get(String key) {
        readLock.lock();
        try {
           return map.get(key);
        } finally {
            readLock.unlock();
        }
    }

    //獲取所有的key
    public Object[] allKeys() {
        readLock.lock();
        try {
            return map.keySet().toArray();
        } finally {
            readLock.unlock();
        }
    }

    //放入元素
    public Object put(String key, String value) {
        writeLock.lock();
        try {
            return map.put(key, value);
        } finally {
            writeLock.unlock();
        }
    }

    //清除所有元素
    public void clear() {
        writeLock.lock();
        try {
           map.clear();
        } finally {
            writeLock.unlock();
        }
    }
}
//做一個緩存,從緩存中取元素
public class CacheDemo {

    //初始化一個讀寫鎖
    private static ReadWriteLock lock = new ReentrantReadWriteLock();
    //用於判斷緩存是否可用,是否有元素
    private static volatile boolean isCache;

    static Object get(String key) {
        Object obj = null;
        //加讀鎖
        lock.readLock().lock();
        try {
            //若緩存可用,直接從緩存中取數據
            if(isCache) {
                obj = Cache.map.get(key);
            } else {
                //釋放讀鎖
                lock.readLock().unlock();
                //加寫鎖,並不會馬上獲取到寫鎖,會等到所有讀鎖釋放
                lock.writeLock().lock();
                try {
                    //若緩存不可用,從數據庫取數據,然後放入緩存
                    if (!isCache) {
                        obj = dataBaseGetData.getData();
                        Cache.map.put(key, obj);
                        isCache = true;
                    }
                    //鎖降級,將寫鎖降級爲讀鎖
                    lock.readLock().lock();
                }  finally {
                    //釋放寫鎖
                    lock.writeLock().unlock();
                }
            }
        } finally {
            //釋放讀鎖
            lock.readLock().unlock();
        }
        return obj;
    }

}

//模擬從數據庫獲取元素
class dataBaseGetData {
    static String getData() {
        System.out.println("從數據庫取元素。。。");
        return "name:hello,age:20";
    }
}

//模擬緩存
class Cache {
    static Map<String, Object> map = new HashMap<>();
}

 

六:模板模式

模板模式就是說,有一個母版,就像PPT一樣,然後把母版複製過來自己去實現自己想要的就可以了,下面是個簡單的例子:

//母版
class mb {
    void title() {
        throw new UnsupportedOperationException();
    }

    void content() {
        throw new UnsupportedOperationException();
    }

    void foot() {
        throw new UnsupportedOperationException();
    }

    public final void show() {

        System.out.println("這是一個標題");
        title();
        System.out.println("字體:微軟雅黑");

        System.out.println("這是內容:");
        content();
        System.out.println("內容結束");

        System.out.println("這是底部:");
        foot();
    }
}

class PPT1 extends mb {
    @Override
    void title() {
        System.out.print("你好啊");
    }

    @Override
    void content() {
        System.out.print("java。。。");
        System.out.print("c++");
        System.out.print("中國");
    }

    @Override
    void foot() {
        System.out.print("結束了");
    }
}

根據上面的例子改造ReentrantLock和ReadWriteLock的實現方式,先做一個模板:

public class CommonLock {

    //鎖擁有者
    Thread owner = null;
    //等待隊列,存放阻塞掛起的線程
    LinkedBlockingDeque<Node> waiters = new LinkedBlockingDeque<>();
    //讀鎖
    AtomicInteger readCount = new AtomicInteger(0);
    //寫鎖
    AtomicInteger writeCount = new AtomicInteger(0);
    class Node {
        //標識是讀鎖還是寫鎖,0爲讀鎖,1爲寫鎖
        int type = 0;
        //線程
        Thread thread = null;
        int arg = 0;

        public Node(int type, Thread thread, int arg) {
            this.type = type;
            this.thread = thread;
            this.arg = arg;
        }
    }

    //獲取讀鎖,即獨佔鎖
    public void lock() {
        int arg = 1;
        //嘗試獲取獨佔鎖,若成功則退出,若失敗繼續搶鎖
        if (!tryLock(arg)) {
            //搶鎖失敗,就放入等待隊列
            Node node = new Node(0, Thread.currentThread(), arg);
            waiters.offer(node);
            //自旋搶鎖
            for(;;) {
                Node head = waiters.peek();
                //判斷當前線程是否在隊列頭部,若在就嘗試搶鎖,否則就掛起
                if (head != null && head.thread == Thread.currentThread()) {
                    if (tryLock(arg)) {
                        //搶鎖成功就把線程從隊列中移除,然後返回
                        waiters.poll();
                        return;
                    } else {
                        //搶鎖失敗就掛起
                        LockSupport.park();
                    }
                } else {
                    //不是隊列頭部就掛起
                    LockSupport.park();
                }
            }
        }

    }


    //釋放寫鎖,即獨佔鎖
    public boolean unLock() {
        int arg = 1;
        if (tryUnLock(arg)) {
            //釋放鎖成功,取出隊列頭部線程,喚醒
            Node headNode = waiters.peek();
            if (headNode != null) {
                LockSupport.unpark(headNode.thread);
            }
            return true;
        }
        return false;
    }

    //獲取讀鎖,即共享鎖
    public void sharedLock() {
        int arg = 1;
        if (trySharedLock(arg) < 0) {
            //搶鎖失敗,就放入等待隊列
            Node node = new Node(1, Thread.currentThread(), arg);
            waiters.offer(node);

            for (;;) {
                Node headNode = waiters.peek();
                //判斷當前線程是否在隊列頭部,若在就嘗試搶鎖,否則就掛起
                if (headNode != null && headNode.thread == Thread.currentThread()) {
                    if (trySharedLock(arg) >= 0) {
                        //搶鎖成功就把線程從隊列中移除,然後返回
                        waiters.poll();
                        //若下一個線程是讀鎖,就把它喚醒
                        Node next = waiters.peek();
                        if (next != null && next.type == 1) {
                            LockSupport.unpark(next.thread);
                        }
                    } else {
                        //搶鎖失敗就掛起
                        LockSupport.park();
                    }
                } else {
                    //不是隊列頭部就掛起
                    LockSupport.park();
                }
            }
        }
    }

    //釋放讀鎖,即共享鎖
    public boolean unSharedLock() {
        int arg = 1;
        if (tryUnSharedLock(arg)) {
            //釋放鎖成功,取出隊列頭部線程,喚醒
            Node headNode = waiters.peek();
            if (headNode != null) {
                LockSupport.unpark(headNode.thread);
            }
            return true;
        }
        return false;
    }

    //嘗試獲取寫鎖,即獨佔鎖
    public boolean tryLock(int arg) {
        throw new UnsupportedOperationException();
    }

    //嘗試釋放寫鎖,即獨佔鎖
    public boolean tryUnLock(int arg) {
        throw new UnsupportedOperationException();
    }

    //嘗試獲取讀鎖,即共享鎖
    public int trySharedLock(int arg) {
        throw new UnsupportedOperationException();

    }

    //嘗試釋放讀鎖,即共享鎖
    public boolean tryUnSharedLock(int arg) {
        throw new UnsupportedOperationException();
    }

}

改造後的ReentrantLock:

 //private Thread owner = null;

    //鎖擁有者
    volatile AtomicReference<Thread> owner = new AtomicReference<>();

    //等待隊列
    private LinkedBlockingQueue<Thread> waiters = new LinkedBlockingQueue<>();

    //記錄重入的次數
    volatile AtomicInteger count = new AtomicInteger(0);

    CommonLock lock = new CommonLock(){
        //嘗試獲取寫鎖,即獨佔鎖
        @Override
        public boolean tryLock(int arg) {
            //若讀鎖已被獲取,就返回false,因爲要獲取寫鎖,要等到所有的讀鎖釋放
            if (lock.readCount.get() != 0) {
                return false;
            }
            int ct = lock.writeCount.get();
            if (ct != 0) {
                //若寫鎖已被獲取,且是當前線程獲取,那麼就把writeCount加1
                if (lock.owner == Thread.currentThread()) {
                    lock.writeCount.set(ct + arg);
                    return true;
                }
            } else {
                //若寫鎖沒被獲取,就用CAS方式去搶鎖
                if (lock.writeCount.compareAndSet(ct, ct + arg)){
                    //搶鎖成功就把當前線程賦予owner
                    lock.owner = Thread.currentThread();
                    return true;
                }
            }
            return false;
        }

        //嘗試釋放寫鎖,即獨佔鎖
        @Override
        public boolean tryUnLock(int arg) {
            //若鎖的擁有者不是當前線程就拋異常
            if (lock.owner != Thread.currentThread()) {
                throw new IllegalMonitorStateException();
            } else {
                //若是當前線程擁有鎖,就把writeCount減一,直到writeCount爲0時,完全釋放鎖
                int ct = lock.writeCount.get();
                int next = ct - arg;
                if (next == 0) {
                    //把owner置爲null,釋放鎖成功
                    lock.owner = null;
                    return true;
                } else {
                    return false;
                }
            }
        }
    };

    @Override
    public void lock() {
       lock.lock();
    }

    @Override
    public void unlock() {
       lock.unLock();
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {

    }

    @Override
    public boolean tryLock() {
        return lock.tryLock(1);
    }

    public boolean tryUnlock() {
        return lock.tryUnLock(1);
    }

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



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

改造後的ReadWriteLock:

public class ReadWriteLock2 implements ReadWriteLock {

    CommonLock lock = new CommonLock(){
        //嘗試獲取寫鎖,即獨佔鎖
        @Override
        public boolean tryLock(int arg) {
            //若讀鎖已被獲取,就返回false,因爲要獲取寫鎖,要等到所有的讀鎖釋放
            if (lock.readCount.get() != 0) {
                return false;
            }
            int ct = lock.writeCount.get();
            if (ct != 0) {
                //若寫鎖已被獲取,且是當前線程獲取,那麼就把writeCount加1
                if (lock.owner == Thread.currentThread()) {
                    lock.writeCount.set(ct + arg);
                    return true;
                }
            } else {
                //若寫鎖沒被獲取,就用CAS方式去搶鎖
                if (lock.writeCount.compareAndSet(ct, ct + arg)){
                    //搶鎖成功就把當前線程賦予owner
                    lock.owner = Thread.currentThread();
                    return true;
                }
            }
            return false;
        }

        //嘗試釋放寫鎖,即獨佔鎖
        @Override
        public boolean tryUnLock(int arg) {
            //若鎖的擁有者不是當前線程就拋異常
            if (lock.owner != Thread.currentThread()) {
                throw new IllegalMonitorStateException();
            } else {
                //若是當前線程擁有鎖,就把writeCount減一,直到writeCount爲0時,完全釋放鎖
                int ct = lock.writeCount.get();
                int next = ct - arg;
                if (next == 0) {
                    //把owner置爲null,釋放鎖成功
                    lock.owner = null;
                    return true;
                } else {
                    return false;
                }
            }
        }

        //嘗試獲取讀鎖,即共享鎖
        @Override
        public int trySharedLock(int arg) {
            //自旋搶鎖
            for (;;) {
                //若寫鎖沒有釋放,且不是當前線程持有,就返回錯誤值-1,因爲寫鎖釋放後,纔可以獲取讀鎖
                if (writeCount.get() != 0 && owner != Thread.currentThread()) {
                    return -1;
                }
                int ct = readCount.get();
                if (readCount.compareAndSet(ct, ct + arg)) {
                    return 1;
                }

            }

        }

        //嘗試釋放讀鎖,即共享鎖
        @Override
        public boolean tryUnSharedLock(int arg) {
            for (;;) {
                int ct = readCount.get();
                int nextCt = ct - arg;
                //直到readCount爲0時纔是完全釋放鎖
                if (readCount.compareAndSet(ct, nextCt)) {
                    return nextCt == 0;
                }
            }
        }
    };

    @Override
    public Lock readLock() {
        return new Lock() {

            @Override
            public void lock() {
                lock.sharedLock();
            }

            @Override
            public void lockInterruptibly() throws InterruptedException {

            }

            @Override
            public boolean tryLock() {
                return lock.trySharedLock(0) == 1;
            }

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

            @Override
            public void unlock() {
                lock.tryUnSharedLock(0);
            }

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

    @Override
    public Lock writeLock() {
        return new Lock() {
            @Override
            public void lock() {
                lock.lock();
            }

            @Override
            public void lockInterruptibly() throws InterruptedException {

            }

            @Override
            public boolean tryLock() {
                return lock.tryLock(1);
            }

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

            @Override
            public void unlock() {
                lock.unLock();
            }

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

 七:AQS抽象隊列同步器

       JUC包就是基於AQS實現的,AQS的設計模式就是模板模式,數據結構是雙向鏈表和鎖狀態(state),底層實現是CAS。它的state不像上面讀寫鎖說的是兩個count,它用一個state實現,即我們知道int類型有8個字節,它的實現是前4個字節可以標識讀鎖,後四個字節可以標識寫鎖。

AQS本文不做詳細解釋。。。

好了到此,Lock接口的相關內容已經結束了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章