Java多線程學習(其他一些鎖的補充)(四)

介紹幾種新鎖的使用方式以及線程池的創建和一些線程池的相關操作。

CountDownLatch

CountDownLatch: 計算機術語叫做閉鎖,也可以理解爲倒計時鎖吧;
看一個小demo

public class CountDownLatchDemo09 {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(6);
        for (int i = 0; i < 6; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "\t離開教室");
                countDownLatch.countDown();
            }).start();
        }
        countDownLatch.await();
        System.out.println(Thread.currentThread().getName() + "\t關門走人");
    }

上面代碼的執行結果是:先執行6遍 離開教室-在關門走人

CountDownLatch的功能:

  • 完成一個需求:指定的線程個數先執行,然後再執行其他的線程
  • 可以自定義先執行的線程數

可以看下源碼裏面的解釋,這個類也是一個外國人覺得sun公司寫的不好,然後自己就幫其修改了juc裏面的源碼:

/**
     * Synchronization control For CountDownLatch.
     * Uses AQS state to represent count.
	 * 倒計時鎖存器的同步控制。
	 * 使用AQS狀態表示計數。
	 *
     */
    private static final class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 4982264981922014374L;
        Sync(int count) {
            setState(count);
        }
        int getCount() {
            return getState();
        }
        protected int tryAcquireShared(int acquires) {
            return (getState() == 0) ? 1 : -1;
        }
        protected boolean tryReleaseShared(int releases) {
            // Decrement count; signal when transition to zero
            for (;;) {
                int c = getState();
                if (c == 0)
                    return false;
                int nextc = c-1;
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }
    }

這個同步內部類就是來完成倒計時鎖的同步控制。

CyclicBarrier

CyclicBarrier: 循環屏障鎖,該鎖跟上面的鎖有類似之處,不同的則是該鎖,在達到屏障值時,可以進行其他的操作,首先還是看下demo:

public class CyclicBarrierDemo10 {
    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
            System.out.println("****召喚神龍****");
        });

        for (int i = 1; i <= 7; i++) {
            int finalI = i;
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "\t收集到第:" + finalI + "顆龍珠" );
                try {
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }, String.valueOf(finalI)).start();
        }
    }
}

結果:

1	收集到第:1顆龍珠
5	收集到第:5顆龍珠
4	收集到第:4顆龍珠
3	收集到第:3顆龍珠
2	收集到第:2顆龍珠
7	收集到第:7顆龍珠
6	收集到第:6顆龍珠
****召喚神龍****
CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
            System.out.println("****召喚神龍****");
        });

看這段代碼,前面就是屏障的界限值,然後後面是一個Action
我們可以看下源碼:

public CyclicBarrier(int parties, Runnable barrierAction) {
        if (parties <= 0) throw new IllegalArgumentException();
        this.parties = parties;
        this.count = parties;
        this.barrierCommand = barrierAction;
    }

這就是對應的構造器,當然後面那個參數是可以傳null的,就不會在打破屏障的時候去執行其他操作,而且這裏只有線程休眠,沒有喚醒,是因爲這段代碼:

public int await() throws InterruptedException, BrokenBarrierException {
        try {
            return dowait(false, 0L);
        } catch (TimeoutException toe) {
            throw new Error(toe); // cannot happen
        }
    }
-----------------------------------------
	if (Thread.interrupted()) {
                breakBarrier();
                throw new InterruptedException();
            }

            int index = --count;
            if (index == 0) {  // tripped
                boolean ranAction = false;
                try {
                    final Runnable command = barrierCommand;
                    if (command != null)
                        command.run();
                    ranAction = true;
                    nextGeneration();
                    return 0;
                } finally {
                    if (!ranAction)
                        breakBarrier();
                }
            }
-----------------------------------------
	private void breakBarrier() {
        generation.broken = true;
        count = parties;
        trip.signalAll();
    }

這裏會根據你傳的參數幫你進行判斷,在打破屏障後會幫你喚醒其他線程。

Semaphore

Semaphore: 信號量鎖,其主要目的有兩個:

  • 一個是多個共享資源的互斥使用
  • 另一個用於併發線程數的控制

他的功能:控制流量,很多線程訪問時,只需限制一下就行,修改一下Semaphore的初始值
依舊看個小demo:

public class SemaphoreDemo11 {
    public static void main(String[] args) throws InterruptedException {
        // 模擬資源類,有3個空車位
        Semaphore semaphore = new Semaphore(3);

        for (int i = 1; i < 6; i++) {
            new Thread(() -> {
                try {
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName() + "\t搶佔到了車位");
                    try {
                        TimeUnit.SECONDS.sleep(3);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "\t離開了車位");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    semaphore.release();
                }
            }, String.valueOf(i)).start();
        }
    }
}

結果:

1	搶佔到了車位
3	搶佔到了車位
2	搶佔到了車位
3	離開了車位
1	離開了車位
4	搶佔到了車位
2	離開了車位
5	搶佔到了車位
4	離開了車位
5	離開了車位

同樣是做到了同步作用,繼續看源代碼:

	public Semaphore(int permits, boolean fair) {
        sync = fair ? new FairSync(permits) : new NonfairSync(permits);
    }

可以傳兩個參數:一個是限制值,一個是boolean,後面的參數決定其底層是使用公平鎖還是非公平鎖,可以看下源碼:

	public Semaphore(int permits) {
        sync = new NonfairSync(permits);
    }
--------------------------------------------
	static final class NonfairSync extends Sync {
        private static final long serialVersionUID = -2694183684443567898L;
        NonfairSync(int permits) {
            super(permits);
        }
        protected int tryAcquireShared(int acquires) {
            return nonfairTryAcquireShared(acquires);
        }
    }

然後繼續往下走就是同步鎖裏面的公平和非公平鎖的原理,可自己閱讀。
當信號計量鎖後面只傳一個參數爲 1 時,效果等同於 synchronized。

ReadWriteLock

ReadWriteLock: 讀寫鎖,首先需要理清讀寫操作的共存關係:

寫寫不可共存,要保證原子性
讀寫不能共存
讀讀可共存

知道這個概念,看個小demo:

public class ReadWriteDemo12 {
    public static void main(String[] args) {
        MyCache myCache = new MyCache();
        // 寫入數據
        for (int i = 0; i < 5; i++) {
            final int finalI = i;
            new Thread(() -> {
                myCache.put(finalI + "", finalI + "");
            }, String.valueOf(finalI)).start();
        }
        // 讀取數據
        for (int i = 0; i < 5; i++) {
            final int finalI = i;
            new Thread(() -> {
                myCache.get(finalI + "");
            }, String.valueOf(finalI)).start();
        }
    }
}

class MyCache{
    private volatile Map<String, Object> map = new HashMap<>(16);

    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    public void put(String key, Object value){
        readWriteLock.writeLock().lock();
        try{
            System.out.println(Thread.currentThread().getName() + "\t寫入數據" + key);
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            map.put(key, value);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            readWriteLock.writeLock().unlock();
        }
        System.out.println(Thread.currentThread().getName() + "\t寫入完成");
    }

    public void get(String key){
        readWriteLock.readLock().lock();
        Object result= null;
        try {
            System.out.println(Thread.currentThread().getName() + "\t讀取數據");
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            result = map.get(key);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            readWriteLock.readLock().unlock();
        }
        System.out.println(Thread.currentThread().getName() + "\t讀取完成" + result);
    }
}

結果:

0	寫入數據0
0	寫入完成
1	寫入數據1
1	寫入完成
2	寫入數據2
2	寫入完成
0	讀取數據
1	讀取數據
2	讀取數據
2	讀取完成2
0	讀取完成0
1	讀取完成1

可以看出寫操作必須是原子性操作的,讀取數據就不需要遵循,看看源碼:

protected WriteLock(ReentrantReadWriteLock lock) {
     sync = lock.sync;
}
-----------------------------------------------------
protected ReadLock(ReentrantReadWriteLock lock) {
     sync = lock.sync;
}

看到同步鎖,也知道後面會涉及到公平鎖和非公平鎖:

/**
     * Creates a new {@code ReentrantReadWriteLock} with
     * the given fairness policy.
     *
     * @param fair {@code true} if this lock should use a fair ordering policy
     */
    public ReentrantReadWriteLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
        readerLock = new ReadLock(this);
        writerLock = new WriteLock(this);
    }

讀寫鎖保證了操作的原子性和可見性(保證在第一時間修改,其他線程所得到的結果都統一)。

BlockingQueue

BlockingQueue: 阻塞隊列

  • 在多線程領域,所謂阻塞,在某些情況下會掛其線程(即阻塞),一旦滿足條件,被掛起的線程又會被喚醒
  • 使用BlockingQueue我們就不需要去關心什麼時候需要去阻塞線程,什麼時候去喚醒線程,因爲這一切都被BlockingQueue包辦了

直接看demo:

public class BlockingQueueDemo13 {
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
        /**
         * add / remove: 會拋出異常
         */
        /*// 添加的返回時true
        System.out.println(blockingQueue.add("a"));
        System.out.println(blockingQueue.add("b"));
        System.out.println(blockingQueue.add("c"));
        // 超過容量3 會報錯 Queue full
        // System.out.println(blockingQueue.add("c"));

        // 刪除的時候,沒有指定元素則會報錯 java.util.NoSuchElementException
        System.out.println(blockingQueue.remove());
        System.out.println(blockingQueue.remove());
        System.out.println(blockingQueue.remove());
        System.out.println(blockingQueue.remove());*/

        /**
         *  offer / poll 不會拋出異常
         */
        /*System.out.println(blockingQueue.offer("a"));
        System.out.println(blockingQueue.offer("b"));
        System.out.println(blockingQueue.offer("c"));
        // 這個是會返回false
        // System.out.println(blockingQueue.offer("c"));

        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        // 這個如果沒有元素可以移除了,會返回null值
        System.out.println(blockingQueue.poll());*/

        /**
         * put / take: 兩者就會一直阻塞,程序一直卡在那
         */
        blockingQueue.put("a");
        blockingQueue.put("a");
        blockingQueue.put("a");
        // blockingQueue.put("a");

        System.out.println(blockingQueue.take());
        System.out.println(blockingQueue.take());
        System.out.println(blockingQueue.take());
        // System.out.println(blockingQueue.take());

        System.out.println(blockingQueue.offer("a", 3L, TimeUnit.SECONDS));
    }
}

上面的註釋很清楚,可釋放代碼每種情況都測試一下,現在看源碼:

public ArrayBlockingQueue(int capacity) {
   this(capacity, false);
}
-------------------------------------------
public ArrayBlockingQueue(int capacity, boolean fair) {
    if (capacity <= 0)
        throw new IllegalArgumentException();
    this.items = new Object[capacity];
    lock = new ReentrantLock(fair);
    notEmpty = lock.newCondition();
    notFull =  lock.newCondition();
}
--------------------------------------------
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

首先是初始化,需要你初始化隊列的大小 ,默認是使用非公平鎖,後面依舊是同樣的操作。這裏底層用的是可重入鎖。

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