同步容器類
早期的同步容器類Vector、Hashtable和Collections.synchronizedXXX創建的同步容器,封裝所有public方法,以保證線程安全。
問題:迭代操作期間可能拋ArrayIndexOutOfBoundsException或ConcurrentModificationException
示例代碼:
//遍歷vector時,其他線程修改vector,可能拋ArrayIndexOutOfBoundsException
for (int i = 0; i < vector.size(); i++) {
//操作vector.get(i);
}
List<Person> personList = Collections.synchronizedList(new ArrayList<Person>());
//遍歷personList時,其他線程修改personList,導致計數器變化,再調用next或hasNext時可能會拋ConcurrentModificationException
for (Person person : personList) {
//操作person
}
所以,需要在遍歷操作期間持有容器的鎖,可能會導致併發性和吞吐量降低。
注:容器的toString、hashCode、equals、containsAll、removeAll、retainAll等也隱含執行遍歷操作
併發容器
Java 5.0中增加了ConcurrentHashMap和CopyOnWriteArrayList以代替同步Map和List,還增加了Queue用來臨時保存一組等待處理的元素,提供了幾種實現ConcurrentLinkedQueue、PriorityQueue;還增加了Queue的擴展類BlockingQueue阻塞隊列
Java6也增加了ConcurrentSkipListMap和ConcurrentSkipListSet,分別替代SortedMap和SortedSet
ConcurrentHashMap(Java)使用了分段鎖(Lock Striping)以使讀取操作線程和寫入操作線程可以併發的訪問Map,它提供的迭代器不會拋出ConcurrentModificationException;在JDK1.8中,ConcurrentHashMap的實現原理摒棄了這種設計,而是選擇了與HashMap類似的數組+鏈表+紅黑樹的方式實現,而加鎖則採用CAS和synchronized實現。
示例代碼:
public class ConcurrentContainer {
private static final ConcurrentHashMap conHashMap = new ConcurrentHashMap();
private static CountDownLatch latch = new CountDownLatch(1);
public static void readMap() {
Thread rThread = new Thread(new Runnable() {
@Override
public void run() {
try {
latch.await();
//每隔1秒遍歷一次
Map.Entry entry;
while (true) {
System.out.println("***********************");
for (Object enObj : conHashMap.entrySet()) {
entry = (Map.Entry) enObj;
System.out.println("[Key=" + entry.getKey() + "]-[Value=" + entry.getValue() + "]");
}
System.out.println("***********************");
System.out.println("");
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
rThread.start();
}
public static void writeMap() {
Thread wThread = new Thread(new Runnable() {
@Override
public void run() {
try {
latch.await();
//每隔10秒遍歷一次
Random ran = new Random(1);
while (true) {
conHashMap.put("key" + (int) (ran.nextFloat() * 1000), Math.abs(ran.nextInt() * 1000));
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
wThread.start();
}
public static void main(String[] args) {
readMap();
writeMap();
latch.countDown();
}
}
CopyOnWriteArrayList/CopyOnWriteArraySet,在每次修改時,都會創建並重新發佈一個新的容器副本,從而實現可變性,它提供的迭代器不會拋出ConcurrentModificationException。每當修改容器時都會複製底層數組,需要一定的開銷,因此,僅當迭代操作遠多於修改操作時,才 應該使用“寫入時複製”容器。
示例代碼:
public class ConListContainer {
private static final CopyOnWriteArrayList cowList = new CopyOnWriteArrayList();
private static CountDownLatch latch = new CountDownLatch(1);
public static void readAndWriteList() throws InterruptedException {
Thread rThread = new Thread(new Runnable() {
@Override
public void run() {
try {
latch.await();
//每隔1秒遍歷一次
while (true) {
System.out.println("***********************");
System.out.println("cowList is: " + cowList);
System.out.println("***********************");
System.out.println("");
Thread.sleep(1000);
if (cowList.size() > 10)
break;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread wThread = new Thread(new Runnable() {
@Override
public void run() {
try {
latch.await();
//每隔10秒遍歷一次
Random ran = new Random(1);
while (true) {
cowList.add(ran.nextInt());
Thread.sleep(500);
if (cowList.size() > 10)
break;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
rThread.start();
wThread.start();
latch.countDown();
rThread.join();
wThread.join();
System.out.println("Finally, cowList is " + cowList);
}
public static void main(String[] args) throws InterruptedException {
readAndWriteList();
}
}
阻塞隊列
阻塞隊列提供了可阻塞的put和take方法,以及支持定時的offer和poll方法。如果隊列已經滿了,那麼put方法將阻塞直到有空間可用;如果隊列爲空,那麼take方法將會阻塞知道有元素可用。BlockingQueue的實現包括ArrayBlockingQueue(基於數組,可做有界隊列)、LinkedBlockingQueue(基於鏈表,可做有界隊列和無界隊列)、SynchronousQueue(隊列中最多只能有一個元素,使用上可選公平模式和非公平模式)等。LinkedBlockingQueue、ArrayBlockingQueue是FIFO隊列,分別類似LinkedList和ArrayList,但比同步List有更好的併發性。BlockingQueue適用於生產者-消費者模式(所有消費者共享一個工作隊列)的問題。
示例代碼:
public class BlockingQueueTest {
private static final BlockingQueue blockingQueue = new ArrayBlockingQueue(20);
private static void testBlockingQueue() throws InterruptedException {
Thread pThread = new Thread(new Runnable() {
@Override
public void run() {
Random rand = new Random(1);
int temp;
try {
while (blockingQueue.size() < 20) {
temp = (int) (1000 * rand.nextFloat());
System.out.println("pThread 放入: " + temp);
blockingQueue.put(temp);
System.out.println("pThread讀到blockingQueue當前有" + blockingQueue.size() + "個元素");
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();
}
}
});
Thread cThread = new Thread(new Runnable() {
@Override
public void run() {
Object obj;
try {
//只要有就一直取
while ((obj = blockingQueue.take()) != null) {
//take方法會一直阻塞知道有元素可拿
Thread.sleep(3000);
System.out.println("cThread拿到了:" + obj);
System.out.println("cThread讀到blockingQueue當前有" + blockingQueue.size() + "個元素");
if (0 == blockingQueue.size()){
break;
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
pThread.start();
cThread.start();
pThread.join();
cThread.join();
System.out.println("最後,blockingQueue當前有: " + blockingQueue.size() + "個元素");
}
public static void main(String[] args) throws InterruptedException {
testBlockingQueue();
}
}
Deque是一個雙端隊列,支持FIFO、FILO,實現了在隊列頭和隊列尾的高效插入和移除,具體實現包括ArrayDeque(非線程安全)和LinkedBlockingDeque(線程安全)。Deque適用於工作密取(Work Stealing)模式(每個消費者都有各自的雙端隊列)的問題,提供了更高的可伸縮性。
示例代碼:
public class BlockingDequeTest {
private static final BlockingDeque blockingDeque = new LinkedBlockingDeque();
private static final CountDownLatch latch = new CountDownLatch(1);
private static final AtomicLong ATOM_INT = new AtomicLong(100L);
private static final String ALL_LETTERS = "abcdefghijklmnopqrstuvwxyz";
private static void testBlockingDeque() throws InterruptedException {
Thread lrThread = new Thread(new Runnable() {
@Override
public void run() {
try {
latch.await();
while (true) {
Object obj = blockingDeque.pollFirst();
if (obj != null) {
System.out.println("lrThread gets: [" + obj + "] from Deque");
if (ATOM_INT.decrementAndGet() < 0L) {
System.out.println("lrThread: tired of this stupid game, BYE!");
break;
}
}
Thread.sleep(40);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread lwThread = new Thread(new Runnable() {
@Override
public void run() {
try {
latch.await();
String str;
for (; ; ) {
str = genRandomStr();
blockingDeque.offerFirst(str);
System.out.println("lwThread writes: [" + str + "] to Deque");
if (ATOM_INT.longValue() < 0L) {
System.out.println("lwThread: I've got a bad feeling about this...");
break;
}
Thread.sleep(200);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread rrThread = new Thread(new Runnable() {
@Override
public void run() {
try {
latch.await();
while (true) {
Object obj = blockingDeque.pollLast();
if (null != obj) {
System.out.println("rrThread gets: [" + obj + "] from Deque");
if (ATOM_INT.decrementAndGet() < 0L) {
System.out.println("rrThread: OUT!");
break;
}
}
Thread.sleep(30);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread rwThread = new Thread(new Runnable() {
@Override
public void run() {
try {
latch.await();
String str;
for (; ; ) {
str = genRandomStr();
blockingDeque.addLast(str);
System.out.println("lwThread writes: [" + str + "] to Deque");
if (ATOM_INT.longValue() < 0L) {
System.out.println("rwThread: Ew!");
break;
}
Thread.sleep(150);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
lrThread.start();
lwThread.start();
rrThread.start();
rwThread.start();
latch.countDown();
lrThread.join();
lwThread.join();
rrThread.join();
rwThread.join();
System.out.println("Finally, blockingDeque is: " + blockingDeque);
}
private static String genRandomStr() {
Random ran = new Random();
String tempStr = "";
int idx1;
for (int i = 0; i < 3; i++) {
idx1 = Math.abs((int) (26 * ran.nextFloat()));
tempStr += ALL_LETTERS.substring(idx1, idx1 + 1);
}
return tempStr + Math.abs((int) (10 * ran.nextFloat()));
}
public static void main(String[] args) throws InterruptedException {
testBlockingDeque();
}
}
阻塞方法與中斷方法
線程可能會阻塞或暫停執行,原因有多種:等待I/O操作結束(BLOCKED),等待獲得一個鎖(WAITING),等待從Thread.sleep方法中醒來(TIMED_WAITING),或是等待另一個線程的結算結果。當代碼中調用了一個將拋出InterruptedException異常的方法時,通常可以傳遞InterruptedException給調用者或通過調用當前線程上的interrupt方法恢復中斷。如:
public class TaskRunnable implements Runnable {
BlockingQueue<Task> queue;
@Override
public void run() {
try {
processTask(queue.take());
} catch (InterruptedException e) {
//恢復被中斷的狀態
Thread.currentThread().interrupt();
}
}
}
同步工具類
閉鎖(CountDownLatch)
閉鎖是一種同步工具類,可以延遲線程的進度直到其到達中止狀態。底層是基於 AQS(AbstractQueuedSynchronizer)實現,可以比join方法對線程有更靈活的控制。
示例代碼:
public class TestHarness {
public long timeTasks(int nThreads, final Runnable task) throws InterruptedException {
final CountDownLatch startGate = new CountDownLatch(1);
final CountDownLatch endGate = new CountDownLatch(nThreads);
for (int i = 0; i < nThreads; i++) {
Thread t = new Thread() {
@Override
public void run() {
try {
startGate.await();
try {
task.run();
} finally {
endGate.countDown();
}
} catch (InterruptedException e) {
}
}
};
}
long start = System.nanoTime();
startGate.countDown();
endGate.await();
long end = System.nanoTime();
return end - start;
}
}
FutureTask也可以用作閉鎖
FutureTask是一個可取消的異步計算,實現了Runnable和Future接口,通常用來包裝一個Callable對象,可以異步執行,並將計算結果返回調用主線程。
示例代碼:
public class FutureTaskTest {
private static AtomicInteger atomicInteger = new AtomicInteger(1);
private static CountDownLatch countDownLatch = new CountDownLatch(3);
private static void testFutureTask() throws ExecutionException, InterruptedException {
ExecutorService executor = Executors.newCachedThreadPool();
List<FutureTask<Integer>> taskList = new ArrayList<FutureTask<Integer>>(10);
for (int i = 0; i < 10; i++) {
FutureTask<Integer> futureTask = new FutureTask<Integer>(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
countDownLatch.await();
Random ran = new Random();
int res = 0;
for (int j = 0; j < 10; j++) {
res += Math.abs(ran.nextInt() / 100);
}
int sleepSec = atomicInteger.getAndIncrement();
System.out.println("Thread-" + Thread.currentThread().getId() + " will run for " + sleepSec + " seconds");
Thread.sleep(1000 * sleepSec);
System.out.println("Thread-" + Thread.currentThread().getId() + " returns " + res);
return res;
}
});
executor.submit(futureTask);
taskList.add(futureTask);
}
countDownLatch.countDown();//3
countDownLatch.countDown();//2
countDownLatch.countDown();//1
int totRes = 0;
for (FutureTask<Integer> task : taskList) {
totRes += task.get().intValue();
}
System.out.println("Final result: " + totRes);
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
testFutureTask();
}
}
信號量(Semaphore)
計數信號量用來控制同時訪問某個特定資源的操作數量,或者同時執行某個指定操作的數量。Semaphore可以用於實現資源池。
示例代碼:
/**
* Running 24/7
*/
public class PublicToilet {
private static final int SPOTS = 5;
private static final Random RANDOM = new Random();
private static final SimpleDateFormat FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
private static final Semaphore SEMAPHORE = new Semaphore(SPOTS, false); // Not a fair WC
private static final AtomicInteger USE_TIMES = new AtomicInteger(0);
private void startService() {
for (int i = 0; i < 20; i++) {
WcGoer goer = new WcGoer();
goer.setErgentLevel((short) (RANDOM.nextInt() % 2));
Thread thread = new Thread(new WcGoer());
thread.start();
}
}
private class WcGoer implements Runnable {
private short ergentLevel;
public short getErgentLevel() {
return ergentLevel;
}
public void setErgentLevel(short ergentLevel) {
this.ergentLevel = ergentLevel;
}
@Override
public void run() {
try {
int useTime;
if (ergentLevel == 0) {
SEMAPHORE.acquire();
} else {
while (!SEMAPHORE.tryAcquire()) {
System.out.println(FORMAT.format(new Date()) + ":" + Thread.currentThread().getName() + " keep waiting");
Thread.sleep(1000 * 3);
}
}
System.out.println(FORMAT.format(new Date()) + ":" + Thread.currentThread().getName() + " enters WC" +
((ergentLevel == 0) ? "" : " in a hurry"));
useTime = Math.abs(RANDOM.nextInt() % 11);
System.out.println(FORMAT.format(new Date()) + ":" + Thread.currentThread().getName() + " will use for " + useTime + " seconds");
Thread.sleep(1000 * useTime);
System.out.println(FORMAT.format(new Date()) + ":" + Thread.currentThread().getName() + " exits WC");
SEMAPHORE.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private static void enterMaintenance() throws InterruptedException {
//occupy all spots
while (!SEMAPHORE.tryAcquire(SPOTS)) {
}
System.exit(0);
}
public static void main(String[] args) {
PublicToilet pt = new PublicToilet();
pt.startService();
}
}
柵欄(Barrier)
柵欄(Barrier)類似於閉鎖,它能阻塞一組線程直到某個事件發生。柵欄與閉鎖的關鍵區別在於,所有線程必須同時到達柵欄位置,才能繼續執行。閉鎖用於等待事件,而柵欄用於等待其他線程。
CyclicBarrier可以使一定數量的參與方反覆地在柵欄位置彙集,它在並行迭代算法中非常有用:這種算法通常將一個問題拆分成一系列互相獨立的子問題。當線程到達柵欄位置時將調用await方法,這個方法將阻塞直到所有線程都到達柵欄位置。如果所有線程都到達了柵欄位置,那麼柵欄將打開,此時所有線程都被釋放,而柵欄將被重置以便下次使用。
另一種形式的柵欄是Exchanger,它是一種兩方(Two-Party)柵欄,各方在柵欄位置上交還數據。
示例代碼:
public class BankRobbing {
private static Random random = new Random();
private static String CHICKEN_OUT = "";
private CyclicBarrier TOUGH_TANK = new CyclicBarrier(5, new Runnable() {
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(3L);
System.out.println("Take a deep breath, guys..");
TimeUnit.SECONDS.sleep(3L);
System.out.println("Ok, let's go make some money!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
private class BankRobber extends Thread {
private String name;
public BankRobber(String name) {
this.name = name;
}
@Override
public void run() {
int time;
System.out.println("Criminal " + name + " setting off for TOUGH_TANK");
try {
time = Math.abs(random.nextInt() % 20);
TimeUnit.SECONDS.sleep(time);
System.out.println("Criminal " + name + " reaches TOUGH_TANK");
if (time < 5 && "".equals(CHICKEN_OUT)) {
CHICKEN_OUT = name;
TOUGH_TANK.await(5, TimeUnit.SECONDS);
} else {
TOUGH_TANK.await();
}
System.out.println("Go,go,go!!!");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
System.out.println(CHICKEN_OUT + " chickens out, " + name + " flees...");
//e.printStackTrace();
} catch (TimeoutException e) {
System.out.println(name + " can't bear the waiting......");
//e.printStackTrace();
}
}
}
public void doRobBank() {
String[] robbers = {"Carl", "Coughlin", "Ben", "Mike", "Douglas"};
for (int i = 0; i < robbers.length; i++) {
new BankRobber(robbers[i]).start();
}
}
public static void main(String[] args) {
BankRobbing bankRobbing = new BankRobbing();
bankRobbing.doRobBank();
}
}
示例代碼:
public class MethBooth {
private Exchanger<Object> exchanger;
public MethBooth(Exchanger<Object> exchanger) {
this.exchanger = exchanger;
}
private class DrugDealer implements Runnable {
private String infiniteDrug;
private int money;
public DrugDealer(String infiniteDrug, int money) {
this.infiniteDrug = infiniteDrug;
this.money = money;
}
@Override
public void run() {
Object object;
System.out.println("DrugDealer: Walking to rendezvous");
try {
for (; ; ) {
TimeUnit.SECONDS.sleep(5);
object = exchanger.exchange(infiniteDrug);
if (object != null && "NARC".equals(object.toString())) {
System.out.println("DrugDealer:COPS! Running away......");
Thread.currentThread().interrupt();
} else {
Integer drugMoney = (Integer) object;
System.out.println("DrugDealer: Getting $" + drugMoney);
money += drugMoney;
}
}
} catch (InterruptedException e) {
System.out.println("DrugDealer: I'm gone, collecting money $" + money + " in total.");
e.printStackTrace();
}
}
}
private class DrugUser implements Runnable {
private int money;
public DrugUser(int money) {
this.money = money;
}
@Override
public void run() {
System.out.println("DrugUser: Driving to rendezvous");
Object object;
try {
while (money > 0) {
TimeUnit.SECONDS.sleep(5);
object = exchanger.exchange(5);
money -= 5;
System.out.println("DrugUser: Giving $5, getting " + object + ", left " + money);
}
object = exchanger.exchange("NARC");
System.out.println("DrugUser: Giving NARC, getting " + object);
object = exchanger.exchange(100, 3, TimeUnit.SECONDS);
System.out.println("DrugUser: Giving $100, getting " + object);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
System.out.println("DrugUser: DrugDealer is gone...");
}
}
}
public void drugDealing() {
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.submit(new Thread(new DrugDealer("BLUE CHERRY", 0)));
executorService.submit(new Thread(new DrugUser(10)));
executorService.shutdown();
}
public static void main(String[] args) {
MethBooth methBooth = new MethBooth(new Exchanger<Object>());
methBooth.drugDealing();
}
}
參考:《Java併發編程實戰》;
Java併發包concurrent——ConcurrentHashMap;
ArrayList和CopyOnWriteArrayList、解讀 java 併發隊列 BlockingQueue;
Java 併發編程系列之閉鎖(CountDownLatch);
Java併發編程筆記之 CountDownLatch閉鎖的源碼分析;
Java併發33:Semaphore基本方法與應用場景實例;
多線程編程的常用類(CountDownLatch, Semaphore, CyclicBarrier 和 Exchanger)。