Java多線程-3-併發容器和工具類

三、併發容器和工具類

1、併發容器

【1】ConcurrentHashMap

多線程情況下的HashMap。注意KEY和VALUE都不能爲null

【2】ConcurrentSkipListMap

多線程情況下的TreeMap

ConcurrentSkipListMap<String, Integer> concurrentSkipListMap = new ConcurrentSkipListMap<String, Integer>();
concurrentSkipListMap.put("a", 1);
concurrentSkipListMap.put("A", 2);
concurrentSkipListMap.put("0", 3);
concurrentSkipListMap.put("b", 4);
System.out.println(concurrentSkipListMap); // {0=3, A=2, a=1, b=4}

System.out.println("0-" + (int)'0'); // 0-48
System.out.println("A-" + (int)'A'); // A-65
System.out.println("a-" + (int)'a'); // a-97
System.out.println("b-" + (int)'b'); // b-98

【3】ConcurrentSkipListSet

多線程情況下的TreeSet

【4】CopyOnWriteArrayList

多線程情況下的ArrayList。對寫操作加鎖,讀操作沒有加鎖,保證了性能

volatile修飾的Object數組,當每次對數組進行修改時,會創建新的數組,並將引用指向它

例如add操作:

public boolean add(E e) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] elements = getArray();
        int len = elements.length;
        Object[] newElements = Arrays.copyOf(elements, len + 1); // 創建新的數組
        newElements[len] = e;
        setArray(newElements); // 指向新的數組
        return true;
    } finally {
        lock.unlock();
    }
}

【5】CopyOnWriteArraySet

內部維護了一個CopyOnWriteArrayList

private final CopyOnWriteArrayList<E> al;

在添加新的元素前,會先判斷是否已經存在該元素,如果沒有找到,纔會保存到容器裏

/**
 * java.util.concurrent.CopyOnWriteArrayList#addIfAbsent(E)方法
 */
public boolean addIfAbsent(E e) {
    Object[] snapshot = getArray();
    return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
    addIfAbsent(e, snapshot);
}

2、併發工具類

【1】CountDownLatch

倒計時閂鎖。事先給出統計數值,每次減1,當減到0時,放行

CountDownLatch countDownLatch = new CountDownLatch(2);

new Thread(() -> {
    System.out.println("A");
    try {
        Thread.sleep(500);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    countDownLatch.countDown(); // 減1
}).start();

new Thread(() -> {
    System.out.println("B");
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    countDownLatch.countDown(); // 減1
}).start();

try {
    countDownLatch.await(); // Main線程等待
} catch (InterruptedException e) {
    e.printStackTrace();
}

System.out.println("結束!");

【2】CyclicBarrier

循環屏障。事先給出統計數值,每次加1,當加到統計數值時,放行

使用方式一:

public static void main(String[] args) {
    CyclicBarrier cyclicBarrier = new CyclicBarrier(3); // 記得算上Main線程

    new Thread(() -> {
        System.out.println("A");
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        try {
            cyclicBarrier.await(); // 等待(加1)
        } catch (InterruptedException | BrokenBarrierException e) {
            e.printStackTrace();
        }
    }).start();

    new Thread(() -> {
        System.out.println("B");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        try {
            cyclicBarrier.await(); // 等待(加1)
        } catch (InterruptedException | BrokenBarrierException e) {
            e.printStackTrace();
        }
    }).start();

    try {
        cyclicBarrier.await(2, TimeUnit.SECONDS); // 只等待2秒鐘(加1)
    } catch (InterruptedException | BrokenBarrierException | TimeoutException e) {
        e.printStackTrace();
    }

    System.out.println("結束!");
}

使用方式二:

CyclicBarrier cyclicBarrier = new CyclicBarrier(2, () -> {
    System.out.println("結束!");
}); // 其他線程都執行完畢後,執行該線程方法

new Thread(() -> {
    System.out.println("A");
    try {
        Thread.sleep(500);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    try {
        cyclicBarrier.await(); // 等待(加1)
    } catch (InterruptedException | BrokenBarrierException e) {
        e.printStackTrace();
    }
}).start();

new Thread(() -> {
    System.out.println("B");
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    try {
        cyclicBarrier.await(); // 等待(加1)
    } catch (InterruptedException | BrokenBarrierException e) {
        e.printStackTrace();
    }
}).start();

【3】Semaphore

信號量。指定一次只允許執行的線程個數

int loop = 10;
int permits = 2;

ExecutorService executorService = Executors.newFixedThreadPool(loop);
Semaphore semaphore = new Semaphore(permits);

for (int i = 0; i < loop; i++) {
    executorService.execute(() -> {
        try {
            semaphore.acquire();
            System.out.println(Thread.currentThread().getName() + " ---> 執行任務");
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            semaphore.release();
        }
    });
}

if (!executorService.isShutdown()) {
    executorService.shutdown();
}

【4】Exchanger

交換者。用於線程間的數據交換。如果有一個線程發起了交換,則會一直等待另外一線程執行交換。如果怕等待時間過長,則可以使用public V exchange(V x, long timeout, TimeUnit unit) throws InterruptedException, TimeoutException方法

Exchanger<String> exchanger = new Exchanger<String>();
new Thread(() -> {
    try {
        System.out.println(Thread.currentThread().getName() + "發出了消息,等待回覆");

        String exchange = exchanger.exchange("hello?");
        Thread.sleep(2000);
        System.out.println(Thread.currentThread().getName() + "收到了:" + exchange);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}, "線程A").start();

new Thread(() -> {
    try {
        Thread.sleep(3000);

        String exchange = exchanger.exchange("hi!");
        System.out.println(Thread.currentThread().getName() + "收到了:" + exchange + ",並且已回覆");
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}, "線程B").start();

發佈了155 篇原創文章 · 獲贊 89 · 訪問量 13萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章