1. 程式人生 > >Java筆記(十八)同步和協作工具類

Java筆記(十八)同步和協作工具類

null tac 構造方法 ken reads arr 內部 reader 參數

同步和協作工具類

一、讀寫鎖ReentrantReadWriteLock

ReadWriteLock接口的定義為:

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

讀操作使用讀鎖,寫操作使用寫鎖。只有"讀-讀"操作是可以並行的,"讀-寫"和"寫-寫"都不行。

始終只有一個線程能進行寫操作,在獲取寫鎖時,只有沒有任何線程持有任何鎖才可以獲取到,

在持有寫鎖時,其他任何線程都獲取不到任何鎖。在沒有其他線程持有寫鎖的情況下,多個線程可以獲取和持有讀鎖。

ReentrantReadWriteLock的兩個構造方法:

public ReentrantReadWriteLock() {
        this(false);
}
public ReentrantReadWriteLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
        readerLock = new ReadLock(this);
        writerLock = new WriteLock(this);
}

其中fair表示是否公平。

二、信號量Semaphore

信號量類Semaphore類用來限制對資源並發訪問的線程數,構造方法:

//permits表示許可數量
public Semaphore(int permits)
//fair表示是否公平
public Semaphore(int permits, boolean fair)

Semaphore方法與鎖類似,主要有兩類方法,獲取許可和釋放許可:

//阻塞獲取許可
public void acquire() throws InterruptedException
//阻塞獲取許可,不響應中斷
public void acquireUninterruptibly()
//批量獲取多個許可
public void acquire(int permits) throws InterruptedException
public void acquireUninterruptibly(int permits) //嘗試獲取 public boolean tryAcquire() //限定等待時間獲取 public boolean tryAcquire(int permits, long timeout, TimeUnit unit) throws InterruptedException //釋放許可 public void release()

限制並發訪問數量不超過100的例子:

public class AccessControlService {
    public static class ConcurrentLimitException extends RuntimeException {
    }
    private static final int MAX_PERMITS = 100;
    private Semaphore permits = new Semaphore(MAX_PERMITS, true);
    public boolean login(String name, String password) {
        //每次acquire都會消耗一個許可
        if (!permits.tryAcquire()) {
            throw new ConcurrentLimitException();
        }
        return true;
    }
    public void logout(String name) {
        permits.release();
    }
}
Semaphore permits = new Semaphore(1);
permits.acquire();
//程序會阻塞在第二個acquire調用
permits.acquire();
System.out.println("acquired");

信號量也是基於AQS實現的。

三、倒計時門栓CountDownLatch

用於需要線程同步的情景。該類相當於一個門栓,一開始是關閉的,所有希望通過該門的線程都需要等待,

然後開始倒計時,倒計時變為0的時候,門栓打開,所有線程通過,它是一次性的,打開後不能關閉。構造函數:

public CountDownLatch(int count)

與多個線程的協作方法:

//檢查計數是否為0如果大於0就等待。await可以被中斷,也可以設置最長等待時間
public void await() throws InterruptedException
public boolean await(long timeout, TimeUnit unit) throws InterruptedException
//countDown檢查計數,如果已經為0,直接返回,否則減少計數,如果新的計數變為0,
//則喚醒所有線程
public void countDown()
public class RacerWithCountDownLatch {
    static class Racer extends Thread {
        CountDownLatch latch;
        public Racer(CountDownLatch latch) {
            this.latch = latch;
        }
        @Override
        public void run() {
            try{
                this.latch.await();
                System.out.println(getName() + " start run "
                        + System.currentTimeMillis());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        int num = 10;
        CountDownLatch latch = new CountDownLatch(1);
        Thread[] threads = new Thread[num];
        for (int i = 0; i < num; i++) {
            threads[i] = new Racer(latch);
            threads[i].start();
        }
        Thread.sleep(1000);
        latch.countDown();
        /*Thread-0 start run 1545714108398
        Thread-3 start run 1545714108398
        Thread-4 start run 1545714108398
        Thread-5 start run 1545714108398
        Thread-6 start run 1545714108398
        Thread-7 start run 1545714108399
        Thread-8 start run 1545714108399
        Thread-9 start run 1545714108399*/
    }
}

四、循環柵欄CyclicBarrier

所有線程在到達柵欄後都需要等待其他線程,等所有線程都到達後再一起通過,

它是循環的,可以用作重復的同步。構造方法:

//parties參與線程個數
public CyclicBarrier(int parties)
//barrierAction表示所有線程到達柵欄後,所有線程執行下一步動作前,
//運行參數中的動作,這個動作由最後一個到達柵欄的線程執行
public CyclicBarrier(int parties, Runnable barrierAction)

主要方法:

//等待其他線程到達柵欄,調用await後表示自己已經到達,如果是最後一個到達的,就執行可選命令,執行完畢後,喚醒所有等待的線程,然後重置內部的同步計數
public int await() throws InterruptedException, BrokenBarrierException
public int await(long timeout, TimeUnit unit) throws InterruptedException,
BrokenBarrierException, TimeoutException

註意:在CyclicBarrier中,參與的線程是互相影響的,只要有其中的一個線程在調用await時被中斷或者超時了,

柵欄就會被破壞。此外,如果柵欄動作拋出了異常,柵欄也會被破壞。被破壞後,所有在調用的await線程就會退出,

拋出BrokenBarrierException。

五、ThreadLocal

1.基本概念和用法

線程本地變量:每個線程都有同一個變量的獨特拷貝。ThreadLocal是一個泛型類,

接受一個類型參數T,它只有一個空的構造方法,有兩個主要的public方法:

//獲取值
public T get()
//設置值
public void set(T value)
public class ThreadLocalBasic {
    static ThreadLocal<Integer> local = new ThreadLocal<Integer>();
    public static void main(String[] args) throws InterruptedException {
        Thread child = new Thread(){
            @Override
            public void run() {
                System.out.println("child thread initial " + local.get()); //null
                local.set(200);
                System.out.println("child thread final: " + local.get()); //200
            }
        };
        local.set(100);
        child.start();
        child.join();
        System.out.println("Main thread final : " + local.get()); //100
    }
}

從上面的例子可以看出,一個線程本地變量,在每個線程都有自己的獨立值。

ThreadLocal的其他方法:

//用於提供初始值
protected T initialValue()
//刪除當前線程的對應值,刪掉後再次調用get就會獲取初始值
public void remove()

應用:是實現線程安全、減少競爭的一種方案。

Java筆記(十八)同步和協作工具類