同步容器类
早期的同步容器类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)。